code wiki / (root) / nx_eqparse_lib.nx

nx_eqparse_lib.nx source

↩ module page · 133 lines · 6113 B

1// nx_eqparse_lib.nx -- READ AN EQUATION A PERSON ACTUALLY TYPED, AND NORMALISE IT. 2// 3// WHY THIS EXISTS (2026-09-03, and the operator is the reason). The /math surface shipped as a page whose 4// formulas were typeset at PUBLISH time and whose worked solutions were three hardcoded equations. The 5// operator's response was exact: "/math is just static output how does that match what i asked for". It 6// did not. A visitor could not type anything, and a page that demonstrates a capability is not the same 7// artifact as a page that PROVIDES it. 8// 9// The solver existed (nx_calc_solve), the worked steps existed (nx_solve_steps_lib), the typesetter 10// existed (nx_mathml_lib). The missing rung between a human and all three was this: turning the text 11// "3x+4=19" into coefficients. That is the whole gap, and it is why the page could only ever be a brochure. 12// 13// SCOPE, DECLARED: polynomials in a single variable x up to degree two, integer coefficients, both sides 14// of one equals sign, with implicit coefficients (x means 1x), unary signs, and ^2 for the square. It does 15// NOT do parentheses, division, other variables, or decimals. Anything outside that is REFUSED BY NAME so 16// a visitor is told what was not understood rather than handed a confident answer to a different question. 17// 18// license_tier: ORIGINAL 19import "syscalls.nx" 20 21const EQ_OK: i64 = 0 22const EQ_ERR_EMPTY: i64 = 0 - 1 23const EQ_ERR_NO_EQUALS: i64 = 0 - 2 // no '=' at all: an expression is not an equation 24const EQ_ERR_TWO_EQUALS: i64 = 0 - 3 25const EQ_ERR_BADCHAR: i64 = 0 - 4 // a character outside the declared scope 26const EQ_ERR_DEGREE: i64 = 0 - 5 // a power above 2 27const EQ_ERR_NO_X: i64 = 0 - 6 // nothing to solve for 28const EQ_MAXLEN: i64 = 512 29// A typed coefficient beyond this is refused rather than silently overflowing the fixed-point arithmetic 30// downstream. Named, because a bound a caller cannot see is a bound that surprises them. 31const EQ_MAX_COEF: i64 = 1000000 32 33func eq_err_name(e: i64) -> *u8 { 34 if e == EQ_OK { return "OK" as *u8 } 35 if e == EQ_ERR_EMPTY { return "EMPTY-nothing-was-typed" as *u8 } 36 if e == EQ_ERR_NO_EQUALS { return "NO-EQUALS-SIGN-an-expression-is-not-an-equation" as *u8 } 37 if e == EQ_ERR_TWO_EQUALS { return "MORE-THAN-ONE-EQUALS-SIGN" as *u8 } 38 if e == EQ_ERR_BADCHAR { return "CHARACTER-OUTSIDE-THE-SUPPORTED-SUBSET-only-digits-x-plus-minus-caret-2-and-equals" as *u8 } 39 if e == EQ_ERR_DEGREE { return "POWER-ABOVE-TWO-this-subset-solves-linear-and-quadratic-only" as *u8 } 40 if e == EQ_ERR_NO_X { return "NO-X-IN-THE-EQUATION-there-is-nothing-to-solve-for" as *u8 } 41 return "UNKNOWN" as *u8 42} 43 44func eq_is_digit(c: i64) -> i64 { if c >= 48 { if c <= 57 { return 1 } } return 0 } 45 46// Parse into coefficients of x^2, x^1, x^0 for the equation moved entirely to the left of the equals. 47// Everything on the RIGHT is subtracted, which is the same move a person makes by hand. 48func eq_parse(src: *u8, n: i64, out: *i64) -> i64 { 49 out[0] = 0 50 out[1] = 0 51 out[2] = 0 52 if n <= 0 { return EQ_ERR_EMPTY } 53 if n > EQ_MAXLEN { return EQ_ERR_BADCHAR } 54 55 var side: i64 = 1 // +1 left of '=', -1 right of it 56 var seen_eq: i64 = 0 57 var sign: i64 = 1 58 var have_digits: i64 = 0 59 var mag: i64 = 0 60 var i: i64 = 0 61 var pending: i64 = 0 // 1 once a term is open (a sign, digits, or an x has been seen) 62 63 while i <= n { 64 var c: i64 = 0 65 if i < n { c = src[i] as i64 } 66 // space and tab are ignored so "3x + 4 = 19" reads the same as "3x+4=19" 67 if c == 32 { i = i + 1 } else { 68 if c == 9 { i = i + 1 } else { 69 70 if eq_is_digit(c) == 1 { 71 mag = mag * 10 + (c - 48) 72 if mag > EQ_MAX_COEF { return EQ_ERR_BADCHAR } 73 have_digits = 1 74 pending = 1 75 i = i + 1 76 } else { 77 // A term ENDS here. Work out its degree by looking at what follows. 78 var deg: i64 = 0 79 var consumed: i64 = 0 80 if c == 120 { // 'x' 81 deg = 1 82 consumed = 1 83 // optional ^2 84 if i + 1 < n { 85 if src[i+1] == (94 as u8) { // '^' 86 if i + 2 < n { 87 let p: i64 = src[i+2] as i64 88 if p == 50 { deg = 2; consumed = 3 } else { 89 if p == 49 { deg = 1; consumed = 3 } else { return EQ_ERR_DEGREE } 90 } 91 } else { return EQ_ERR_DEGREE } 92 } 93 } 94 pending = 1 95 } 96 if deg > 0 { 97 var coef: i64 = mag 98 if have_digits == 0 { coef = 1 } // a bare x means one x 99 out[deg] = out[deg] + side * sign * coef 100 mag = 0 101 have_digits = 0 102 sign = 1 103 i = i + consumed 104 pending = 0 105 } else { 106 // Not an x: flush any pending constant, then handle the operator. 107 if pending == 1 { if have_digits == 1 { 108 out[0] = out[0] + side * sign * mag 109 } } 110 mag = 0 111 have_digits = 0 112 sign = 1 113 pending = 0 114 115 if i >= n { i = i + 1 } else { 116 if c == 43 { sign = 1; i = i + 1 } else { // '+' 117 if c == 45 { sign = 0 - 1; i = i + 1 } else { // '-' 118 if c == 61 { // '=' 119 if seen_eq == 1 { return EQ_ERR_TWO_EQUALS } 120 seen_eq = 1 121 side = 0 - 1 122 i = i + 1 123 } else { return EQ_ERR_BADCHAR } } } 124 } 125 } 126 } 127 } } 128 } 129 130 if seen_eq == 0 { return EQ_ERR_NO_EQUALS } 131 if out[1] == 0 { if out[2] == 0 { return EQ_ERR_NO_X } } 132 return EQ_OK 133}