code wiki / (root) / nx_lex_kinds.nx

nx_lex_kinds.nx source

↩ module page · 233 lines · 9172 B

1// lex_kinds.nx -- Tok record + named TokenKind constants. 2// 3// Shared by lex.nx (producer) and parse.nx (consumer). Both used 4// to carry their own copy of the `Tok` struct and compare 5// `t.kind == 67` with a comment saying "LPAREN"; with this module 6// imported they use `t.kind == TK_LPAREN` directly. 7// 8// Numbers below must match the comment table in lex.nx and the 9// emission sites in lex.nx's lexer -- do not renumber without 10// auditing both callers and the existing test fixtures. 11 12// ---- Tok record --------------------------------------------------- 13// 14// text[] (64 bytes) lives inline as 8 i64 words starting at offset 15// 32. Access via tok_text_ptr() defined by lex.nx. 16 17struct Tok { 18 kind: i64, 19 line: i64, 20 col: i64, 21 int_val: i64, 22 text0: i64, 23 text1: i64, 24 text2: i64, 25 text3: i64, 26 text4: i64, 27 text5: i64, 28 text6: i64, 29 text7: i64, 30 // Overflow buffer for TK_STRING longer than 63 bytes; mirrors 31 // the str_data/str_len fields added to nxc2/lex.h. 32 str_data: i64, 33 str_len: i64, 34} 35 36const TOK_BYTES: i64 = 112 37 38// ---- literals ----------------------------------------------------- 39 40const TK_EOF: i64 = 0 41const TK_INT: i64 = 1 42const TK_IDENT: i64 = 2 43const TK_STRING: i64 = 3 44// Floating-point literal. Lexer splits the number into whole / 45// fractional parts and stores both in the Tok: 46// t.int_val = whole part 47// t.text0 = fractional digits as i64 (e.g. "14" in "3.14" -> 14) 48// t.text1 = number of fractional digits (2 in the example) 49// Parser converts to IEEE 754 via quant.nx when building the IR. 50// Keeping whole/frac as two ints in the token avoids pulling fp 51// conversion into the lexer and lets parse.nx decide f32 vs f64 52// based on the declared variable type. 53const TK_FLOAT: i64 = 4 54 55// Explicit-f32 float literal -- written `1.5f32` (or `1.5F32`) in 56// source. Same int_val / text0 / text1 packing as TK_FLOAT (whole / 57// frac_num / frac_digits); the kind tells parse.nx to use 58// fp32_from_parts + TY_F32 instead of the default fp64 path. 59// `1.5f64` is allowed as an explicit-default and emits TK_FLOAT. 60const TK_FLOAT_F32: i64 = 5 61 62// ---- keywords ----------------------------------------------------- 63 64const TK_FUNC: i64 = 10 65const TK_LET: i64 = 11 66const TK_VAR: i64 = 12 67const TK_CONST: i64 = 13 68const TK_STATIC: i64 = 14 69const TK_IF: i64 = 15 70const TK_ELSE: i64 = 16 71const TK_WHILE: i64 = 17 72const TK_LOOP: i64 = 18 73const TK_FOR: i64 = 19 74const TK_IN: i64 = 20 75const TK_BREAK: i64 = 21 76const TK_CONTINUE: i64 = 22 77const TK_RETURN: i64 = 23 78const TK_TRUE: i64 = 24 79const TK_FALSE: i64 = 25 80const TK_STRUCT: i64 = 26 81const TK_ENUM: i64 = 27 82// @priv VISIBILITY ATTRIBUTE. Ported from the laptop SSOT 2026-07-31 -- buildroot had NO const at slot 33 83// (verified free before claiming it), so the trees can converge without renumbering anything. 84const TK_PRIV: i64 = 33 85const TK_MATCH: i64 = 28 86const TK_COMPTIME: i64 = 29 87const TK_EXTERN: i64 = 30 88const TK_AS: i64 = 31 89// V-LANGEXT M2: if-then-else as expression keyword. Distinct from 90// statement-form `if c { ... } else { ... }` (which uses braces); the 91// expression form `if c then a else b` matches Haskell/OCaml/Elm 92// convention. Required to disambiguate at parse_primary. 93const TK_THEN: i64 = 32 94 95// ---- arithmetic / assign ----------------------------------------- 96 97const TK_PLUS: i64 = 40 98const TK_MINUS: i64 = 41 99const TK_STAR: i64 = 42 100const TK_SLASH: i64 = 43 101const TK_PERCENT: i64 = 44 102const TK_ASSIGN: i64 = 45 103 104// ---- comparisons -------------------------------------------------- 105 106const TK_EQ: i64 = 46 107const TK_NE: i64 = 47 108const TK_LT: i64 = 48 109const TK_GT: i64 = 49 110const TK_LE: i64 = 50 111const TK_GE: i64 = 51 112 113// ---- logical / bitwise -------------------------------------------- 114 115const TK_AND_AND: i64 = 52 116const TK_OR_OR: i64 = 53 117const TK_BANG: i64 = 54 118const TK_AMP: i64 = 55 119const TK_PIPE: i64 = 56 120const TK_CARET: i64 = 57 121const TK_TILDE: i64 = 58 122const TK_SHL: i64 = 59 123const TK_SHR: i64 = 60 124 125// ---- structural punctuation --------------------------------------- 126 127const TK_ARROW: i64 = 61 // -> 128const TK_DOT_DOT: i64 = 62 // .. 129const TK_DOT: i64 = 63 130const TK_COLON: i64 = 64 131const TK_SEMI: i64 = 65 132const TK_COMMA: i64 = 66 133const TK_LPAREN: i64 = 67 134const TK_RPAREN: i64 = 68 135const TK_LBRACE: i64 = 69 136const TK_RBRACE: i64 = 70 137const TK_LBRACKET: i64 = 71 138const TK_RBRACKET: i64 = 72 139const TK_AT: i64 = 73 140const TK_HASH: i64 = 74 141const TK_COLON_COLON: i64 = 75 // :: 142const TK_FAT_ARROW: i64 = 76 // => (used by `match` arms) 143const TK_QUESTION: i64 = 77 // ? (C-style ternary cond ? a : b) 144 145// ---- shared Tok helpers ---- 146// 147// Kept here because they depend only on the Tok layout. Both lex.nx 148// (token producer) and parse.nx (token consumer) used to carry local 149// copies -- now they share one. 150 151func tok_text_ptr(t: *Tok) -> *u8 { 152 let base: i64 = t as i64 153 return (base + 32) as *u8 154} 155 156// Compare `n` bytes between `a` and `b`. If the shorter side (`b`) 157// isn't null-terminated at position n the match fails -- matches the 158// C strncmp-style "prefix of a equals exactly b" semantics used by 159// keyword lookup. 160// ---- LN15: FREE MULTI-LINE EXPRESSIONS ---------------------------- 161// 162// ONE question, asked by parse.nx's at_stmt_boundary: when a token is 163// the FIRST on a new source line, does it CONTINUE the expression on 164// the line above, or can it legitimately START a fresh statement? 165// 166// It lives here, beside the kinds, for the same reason tok_text_ptr and 167// streq_n do: it is a pure function of a token kind, and lex.nx (the 168// producer) and parse.nx (the consumer) both already import this module. 169// So the shared ruler costs no new import edge and there is only ever 170// ONE of it -- a second copy in the parser is how the two sides drift. 171// 172// WHY THE AMBIGUOUS PREFIXES ARE NOT AMBIGUOUS AT STATEMENT LEVEL. 173// NishiLang has exactly ONE operator-led statement form: parse_stmt_star, 174// the pointer store `*p = v`. No statement form begins with `+`, `-` or 175// `&`. A line `- b` can therefore only reach parse_stmt_expr_fallback, 176// where it computes a value and throws it away -- and since 2026-08-13 177// that fallback REFUSES exactly that as a discarded pure expression. 178// 179// So the programs this predicate reclassifies are precisely the programs 180// that DO NOT BUILD TODAY, and joining them cannot change the meaning of 181// any program that does. That is the safety argument, and it is checkable 182// rather than asserted: the corpus A/B must come back byte-identical. 183// 184// `~` and the logical-not stay boundaries: they are unary-ONLY, so a line 185// opening with one can never have been a continuation of anything. 186// 187// HISTORY. The det2x2 defect (2026-07-09, runtime-wide sweep) was this 188// class before that refusal existed: `let d = a*d` / `- b*c` silently 189// kept only the first term. The refusal made the wrong value LOUD. 190// This predicate makes it CORRECT. 191 192const TLC_BOUNDARY: i64 = 0 // may start a statement -- keep the boundary 193const TLC_CONTINUATION: i64 = 1 // no prefix form exists -- join the line above 194const TLC_STORE_OR_JOIN: i64 = 2 // TK_STAR only: `*p = v` stores, `* b` joins 195 196func tok_line_continuation(k: i64) -> i64 { 197 // Always-binary (established 2026-05-16): no prefix form exists. 198 if k == TK_PIPE { return TLC_CONTINUATION } 199 if k == TK_CARET { return TLC_CONTINUATION } 200 if k == TK_SLASH { return TLC_CONTINUATION } 201 if k == TK_PERCENT { return TLC_CONTINUATION } 202 if k == TK_EQ { return TLC_CONTINUATION } 203 if k == TK_NE { return TLC_CONTINUATION } 204 if k == TK_LT { return TLC_CONTINUATION } 205 if k == TK_GT { return TLC_CONTINUATION } 206 if k == TK_LE { return TLC_CONTINUATION } 207 if k == TK_GE { return TLC_CONTINUATION } 208 if k == TK_AND_AND { return TLC_CONTINUATION } 209 if k == TK_OR_OR { return TLC_CONTINUATION } 210 if k == TK_SHL { return TLC_CONTINUATION } 211 if k == TK_SHR { return TLC_CONTINUATION } 212 if k == TK_ASSIGN { return TLC_CONTINUATION } 213 // LN15 adds these three: legal unary PREFIXES, but no statement FORM 214 // begins with them, so at statement level they can only have been a 215 // continuation of the line above. 216 if k == TK_PLUS { return TLC_CONTINUATION } 217 if k == TK_MINUS { return TLC_CONTINUATION } 218 if k == TK_AMP { return TLC_CONTINUATION } 219 // The single genuine ambiguity, handed back to the caller because 220 // deciding it needs the token stream, not just the kind. 221 if k == TK_STAR { return TLC_STORE_OR_JOIN } 222 return TLC_BOUNDARY 223} 224 225func streq_n(a: *u8, b: *u8, n: i64) -> i64 { 226 var i: i64 = 0 227 while i < n { 228 if a[i] != b[i] { return 0 } 229 i = i + 1 230 } 231 if b[n] != 0 { return 0 } 232 return 1 233}