nx_css_number_parse.nx source
↩ module page · 111 lines · 4015 B
1// nx_css_number_parse.nx -- decode a CSS NUMBER token body into a
2// fixed-point integer in thousandths (Q3 millis). Phase 2.5 bridge
3// primitive between value-as-bytes (from nx_css_tokenize /
4// nx_css_parse / nx_css_apply) and integer arithmetic in the
5// eventual nx_layout pipeline.
6//
7// "16" -> 16000
8// "1.5" -> 1500
9// "0.25" -> 250
10// "0" -> 0
11// "100" -> 100000
12// "1.234" -> 1234 (rounds toward zero past 3 decimal places)
13//
14// Why thousandths: matches the CSS spec resolution (browsers carry
15// values at 1/96 inch precision; thousandths give us four-decimal-
16// digit headroom for ratio math while staying in pure integer
17// arithmetic per bits-up substrate -- no FP unit needed).
18//
19// What it does NOT handle (Phase 2b improvements):
20// - negative numbers (margin: -10px) -- needs leading-minus parser
21// - exponent notation (1e3) -- rare in CSS
22// - "+" sign prefix
23// - calc() expressions
24//
25// Per cardinal feedback-honest-perf-verdict-no-aspirational-claims:
26// gap list is EXACT.
27//
28// genealogy_id: w3c_css_values_module_level_4_number_syntax
29// lineage_id: nishi_browser_css_number_parse_v1
30//
31// nx_safety_envelope:
32// intended_use: "CSS number decode -- layout/paint input"
33// sil_target: SIL1
34// evidence: [W3C_CSS_Values_canonical_basis,
35// bounded_decimal_digits,
36// integer_only_no_FP]
37// verdict: NOT_YET_EVALUATED
38
39import "nx_syscalls.nx"
40
41const NX_CSS_NUMBER_PARSE_ERROR: i64 = 9223372036854775807 // i64.max; OOB sentinel
42
43// Parse the NUMBER body bytes at src[off..off+len). Returns the
44// value in thousandths (e.g., "1.5" -> 1500). Returns
45// NX_CSS_NUMBER_PARSE_ERROR on:
46// - empty input
47// - leading '.' or trailing '.'
48// - non-digit / non-'.' byte
49// - multiple '.'
50func nx_css_number_parse(src: *u8, off: i64, len: i64) -> i64 {
51 if len <= 0 { return NX_CSS_NUMBER_PARSE_ERROR }
52
53 // Leading-minus: a negative value (margin: -10px). The tokenizer now emits the '-' as part of the
54 // NUMBER body, so the sign is parsed HERE and the magnitude is negated at the end. ('+' is still
55 // unsupported -- rare in CSS, and the tokenizer never emits a '+'-led number.)
56 var neg: i64 = 0
57 var soff: i64 = off
58 var slen: i64 = len
59 if (src[off] as i64 & 255) == 45 {
60 neg = 1
61 soff = off + 1
62 slen = len - 1
63 if slen <= 0 { return NX_CSS_NUMBER_PARSE_ERROR }
64 }
65
66 var integer_part: i64 = 0
67 var frac_part: i64 = 0
68 var frac_digits: i64 = 0
69 var saw_dot: i64 = 0
70 var saw_integer_digit: i64 = 0
71
72 var i: i64 = 0
73 while i < slen {
74 let c: i64 = (src[soff + i] as i64) & 255
75 if c == 46 { // '.'
76 if saw_dot == 1 { return NX_CSS_NUMBER_PARSE_ERROR }
77 if saw_integer_digit == 0 { return NX_CSS_NUMBER_PARSE_ERROR }
78 saw_dot = 1
79 i = i + 1
80 } else {
81 if c < 48 { return NX_CSS_NUMBER_PARSE_ERROR }
82 if c > 57 { return NX_CSS_NUMBER_PARSE_ERROR }
83 let digit: i64 = c - 48
84 if saw_dot == 0 {
85 integer_part = integer_part * 10 + digit
86 saw_integer_digit = 1
87 } else {
88 if frac_digits < 3 {
89 frac_part = frac_part * 10 + digit
90 frac_digits = frac_digits + 1
91 }
92 // Past 3 decimal places: ignore (rounds toward zero).
93 }
94 i = i + 1
95 }
96 }
97
98 if saw_integer_digit == 0 { return NX_CSS_NUMBER_PARSE_ERROR }
99 if saw_dot == 1 {
100 if frac_digits == 0 { return NX_CSS_NUMBER_PARSE_ERROR }
101 }
102
103 // Scale frac_part to thousandths.
104 if frac_digits == 1 { frac_part = frac_part * 100 }
105 if frac_digits == 2 { frac_part = frac_part * 10 }
106 // frac_digits == 3: already thousandths.
107
108 let mag: i64 = integer_part * 1000 + frac_part
109 if neg == 1 { return 0 - mag }
110 return mag
111}