code wiki / (root) / nx_css_number_parse.nx

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}