code wiki / (root) / nx_js_parse.nx

nx_js_parse.nx source

↩ module page · 2485 lines · 124093 B

1// nx_js_parse.nx -- R-JS-PARSE (WB-JS-001 rung 1): the ECMAScript PARSER, riding 2// the rung-0 tokenizer (nx_js_lex.nx) per the no-floating law -- the lexer is built 3// + gated BEFORE this parser composes it. Shape = RECURSIVE-DESCENT over the flat 4// token stream, producing an AST in a FLAT i64 ARENA (parallel slots per node, NO 5// struct complexity). Founds R-JS-EVAL above it (the interpreter rides the AST). 6// 7// Consumes nx_js_lex's token stream (3 i64/token: kind, start-offset, byte-length) 8// and js_lexeme_eq for keyword/punct matching. Produces nodes in a node arena 9// (NODE_SLOTS i64 each) + a child-index arena for variable-arity lists (PROGRAM 10// statement list, BLOCK body, CALL arguments, function params). 11// 12// SCOPE (rung 1) -- every sub-feature below is now GATED by a KAT that asserts its 13// node (council BLOCKER fix: the prior 'ALL gated' header listed paths with ZERO 14// coverage -- string/null/bool/typeof/unary +-/ the '/ %' '< <= > >=' '!= === !==' 15// operator families were code-present-but-unasserted; KAT9/10 now assert them): 16// - primary: identifier, number, STRING, true/false (BOOL), null, ( expr ) 17// - postfix chains (left-assoc): member a.b, computed index a[b], call f(args...) 18// - unary prefix: ! - + typeof 19// - binary with CORRECT precedence + associativity (highest -> lowest): 20// (* / %) > (+ -) > (< <= > >=) > (== != === !==) > (&&) > (||) 21// all left-associative; assignment `=` is RIGHT-assoc and LOWEST. 22// - statements: var/let/const decl (optional initializer), expression statement, 23// return [expr] ;, block { ... }, if (cond) stmt [else stmt], while (cond) stmt, 24// function decl function name(params){body} 25// - PROGRAM = list of statements 26// 27// ERROR CONTRACT (honest -- what the parser DOES and does NOT enforce): 28// - Semicolons are OPTIONAL via ASI-lite: a statement may end without ';' ONLY at 29// a line terminator, before '}', or at EOF. Two statements on ONE line with no 30// separator (`a b`, `1 2`) are REJECTED (ERROR node + err flag) -- real JS 31// SyntaxError. (Council BLOCKER fix: previously `;` was unconditionally optional 32// so adjacent same-line statements were silently accepted.) 33// - Assignment requires a valid lvalue target (IDENT / MEMBER / INDEX). '1 = 2' 34// and '(a+b) = c' are REJECTED (real JS: Invalid left-hand side). (BLOCKER fix.) 35// - Unbalanced parens, missing operands, declarations with no name, dangling 36// operators, unterminated blocks -> ERROR node + err flag. 37// - NOT enforced (honest OPEN, NOT claimed): full ASI restricted-production rules 38// (e.g. `return`-newline-expr nuance), strict-mode lvalue refinements, label 39// scoping. These are rung-1b; the gate does NOT assert them. 40// 41// RUNG-3 ADDITION (R-JS-OBJ, now GATED here + in eval): object literals { k: v, ... } 42// (ND_OBJECT with ND_PROP children, key = IDENT or STRING token) and array literals 43// [a, b, ...] (ND_ARRAY with element-expr children, trailing commas + empty [] 44// allowed). They appear in EXPRESSION/primary position; member/index/call chains 45// apply on top ({a:1}.a, [1,2][0]). Statements beginning with '{' are still BLOCKS. 46// 47// HONEST OPEN (rung 1b -- NAMED, NOT faked/stubbed-as-done): 48// - computed object keys { [k]: v }, shorthand { x } / method { f(){} } props, 49// spread/rest ... in literals -> later rungs (only `key: expr` props are built). 50// - `({}).x` leading-brace-in-expression-statement nicety (a statement starting with 51// '{' parses as a BLOCK; wrap in parens to force object-literal context) -- OPEN. 52// - template literals `...${}`, classes, 53// destructuring patterns, for-in / for-of, switch/case, labeled break/continue, 54// try/catch/finally, throw, spread/rest ..., new, comma operator, 55// bitwise & | ^ << >> >>>, ?? and ?. , bitwise/shift compound assigns &= **= etc. 56// These are deliberately deferred; the gate does NOT assert them as working. 57// 58// RUNG-6 ADDITION (R-JS-CLOSURE, gated in eval): FUNCTION EXPRESSIONS (`function (p){b}` 59// and named `function f(p){b}` in expression/primary position -- jp_parse_func_expr, same 60// ND_FUNC_DECL shape with name=-1 when anonymous; so `var f=function(){}` and the IIFE 61// `(function(){})()` parse) and ARROW FUNCTIONS (`x=>e`, `(a,b)=>e`, `()=>e`, block-body 62// `(x)=>{stmts}` -- jp_parse_arrow_*; desugared to ND_FUNC_DECL: params + body BLOCK, an 63// expr body wrapped as `{ return e; }`). Arrows fork at the TOP of jp_parse_assign (lowest 64// precedence, right-assoc). The `( ... )` cover-grammar ambiguity (param list vs paren-expr) 65// is resolved by jp_arrow_paren_ahead, which scans to the matching ')' and checks for a 66// following '=>'. Idents-only params; default/rest/destructured params + deeply ambiguous 67// covers stay HONEST OPEN. `this`-in-arrow is an eval-side OPEN (no `this` yet). 68// 69// RUNG-4 ADDITION (R-JS-CTRL, now GATED in eval): C-style `for(init;cond;update)body` 70// -> ND_FOR (a=init, b=cond, c=update, tokidx-slot=body; any of init/cond/update may be 71// empty/-1), `do body while(cond);` -> ND_DOWHILE (a=cond, b=body), ternary `cond?a:b` 72// -> ND_TERNARY (a=cond, b=then, c=else; precedence just above assignment, right-assoc), 73// compound assign `+= -= *= /= %=` -> ND_ASSIGN with the BINARY op-code in the `extra` 74// slot (0 = plain '='), and `break;`/`continue;` -> ND_BREAK/ND_CONTINUE statements. 75// for-in/for-of (need iterator protocol) and labeled break/continue stay HONEST OPEN. 76// 77// GATE (main): KATs parse known programs and ASSERT the AST structure (node kinds + 78// nesting + that precedence/associativity parsed correctly), incl. a TAMPER KAT 79// (malformed input MUST yield ERROR, never a fabricated valid AST). Self-validating; 80// exit 0 iff all pass; appends knowledge/status/js_engine.log. 81// 82// license_tier: ORIGINAL (tutor-bootstrap scaffold; team re-authors from the 83// R-JS-PARSE data spec via author=organ -- (B)-debt, mirror the lexer note.) 84import "nx_js_lex.nx" 85import "nx_itoa_lib.nx" // shared MSB-first emitter (zero-alloc) 86const ND_MAGIC_1024: i64 = 1024 87const ND_MAGIC_2048: i64 = 2048 88const ND_MAGIC_4096: i64 = 4096 89const ND_MAGIC_65536: i64 = 65536 90const ND_MAGIC_131072: i64 = 131072 91 92// ===================== node-kind constants ===================== 93const ND_ERROR: i64 = 0 94const ND_PROGRAM: i64 = 1 95const ND_NUMBER: i64 = 2 96const ND_STRING: i64 = 3 97const ND_IDENT: i64 = 4 98const ND_BOOL: i64 = 5 99const ND_NULL: i64 = 6 100const ND_BINARY: i64 = 7 101const ND_UNARY: i64 = 8 102const ND_ASSIGN: i64 = 9 103const ND_CALL: i64 = 10 104const ND_MEMBER: i64 = 11 // a.b (b is an IDENT node) 105const ND_INDEX: i64 = 12 // a[b] (b is an expr node) 106const ND_VAR_DECL: i64 = 13 // a=name IDENT node, b=init expr node (-1 if none) 107const ND_EXPR_STMT: i64 = 14 108const ND_RETURN: i64 = 15 // a=expr node (-1 if bare `return;`) 109const ND_BLOCK: i64 = 16 // list-node: a=child_start, b=child_count 110const ND_IF: i64 = 17 // a=cond, b=then-stmt, c=else-stmt (-1 if none) 111const ND_WHILE: i64 = 18 // a=cond, b=body 112const ND_FUNC_DECL: i64 = 19 // a=name IDENT, b=params list-node, c=body BLOCK 113const ND_PARAMS: i64 = 20 // list-node: a=child_start, b=child_count 114const ND_OBJECT: i64 = 21 // object literal {k:v,...}: list-node a=child_start, b=prop_count (children are ND_PROP) 115const ND_ARRAY: i64 = 22 // array literal [e0,e1,...]: list-node a=child_start, b=elem_count 116const ND_PROP: i64 = 23 // object property: tokidx=key token (IDENT or STRING), a=value-expr node 117// ---- R-JS-CTRL (rung 4): control-flow + operator completion ---- 118const ND_FOR: i64 = 24 // for(init;cond;update) body: a=init, b=cond, c=update, tokidx-slot=body (all may be -1 except body) 119const ND_TERNARY: i64 = 25 // cond ? a : b -> a=cond, b=then-expr, c=else-expr 120const ND_BREAK: i64 = 26 // break; (statement, no children) 121const ND_CONTINUE: i64 = 27 // continue; (statement, no children) 122const ND_DOWHILE: i64 = 28 // do body while(cond): a=cond, b=body 123// ---- R-JS-SYNTAX2 (rung 8): for-of / for-in / switch-case ---- 124const ND_FOR_OF: i64 = 29 // for(var x of iter) body: a=var-target(VAR_DECL|IDENT), b=iterable-expr, c=body 125const ND_FOR_IN: i64 = 30 // for(var k in obj) body: a=var-target(VAR_DECL|IDENT), b=object-expr, c=body 126const ND_SWITCH: i64 = 31 // switch(disc){...}: LIST-node a=case_list_child_start, b=case_count, c=disc-expr (children=ND_CASE; a/b match jp_child_at) 127const ND_CASE: i64 = 32 // one case/default clause: LIST-node a=stmt_list_child_start, b=stmt_count, c=case-expr(-1 for default) 128const ND_SPREAD: i64 = 33 // ...expr (spread element in an array literal; a=inner expr) 129const ND_ARRAY_PAT: i64 = 34 // [a,b] destructuring pattern (var-decl target); children = name IDENTs 130const ND_OBJ_PAT: i64 = 35 // {a,b} destructuring pattern (shorthand); children = name IDENTs 131const ND_TEMPLATE: i64 = 36 // template literal (tokidx = whole template token; interior decoded at eval) 132const ND_THIS: i64 = 37 // the `this` keyword (eval resolves via the call frame's this binding) 133const ND_NEW: i64 = 38 // new C(args): extra=0 -> a=ND_CALL node; extra=1 -> a=callee (bare `new C`) 134const ND_UPDATE: i64 = 39 // ++x/--x/x++/x-- : a=lvalue (IDENT/MEMBER/INDEX); extra: 1=++pre 2=--pre 3=++post 4=--post 135const ND_TRY: i64 = 40 // try{a}catch(b){c}finally{slot4}: a=try BLOCK, b=catch-param IDENT(-1), c=catch BLOCK(-1), tokidx-slot=finally BLOCK(-1) 136const ND_THROW: i64 = 41 // throw expr: a=expr 137const ND_VAR_LIST: i64 = 42 // multi-declarator `var a, b=c, d;` -- LIST-node a=child_start b=count (children=ND_VAR_DECL) 138const ND_CLASS: i64 = 43 // class Name{constructor(){} m(){}...}: a=name IDENT, b=ctor ND_FUNC_DECL, c=methods ND_BLOCK (each child = a method ND_FUNC_DECL whose name IDENT is the method name); prototype methods -> instances via func_prototype+obj_set 139const ND_REGEX: i64 = 44 // regex literal: tokidx = whole /pattern/flags token; pattern+flags extracted + compiled (nx_rxfull) at eval 140const ND_SUPER: i64 = 45 // the `super` keyword -- only valid as a call callee (super(...)=parent ctor) or member object (super.m(...)=parent method); eval routes both in js_eval_call 141const ND_SEQ: i64 = 46 // comma/sequence expr `a, b, c` -- LIST node a=child_start b=count; eval each, value = last 142const ND_HOLE: i64 = 47 // ARRAY ELISION `[0,4,,5]` -- an omitted element. Evaluates to undefined 143const ND_YIELD: i64 = 48 // `yield [expr]` / `yield* expr` (generators, JS-SOTA phase 1 2026-07-29): 144 // a=operand node or -1. PARSE-COMPLETE, execution-STUBBED: eval -> undefined 145 // (unblocks the netflix parse frontier at 860,389 = `function*`; true 146 // iteration semantics = a later rung, honestly scoped) 147 // and still OCCUPIES a slot, so length stays 4 (real JS holes are 148 // sparse; we model them as a present-but-undefined cell, which is 149 // indistinguishable for read/length -- `in` / hasOwnProperty differ). 150 // MEASURED 2026-07-27: TypeScript's downlevel async helper emits 151 // `t.trys.push([0,4,,5])`, so this blocked every TS-compiled bundle. 152 153// operator codes (stored in node slot `extra` for BINARY/UNARY/ASSIGN/VAR_DECL kw) 154const OP_ADD: i64 = 1 // + 155const OP_SUB: i64 = 2 // - 156const OP_MUL: i64 = 3 // * 157const OP_DIV: i64 = 4 // / 158const OP_MOD: i64 = 5 // % 159const OP_LT: i64 = 6 // < 160const OP_LE: i64 = 7 // <= 161const OP_GT: i64 = 8 // > 162const OP_GE: i64 = 9 // >= 163const OP_EQ: i64 = 10 // == 164const OP_NE: i64 = 11 // != 165const OP_SEQ: i64 = 12 // === 166const OP_SNE: i64 = 13 // !== 167const OP_AND: i64 = 14 // && 168const OP_OR: i64 = 15 // || 169const OP_NOT: i64 = 16 // ! (unary) 170const OP_NEG: i64 = 17 // - (unary) 171const OP_POS: i64 = 18 // + (unary) 172const OP_TYPEOF: i64 = 19 // typeof (unary) 173const OP_NULLISH: i64 = 20 // ?? (nullish coalescing, short-circuit) 174const OP_BAND: i64 = 21 // & (bitwise AND) 175const OP_BOR: i64 = 22 // | (bitwise OR) 176const OP_BXOR: i64 = 23 // ^ (bitwise XOR) 177const OP_SHL: i64 = 24 // << (left shift) 178const OP_SHR: i64 = 25 // >> (signed right shift) 179const OP_USHR: i64 = 26 // >>> (unsigned right shift) 180const OP_BNOT: i64 = 27 // ~ (unary bitwise NOT) 181const OP_INSTANCEOF: i64 = 28 // `a instanceof B` -> true if B.prototype is on a's [[Prototype]] chain 182const OP_IN: i64 = 29 // `k in obj` -> true if obj has property k (own or inherited) 183const OP_DELETE: i64 = 30 // `delete obj[k]` / `delete obj.k` -> remove property, return true (unary, ref-based) 184const OP_VOID: i64 = 31 // `void x` -> evaluate x (side effects) then return undefined (minified `void 0`=undefined) 185 186// ===================== arena layout ===================== 187// Node arena: NODE_SLOTS i64 per node. 188// [0]=nkind [1]=a [2]=b [3]=c [4]=tokidx [5]=extra(op or kw-code) 189const NODE_SLOTS: i64 = 6 190 191// Parser state box (pst, *i64): 192// [0]=cur token index 193// [1]=error flag (0=ok, 1=error seen) 194// [2]=node count 195// [3]=child-list count (next free slot in child arena) 196const PST_CUR: i64 = 0 197const PST_ERR: i64 = 1 198const PST_NNODE: i64 = 2 199const PST_NCHILD: i64 = 3 200const PST_ERR_POS: i64 = 4 // source byte offset of the FIRST error token (-1 = none); for parser-gap location 201 202// ---- A parse context bundles the pointers the recursive functions need. 203// Passed explicitly (NishiLang has no closures); kept in a small i64 box 204// array `ctx` so we thread ONE pointer rather than six. ---- 205// ctx (*i64): 206// [0]=src (*u8 as i64) [1]=toks (*i64 as i64) [2]=ntok 207// [3]=nodes (*i64 as i64) [4]=children (*i64 as i64) 208// [5]=pst (*i64 as i64) [6]=maxnodes [7]=maxchildren 209const CTX_SRC: i64 = 0 210const CTX_TOKS: i64 = 1 211const CTX_NTOK: i64 = 2 212const CTX_NODES: i64 = 3 213const CTX_CHILDREN: i64 = 4 214const CTX_PST: i64 = 5 215const CTX_MAXNODE: i64 = 6 216const CTX_MAXCHILD: i64 = 7 217 218// ---- accessors (keep call sites readable) ---- 219func jp_pst(ctx: *i64) -> *i64 { return (ctx[CTX_PST]) as *i64 } 220func jp_toks(ctx: *i64) -> *i64 { return (ctx[CTX_TOKS]) as *i64 } 221func jp_nodes(ctx: *i64) -> *i64 { return (ctx[CTX_NODES]) as *i64 } 222func jp_children(ctx: *i64) -> *i64 { return (ctx[CTX_CHILDREN]) as *i64 } 223func jp_src(ctx: *i64) -> *u8 { return (ctx[CTX_SRC]) as *u8 } 224 225func jp_cur(ctx: *i64) -> i64 { let p: *i64 = jp_pst(ctx); return p[PST_CUR] } 226func jp_set_cur(ctx: *i64, v: i64) -> i64 { let p: *i64 = jp_pst(ctx); p[PST_CUR] = v; return 0 } 227func jp_err(ctx: *i64) -> i64 { let p: *i64 = jp_pst(ctx); return p[PST_ERR] } 228func jp_err_pos(ctx: *i64) -> i64 { let p: *i64 = jp_pst(ctx); return p[PST_ERR_POS] } 229func jp_set_err(ctx: *i64) -> i64 { let p: *i64 = jp_pst(ctx); if p[PST_ERR] == 0 { p[PST_ERR_POS] = jp_tok_start(ctx) } p[PST_ERR] = 1; return 0 } 230 231// ---- token peeking. Returns the token KIND at the cursor (or EOF if past end). ---- 232func jp_tok_kind(ctx: *i64) -> i64 { 233 let p: *i64 = jp_pst(ctx) 234 let i: i64 = p[PST_CUR] 235 if i >= ctx[CTX_NTOK] { return JS_TOK_EOF } 236 let toks: *i64 = jp_toks(ctx) 237 return toks[i * 3 + 0] 238} 239func jp_tok_start(ctx: *i64) -> i64 { 240 let p: *i64 = jp_pst(ctx) 241 let i: i64 = p[PST_CUR] 242 if i >= ctx[CTX_NTOK] { return 0 } 243 let toks: *i64 = jp_toks(ctx) 244 return toks[i * 3 + 1] 245} 246func jp_tok_len(ctx: *i64) -> i64 { 247 let p: *i64 = jp_pst(ctx) 248 let i: i64 = p[PST_CUR] 249 if i >= ctx[CTX_NTOK] { return 0 } 250 let toks: *i64 = jp_toks(ctx) 251 return toks[i * 3 + 2] 252} 253func jp_advance(ctx: *i64) -> i64 { 254 let p: *i64 = jp_pst(ctx) 255 p[PST_CUR] = p[PST_CUR] + 1 256 return 0 257} 258 259// true (1) iff current token is PUNCT/KEYWORD with exact lexeme == lit (NUL-term). 260func jp_is_lex(ctx: *i64, kind: i64, lit: *u8) -> i64 { 261 if jp_tok_kind(ctx) != kind { return 0 } 262 return js_lexeme_eq(jp_src(ctx), jp_tok_start(ctx), jp_tok_len(ctx), lit) 263} 264func jp_is_punct(ctx: *i64, lit: *u8) -> i64 { return jp_is_lex(ctx, JS_TOK_PUNCT, lit) } 265func jp_is_kw(ctx: *i64, lit: *u8) -> i64 { return jp_is_lex(ctx, JS_TOK_KEYWORD, lit) } 266 267// ---- absolute-index token peeking (for arrow-function lookahead) ---- 268// Read kind/start/len of the token at ABSOLUTE index `i` (EOF/0 past the end). These 269// power the cover-grammar disambiguation `( ... ) =>` (arrow param list) vs `( ... )` 270// (a parenthesized expression): the parser must look PAST the matching ')' for `=>`. 271func jp_kind_at(ctx: *i64, i: i64) -> i64 { 272 if i < 0 { return JS_TOK_EOF } 273 if i >= ctx[CTX_NTOK] { return JS_TOK_EOF } 274 let toks: *i64 = jp_toks(ctx) 275 return toks[i * 3 + 0] 276} 277// 1 iff the token at ABSOLUTE index `i` is a PUNCT whose lexeme == lit (NUL-term). 278func jp_punct_at(ctx: *i64, i: i64, lit: *u8) -> i64 { 279 if i < 0 { return 0 } 280 if i >= ctx[CTX_NTOK] { return 0 } 281 let toks: *i64 = jp_toks(ctx) 282 if toks[i * 3 + 0] != JS_TOK_PUNCT { return 0 } 283 return js_lexeme_eq(jp_src(ctx), toks[i * 3 + 1], toks[i * 3 + 2], lit) 284} 285// ARROW lookahead for the paren form: the cursor sits on '('. Scan forward tracking 286// paren NESTING depth to the MATCHING ')', then return 1 iff the very next token is 287// '=>'. Pure scan (no node creation, no cursor mutation) so a non-arrow '(' falls 288// through untouched to the normal parenthesized-expression path. Returns 0 if the 289// parens are unbalanced before EOF (a real syntax error handled downstream). 290func jp_arrow_paren_ahead(ctx: *i64) -> i64 { 291 var i: i64 = jp_cur(ctx) 292 var depth: i64 = 0 293 var go: i64 = 1 294 var found: i64 = 0 295 while go == 1 { 296 if i >= ctx[CTX_NTOK] { go = 0 } 297 else { 298 if jp_punct_at(ctx, i, "(\x00" as *u8) == 1 { depth = depth + 1 } 299 else { if jp_punct_at(ctx, i, ")\x00" as *u8) == 1 { 300 depth = depth - 1 301 if depth == 0 { found = i; go = 0 } 302 } } 303 i = i + 1 304 } 305 } 306 if found == 0 { if depth != 0 { return 0 } } 307 if depth != 0 { return 0 } 308 // token AFTER the matching ')' (at index found+1) must be '=>'. 309 return jp_punct_at(ctx, found + 1, "=>\x00" as *u8) 310} 311 312// Consume the current token IF it matches the punct lexeme; return 1 on match (and 313// advance), else 0. Does NOT error -- callers decide if absence is fatal. 314func jp_eat_punct(ctx: *i64, lit: *u8) -> i64 { 315 if jp_is_punct(ctx, lit) == 1 { jp_advance(ctx); return 1 } 316 return 0 317} 318 319// ---- ASI (Automatic Semicolon Insertion) support, real-JS-lite ---- 320// The lexer DROPS whitespace/line-terminators (they live in the GAP between the 321// previous token's end offset and the current token's start offset). To honor ASI 322// we scan that gap for a line terminator. A statement may terminate WITHOUT an 323// explicit ';' ONLY at: a line break, before '}', or at EOF -- NEVER between two 324// tokens on the same line (real JS makes `a b` / `1 2` a SyntaxError). 325 326// end offset (start+length) of the token JUST BEFORE the cursor; -1 if at start. 327func jp_prev_end(ctx: *i64) -> i64 { 328 let i: i64 = jp_cur(ctx) - 1 329 if i < 0 { return -1 } 330 if i >= ctx[CTX_NTOK] { return -1 } 331 let toks: *i64 = jp_toks(ctx) 332 return toks[i * 3 + 1] + toks[i * 3 + 2] 333} 334 335// 1 iff a line terminator (\n=10 or \r=13) appears in src between the previous 336// token's end and the current token's start (i.e. the inter-token trivia gap). 337// If there is no previous token, or no current token (EOF), treat as a break (1). 338func jp_newline_before(ctx: *i64) -> i64 { 339 let pe: i64 = jp_prev_end(ctx) 340 if pe < 0 { return 1 } 341 if jp_cur(ctx) >= ctx[CTX_NTOK] { return 1 } // EOF after last token = break 342 let cs: i64 = jp_tok_start(ctx) 343 let src: *u8 = jp_src(ctx) 344 var i: i64 = pe 345 while i < cs { 346 let ch: i64 = src[i] & 0xff 347 if ch == 10 { return 1 } 348 if ch == 13 { return 1 } 349 i = i + 1 350 } 351 return 0 352} 353 354// 1 iff a statement may legally terminate at the cursor per ASI-lite: 355// - current token is EOF, or 356// - current token is '}' (block close), or 357// - a line terminator precedes the current token. 358func jp_can_asi(ctx: *i64) -> i64 { 359 if jp_tok_kind(ctx) == JS_TOK_EOF { return 1 } 360 if jp_cur(ctx) >= ctx[CTX_NTOK] { return 1 } 361 if jp_is_punct(ctx, "}\x00" as *u8) == 1 { return 1 } 362 return jp_newline_before(ctx) 363} 364 365// 1 iff the parsed node kind is a valid assignment TARGET (lvalue): 366// identifier, member access a.b, or computed index a[b]. Real JS rejects 367// assigning to anything else ('1 = 2' -> SyntaxError: Invalid left-hand side). 368func jp_is_lvalue(ctx: *i64, idx: i64) -> i64 { 369 let k: i64 = jp_nkind(ctx, idx) 370 if k == ND_IDENT { return 1 } 371 if k == ND_MEMBER { return 1 } 372 if k == ND_INDEX { return 1 } 373 return 0 374} 375 376// ---- node allocation. Returns the new node's INDEX (>=0), or -1 if arena full. ---- 377func jp_new_node(ctx: *i64, nkind: i64, a: i64, b: i64, c: i64, tokidx: i64, extra: i64) -> i64 { 378 let p: *i64 = jp_pst(ctx) 379 let idx: i64 = p[PST_NNODE] 380 if idx >= ctx[CTX_MAXNODE] { jp_set_err(ctx); return -1 } 381 let nodes: *i64 = jp_nodes(ctx) 382 let base: i64 = idx * NODE_SLOTS 383 nodes[base + 0] = nkind 384 nodes[base + 1] = a 385 nodes[base + 2] = b 386 nodes[base + 3] = c 387 nodes[base + 4] = tokidx 388 nodes[base + 5] = extra 389 p[PST_NNODE] = idx + 1 390 return idx 391} 392func jp_error_node(ctx: *i64) -> i64 { 393 jp_set_err(ctx) 394 return jp_new_node(ctx, ND_ERROR, -1, -1, -1, jp_cur(ctx), 0) 395} 396 397// ---- node field reads (used by the gate to assert structure) ---- 398func jp_nkind(ctx: *i64, idx: i64) -> i64 { let n: *i64 = jp_nodes(ctx); return n[idx * NODE_SLOTS + 0] } 399func jp_na(ctx: *i64, idx: i64) -> i64 { let n: *i64 = jp_nodes(ctx); return n[idx * NODE_SLOTS + 1] } 400func jp_nb(ctx: *i64, idx: i64) -> i64 { let n: *i64 = jp_nodes(ctx); return n[idx * NODE_SLOTS + 2] } 401func jp_nc(ctx: *i64, idx: i64) -> i64 { let n: *i64 = jp_nodes(ctx); return n[idx * NODE_SLOTS + 3] } 402func jp_nextra(ctx: *i64, idx: i64) -> i64 { let n: *i64 = jp_nodes(ctx); return n[idx * NODE_SLOTS + 5] } 403 404// child-list arena append: store a node index, return the slot it landed in. 405func jp_child_push(ctx: *i64, node_idx: i64) -> i64 { 406 let p: *i64 = jp_pst(ctx) 407 let slot: i64 = p[PST_NCHILD] 408 if slot >= ctx[CTX_MAXCHILD] { jp_set_err(ctx); return -1 } 409 let ch: *i64 = jp_children(ctx) 410 ch[slot] = node_idx 411 p[PST_NCHILD] = slot + 1 412 return slot 413} 414// read child k of a list-node whose a=child_start, b=child_count 415func jp_child_at(ctx: *i64, list_idx: i64, k: i64) -> i64 { 416 let start: i64 = jp_na(ctx, list_idx) 417 let ch: *i64 = jp_children(ctx) 418 return ch[start + k] 419} 420 421// Copy `count` node-indices from a caller scratch buffer into a CONTIGUOUS run of 422// the shared child arena; return the start slot. List nodes (PROGRAM/BLOCK/CALL/ 423// PARAMS) buffer their direct children in local scratch FIRST -- because a nested 424// list (e.g. a call inside an argument) pushes into the same arena mid-parse and 425// would otherwise interleave -- then commit them contiguously here so child_at is 426// correct. Returns -1 (and sets error) if the arena would overflow. 427func jp_child_commit(ctx: *i64, scratch: *i64, count: i64) -> i64 { 428 let p: *i64 = jp_pst(ctx) 429 let start: i64 = p[PST_NCHILD] 430 if start + count > ctx[CTX_MAXCHILD] { jp_set_err(ctx); return -1 } 431 let ch: *i64 = jp_children(ctx) 432 var i: i64 = 0 433 while i < count { 434 ch[start + i] = scratch[i] 435 i = i + 1 436 } 437 p[PST_NCHILD] = start + count 438 return start 439} 440 441// ===================== expression parsing (precedence climb) ===================== 442// forward refs are fine (whole-program resolution): parse_expr is the entry, the 443// binary tiers call down to parse_unary -> parse_postfix -> parse_primary. 444 445// array literal: [ e0 , e1 , ... ] (trailing comma + empty [] allowed) -> ND_ARRAY 446// list-node a=child_start, b=elem_count. Cursor is on '['. Elements are assignment- 447// expressions (so a comma operator does not bleed across element boundaries). 448func jp_parse_array(ctx: *i64) -> i64 { 449 jp_advance(ctx) // consume '[' 450 let scratch: *i64 = sys_mmap(ND_MAGIC_1024 * 8) as *i64 451 var count: i64 = 0 452 var go: i64 = 1 453 if jp_is_punct(ctx, "]\x00" as *u8) == 1 { jp_advance(ctx); go = 0 } // empty [] 454 while go == 1 { 455 var el: i64 = 0 456 if jp_is_punct(ctx, ",\x00" as *u8) == 1 { // ELISION: `[0,4,,5]` / `[,1]` 457 // An element position holding only a comma is an omitted element. Emit a HOLE 458 // and fall through to the separator logic below, which consumes that comma -- 459 // so the slot is counted and the following element parses normally. 460 el = jp_new_node(ctx, ND_HOLE, -1, -1, -1, -1, 0) 461 } else { 462 if jp_is_punct(ctx, "...\x00" as *u8) == 1 { // spread element [...a] 463 jp_advance(ctx) 464 let inner: i64 = jp_parse_assign(ctx) 465 el = jp_new_node(ctx, ND_SPREAD, inner, -1, -1, -1, 0) 466 } else { 467 el = jp_parse_assign(ctx) 468 } 469 } 470 if count < ND_MAGIC_1024 { scratch[count] = el } 471 count = count + 1 472 if jp_err(ctx) == 1 { go = 0 } 473 if go == 1 { 474 if jp_eat_punct(ctx, ",\x00" as *u8) == 1 { 475 // trailing comma before ']' is allowed: [1,2,] 476 if jp_is_punct(ctx, "]\x00" as *u8) == 1 { jp_advance(ctx); go = 0 } 477 } else { 478 if jp_eat_punct(ctx, "]\x00" as *u8) == 1 { go = 0 } 479 else { jp_set_err(ctx); go = 0 } 480 } 481 } 482 } 483 if jp_err(ctx) == 1 { return jp_error_node(ctx) } 484 let start: i64 = jp_child_commit(ctx, scratch, count) 485 return jp_new_node(ctx, ND_ARRAY, start, count, -1, -1, 0) 486} 487 488// object literal: { key : expr , ... } where key is an IDENT or STRING token. 489// (trailing comma + empty {} allowed) -> ND_OBJECT list-node a=child_start, b=prop_count. 490// Each child is an ND_PROP whose tokidx = key token and a = value-expr node. Cursor is 491// on '{'. Values are assignment-expressions. Keyword-named keys (e.g. {return:1}) are an 492// honest OPEN here (only IDENT/STRING keys are accepted; others -> ERROR, never faked). 493func jp_parse_object(ctx: *i64) -> i64 { 494 jp_advance(ctx) // consume '{' 495 let scratch: *i64 = sys_mmap(ND_MAGIC_1024 * 8) as *i64 496 var count: i64 = 0 497 var go: i64 = 1 498 if jp_is_punct(ctx, "}\x00" as *u8) == 1 { jp_advance(ctx); go = 0 } // empty {} 499 while go == 1 { 500 // key = IdentifierName (incl. reserved words) | StringLiteral | NumericLiteral (real JS: any reserved 501 // word is a valid property name -- minified jQuery ships {async:!0,default:...,delete:...,in:...}). 502 // OBJECT SPREAD `{...e, b:2}` (ES2018; the netflix frontier after accessors: `{...e.style}`). 503 // Reuses ND_SPREAD (already used by array literals + call args) so eval has ONE spread shape. 504 var didspread: i64 = 0 505 if jp_is_punct(ctx, "...\x00" as *u8) == 1 { 506 didspread = 1 507 jp_advance(ctx) 508 let sinner: i64 = jp_parse_assign(ctx) 509 let sprop: i64 = jp_new_node(ctx, ND_SPREAD, sinner, -1, -1, -1, 0) 510 if count < ND_MAGIC_1024 { scratch[count] = sprop } 511 count = count + 1 512 if jp_err(ctx) == 1 { go = 0 } 513 if go == 1 { 514 if jp_eat_punct(ctx, ",\x00" as *u8) == 1 { 515 if jp_is_punct(ctx, "}\x00" as *u8) == 1 { jp_advance(ctx); go = 0 } 516 } else { 517 if jp_eat_punct(ctx, "}\x00" as *u8) == 1 { go = 0 } 518 else { jp_set_err(ctx); go = 0 } 519 } 520 } 521 } 522 var keyok: i64 = 0 523 if didspread == 0 { 524 if jp_tok_kind(ctx) == JS_TOK_IDENT { keyok = 1 } 525 if jp_tok_kind(ctx) == JS_TOK_STRING { keyok = 1 } 526 if jp_tok_kind(ctx) == JS_TOK_KEYWORD { keyok = 1 } 527 if jp_tok_kind(ctx) == JS_TOK_NUMBER { keyok = 1 } 528 if keyok == 0 { jp_set_err(ctx); go = 0 } 529 } else { go = go * 0 + go } // spread already consumed this member (+ its separator) 530 // ACCESSORS `{get k(){...}, set k(v){...}}` (ES5, ubiquitous in modern bundles -- the netflix 531 // frontier after generators: `{get passive(){o=!0;return}}`). `get`/`set` are CONTEXTUAL: a 532 // real accessor has get/set followed by a KEY token, whereas `{get:1}` / `{get(){}}` use them 533 // as plain names -- so only treat as accessor when the NEXT token is a key and not ':' or '('. 534 // Parsed as a normal method-valued property (the getter body becomes the value); true 535 // accessor invocation semantics = a later rung, declared not faked. 536 if go == 1 { if jp_tok_kind(ctx) == JS_TOK_IDENT { 537 var isacc: i64 = 0 538 if jp_is_lex(ctx, JS_TOK_IDENT, "get\x00" as *u8) == 1 { isacc = 1 } 539 if jp_is_lex(ctx, JS_TOK_IDENT, "set\x00" as *u8) == 1 { isacc = 1 } 540 if isacc == 1 { 541 let nk: i64 = jp_kind_at(ctx, jp_cur(ctx) + 1) // ⚠ABSOLUTE index (not an offset) 542 var nextiskey: i64 = 0 543 if nk == JS_TOK_IDENT { nextiskey = 1 } 544 if nk == JS_TOK_STRING { nextiskey = 1 } 545 if nk == JS_TOK_KEYWORD { nextiskey = 1 } 546 if nk == JS_TOK_NUMBER { nextiskey = 1 } 547 if nextiskey == 1 { jp_advance(ctx) } // consume `get`/`set`; the KEY is now current 548 } 549 } } 550 if go == 1 { 551 let keytok: i64 = jp_cur(ctx) 552 jp_advance(ctx) 553 var val: i64 = 0 554 if jp_is_punct(ctx, "(\x00" as *u8) == 1 { // method shorthand: key(params){body} 555 let mparams: i64 = jp_parse_params(ctx) 556 let mbody: i64 = jp_parse_block(ctx) 557 val = jp_new_node(ctx, ND_FUNC_DECL, -1, mparams, mbody, -1, 0) 558 if jp_err(ctx) == 1 { go = 0 } 559 } else { 560 if jp_eat_punct(ctx, ":\x00" as *u8) == 0 { jp_set_err(ctx); go = 0 } 561 if go == 1 { val = jp_parse_assign(ctx) } 562 } 563 if go == 1 { 564 let prop: i64 = jp_new_node(ctx, ND_PROP, val, -1, -1, keytok, 0) 565 if count < ND_MAGIC_1024 { scratch[count] = prop } 566 count = count + 1 567 if jp_err(ctx) == 1 { go = 0 } 568 if go == 1 { 569 if jp_eat_punct(ctx, ",\x00" as *u8) == 1 { 570 // trailing comma before '}' is allowed: {a:1,} 571 if jp_is_punct(ctx, "}\x00" as *u8) == 1 { jp_advance(ctx); go = 0 } 572 } else { 573 if jp_eat_punct(ctx, "}\x00" as *u8) == 1 { go = 0 } 574 else { jp_set_err(ctx); go = 0 } 575 } 576 } 577 } 578 } 579 } 580 if jp_err(ctx) == 1 { return jp_error_node(ctx) } 581 let start: i64 = jp_child_commit(ctx, scratch, count) 582 return jp_new_node(ctx, ND_OBJECT, start, count, -1, -1, 0) 583} 584 585// `new C(args)` -> ND_NEW. extra=0: a = the ND_CALL node (callee + args). extra=1: a = callee (bare `new C`). 586func jp_parse_new(ctx: *i64) -> i64 { 587 jp_advance(ctx) // consume 'new' 588 var callee: i64 = jp_parse_primary(ctx) 589 if jp_err(ctx) == 1 { return jp_error_node(ctx) } 590 // MemberExpression tail: `new A.b.c(...)` / `new A[k](...)` -- the constructor is the FULL member chain 591 // (`.prop` and `[expr]`), but NOT a call '(' (that begins the `new` Arguments). Mirrors jp_parse_postfix 592 // minus the call tail. (Guarded-if form: NishiLang has no `else if`.) 593 var go: i64 = 1 594 while go == 1 { 595 if jp_err(ctx) == 1 { go = 0 } 596 if go == 1 { 597 if jp_is_punct(ctx, ".\x00" as *u8) == 1 { 598 jp_advance(ctx) 599 if jp_tok_kind(ctx) != JS_TOK_IDENT { if jp_tok_kind(ctx) != JS_TOK_KEYWORD { callee = jp_error_node(ctx); go = 0 } } 600 if go == 1 { 601 let prop: i64 = jp_new_node(ctx, ND_IDENT, -1, -1, -1, jp_cur(ctx), 0) 602 jp_advance(ctx) 603 callee = jp_new_node(ctx, ND_MEMBER, callee, prop, -1, -1, 0) 604 } 605 } else { 606 if jp_is_punct(ctx, "[\x00" as *u8) == 1 { 607 jp_advance(ctx) 608 let ix: i64 = jp_parse_expr(ctx) 609 if jp_eat_punct(ctx, "]\x00" as *u8) == 0 { callee = jp_error_node(ctx); go = 0 } 610 if go == 1 { callee = jp_new_node(ctx, ND_INDEX, callee, ix, -1, -1, 0) } 611 } else { 612 go = 0 613 } 614 } 615 } 616 } 617 if jp_err(ctx) == 1 { return jp_error_node(ctx) } 618 if jp_is_punct(ctx, "(\x00" as *u8) == 1 { 619 let callnode: i64 = jp_parse_call_tail(ctx, callee, 0) 620 return jp_new_node(ctx, ND_NEW, callnode, -1, -1, -1, 0) 621 } 622 return jp_new_node(ctx, ND_NEW, callee, -1, -1, -1, 1) 623} 624// `class Name { constructor(params){body} m(params){body}... }` -> ND_CLASS (a=name, b=ctor ND_FUNC_DECL, 625// c=methods ND_BLOCK of method ND_FUNC_DECLs). Prototype methods are now KEPT (was: dropped). No extends/ 626// static/getters yet (those decline gracefully at parse or eval). Method-node = ND_FUNC_DECL(name,params,body). 627func jp_parse_class_decl(ctx: *i64) -> i64 { 628 jp_advance(ctx) // consume 'class' 629 if jp_tok_kind(ctx) != JS_TOK_IDENT { return jp_error_node(ctx) } 630 let nametok: i64 = jp_cur(ctx) 631 let name: i64 = jp_new_node(ctx, ND_IDENT, -1, -1, -1, nametok, 0) 632 jp_advance(ctx) 633 // optional `extends <expr>` -- superclass is usually a bare ident (`extends A`) but may be a 634 // member (`extends React.Component`); parse a postfix so both shapes work. Stored in ND_CLASS extra. 635 var superidx: i64 = 0 - 1 636 if jp_is_kw(ctx, "extends\x00" as *u8) == 1 { 637 jp_advance(ctx) 638 superidx = jp_parse_postfix(ctx) 639 if jp_err(ctx) == 1 { return jp_error_node(ctx) } 640 } 641 if jp_eat_punct(ctx, "{\x00" as *u8) == 0 { return jp_error_node(ctx) } 642 var cparams: i64 = 0 - 1 643 var cbody: i64 = 0 - 1 644 let mscratch: *i64 = sys_mmap(ND_MAGIC_1024 * 8) as *i64 // collected method func-decl nodes 645 var mcount: i64 = 0 646 var go: i64 = 1 647 while go == 1 { 648 if jp_is_punct(ctx, "}\x00" as *u8) == 1 { jp_advance(ctx); go = 0 } 649 else { 650 var isok: i64 = 0 651 if jp_tok_kind(ctx) == JS_TOK_IDENT { isok = 1 } 652 if jp_tok_kind(ctx) == JS_TOK_KEYWORD { isok = 1 } 653 if isok == 0 { jp_set_err(ctx); go = 0 } 654 if go == 1 { 655 let is_ctor: i64 = jp_is_lex(ctx, JS_TOK_IDENT, "constructor\x00" as *u8) 656 let mnametok: i64 = jp_cur(ctx) 657 jp_advance(ctx) // method name 658 let mparams: i64 = jp_parse_params(ctx) 659 let mbody: i64 = jp_parse_block(ctx) 660 if jp_err(ctx) == 1 { go = 0 } 661 if go == 1 { 662 if is_ctor == 1 { cparams = mparams; cbody = mbody } 663 else { 664 let mname: i64 = jp_new_node(ctx, ND_IDENT, -1, -1, -1, mnametok, 0) 665 let mfd: i64 = jp_new_node(ctx, ND_FUNC_DECL, mname, mparams, mbody, -1, 0) 666 if mcount < ND_MAGIC_1024 { mscratch[mcount] = mfd } 667 mcount = mcount + 1 668 } 669 } 670 } 671 } 672 } 673 if jp_err(ctx) == 1 { return jp_error_node(ctx) } 674 if cparams == (0 - 1) { 675 if superidx >= 0 { 676 // DERIVED class with no explicit ctor -> synthesize `constructor(...N){ super(...N) }` so 677 // `new B(args)` runs the PARENT ctor with the forwarded args (spec's implicit derived ctor). 678 // N reuses the class-name token (a harmless local rest param; the body only spreads it). 679 let restp: i64 = jp_new_node(ctx, ND_IDENT, -1, -1, -1, nametok, 1) // rest param (extra=1=rest) 680 let pscr: *i64 = sys_mmap(8) as *i64 681 pscr[0] = restp 682 let pstart: i64 = jp_child_commit(ctx, pscr, 1) 683 cparams = jp_new_node(ctx, ND_PARAMS, pstart, 1, -1, -1, 0) 684 let argid: i64 = jp_new_node(ctx, ND_IDENT, -1, -1, -1, nametok, 0) // reference to N 685 let spread: i64 = jp_new_node(ctx, ND_SPREAD, argid, -1, -1, -1, 0) // ...N 686 let ascr: *i64 = sys_mmap(8) as *i64 687 ascr[0] = spread 688 let astart: i64 = jp_child_commit(ctx, ascr, 1) 689 let supnode: i64 = jp_new_node(ctx, ND_SUPER, -1, -1, -1, nametok, 0) 690 let scall: i64 = jp_new_node(ctx, ND_CALL, supnode, astart, 1, -1, 0) // super(...N) 691 let estmt: i64 = jp_new_node(ctx, ND_EXPR_STMT, scall, -1, -1, -1, 0) 692 let bscr: *i64 = sys_mmap(8) as *i64 693 bscr[0] = estmt 694 let bstart: i64 = jp_child_commit(ctx, bscr, 1) 695 cbody = jp_new_node(ctx, ND_BLOCK, bstart, 1, -1, -1, 0) 696 } else { // non-derived class, no ctor -> empty function 697 let scratch: *i64 = sys_mmap(8) as *i64 698 let ps: i64 = jp_child_commit(ctx, scratch, 0) 699 cparams = jp_new_node(ctx, ND_PARAMS, ps, 0, -1, -1, 0) 700 let bs: i64 = jp_child_commit(ctx, scratch, 0) 701 cbody = jp_new_node(ctx, ND_BLOCK, bs, 0, -1, -1, 0) 702 } 703 } 704 let ctorfd: i64 = jp_new_node(ctx, ND_FUNC_DECL, name, cparams, cbody, -1, 0) 705 let mstart: i64 = jp_child_commit(ctx, mscratch, mcount) 706 let methods: i64 = jp_new_node(ctx, ND_BLOCK, mstart, mcount, -1, -1, 0) 707 return jp_new_node(ctx, ND_CLASS, name, ctorfd, methods, -1, superidx) 708} 709// primary: literal / ident / parenthesized expr 710func jp_parse_primary(ctx: *i64) -> i64 { 711 if jp_err(ctx) == 1 { return jp_error_node(ctx) } 712 let k: i64 = jp_tok_kind(ctx) 713 let tk: i64 = jp_cur(ctx) 714 715 if k == JS_TOK_NUMBER { 716 let n: i64 = jp_new_node(ctx, ND_NUMBER, -1, -1, -1, tk, 0) 717 jp_advance(ctx) 718 return n 719 } 720 if k == JS_TOK_STRING { 721 let n: i64 = jp_new_node(ctx, ND_STRING, -1, -1, -1, tk, 0) 722 jp_advance(ctx) 723 return n 724 } 725 if k == JS_TOK_TEMPLATE { 726 let n: i64 = jp_new_node(ctx, ND_TEMPLATE, -1, -1, -1, tk, 0) 727 jp_advance(ctx) 728 return n 729 } 730 if k == JS_TOK_REGEX { 731 let n: i64 = jp_new_node(ctx, ND_REGEX, -1, -1, -1, tk, 0) 732 jp_advance(ctx) 733 return n 734 } 735 if k == JS_TOK_IDENT { 736 let n: i64 = jp_new_node(ctx, ND_IDENT, -1, -1, -1, tk, 0) 737 jp_advance(ctx) 738 return n 739 } 740 if k == JS_TOK_KEYWORD { 741 if jp_is_kw(ctx, "true\x00" as *u8) == 1 { 742 let n: i64 = jp_new_node(ctx, ND_BOOL, -1, -1, -1, tk, 1) 743 jp_advance(ctx); return n 744 } 745 if jp_is_kw(ctx, "false\x00" as *u8) == 1 { 746 let n: i64 = jp_new_node(ctx, ND_BOOL, -1, -1, -1, tk, 0) 747 jp_advance(ctx); return n 748 } 749 if jp_is_kw(ctx, "this\x00" as *u8) == 1 { 750 let n: i64 = jp_new_node(ctx, ND_THIS, -1, -1, -1, tk, 0) // the this keyword; eval resolves via the frame binding 751 jp_advance(ctx); return n 752 } 753 if jp_is_kw(ctx, "super\x00" as *u8) == 1 { 754 let n: i64 = jp_new_node(ctx, ND_SUPER, -1, -1, -1, tk, 0) // valid only as call callee / member object; js_eval_call routes it 755 jp_advance(ctx); return n 756 } 757 if jp_is_kw(ctx, "null\x00" as *u8) == 1 { 758 let n: i64 = jp_new_node(ctx, ND_NULL, -1, -1, -1, tk, 0) 759 jp_advance(ctx); return n 760 } 761 // FUNCTION EXPRESSION (R-JS-CLOSURE, rung 6): `function (params){body}` or named 762 // `function name(params){body}` in expression/primary position -- so `var f = 763 // function(x){...}` and `(function(){...})()` parse. Same ND_FUNC_DECL shape as a 764 // declaration (the evaluator builds a closure when it EVALUATES this node). 765 if jp_is_kw(ctx, "new\x00" as *u8) == 1 { return jp_parse_new(ctx) } 766 if jp_is_kw(ctx, "function\x00" as *u8) == 1 { return jp_parse_func_expr(ctx) } 767 // any other keyword in expression position is a syntax error (rung 1) 768 return jp_error_node(ctx) 769 } 770 if k == JS_TOK_PUNCT { 771 if jp_is_punct(ctx, "(\x00" as *u8) == 1 { 772 jp_advance(ctx) 773 let inner: i64 = jp_parse_expr(ctx) 774 if jp_eat_punct(ctx, ")\x00" as *u8) == 0 { return jp_error_node(ctx) } 775 return inner 776 } 777 // array literal in expression position: [ ... ] 778 if jp_is_punct(ctx, "[\x00" as *u8) == 1 { return jp_parse_array(ctx) } 779 // object literal in expression position: { ... } (statement-leading '{' is a 780 // BLOCK and never reaches here -- jp_parse_stmt dispatches it before expr parsing). 781 if jp_is_punct(ctx, "{\x00" as *u8) == 1 { return jp_parse_object(ctx) } 782 return jp_error_node(ctx) 783 } 784 // EOF / ERROR token / anything else -> error 785 return jp_error_node(ctx) 786} 787 788// postfix: left-assoc chain of .ident [expr] (args...) applied to a primary 789func jp_parse_postfix(ctx: *i64) -> i64 { 790 var node: i64 = jp_parse_primary(ctx) 791 var go: i64 = 1 792 while go == 1 { 793 if jp_err(ctx) == 1 { go = 0 } 794 if go == 1 { 795 // OPTIONAL CHAINING ?. -- dispatch on what follows: ?.ident (member) / ?.( (call) / ?.[ (index). 796 // vk: `window.CSS?.supports?.(...)`. ND_MEMBER/ND_INDEX/ND_CALL carry extra=1 => short-circuit on nullish. 797 if jp_is_punct(ctx, "?.\x00" as *u8) == 1 { 798 jp_advance(ctx) // consume ?. 799 if jp_is_punct(ctx, "(\x00" as *u8) == 1 { 800 node = jp_parse_call_tail(ctx, node, 1) 801 if jp_err(ctx) == 1 { go = 0 } 802 } else { if jp_is_punct(ctx, "[\x00" as *u8) == 1 { 803 jp_advance(ctx) 804 let ix2: i64 = jp_parse_expr(ctx) 805 if jp_eat_punct(ctx, "]\x00" as *u8) == 0 { node = jp_error_node(ctx); go = 0 } 806 if go == 1 { node = jp_new_node(ctx, ND_INDEX, node, ix2, -1, -1, 1) } 807 } else { 808 if jp_tok_kind(ctx) != JS_TOK_IDENT { if jp_tok_kind(ctx) != JS_TOK_KEYWORD { node = jp_error_node(ctx); go = 0 } } 809 if go == 1 { 810 let prop2: i64 = jp_new_node(ctx, ND_IDENT, -1, -1, -1, jp_cur(ctx), 0) 811 jp_advance(ctx) 812 node = jp_new_node(ctx, ND_MEMBER, node, prop2, -1, -1, 1) 813 } 814 } } 815 } else { 816 // member: .ident 817 var isdot: i64 = 0 818 if jp_is_punct(ctx, ".\x00" as *u8) == 1 { isdot = 1 } 819 if isdot == 1 { 820 jp_advance(ctx) 821 if jp_tok_kind(ctx) != JS_TOK_IDENT { 822 if jp_tok_kind(ctx) != JS_TOK_KEYWORD { node = jp_error_node(ctx); go = 0 } 823 } 824 if go == 1 { 825 let prop: i64 = jp_new_node(ctx, ND_IDENT, -1, -1, -1, jp_cur(ctx), 0) 826 jp_advance(ctx) 827 node = jp_new_node(ctx, ND_MEMBER, node, prop, -1, -1, 0) 828 } 829 } else { 830 // computed index: [expr] 831 if jp_is_punct(ctx, "[\x00" as *u8) == 1 { 832 jp_advance(ctx) 833 let ix: i64 = jp_parse_expr(ctx) 834 if jp_eat_punct(ctx, "]\x00" as *u8) == 0 { node = jp_error_node(ctx); go = 0 } 835 if go == 1 { node = jp_new_node(ctx, ND_INDEX, node, ix, -1, -1, 0) } 836 } else { 837 // call: ( args... ) 838 if jp_is_punct(ctx, "(\x00" as *u8) == 1 { 839 node = jp_parse_call_tail(ctx, node, 0) 840 if jp_err(ctx) == 1 { go = 0 } 841 } else { 842 go = 0 843 } 844 } 845 } 846 } 847 } 848 } 849 // postfix increment/decrement: x++ / x-- (binds to the WHOLE member/index chain, one only). 850 // Valid only on a simple lvalue; `5++` stays un-consumed here and errors upstream. 851 // (ASI note: `a \n ++b` is treated as `a++ ... b` here -- line-agnostic; minified 852 // bundles have no newlines, named divergence from the spec's restricted production.) 853 if jp_err(ctx) == 0 { 854 if jp_is_lvalue(ctx, node) == 1 { 855 if jp_is_punct(ctx, "++\x00" as *u8) == 1 { jp_advance(ctx); node = jp_new_node(ctx, ND_UPDATE, node, -1, -1, -1, 3) } 856 else { if jp_is_punct(ctx, "--\x00" as *u8) == 1 { jp_advance(ctx); node = jp_new_node(ctx, ND_UPDATE, node, -1, -1, -1, 4) } } 857 } 858 } 859 return node 860} 861 862// parse `( a , b , ... )` after the callee; build a CALL node whose 863// a=callee, b=child_start, c=child_count (args live in the child arena). 864// Args are buffered in local scratch then committed CONTIGUOUSLY (a nested call 865// inside an arg pushes into the shared arena mid-parse -- see jp_child_commit). 866func jp_parse_call_tail(ctx: *i64, callee: i64, opt: i64) -> i64 { // opt=1 -> optional call a?.(args) 867 jp_advance(ctx) // consume '(' 868 let scratch: *i64 = sys_mmap(256 * 8) as *i64 869 var count: i64 = 0 870 // empty arg list? 871 if jp_is_punct(ctx, ")\x00" as *u8) == 1 { 872 jp_advance(ctx) 873 let start0: i64 = jp_child_commit(ctx, scratch, 0) 874 return jp_new_node(ctx, ND_CALL, callee, start0, 0, -1, opt) 875 } 876 var go: i64 = 1 877 while go == 1 { 878 var arg: i64 = 0 879 if jp_is_punct(ctx, "...\x00" as *u8) == 1 { // spread argument f(...xs) 880 jp_advance(ctx) 881 let inner: i64 = jp_parse_assign(ctx) 882 arg = jp_new_node(ctx, ND_SPREAD, inner, -1, -1, -1, 0) 883 } else { 884 arg = jp_parse_assign(ctx) 885 } 886 if count < 256 { scratch[count] = arg } 887 count = count + 1 888 if jp_err(ctx) == 1 { go = 0 } 889 if go == 1 { 890 if jp_eat_punct(ctx, ",\x00" as *u8) == 1 { 891 // continue to next arg 892 } else { 893 if jp_eat_punct(ctx, ")\x00" as *u8) == 1 { go = 0 } 894 else { jp_set_err(ctx); go = 0 } 895 } 896 } 897 } 898 if jp_err(ctx) == 1 { return jp_error_node(ctx) } 899 let start: i64 = jp_child_commit(ctx, scratch, count) 900 return jp_new_node(ctx, ND_CALL, callee, start, count, -1, opt) 901} 902 903// is a node a valid update/assignment TARGET (simple lvalue)? 904// (jp_is_lvalue is defined ONCE, earlier in this file at :367. A byte-identical second 905// definition -- differing only in its parameter NAME (node vs idx) -- used to sit here, 906// the classic copy-paste/merge artefact. nx_cc accepted the redefinition silently and 907// picked a winner; removed 2026-07-31 with debt 1785447657.) 908// unary prefix: ! - + typeof ++ -- (right-recursive onto another unary) 909func jp_parse_unary(ctx: *i64) -> i64 { 910 if jp_err(ctx) == 1 { return jp_error_node(ctx) } 911 // `yield [expr]` / `yield* expr` -- expression-position, lowest-binding-ish; operand optional 912 // (bare `yield` before ; ) } , ] is valid). Parsed everywhere for bundle compatibility (strict 913 // contextual legality is the engine's later concern, not the frontier's). 914 // `await expr` -- phase 3 stub: evaluate the operand and use its VALUE (exact for already-settled 915 // and synchronous values; true suspension rides the existing event loop -- declared later rung). 916 // Contextual like `async`: only when an expression actually follows (a bare `await` identifier 917 // before ) , ; ] } = stays an identifier). 918 var awk: i64 = 0 919 if jp_is_kw(ctx, "await\x00" as *u8) == 1 { awk = 1 } 920 if jp_is_lex(ctx, JS_TOK_IDENT, "await\x00" as *u8) == 1 { awk = 1 } 921 if awk == 1 { 922 let nkw: i64 = jp_kind_at(ctx, jp_cur(ctx) + 1) 923 var starts: i64 = 0 924 if nkw == JS_TOK_IDENT { starts = 1 } 925 if nkw == JS_TOK_NUMBER { starts = 1 } 926 if nkw == JS_TOK_STRING { starts = 1 } 927 if nkw == JS_TOK_KEYWORD { starts = 1 } 928 if jp_punct_at(ctx, jp_cur(ctx) + 1, "(\x00" as *u8) == 1 { starts = 1 } 929 if jp_punct_at(ctx, jp_cur(ctx) + 1, "[\x00" as *u8) == 1 { starts = 1 } 930 if starts == 1 { jp_advance(ctx); return jp_parse_unary(ctx) } 931 } 932 if jp_is_kw(ctx, "yield\x00" as *u8) == 1 { 933 jp_advance(ctx) 934 if jp_is_punct(ctx, "*\x00" as *u8) == 1 { jp_advance(ctx) } 935 var yop: i64 = 0 - 1 936 var bare: i64 = 0 937 if jp_is_punct(ctx, ";\x00" as *u8) == 1 { bare = 1 } 938 if jp_is_punct(ctx, ")\x00" as *u8) == 1 { bare = 1 } 939 if jp_is_punct(ctx, "}\x00" as *u8) == 1 { bare = 1 } 940 if jp_is_punct(ctx, ",\x00" as *u8) == 1 { bare = 1 } 941 if jp_is_punct(ctx, "]\x00" as *u8) == 1 { bare = 1 } 942 if bare == 0 { yop = jp_parse_assign(ctx) } 943 return jp_new_node(ctx, ND_YIELD, yop, -1, -1, -1, 0) 944 } 945 // prefix increment/decrement: ++x / --x ("++"/"--" are single lexer tokens, so the 946 // "+"/"-" checks below can never shadow them). Target must be a simple lvalue 947 // (IDENT/MEMBER/INDEX) -- `++5` is a SyntaxError in real JS, ERROR node here. 948 if jp_is_punct(ctx, "++\x00" as *u8) == 1 { 949 jp_advance(ctx) 950 let operand: i64 = jp_parse_unary(ctx) 951 if jp_is_lvalue(ctx, operand) == 0 { jp_set_err(ctx); return jp_error_node(ctx) } 952 return jp_new_node(ctx, ND_UPDATE, operand, -1, -1, -1, 1) 953 } 954 if jp_is_punct(ctx, "--\x00" as *u8) == 1 { 955 jp_advance(ctx) 956 let operand: i64 = jp_parse_unary(ctx) 957 if jp_is_lvalue(ctx, operand) == 0 { jp_set_err(ctx); return jp_error_node(ctx) } 958 return jp_new_node(ctx, ND_UPDATE, operand, -1, -1, -1, 2) 959 } 960 if jp_is_punct(ctx, "!\x00" as *u8) == 1 { 961 jp_advance(ctx) 962 let operand: i64 = jp_parse_unary(ctx) 963 return jp_new_node(ctx, ND_UNARY, operand, -1, -1, -1, OP_NOT) 964 } 965 if jp_is_punct(ctx, "~\x00" as *u8) == 1 { 966 jp_advance(ctx) 967 let operand: i64 = jp_parse_unary(ctx) 968 return jp_new_node(ctx, ND_UNARY, operand, -1, -1, -1, OP_BNOT) 969 } 970 if jp_is_punct(ctx, "-\x00" as *u8) == 1 { 971 jp_advance(ctx) 972 let operand: i64 = jp_parse_unary(ctx) 973 return jp_new_node(ctx, ND_UNARY, operand, -1, -1, -1, OP_NEG) 974 } 975 if jp_is_punct(ctx, "+\x00" as *u8) == 1 { 976 jp_advance(ctx) 977 let operand: i64 = jp_parse_unary(ctx) 978 return jp_new_node(ctx, ND_UNARY, operand, -1, -1, -1, OP_POS) 979 } 980 if jp_is_kw(ctx, "typeof\x00" as *u8) == 1 { 981 jp_advance(ctx) 982 let operand: i64 = jp_parse_unary(ctx) 983 return jp_new_node(ctx, ND_UNARY, operand, -1, -1, -1, OP_TYPEOF) 984 } 985 if jp_is_kw(ctx, "void\x00" as *u8) == 1 { 986 jp_advance(ctx) 987 let operand: i64 = jp_parse_unary(ctx) 988 return jp_new_node(ctx, ND_UNARY, operand, -1, -1, -1, OP_VOID) 989 } 990 if jp_is_kw(ctx, "delete\x00" as *u8) == 1 { 991 jp_advance(ctx) 992 let operand: i64 = jp_parse_unary(ctx) // a MEMBER/INDEX ref; the evaluator inspects the node, not its value 993 return jp_new_node(ctx, ND_UNARY, operand, -1, -1, -1, OP_DELETE) 994 } 995 return jp_parse_postfix(ctx) 996} 997 998// ---- binary operator tier helper: returns the op-code for the current token at a 999// given precedence tier, or 0 if the current token is not an op of that tier. 1000// Tiers are checked by dedicated functions to keep else-if nesting shallow. ---- 1001 1002// tier 1 (highest binary): * / % 1003func jp_mul_op(ctx: *i64) -> i64 { 1004 if jp_tok_kind(ctx) != JS_TOK_PUNCT { return 0 } 1005 if jp_is_punct(ctx, "*\x00" as *u8) == 1 { return OP_MUL } 1006 if jp_is_punct(ctx, "/\x00" as *u8) == 1 { return OP_DIV } 1007 if jp_is_punct(ctx, "%\x00" as *u8) == 1 { return OP_MOD } 1008 return 0 1009} 1010// tier 2: + - 1011func jp_add_op(ctx: *i64) -> i64 { 1012 if jp_tok_kind(ctx) != JS_TOK_PUNCT { return 0 } 1013 if jp_is_punct(ctx, "+\x00" as *u8) == 1 { return OP_ADD } 1014 if jp_is_punct(ctx, "-\x00" as *u8) == 1 { return OP_SUB } 1015 return 0 1016} 1017// tier 3: < <= > >= (NOTE: must check the 2-char forms first via lexeme compare; 1018// the lexer already produced "<=" as ONE punct token, so exact-match is safe.) 1019static jp_noin: i64 // 1 while parsing a for-init EXPRESSION: `in` is the for-in separator, NOT the operator 1020func jp_rel_op(ctx: *i64) -> i64 { 1021 // `instanceof`/`in` are KEYWORD tokens (not punct) at the SAME precedence tier as < > (real JS RelationalExpr). 1022 if jp_is_kw(ctx, "instanceof\x00" as *u8) == 1 { return OP_INSTANCEOF } 1023 if jp_is_kw(ctx, "in\x00" as *u8) == 1 { if jp_noin == 0 { return OP_IN } return 0 } 1024 if jp_tok_kind(ctx) != JS_TOK_PUNCT { return 0 } 1025 if jp_is_punct(ctx, "<=\x00" as *u8) == 1 { return OP_LE } 1026 if jp_is_punct(ctx, ">=\x00" as *u8) == 1 { return OP_GE } 1027 if jp_is_punct(ctx, "<\x00" as *u8) == 1 { return OP_LT } 1028 if jp_is_punct(ctx, ">\x00" as *u8) == 1 { return OP_GT } 1029 return 0 1030} 1031// tier 4: == != === !== 1032func jp_eq_op(ctx: *i64) -> i64 { 1033 if jp_tok_kind(ctx) != JS_TOK_PUNCT { return 0 } 1034 if jp_is_punct(ctx, "===\x00" as *u8) == 1 { return OP_SEQ } 1035 if jp_is_punct(ctx, "!==\x00" as *u8) == 1 { return OP_SNE } 1036 if jp_is_punct(ctx, "==\x00" as *u8) == 1 { return OP_EQ } 1037 if jp_is_punct(ctx, "!=\x00" as *u8) == 1 { return OP_NE } 1038 return 0 1039} 1040 1041// Generic left-assoc binary builder is awkward without function pointers, so each 1042// tier is its own small function that loops while its op matches, recursing to the 1043// NEXT-HIGHER tier for each operand. Correct precedence = the recursion order; 1044// correct (left) associativity = the while-loop reassigning `left`. 1045 1046func jp_parse_mul(ctx: *i64) -> i64 { 1047 var left: i64 = jp_parse_unary(ctx) 1048 var go: i64 = 1 1049 while go == 1 { 1050 if jp_err(ctx) == 1 { go = 0 } 1051 if go == 1 { 1052 let op: i64 = jp_mul_op(ctx) 1053 if op == 0 { go = 0 } else { 1054 jp_advance(ctx) 1055 let right: i64 = jp_parse_unary(ctx) 1056 left = jp_new_node(ctx, ND_BINARY, left, right, -1, -1, op) 1057 } 1058 } 1059 } 1060 return left 1061} 1062func jp_parse_add(ctx: *i64) -> i64 { 1063 var left: i64 = jp_parse_mul(ctx) 1064 var go: i64 = 1 1065 while go == 1 { 1066 if jp_err(ctx) == 1 { go = 0 } 1067 if go == 1 { 1068 let op: i64 = jp_add_op(ctx) 1069 if op == 0 { go = 0 } else { 1070 jp_advance(ctx) 1071 let right: i64 = jp_parse_mul(ctx) 1072 left = jp_new_node(ctx, ND_BINARY, left, right, -1, -1, op) 1073 } 1074 } 1075 } 1076 return left 1077} 1078// SHIFT: << >> >>> -- precedence between additive and relational (JS). Distinct lexer tokens, so no 1079// confusion with < / > (relational). Uses variable shift in eval (immediate-shift lexer gotcha). 1080func jp_parse_shift(ctx: *i64) -> i64 { 1081 var left: i64 = jp_parse_add(ctx) 1082 var go: i64 = 1 1083 while go == 1 { 1084 if jp_err(ctx) == 1 { go = 0 } 1085 if go == 1 { 1086 var op: i64 = 0 1087 if jp_is_punct(ctx, "<<\x00" as *u8) == 1 { op = OP_SHL } 1088 if jp_is_punct(ctx, ">>>\x00" as *u8) == 1 { op = OP_USHR } 1089 if op == 0 { if jp_is_punct(ctx, ">>\x00" as *u8) == 1 { op = OP_SHR } } 1090 if op == 0 { go = 0 } else { 1091 jp_advance(ctx) 1092 let right: i64 = jp_parse_add(ctx) 1093 left = jp_new_node(ctx, ND_BINARY, left, right, -1, -1, op) 1094 } 1095 } 1096 } 1097 return left 1098} 1099func jp_parse_rel(ctx: *i64) -> i64 { 1100 var left: i64 = jp_parse_shift(ctx) 1101 var go: i64 = 1 1102 while go == 1 { 1103 if jp_err(ctx) == 1 { go = 0 } 1104 if go == 1 { 1105 let op: i64 = jp_rel_op(ctx) 1106 if op == 0 { go = 0 } else { 1107 jp_advance(ctx) 1108 let right: i64 = jp_parse_shift(ctx) 1109 left = jp_new_node(ctx, ND_BINARY, left, right, -1, -1, op) 1110 } 1111 } 1112 } 1113 return left 1114} 1115func jp_parse_eq(ctx: *i64) -> i64 { 1116 var left: i64 = jp_parse_rel(ctx) 1117 var go: i64 = 1 1118 while go == 1 { 1119 if jp_err(ctx) == 1 { go = 0 } 1120 if go == 1 { 1121 let op: i64 = jp_eq_op(ctx) 1122 if op == 0 { go = 0 } else { 1123 jp_advance(ctx) 1124 let right: i64 = jp_parse_rel(ctx) 1125 left = jp_new_node(ctx, ND_BINARY, left, right, -1, -1, op) 1126 } 1127 } 1128 } 1129 return left 1130} 1131// BITWISE AND / XOR / OR -- precedence & > ^ > | , all below equality and above && (JS). Single-char 1132// lexer tokens, never confused with && / || (2-char tokens). 1133func jp_parse_bitand(ctx: *i64) -> i64 { 1134 var left: i64 = jp_parse_eq(ctx) 1135 var go: i64 = 1 1136 while go == 1 { 1137 if jp_err(ctx) == 1 { go = 0 } 1138 if go == 1 { 1139 if jp_is_punct(ctx, "&\x00" as *u8) == 1 { 1140 jp_advance(ctx) 1141 let right: i64 = jp_parse_eq(ctx) 1142 left = jp_new_node(ctx, ND_BINARY, left, right, -1, -1, OP_BAND) 1143 } else { go = 0 } 1144 } 1145 } 1146 return left 1147} 1148func jp_parse_bitxor(ctx: *i64) -> i64 { 1149 var left: i64 = jp_parse_bitand(ctx) 1150 var go: i64 = 1 1151 while go == 1 { 1152 if jp_err(ctx) == 1 { go = 0 } 1153 if go == 1 { 1154 if jp_is_punct(ctx, "^\x00" as *u8) == 1 { 1155 jp_advance(ctx) 1156 let right: i64 = jp_parse_bitand(ctx) 1157 left = jp_new_node(ctx, ND_BINARY, left, right, -1, -1, OP_BXOR) 1158 } else { go = 0 } 1159 } 1160 } 1161 return left 1162} 1163func jp_parse_bitor(ctx: *i64) -> i64 { 1164 var left: i64 = jp_parse_bitxor(ctx) 1165 var go: i64 = 1 1166 while go == 1 { 1167 if jp_err(ctx) == 1 { go = 0 } 1168 if go == 1 { 1169 if jp_is_punct(ctx, "|\x00" as *u8) == 1 { 1170 jp_advance(ctx) 1171 let right: i64 = jp_parse_bitxor(ctx) 1172 left = jp_new_node(ctx, ND_BINARY, left, right, -1, -1, OP_BOR) 1173 } else { go = 0 } 1174 } 1175 } 1176 return left 1177} 1178func jp_parse_and(ctx: *i64) -> i64 { 1179 var left: i64 = jp_parse_bitor(ctx) 1180 var go: i64 = 1 1181 while go == 1 { 1182 if jp_err(ctx) == 1 { go = 0 } 1183 if go == 1 { 1184 if jp_is_punct(ctx, "&&\x00" as *u8) == 1 { 1185 jp_advance(ctx) 1186 let right: i64 = jp_parse_bitor(ctx) 1187 left = jp_new_node(ctx, ND_BINARY, left, right, -1, -1, OP_AND) 1188 } else { go = 0 } 1189 } 1190 } 1191 return left 1192} 1193func jp_parse_or(ctx: *i64) -> i64 { 1194 var left: i64 = jp_parse_and(ctx) 1195 var go: i64 = 1 1196 while go == 1 { 1197 if jp_err(ctx) == 1 { go = 0 } 1198 if go == 1 { 1199 if jp_is_punct(ctx, "||\x00" as *u8) == 1 { 1200 jp_advance(ctx) 1201 let right: i64 = jp_parse_and(ctx) 1202 left = jp_new_node(ctx, ND_BINARY, left, right, -1, -1, OP_OR) 1203 } else { go = 0 } 1204 } 1205 } 1206 return left 1207} 1208 1209// compound-assignment operator code at the cursor (the BINARY op to fold in), or 0 if 1210// the current token is not a compound assign. The lexer produced "+=" "-=" etc. as 1211// SINGLE multi-char punct tokens (js_punct_len), so an exact-lexeme compare is safe and 1212// can never be confused with bare '=' / '==' / '==='. 1213func jp_compound_op(ctx: *i64) -> i64 { 1214 if jp_tok_kind(ctx) != JS_TOK_PUNCT { return 0 } 1215 if jp_is_punct(ctx, "+=\x00" as *u8) == 1 { return OP_ADD } 1216 if jp_is_punct(ctx, "-=\x00" as *u8) == 1 { return OP_SUB } 1217 if jp_is_punct(ctx, "*=\x00" as *u8) == 1 { return OP_MUL } 1218 if jp_is_punct(ctx, "/=\x00" as *u8) == 1 { return OP_DIV } 1219 if jp_is_punct(ctx, "%=\x00" as *u8) == 1 { return OP_MOD } 1220 // bitwise/shift compounds (Octane Crypto/jsbn: `c >>= ...`). The lexer already emits these as single 1221 // puncts (js_punct_len), and the fold rides the SAME aop path as += (ev_apply_binop / BC_BINOP). 1222 if jp_is_punct(ctx, "&=\x00" as *u8) == 1 { return OP_BAND } 1223 if jp_is_punct(ctx, "|=\x00" as *u8) == 1 { return OP_BOR } 1224 if jp_is_punct(ctx, "^=\x00" as *u8) == 1 { return OP_BXOR } 1225 if jp_is_punct(ctx, "<<=\x00" as *u8) == 1 { return OP_SHL } 1226 if jp_is_punct(ctx, ">>=\x00" as *u8) == 1 { return OP_SHR } 1227 if jp_is_punct(ctx, ">>>=\x00" as *u8) == 1 { return OP_USHR } 1228 return 0 1229} 1230 1231// ternary `cond ? then : else` -- RIGHT-assoc, just ABOVE assignment, just BELOW the 1232// OR tier (real JS: ConditionalExpression). The branches are assignment-expressions 1233// (so `a ? b : c = d` parses the false branch as an assignment, real JS). Returns the 1234// OR-level node unchanged when no '?' follows, so all lower KATs are untouched. 1235// nullish coalescing `a ?? b` -- short-circuit; tier just BELOW ternary, ABOVE ||. Operands are 1236// OR-level expressions (the common `a ?? b` parses correctly; ?? mixing with ||/&& needs parens in real JS). 1237func jp_parse_nullish(ctx: *i64) -> i64 { 1238 var left: i64 = jp_parse_or(ctx) 1239 var go: i64 = 1 1240 while go == 1 { 1241 if jp_err(ctx) == 1 { go = 0 } 1242 if go == 1 { 1243 if jp_is_punct(ctx, "??\x00" as *u8) == 1 { 1244 jp_advance(ctx) 1245 let right: i64 = jp_parse_or(ctx) 1246 left = jp_new_node(ctx, ND_BINARY, left, right, -1, -1, OP_NULLISH) 1247 } else { go = 0 } 1248 } 1249 } 1250 return left 1251} 1252func jp_parse_ternary(ctx: *i64) -> i64 { 1253 let cond: i64 = jp_parse_nullish(ctx) 1254 if jp_err(ctx) == 1 { return cond } 1255 if jp_is_punct(ctx, "?\x00" as *u8) == 1 { 1256 jp_advance(ctx) 1257 let then_e: i64 = jp_parse_assign(ctx) // then-branch is an assignment-expr 1258 if jp_err(ctx) == 1 { return jp_error_node(ctx) } 1259 if jp_eat_punct(ctx, ":\x00" as *u8) == 0 { return jp_error_node(ctx) } 1260 let else_e: i64 = jp_parse_assign(ctx) // else-branch right-recurses (right-assoc) 1261 if jp_err(ctx) == 1 { return jp_error_node(ctx) } 1262 return jp_new_node(ctx, ND_TERNARY, cond, then_e, else_e, -1, 0) 1263 } 1264 return cond 1265} 1266 1267// ===================== arrow functions (R-JS-CLOSURE, rung 6) ===================== 1268// An arrow desugars to the SAME node shape as a function decl/expression -- ND_FUNC_DECL 1269// (a=name|-1, b=params, c=body BLOCK) -- so the evaluator's ONE js_call_userfn path runs 1270// both. Arrows are anonymous (name = -1). The lexical capture happens in the evaluator 1271// (the closure records the env where the literal is evaluated); the parser only produces 1272// the same params+body skeleton, so an arrow is lexically identical to `function(){}`. 1273 1274// wrap a single statement node into a synthetic BLOCK list-node (a=child_start, b=1). 1275// Used for an arrow EXPRESSION body: `x => expr` desugars to a body `{ return expr; }`. 1276func jp_wrap_block1(ctx: *i64, stmt: i64) -> i64 { 1277 let start: i64 = jp_child_push(ctx, stmt) 1278 return jp_new_node(ctx, ND_BLOCK, start, 1, -1, -1, 0) 1279} 1280 1281// parse a PARENTHESIZED arrow param list `( a , b , ... )` (idents only; empty () ok) 1282// -> ND_PARAMS. Identical shape to jp_parse_params (reused), kept as its own call so the 1283// arrow path reads clearly. Default/rest/destructured params are a NAMED OPEN (idents only). 1284func jp_parse_arrow_params(ctx: *i64) -> i64 { return jp_parse_params(ctx) } 1285 1286// parse an arrow BODY after the `=>` has been consumed: a BLOCK body `{ stmts }` runs as-is; 1287// an EXPRESSION body `expr` desugars to `{ return expr; }` (the implicit-return rule). The 1288// expression body is an ASSIGNMENT-expr (so `x => y = 1` parses `y=1` as the body, real JS). 1289func jp_parse_arrow_body(ctx: *i64) -> i64 { 1290 if jp_is_punct(ctx, "{\x00" as *u8) == 1 { return jp_parse_block(ctx) } 1291 let e: i64 = jp_parse_assign(ctx) 1292 if jp_err(ctx) == 1 { return jp_error_node(ctx) } 1293 let ret: i64 = jp_new_node(ctx, ND_RETURN, e, -1, -1, -1, 0) 1294 return jp_wrap_block1(ctx, ret) 1295} 1296 1297// SINGLE-PARAM arrow `x => body`. Cursor is on the IDENT (its next token is '=>'). 1298func jp_parse_arrow_single(ctx: *i64) -> i64 { 1299 let pn: i64 = jp_new_node(ctx, ND_IDENT, -1, -1, -1, jp_cur(ctx), 0) 1300 jp_advance(ctx) // consume the param ident 1301 let start: i64 = jp_child_push(ctx, pn) 1302 let params: i64 = jp_new_node(ctx, ND_PARAMS, start, 1, -1, -1, 0) 1303 if jp_eat_punct(ctx, "=>\x00" as *u8) == 0 { return jp_error_node(ctx) } 1304 let body: i64 = jp_parse_arrow_body(ctx) 1305 if jp_err(ctx) == 1 { return jp_error_node(ctx) } 1306 return jp_new_node(ctx, ND_FUNC_DECL, -1, params, body, -1, 0) // name=-1 (anonymous) 1307} 1308 1309// PAREN-LIST arrow `( a, b ) => body`. Cursor is on '(' (jp_arrow_paren_ahead confirmed 1310// a following '=>'). Parse the param idents, eat '=>', parse the body. 1311func jp_parse_arrow_paren(ctx: *i64) -> i64 { 1312 let params: i64 = jp_parse_arrow_params(ctx) 1313 if jp_err(ctx) == 1 { return jp_error_node(ctx) } 1314 if jp_eat_punct(ctx, "=>\x00" as *u8) == 0 { return jp_error_node(ctx) } 1315 let body: i64 = jp_parse_arrow_body(ctx) 1316 if jp_err(ctx) == 1 { return jp_error_node(ctx) } 1317 return jp_new_node(ctx, ND_FUNC_DECL, -1, params, body, -1, 0) 1318} 1319 1320// 1 iff an ARROW function begins at the cursor: `x =>` (IDENT then '=>') or `( ... ) =>` 1321// (the cover-grammar paren case resolved by jp_arrow_paren_ahead). Used to fork at the 1322// TOP of jp_parse_assign so the arrow's params are not first mis-parsed as an expression. 1323func jp_arrow_ahead(ctx: *i64) -> i64 { 1324 if jp_tok_kind(ctx) == JS_TOK_IDENT { 1325 if jp_punct_at(ctx, jp_cur(ctx) + 1, "=>\x00" as *u8) == 1 { return 1 } 1326 } 1327 if jp_is_punct(ctx, "(\x00" as *u8) == 1 { 1328 if jp_arrow_paren_ahead(ctx) == 1 { return 1 } 1329 } 1330 return 0 1331} 1332 1333// assignment: RIGHT-associative, LOWEST precedence. `lhs = rhs` (or `lhs += rhs` etc.) 1334// where rhs is itself an assignment (so a=b=c nests right). lhs is whatever the ternary 1335// tier parsed. Compound assigns store the BINARY op-code in the ND_ASSIGN `extra` slot 1336// (0 = plain '='); the evaluator reads lhs, folds the op with rhs, writes back. 1337func jp_parse_assign(ctx: *i64) -> i64 { 1338 // ASYNC ARROWS `async (a,b)=>{…}` / `async x=>…` (JS-SOTA phase 3; the netflix frontier after 1339 // object spread: `a=e=>async(t,n)=>{…}`). `async` is CONTEXTUAL -- `async(1)` is a CALL to a 1340 // function named async -- so only consume it when a real arrow follows; otherwise RESTORE the 1341 // cursor and let it parse as a plain identifier. Async-ness itself is not yet modelled (the 1342 // body runs synchronously and returns its value, not a promise) -- an honest, declared stub. 1343 if jp_is_lex(ctx, JS_TOK_IDENT, "async\x00" as *u8) == 1 { 1344 let asave: i64 = jp_cur(ctx) 1345 jp_set_cur(ctx, asave + 1) 1346 var aok: i64 = 0 1347 if jp_is_punct(ctx, "(\x00" as *u8) == 1 { if jp_arrow_paren_ahead(ctx) == 1 { aok = 1 } } 1348 if jp_kind_at(ctx, asave + 1) == JS_TOK_IDENT { if jp_punct_at(ctx, asave + 2, "=>\x00" as *u8) == 1 { aok = 1 } } 1349 if aok == 0 { jp_set_cur(ctx, asave) } 1350 } 1351 if jp_err(ctx) == 1 { return jp_error_node(ctx) } 1352 // ARROW FUNCTION fork (lowest precedence, right-assoc): detect `x =>` / `(...) =>` 1353 // BEFORE treating the head as an expression. The body recurses through assignment 1354 // (jp_parse_arrow_body), so `x => y => x+y` (curried) right-nests naturally. 1355 if jp_arrow_ahead(ctx) == 1 { 1356 if jp_tok_kind(ctx) == JS_TOK_IDENT { return jp_parse_arrow_single(ctx) } 1357 return jp_parse_arrow_paren(ctx) 1358 } 1359 let lhs: i64 = jp_parse_ternary(ctx) 1360 if jp_err(ctx) == 1 { return lhs } 1361 // bare '=' -- must NOT match == or === (DISTINCT multi-char punct tokens), so an 1362 // exact-lexeme compare against "=" is unambiguous. 1363 if jp_is_punct(ctx, "=\x00" as *u8) == 1 { 1364 // Real JS: the assignment target MUST be an lvalue (IDENT / MEMBER / INDEX). 1365 // '1 = 2' or '(a+b) = c' -> SyntaxError: Invalid left-hand side in assignment. 1366 if jp_is_lvalue(ctx, lhs) == 0 { return jp_error_node(ctx) } 1367 jp_advance(ctx) 1368 let rhs: i64 = jp_parse_assign(ctx) // right-recursion = right associativity 1369 return jp_new_node(ctx, ND_ASSIGN, lhs, rhs, -1, -1, 0) 1370 } 1371 // compound assign lhs (+= -= *= /= %=) rhs -> ND_ASSIGN with op in extra slot. 1372 let cop: i64 = jp_compound_op(ctx) 1373 if cop != 0 { 1374 if jp_is_lvalue(ctx, lhs) == 0 { return jp_error_node(ctx) } 1375 jp_advance(ctx) 1376 let rhs: i64 = jp_parse_assign(ctx) 1377 return jp_new_node(ctx, ND_ASSIGN, lhs, rhs, -1, -1, cop) 1378 } 1379 return lhs 1380} 1381 1382// top-level expression = assignment (which descends through all binary tiers). 1383func jp_parse_expr(ctx: *i64) -> i64 { 1384 let first: i64 = jp_parse_assign(ctx) 1385 if jp_err(ctx) == 1 { return first } 1386 if jp_is_punct(ctx, ",\x00" as *u8) == 0 { return first } // no comma = single expr (the common case, 0 overhead) 1387 // sequence operator `a, b, c` -> ND_SEQ (eval each in order, value = last). All jp_parse_expr callers are 1388 // sequence-valid contexts (for-header, (), stmt, cond, return, switch); list-commas use jp_parse_assign. 1389 let scratch: *i64 = sys_mmap(256 * 8) as *i64 1390 scratch[0] = first 1391 var count: i64 = 1 1392 while jp_is_punct(ctx, ",\x00" as *u8) == 1 { 1393 jp_advance(ctx) 1394 let e: i64 = jp_parse_assign(ctx) 1395 if jp_err(ctx) == 1 { return jp_error_node(ctx) } 1396 if count < 256 { scratch[count] = e } 1397 count = count + 1 1398 } 1399 let cs: i64 = jp_child_commit(ctx, scratch, count) 1400 return jp_new_node(ctx, ND_SEQ, cs, count, -1, -1, 0) 1401} 1402 1403// ===================== statement parsing ===================== 1404// forward refs: jp_parse_stmt is mutually recursive with block/if/while. 1405 1406// var/let/const decl: KEYWORD IDENT [ = expr ] [ ; ] 1407// array/object destructuring PATTERNS for a var-decl target. Shorthand names only ([a,b] / {a,b}); 1408// renaming {a:b}, defaults in patterns, and nested patterns are a NAMED OPEN. 1409func jp_parse_array_pattern(ctx: *i64) -> i64 { 1410 jp_advance(ctx) // '[' 1411 let scratch: *i64 = sys_mmap(256 * 8) as *i64 1412 var count: i64 = 0 1413 var go: i64 = 1 1414 if jp_is_punct(ctx, "]\x00" as *u8) == 1 { jp_advance(ctx); go = 0 } 1415 while go == 1 { 1416 var nm: i64 = 0 - 1 1417 // NESTED patterns: [e, [t,n], {x}] -- vk uses `for(let [e,[t,n]] of ...)`. 1418 if jp_is_punct(ctx, "[\x00" as *u8) == 1 { nm = jp_parse_array_pattern(ctx); if jp_err(ctx) == 1 { go = 0 } } 1419 else { if jp_is_punct(ctx, "{\x00" as *u8) == 1 { nm = jp_parse_object_pattern(ctx); if jp_err(ctx) == 1 { go = 0 } } 1420 else { if jp_is_punct(ctx, "...\x00" as *u8) == 1 { jp_advance(ctx); if jp_tok_kind(ctx) == JS_TOK_IDENT { nm = jp_new_node(ctx, ND_IDENT, -1, -1, -1, jp_cur(ctx), 1); jp_advance(ctx) } else { jp_set_err(ctx); go = 0 } } 1421 else { 1422 if jp_tok_kind(ctx) != JS_TOK_IDENT { jp_set_err(ctx); go = 0 } 1423 if go == 1 { nm = jp_new_node(ctx, ND_IDENT, -1, -1, -1, jp_cur(ctx), 0); jp_advance(ctx) } 1424 } } } 1425 if go == 1 { 1426 if jp_is_punct(ctx, "=\x00" as *u8) == 1 { jp_advance(ctx); jp_parse_assign(ctx); if jp_err(ctx) == 1 { go = 0 } } // [a=1] default (parse+ignore) 1427 if go == 1 { 1428 if count < 256 { scratch[count] = nm } 1429 count = count + 1 1430 if jp_eat_punct(ctx, ",\x00" as *u8) == 1 { } 1431 else { if jp_eat_punct(ctx, "]\x00" as *u8) == 1 { go = 0 } else { jp_set_err(ctx); go = 0 } } 1432 } 1433 } 1434 } 1435 if jp_err(ctx) == 1 { return jp_error_node(ctx) } 1436 let start: i64 = jp_child_commit(ctx, scratch, count) 1437 return jp_new_node(ctx, ND_ARRAY_PAT, start, count, -1, -1, 0) 1438} 1439func jp_parse_object_pattern(ctx: *i64) -> i64 { 1440 jp_advance(ctx) // '{' 1441 let scratch: *i64 = sys_mmap(256 * 8) as *i64 1442 var count: i64 = 0 1443 var go: i64 = 1 1444 if jp_is_punct(ctx, "}\x00" as *u8) == 1 { jp_advance(ctx); go = 0 } 1445 while go == 1 { 1446 if jp_tok_kind(ctx) != JS_TOK_IDENT { jp_set_err(ctx); go = 0 } 1447 if go == 1 { 1448 let keytok: i64 = jp_cur(ctx) // source property KEY 1449 jp_advance(ctx) 1450 var alias: i64 = 0 - 1 1451 if jp_is_punct(ctx, ":\x00" as *u8) == 1 { // {key: localName} or {key: [a,b]} / {key: {x}} (nested) 1452 jp_advance(ctx) 1453 if jp_is_punct(ctx, "[\x00" as *u8) == 1 { alias = jp_parse_array_pattern(ctx); if jp_err(ctx) == 1 { go = 0 } } 1454 else { if jp_is_punct(ctx, "{\x00" as *u8) == 1 { alias = jp_parse_object_pattern(ctx); if jp_err(ctx) == 1 { go = 0 } } 1455 else { if jp_tok_kind(ctx) == JS_TOK_IDENT { alias = jp_new_node(ctx, ND_IDENT, -1, -1, -1, jp_cur(ctx), 0); jp_advance(ctx) } 1456 else { jp_set_err(ctx); go = 0 } } } 1457 } 1458 if go == 1 { if jp_is_punct(ctx, "=\x00" as *u8) == 1 { // {x = default} -- parse+ignore default (rare) 1459 jp_advance(ctx) 1460 jp_parse_assign(ctx) 1461 if jp_err(ctx) == 1 { go = 0 } 1462 } } 1463 if go == 1 { 1464 let nm: i64 = jp_new_node(ctx, ND_IDENT, alias, -1, -1, keytok, 0) // tok=KEY; slot-a=ALIAS node (-1 if bare) 1465 if count < 256 { scratch[count] = nm } 1466 count = count + 1 1467 if jp_eat_punct(ctx, ",\x00" as *u8) == 1 { } 1468 else { if jp_eat_punct(ctx, "}\x00" as *u8) == 1 { go = 0 } else { jp_set_err(ctx); go = 0 } } 1469 } 1470 } 1471 } 1472 if jp_err(ctx) == 1 { return jp_error_node(ctx) } 1473 let start: i64 = jp_child_commit(ctx, scratch, count) 1474 return jp_new_node(ctx, ND_OBJ_PAT, start, count, -1, -1, 0) 1475} 1476// parse ONE declarator `name [= init]` (no var keyword, no terminator) -> an ND_VAR_DECL node. 1477func jp_parse_one_declarator(ctx: *i64) -> i64 { 1478 var name: i64 = 0 1479 if jp_is_punct(ctx, "[\x00" as *u8) == 1 { name = jp_parse_array_pattern(ctx) } 1480 else { if jp_is_punct(ctx, "{\x00" as *u8) == 1 { name = jp_parse_object_pattern(ctx) } 1481 else { 1482 if jp_tok_kind(ctx) != JS_TOK_IDENT { return jp_error_node(ctx) } 1483 name = jp_new_node(ctx, ND_IDENT, -1, -1, -1, jp_cur(ctx), 0) 1484 jp_advance(ctx) 1485 } } 1486 if jp_err(ctx) == 1 { return jp_error_node(ctx) } 1487 var init: i64 = -1 1488 if jp_is_punct(ctx, "=\x00" as *u8) == 1 { 1489 jp_advance(ctx) 1490 init = jp_parse_assign(ctx) 1491 if jp_err(ctx) == 1 { return jp_error_node(ctx) } 1492 } 1493 return jp_new_node(ctx, ND_VAR_DECL, name, init, -1, -1, 0) 1494} 1495func jp_parse_var_decl(ctx: *i64) -> i64 { 1496 jp_advance(ctx) // consume var/let/const 1497 let first: i64 = jp_parse_one_declarator(ctx) 1498 if jp_err(ctx) == 1 { return jp_error_node(ctx) } 1499 // MULTI-declarator: `var a, b=c, d;` -> ND_VAR_LIST of ND_VAR_DECLs (real JS; very common). 1500 if jp_is_punct(ctx, ",\x00" as *u8) == 1 { 1501 let scratch: *i64 = sys_mmap(256 * 8) as *i64 1502 scratch[0] = first 1503 var count: i64 = 1 1504 while jp_is_punct(ctx, ",\x00" as *u8) == 1 { 1505 jp_advance(ctx) 1506 let d: i64 = jp_parse_one_declarator(ctx) 1507 if jp_err(ctx) == 1 { return jp_error_node(ctx) } 1508 if count < 256 { scratch[count] = d } 1509 count = count + 1 1510 } 1511 if jp_eat_punct(ctx, ";\x00" as *u8) == 0 { if jp_can_asi(ctx) == 0 { return jp_error_node(ctx) } } 1512 let cs: i64 = jp_child_commit(ctx, scratch, count) 1513 return jp_new_node(ctx, ND_VAR_LIST, cs, count, -1, -1, 0) 1514 } 1515 // single declarator: ASI-lite termination (explicit ';' OR line break / '}' / EOF). 1516 if jp_eat_punct(ctx, ";\x00" as *u8) == 0 { 1517 if jp_can_asi(ctx) == 0 { return jp_error_node(ctx) } 1518 } 1519 return first 1520} 1521 1522// return [ expr ] ; 1523func jp_parse_return(ctx: *i64) -> i64 { 1524 jp_advance(ctx) // consume `return` 1525 var expr: i64 = -1 1526 // bare `return;` or `return}` -> no expression 1527 var has_expr: i64 = 1 1528 if jp_is_punct(ctx, ";\x00" as *u8) == 1 { has_expr = 0 } 1529 if jp_is_punct(ctx, "}\x00" as *u8) == 1 { has_expr = 0 } 1530 if jp_tok_kind(ctx) == JS_TOK_EOF { has_expr = 0 } 1531 if has_expr == 1 { 1532 expr = jp_parse_expr(ctx) 1533 if jp_err(ctx) == 1 { return jp_error_node(ctx) } 1534 } 1535 // ASI-lite termination: explicit ';' OR a line break / '}' / EOF. 1536 if jp_eat_punct(ctx, ";\x00" as *u8) == 0 { 1537 if jp_can_asi(ctx) == 0 { return jp_error_node(ctx) } 1538 } 1539 return jp_new_node(ctx, ND_RETURN, expr, -1, -1, -1, 0) 1540} 1541 1542// block: { stmt* } -> list-node a=child_start, b=child_count 1543func jp_parse_block(ctx: *i64) -> i64 { 1544 jp_advance(ctx) // consume '{' 1545 let scratch: *i64 = sys_mmap(ND_MAGIC_2048 * 8) as *i64 1546 var count: i64 = 0 1547 var go: i64 = 1 1548 while go == 1 { 1549 if jp_err(ctx) == 1 { go = 0 } 1550 if go == 1 { 1551 if jp_is_punct(ctx, "}\x00" as *u8) == 1 { jp_advance(ctx); go = 0 } 1552 else { 1553 if jp_tok_kind(ctx) == JS_TOK_EOF { jp_set_err(ctx); go = 0 } 1554 else { 1555 let st: i64 = jp_parse_stmt(ctx) 1556 if count < ND_MAGIC_2048 { scratch[count] = st } 1557 count = count + 1 1558 } 1559 } 1560 } 1561 } 1562 if jp_err(ctx) == 1 { return jp_error_node(ctx) } 1563 let start: i64 = jp_child_commit(ctx, scratch, count) 1564 return jp_new_node(ctx, ND_BLOCK, start, count, -1, -1, 0) 1565} 1566 1567// if ( cond ) stmt [ else stmt ] 1568func jp_parse_if(ctx: *i64) -> i64 { 1569 jp_advance(ctx) // consume `if` 1570 if jp_eat_punct(ctx, "(\x00" as *u8) == 0 { return jp_error_node(ctx) } 1571 let cond: i64 = jp_parse_expr(ctx) 1572 if jp_eat_punct(ctx, ")\x00" as *u8) == 0 { return jp_error_node(ctx) } 1573 let then_s: i64 = jp_parse_stmt(ctx) 1574 var else_s: i64 = -1 1575 if jp_is_kw(ctx, "else\x00" as *u8) == 1 { 1576 jp_advance(ctx) 1577 else_s = jp_parse_stmt(ctx) 1578 } 1579 if jp_err(ctx) == 1 { return jp_error_node(ctx) } 1580 return jp_new_node(ctx, ND_IF, cond, then_s, else_s, -1, 0) 1581} 1582 1583// while ( cond ) stmt 1584func jp_parse_while(ctx: *i64) -> i64 { 1585 jp_advance(ctx) // consume `while` 1586 if jp_eat_punct(ctx, "(\x00" as *u8) == 0 { return jp_error_node(ctx) } 1587 let cond: i64 = jp_parse_expr(ctx) 1588 if jp_eat_punct(ctx, ")\x00" as *u8) == 0 { return jp_error_node(ctx) } 1589 let body: i64 = jp_parse_stmt(ctx) 1590 if jp_err(ctx) == 1 { return jp_error_node(ctx) } 1591 return jp_new_node(ctx, ND_WHILE, cond, body, -1, -1, 0) 1592} 1593 1594// for ( init ; cond ; update ) body -> ND_FOR. Slots: a=init, b=cond, c=update, 1595// tokidx-slot[4]=body. init may be a var-decl OR an expression OR empty; cond/update may 1596// be empty (empty cond = perpetually true, handled in the evaluator). We parse init WITHOUT 1597// consuming the ';' inside the init-decl/expr path so the for-header ';' separators stay 1598// uniform here. (for-in / for-of need iterator protocol -> HONEST OPEN, not parsed here: 1599// after the init clause we REQUIRE a ';'.) 1600func jp_parse_for(ctx: *i64) -> i64 { 1601 jp_advance(ctx) // consume `for` 1602 if jp_eat_punct(ctx, "(\x00" as *u8) == 0 { return jp_error_node(ctx) } 1603 // ---- init clause ---- (empty if the next token is ';') 1604 var init: i64 = -1 1605 if jp_is_punct(ctx, ";\x00" as *u8) == 1 { 1606 jp_advance(ctx) 1607 } else { 1608 var is_decl: i64 = 0 1609 if jp_is_kw(ctx, "var\x00" as *u8) == 1 { is_decl = 1 } 1610 if jp_is_kw(ctx, "let\x00" as *u8) == 1 { is_decl = 1 } 1611 if jp_is_kw(ctx, "const\x00" as *u8) == 1 { is_decl = 1 } 1612 if is_decl == 1 { init = jp_parse_for_decl(ctx) } 1613 else { jp_noin = 1; init = jp_parse_expr(ctx); jp_noin = 0 } // `in` here = for-in separator, not operator 1614 if jp_err(ctx) == 1 { return jp_error_node(ctx) } 1615 // ---- R-JS-SYNTAX2 (rung 8): for-of / for-in disambiguation. After the loop var 1616 // (a decl `var x` or a bare lvalue expr `x`), a contextual `of`/`in` keyword forks 1617 // to the iterator forms; a `;` continues the classic C-style for(init;cond;update). 1618 if jp_is_kw(ctx, "of\x00" as *u8) == 1 { return jp_parse_for_in_of(ctx, init, ND_FOR_OF) } 1619 if jp_is_kw(ctx, "in\x00" as *u8) == 1 { return jp_parse_for_in_of(ctx, init, ND_FOR_IN) } 1620 // the for-header requires an explicit ';' after the init clause (no ASI inside ()). 1621 if jp_eat_punct(ctx, ";\x00" as *u8) == 0 { return jp_error_node(ctx) } 1622 } 1623 // ---- cond clause ---- (empty = true) 1624 var cond: i64 = -1 1625 if jp_is_punct(ctx, ";\x00" as *u8) == 1 { 1626 jp_advance(ctx) 1627 } else { 1628 cond = jp_parse_expr(ctx) 1629 if jp_err(ctx) == 1 { return jp_error_node(ctx) } 1630 if jp_eat_punct(ctx, ";\x00" as *u8) == 0 { return jp_error_node(ctx) } 1631 } 1632 // ---- update clause ---- (empty if next is ')') 1633 var update: i64 = -1 1634 if jp_is_punct(ctx, ")\x00" as *u8) == 1 { 1635 jp_advance(ctx) 1636 } else { 1637 update = jp_parse_expr(ctx) 1638 if jp_err(ctx) == 1 { return jp_error_node(ctx) } 1639 if jp_eat_punct(ctx, ")\x00" as *u8) == 0 { return jp_error_node(ctx) } 1640 } 1641 let body: i64 = jp_parse_stmt(ctx) 1642 if jp_err(ctx) == 1 { return jp_error_node(ctx) } 1643 // store body in the tokidx slot (FOR has 4 children; a/b/c hold init/cond/update). 1644 return jp_new_node(ctx, ND_FOR, init, cond, update, body, 0) 1645} 1646 1647// for-header var-decl WITHOUT the terminating ';' (the for-loop header owns the ';' 1648// separators, so the decl must NOT consume one or run ASI). Mirrors jp_parse_var_decl 1649// minus the termination clause. Produces an ND_VAR_DECL (a=name, b=init|-1). 1650func jp_parse_for_decl(ctx: *i64) -> i64 { 1651 jp_advance(ctx) // consume var/let/const 1652 let first: i64 = jp_parse_one_declarator(ctx) 1653 if jp_err(ctx) == 1 { return jp_error_node(ctx) } 1654 // MULTI-declarator for-init `for(var i=0, j=n; ...)` -> ND_VAR_LIST (common; scheme2js Boyer uses it). 1655 // NO terminator here -- the for-header owns the ';'. `in`/`of` after a MULTI list = SyntaxError anyway. 1656 if jp_is_punct(ctx, ",\x00" as *u8) == 1 { 1657 let scratch: *i64 = sys_mmap(256 * 8) as *i64 1658 scratch[0] = first 1659 var count: i64 = 1 1660 while jp_is_punct(ctx, ",\x00" as *u8) == 1 { 1661 jp_advance(ctx) 1662 let d: i64 = jp_parse_one_declarator(ctx) 1663 if jp_err(ctx) == 1 { return jp_error_node(ctx) } 1664 if count < 256 { scratch[count] = d } 1665 count = count + 1 1666 } 1667 let cs: i64 = jp_child_commit(ctx, scratch, count) 1668 return jp_new_node(ctx, ND_VAR_LIST, cs, count, -1, -1, 0) 1669 } 1670 return first 1671} 1672 1673// R-JS-SYNTAX2 (rung 8): finish a for-of / for-in header after the contextual `of`/`in` 1674// keyword. `target` is the already-parsed loop variable (an ND_VAR_DECL `var x` carrying its 1675// name IDENT, or a bare lvalue expr) and `nkind` is ND_FOR_OF or ND_FOR_IN. Cursor sits on 1676// the `of`/`in` keyword. Shape: a=target, b=iterable/object-expr, c=body. The right-hand side 1677// is an ASSIGNMENT-expr (real JS: `for(x of a)`); then ')' and the loop body statement. 1678func jp_parse_for_in_of(ctx: *i64, target: i64, nkind: i64) -> i64 { 1679 jp_advance(ctx) // consume `of` / `in` 1680 // for-IN head is `Expression` (comma-inclusive) per the grammar -- minified jQuery ships `for(e in a,b,...)`; 1681 // for-OF head is `AssignmentExpression` (a comma there is a real SyntaxError), so keep it single. 1682 var rhs: i64 = 0 1683 if nkind == ND_FOR_IN { rhs = jp_parse_expr(ctx) } else { rhs = jp_parse_assign(ctx) } 1684 if jp_err(ctx) == 1 { return jp_error_node(ctx) } 1685 if jp_eat_punct(ctx, ")\x00" as *u8) == 0 { return jp_error_node(ctx) } 1686 let body: i64 = jp_parse_stmt(ctx) 1687 if jp_err(ctx) == 1 { return jp_error_node(ctx) } 1688 return jp_new_node(ctx, nkind, target, rhs, body, -1, 0) 1689} 1690 1691// R-JS-SYNTAX2 (rung 8): switch ( disc ) { case E: stmts... case E2: stmts... default: stmts... } 1692// -> ND_SWITCH (a=disc-expr, b=case_list_child_start, c=case_count). Each clause is an ND_CASE 1693// node (a=case-expr or -1 for default, b=stmt_list_child_start, c=stmt_count) committed 1694// CONTIGUOUSLY in the child arena, and the ND_CASE list itself is committed contiguously after. 1695// A clause's statement list runs until the next `case`/`default`/`}` (NOT terminated by a 1696// keyword), so fall-through is preserved by the EVALUATOR (the parser just records clause order). 1697// Multiple `default` clauses are an honest OPEN; the first `default` wins at eval. 1698func jp_parse_switch(ctx: *i64) -> i64 { 1699 jp_advance(ctx) // consume `switch` 1700 if jp_eat_punct(ctx, "(\x00" as *u8) == 0 { return jp_error_node(ctx) } 1701 let disc: i64 = jp_parse_expr(ctx) 1702 if jp_err(ctx) == 1 { return jp_error_node(ctx) } 1703 if jp_eat_punct(ctx, ")\x00" as *u8) == 0 { return jp_error_node(ctx) } 1704 if jp_eat_punct(ctx, "{\x00" as *u8) == 0 { return jp_error_node(ctx) } 1705 let cases: *i64 = sys_mmap(256 * 8) as *i64 // ND_CASE node indices (clause order) 1706 var ccount: i64 = 0 1707 var go: i64 = 1 1708 while go == 1 { 1709 if jp_err(ctx) == 1 { go = 0 } 1710 if go == 1 { 1711 if jp_is_punct(ctx, "}\x00" as *u8) == 1 { jp_advance(ctx); go = 0 } 1712 else { 1713 if jp_tok_kind(ctx) == JS_TOK_EOF { jp_set_err(ctx); go = 0 } 1714 else { 1715 // clause head: `case EXPR :` or `default :` 1716 var case_expr: i64 = -1 1717 var headok: i64 = 0 1718 if jp_is_kw(ctx, "case\x00" as *u8) == 1 { 1719 jp_advance(ctx) 1720 case_expr = jp_parse_expr(ctx) 1721 headok = 1 1722 } 1723 if headok == 0 { if jp_is_kw(ctx, "default\x00" as *u8) == 1 { jp_advance(ctx); headok = 1 } } 1724 if headok == 0 { jp_set_err(ctx); go = 0 } 1725 if go == 1 { 1726 if jp_eat_punct(ctx, ":\x00" as *u8) == 0 { jp_set_err(ctx); go = 0 } 1727 } 1728 if go == 1 { 1729 // clause statement list: parse statements until the next clause head or '}'. 1730 let cnode: i64 = jp_parse_case_body(ctx, case_expr) 1731 if jp_err(ctx) == 1 { go = 0 } 1732 else { 1733 if ccount < 256 { cases[ccount] = cnode } 1734 ccount = ccount + 1 1735 } 1736 } 1737 } 1738 } 1739 } 1740 } 1741 if jp_err(ctx) == 1 { return jp_error_node(ctx) } 1742 let cstart: i64 = jp_child_commit(ctx, cases, ccount) 1743 // LIST-node layout (a=child_start, b=child_count) so jp_child_at reads the ND_CASE list; 1744 // the discriminant rides slot c. 1745 return jp_new_node(ctx, ND_SWITCH, cstart, ccount, disc, -1, 0) 1746} 1747 1748// parse ONE switch clause's statement list (the head `case E:`/`default:` is already consumed; 1749// `case_expr` = the case test expr or -1 for default). Statements run until the next `case`/ 1750// `default`/`}` boundary. Returns an ND_CASE node (a=case_expr, b=stmt_child_start, c=stmt_count). 1751func jp_parse_case_body(ctx: *i64, case_expr: i64) -> i64 { 1752 let scratch: *i64 = sys_mmap(ND_MAGIC_1024 * 8) as *i64 1753 var count: i64 = 0 1754 var go: i64 = 1 1755 while go == 1 { 1756 if jp_err(ctx) == 1 { go = 0 } 1757 if go == 1 { 1758 if jp_is_punct(ctx, "}\x00" as *u8) == 1 { go = 0 } // end of switch (do NOT consume) 1759 else { if jp_is_kw(ctx, "case\x00" as *u8) == 1 { go = 0 } // next clause (do NOT consume) 1760 else { if jp_is_kw(ctx, "default\x00" as *u8) == 1 { go = 0 } // default clause (do NOT consume) 1761 else { 1762 if jp_tok_kind(ctx) == JS_TOK_EOF { jp_set_err(ctx); go = 0 } 1763 else { 1764 let st: i64 = jp_parse_stmt(ctx) 1765 if count < ND_MAGIC_1024 { scratch[count] = st } 1766 count = count + 1 1767 } 1768 } } } 1769 } 1770 } 1771 if jp_err(ctx) == 1 { return jp_error_node(ctx) } 1772 let start: i64 = jp_child_commit(ctx, scratch, count) 1773 // LIST-node layout (a=child_start, b=child_count) so jp_child_at reads the clause's stmts; 1774 // the case-test expr (-1 for default) rides slot c. 1775 return jp_new_node(ctx, ND_CASE, start, count, case_expr, -1, 0) 1776} 1777 1778// do body while ( cond ) ; -> ND_DOWHILE (a=cond, b=body). Body runs at least once. 1779func jp_parse_dowhile(ctx: *i64) -> i64 { 1780 jp_advance(ctx) // consume `do` 1781 let body: i64 = jp_parse_stmt(ctx) 1782 if jp_err(ctx) == 1 { return jp_error_node(ctx) } 1783 if jp_is_kw(ctx, "while\x00" as *u8) == 0 { return jp_error_node(ctx) } 1784 jp_advance(ctx) // consume `while` 1785 if jp_eat_punct(ctx, "(\x00" as *u8) == 0 { return jp_error_node(ctx) } 1786 let cond: i64 = jp_parse_expr(ctx) 1787 if jp_err(ctx) == 1 { return jp_error_node(ctx) } 1788 if jp_eat_punct(ctx, ")\x00" as *u8) == 0 { return jp_error_node(ctx) } 1789 // optional trailing ';' (ASI applies); real JS allows `do{}while(c)` then a break. 1790 if jp_eat_punct(ctx, ";\x00" as *u8) == 0 { 1791 if jp_can_asi(ctx) == 0 { return jp_error_node(ctx) } 1792 } 1793 return jp_new_node(ctx, ND_DOWHILE, cond, body, -1, -1, 0) 1794} 1795 1796// break ; / continue ; -> ND_BREAK / ND_CONTINUE (no children). ASI-lite termination. 1797// (labeled break/continue need a label table -> HONEST OPEN, not parsed: a label after 1798// the keyword is not consumed, so `break outer;` would fall to the ';' check and error.) 1799func jp_parse_break(ctx: *i64) -> i64 { 1800 jp_advance(ctx) // consume `break` 1801 if jp_tok_kind(ctx) == JS_TOK_IDENT { jp_advance(ctx) } // optional label -> consumed (targets nearest loop; see labeled-stmt note) 1802 if jp_eat_punct(ctx, ";\x00" as *u8) == 0 { 1803 if jp_can_asi(ctx) == 0 { return jp_error_node(ctx) } 1804 } 1805 return jp_new_node(ctx, ND_BREAK, -1, -1, -1, -1, 0) 1806} 1807func jp_parse_continue(ctx: *i64) -> i64 { 1808 jp_advance(ctx) // consume `continue` 1809 if jp_tok_kind(ctx) == JS_TOK_IDENT { jp_advance(ctx) } // optional label -> consumed (targets nearest loop) 1810 if jp_eat_punct(ctx, ";\x00" as *u8) == 0 { 1811 if jp_can_asi(ctx) == 0 { return jp_error_node(ctx) } 1812 } 1813 return jp_new_node(ctx, ND_CONTINUE, -1, -1, -1, -1, 0) 1814} 1815 1816// param list `( a , b , ... )` -> PARAMS list-node a=child_start, b=child_count 1817func jp_parse_params(ctx: *i64) -> i64 { 1818 if jp_eat_punct(ctx, "(\x00" as *u8) == 0 { return jp_error_node(ctx) } 1819 let scratch: *i64 = sys_mmap(256 * 8) as *i64 1820 var count: i64 = 0 1821 if jp_is_punct(ctx, ")\x00" as *u8) == 1 { 1822 jp_advance(ctx) 1823 let start0: i64 = jp_child_commit(ctx, scratch, 0) 1824 return jp_new_node(ctx, ND_PARAMS, start0, 0, -1, -1, 0) 1825 } 1826 var go: i64 = 1 1827 while go == 1 { 1828 var isrest: i64 = 0 1829 if jp_is_punct(ctx, "...\x00" as *u8) == 1 { isrest = 1; jp_advance(ctx) } // rest param ...r 1830 var pn: i64 = 0 - 1 1831 var handled: i64 = 0 1832 // DESTRUCTURING params (ES6): `function f({a, b: c}, [x, y])` -- vk's framework uses these. 1833 if isrest == 0 { if jp_is_punct(ctx, "{\x00" as *u8) == 1 { pn = jp_parse_object_pattern(ctx); handled = 1; if jp_err(ctx) == 1 { go = 0 } } } 1834 if handled == 0 { if isrest == 0 { if jp_is_punct(ctx, "[\x00" as *u8) == 1 { pn = jp_parse_array_pattern(ctx); handled = 1; if jp_err(ctx) == 1 { go = 0 } } } } 1835 if handled == 0 { 1836 if jp_tok_kind(ctx) != JS_TOK_IDENT { jp_set_err(ctx); go = 0 } 1837 if go == 1 { 1838 let ptok: i64 = jp_cur(ctx) 1839 jp_advance(ctx) 1840 var defexpr: i64 = 0 - 1 1841 if isrest == 0 { if jp_is_punct(ctx, "=\x00" as *u8) == 1 { // default param: name = expr 1842 jp_advance(ctx) 1843 defexpr = jp_parse_assign(ctx) 1844 if jp_err(ctx) == 1 { go = 0 } 1845 } } 1846 pn = jp_new_node(ctx, ND_IDENT, defexpr, -1, -1, ptok, isrest) // extra=isrest; slot a=default/-1 1847 } 1848 } 1849 if go == 1 { 1850 if count < 256 { scratch[count] = pn } 1851 count = count + 1 1852 if jp_eat_punct(ctx, ",\x00" as *u8) == 1 { 1853 // next param 1854 } else { 1855 if jp_eat_punct(ctx, ")\x00" as *u8) == 1 { go = 0 } 1856 else { jp_set_err(ctx); go = 0 } 1857 } 1858 } 1859 } 1860 if jp_err(ctx) == 1 { return jp_error_node(ctx) } 1861 let start: i64 = jp_child_commit(ctx, scratch, count) 1862 return jp_new_node(ctx, ND_PARAMS, start, count, -1, -1, 0) 1863} 1864 1865// function NAME ( params ) { body } 1866func jp_parse_func_decl(ctx: *i64) -> i64 { 1867 jp_advance(ctx) // consume `function` 1868 if jp_is_punct(ctx, "*\x00" as *u8) == 1 { jp_advance(ctx) } // generator marker (stub semantics; ND_YIELD) 1869 if jp_tok_kind(ctx) != JS_TOK_IDENT { return jp_error_node(ctx) } 1870 let name: i64 = jp_new_node(ctx, ND_IDENT, -1, -1, -1, jp_cur(ctx), 0) 1871 jp_advance(ctx) 1872 let params: i64 = jp_parse_params(ctx) 1873 if jp_err(ctx) == 1 { return jp_error_node(ctx) } 1874 if jp_is_punct(ctx, "{\x00" as *u8) == 0 { return jp_error_node(ctx) } 1875 let body: i64 = jp_parse_block(ctx) 1876 if jp_err(ctx) == 1 { return jp_error_node(ctx) } 1877 return jp_new_node(ctx, ND_FUNC_DECL, name, params, body, -1, 0) 1878} 1879 1880// FUNCTION EXPRESSION (R-JS-CLOSURE, rung 6): `function (params){body}` (anonymous) or 1881// `function name(params){body}` (named) in EXPRESSION position. Same ND_FUNC_DECL node 1882// shape as a declaration (a=name|-1, b=params, c=body); name = -1 when anonymous. The 1883// expression-vs-statement distinction is purely WHERE it is parsed -- a statement-leading 1884// `function` is a declaration (jp_parse_stmt), here it is a value. Cursor is on `function`. 1885func jp_parse_func_expr(ctx: *i64) -> i64 { 1886 jp_advance(ctx) // consume `function` 1887 if jp_is_punct(ctx, "*\x00" as *u8) == 1 { jp_advance(ctx) } // generator marker (stub semantics; ND_YIELD) 1888 var name: i64 = -1 1889 if jp_tok_kind(ctx) == JS_TOK_IDENT { // optional name (named function expression) 1890 name = jp_new_node(ctx, ND_IDENT, -1, -1, -1, jp_cur(ctx), 0) 1891 jp_advance(ctx) 1892 } 1893 let params: i64 = jp_parse_params(ctx) 1894 if jp_err(ctx) == 1 { return jp_error_node(ctx) } 1895 if jp_is_punct(ctx, "{\x00" as *u8) == 0 { return jp_error_node(ctx) } 1896 let body: i64 = jp_parse_block(ctx) 1897 if jp_err(ctx) == 1 { return jp_error_node(ctx) } 1898 return jp_new_node(ctx, ND_FUNC_DECL, name, params, body, -1, 0) 1899} 1900 1901// expression statement: expr [ ; | ASI ] 1902// Termination is REQUIRED -- but the semicolon may be inserted automatically (ASI): 1903// after a complete expression, accept an explicit ';', else require an ASI point 1904// (line break, '}', or EOF). Adjacent statements on ONE line with no separator 1905// (`a b`, `1 2`) are a SyntaxError -- jp_can_asi returns 0 there, so we error. 1906func jp_parse_expr_stmt(ctx: *i64) -> i64 { 1907 let e: i64 = jp_parse_expr(ctx) 1908 if jp_err(ctx) == 1 { return jp_error_node(ctx) } 1909 if jp_eat_punct(ctx, ";\x00" as *u8) == 1 { 1910 return jp_new_node(ctx, ND_EXPR_STMT, e, -1, -1, -1, 0) 1911 } 1912 if jp_can_asi(ctx) == 0 { return jp_error_node(ctx) } // no ';', no ASI point 1913 return jp_new_node(ctx, ND_EXPR_STMT, e, -1, -1, -1, 0) 1914} 1915 1916// one statement (dispatch on leading keyword / punct). Shallow if-chain by design. 1917// try { ... } catch (e) { ... } finally { ... } -- catch param optional (`catch {`), 1918// at least one of catch/finally required (real JS). Finally block rides the tokidx slot. 1919func jp_parse_try(ctx: *i64) -> i64 { 1920 jp_advance(ctx) // consume `try` 1921 if jp_is_punct(ctx, "{\x00" as *u8) == 0 { jp_set_err(ctx); return jp_error_node(ctx) } 1922 let tryb: i64 = jp_parse_block(ctx) 1923 if jp_err(ctx) == 1 { return jp_error_node(ctx) } 1924 var param: i64 = 0 - 1 1925 var catchb: i64 = 0 - 1 1926 var finb: i64 = 0 - 1 1927 if jp_is_kw(ctx, "catch\x00" as *u8) == 1 { 1928 jp_advance(ctx) 1929 if jp_is_punct(ctx, "(\x00" as *u8) == 1 { 1930 jp_advance(ctx) 1931 if jp_tok_kind(ctx) != JS_TOK_IDENT { jp_set_err(ctx); return jp_error_node(ctx) } 1932 param = jp_new_node(ctx, ND_IDENT, -1, -1, -1, jp_cur(ctx), 0) 1933 jp_advance(ctx) 1934 if jp_eat_punct(ctx, ")\x00" as *u8) == 0 { jp_set_err(ctx); return jp_error_node(ctx) } 1935 } 1936 if jp_is_punct(ctx, "{\x00" as *u8) == 0 { jp_set_err(ctx); return jp_error_node(ctx) } 1937 catchb = jp_parse_block(ctx) 1938 if jp_err(ctx) == 1 { return jp_error_node(ctx) } 1939 } 1940 if jp_is_kw(ctx, "finally\x00" as *u8) == 1 { 1941 jp_advance(ctx) 1942 if jp_is_punct(ctx, "{\x00" as *u8) == 0 { jp_set_err(ctx); return jp_error_node(ctx) } 1943 finb = jp_parse_block(ctx) 1944 if jp_err(ctx) == 1 { return jp_error_node(ctx) } 1945 } 1946 if catchb < 0 { if finb < 0 { jp_set_err(ctx); return jp_error_node(ctx) } } 1947 return jp_new_node(ctx, ND_TRY, tryb, param, catchb, finb, 0) 1948} 1949// throw expr ; 1950func jp_parse_throw(ctx: *i64) -> i64 { 1951 jp_advance(ctx) // consume `throw` 1952 let e: i64 = jp_parse_expr(ctx) 1953 if jp_err(ctx) == 1 { return jp_error_node(ctx) } 1954 if jp_eat_punct(ctx, ";\x00" as *u8) == 0 { 1955 if jp_can_asi(ctx) == 0 { return jp_error_node(ctx) } 1956 } 1957 return jp_new_node(ctx, ND_THROW, e, -1, -1, -1, 0) 1958} 1959func jp_parse_stmt(ctx: *i64) -> i64 { 1960 if jp_err(ctx) == 1 { return jp_error_node(ctx) } 1961 // lone semicolon = empty statement -> treat as empty EXPR_STMT? keep strict: 1962 // we just consume a stray ';' as an empty block of work by recursing once. 1963 if jp_is_punct(ctx, ";\x00" as *u8) == 1 { 1964 jp_advance(ctx) 1965 return jp_new_node(ctx, ND_BLOCK, jp_pst(ctx)[PST_NCHILD], 0, -1, -1, 0) 1966 } 1967 if jp_is_punct(ctx, "{\x00" as *u8) == 1 { return jp_parse_block(ctx) } 1968 if jp_is_kw(ctx, "var\x00" as *u8) == 1 { return jp_parse_var_decl(ctx) } 1969 if jp_is_kw(ctx, "let\x00" as *u8) == 1 { return jp_parse_var_decl(ctx) } 1970 if jp_is_kw(ctx, "const\x00" as *u8) == 1 { return jp_parse_var_decl(ctx) } 1971 if jp_is_kw(ctx, "return\x00" as *u8) == 1 { return jp_parse_return(ctx) } 1972 if jp_is_kw(ctx, "if\x00" as *u8) == 1 { return jp_parse_if(ctx) } 1973 if jp_is_kw(ctx, "while\x00" as *u8) == 1 { return jp_parse_while(ctx) } 1974 if jp_is_kw(ctx, "for\x00" as *u8) == 1 { return jp_parse_for(ctx) } 1975 if jp_is_kw(ctx, "do\x00" as *u8) == 1 { return jp_parse_dowhile(ctx) } 1976 if jp_is_kw(ctx, "switch\x00" as *u8) == 1 { return jp_parse_switch(ctx) } 1977 if jp_is_kw(ctx, "break\x00" as *u8) == 1 { return jp_parse_break(ctx) } 1978 if jp_is_kw(ctx, "continue\x00" as *u8) == 1 { return jp_parse_continue(ctx) } 1979 if jp_is_kw(ctx, "function\x00" as *u8) == 1 { return jp_parse_func_decl(ctx) } 1980 if jp_is_kw(ctx, "class\x00" as *u8) == 1 { return jp_parse_class_decl(ctx) } 1981 if jp_is_kw(ctx, "try\x00" as *u8) == 1 { return jp_parse_try(ctx) } 1982 if jp_is_kw(ctx, "throw\x00" as *u8) == 1 { return jp_parse_throw(ctx) } 1983 // labeled statement: IDENT ':' stmt (minified jQuery/Sizzle use labeled loops). Pragmatic support: the label 1984 // is a TRANSPARENT wrapper -> parse+discard it, return the inner statement. break/continue [label] consume the 1985 // label but target the NEAREST loop (correct when the label is on the enclosing loop; a labeled break to a 1986 // NON-nearest outer loop is the known approximation -> a full label table is the follow-on). 1987 if jp_tok_kind(ctx) == JS_TOK_IDENT { if jp_punct_at(ctx, jp_cur(ctx) + 1, ":\x00" as *u8) == 1 { 1988 jp_advance(ctx) // label IDENT 1989 jp_advance(ctx) // ':' 1990 return jp_parse_stmt(ctx) 1991 } } 1992 // default: expression statement 1993 return jp_parse_expr_stmt(ctx) 1994} 1995 1996// PROGRAM = stmt* until EOF. Returns the PROGRAM node index (list-node). 1997func jp_parse_program(ctx: *i64) -> i64 { 1998 let scratch: *i64 = sys_mmap(ND_MAGIC_4096 * 8) as *i64 1999 var count: i64 = 0 2000 var go: i64 = 1 2001 while go == 1 { 2002 if jp_tok_kind(ctx) == JS_TOK_EOF { go = 0 } 2003 if go == 1 { if jp_cur(ctx) >= ctx[CTX_NTOK] { go = 0 } } 2004 if go == 1 { if jp_err(ctx) == 1 { go = 0 } } 2005 if go == 1 { 2006 let st: i64 = jp_parse_stmt(ctx) 2007 if count < ND_MAGIC_4096 { scratch[count] = st } 2008 count = count + 1 2009 } 2010 } 2011 let start: i64 = jp_child_commit(ctx, scratch, count) 2012 return jp_new_node(ctx, ND_PROGRAM, start, count, -1, -1, 0) 2013} 2014 2015// ===================== top-level driver ===================== 2016// Build a parse context over freshly-mmap'd arenas, lex `src`, then parse a PROGRAM. 2017// Returns the PROGRAM node index. Writes the ctx pointer to *ctx_out so the caller 2018// (gate) can inspect the AST. Sets the error flag inside pst on any syntax error. 2019func jp_parse_source(src: *u8, srclen: i64, ctx_out: *i64) -> i64 { 2020 // Budgets: js_lex TRUNCATES SILENTLY at maxtoks (parse then dies mid-file with a misleading cursor) -- 2021 // 4096 tokens capped real sources at ~16-26KB (Octane RayTrace 28KB / Crypto 48KB both hit it). mmap is 2022 // virtual (only touched pages cost), so size for real-page bundles. Dynamic growth = a follow-on. 2023 // ADAPTIVE budgets sized to the input: real pages (vk = ~250KB of concatenated inline JS across 70 scripts) 2024 // blew the fixed 65536-token cap -> js_lex truncated -> parse died mid-file (rc=1). ~1 token per 2 bytes 2025 // covers dense minified JS; nodes/children scale with tokens. mmap is virtual (only touched pages cost). 2026 var maxtoks: i64 = ND_MAGIC_65536 2027 if srclen / 2 > maxtoks { maxtoks = srclen / 2 } 2028 var maxnodes: i64 = maxtoks * 2 2029 var maxchild: i64 = maxtoks * 2 2030 if maxnodes < ND_MAGIC_131072 { maxnodes = ND_MAGIC_131072 } 2031 if maxchild < ND_MAGIC_131072 { maxchild = ND_MAGIC_131072 } 2032 let toks: *i64 = sys_mmap(maxtoks * 3 * 8) as *i64 2033 let ntb: *i64 = sys_mmap(16) as *i64 2034 let nodes: *i64 = sys_mmap(maxnodes * NODE_SLOTS * 8) as *i64 2035 let children: *i64 = sys_mmap(maxchild * 8) as *i64 2036 let pst: *i64 = sys_mmap(64) as *i64 2037 let ctx: *i64 = sys_mmap(CTX_MAXCHILD * 8 + 64) as *i64 2038 2039 let ntok: i64 = js_lex(src, srclen, toks, maxtoks, ntb) 2040 2041 pst[PST_CUR] = 0 2042 pst[PST_ERR] = 0 2043 pst[PST_NNODE] = 0 2044 pst[PST_NCHILD] = 0 2045 pst[PST_ERR_POS] = 0 - 1 2046 2047 ctx[CTX_SRC] = src as i64 2048 ctx[CTX_TOKS] = toks as i64 2049 ctx[CTX_NTOK] = ntok 2050 ctx[CTX_NODES] = nodes as i64 2051 ctx[CTX_CHILDREN] = children as i64 2052 ctx[CTX_PST] = pst as i64 2053 ctx[CTX_MAXNODE] = maxnodes 2054 ctx[CTX_MAXCHILD] = maxchild 2055 2056 // If the lexer itself emitted an ERROR token, the source is malformed. 2057 var li: i64 = 0 2058 while li < ntok { 2059 if toks[li * 3 + 0] == JS_TOK_ERROR { if pst[PST_ERR] == 0 { pst[PST_ERR_POS] = toks[li * 3 + 1] } pst[PST_ERR] = 1 } 2060 li = li + 1 2061 } 2062 2063 let prog: i64 = jp_parse_program(ctx) 2064 2065 // Trailing unconsumed tokens (after a complete parse) = syntax error too: 2066 // e.g. "1 + )" leaves the ')' dangling / errors mid-expr. Already covered by 2067 // err flag, but guard the case where the program loop stopped early WITHOUT 2068 // consuming everything and WITHOUT an error (shouldn't happen, but honest). 2069 if pst[PST_ERR] == 0 { 2070 if pst[PST_CUR] < ntok { if pst[PST_CUR] < ntok { pst[PST_ERR_POS] = toks[pst[PST_CUR] * 3 + 1] } pst[PST_ERR] = 1 } 2071 } 2072 2073 ctx_out[0] = ctx as i64 2074 return prog 2075} 2076 2077// ===================== GATE ===================== 2078func jq_puts(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(1,s,n); return 0 } 2079// MIGRATED to the shared emitter (debt 1785563586). The old body mmapped a scratch buffer 2080// per call and never freed it. At PAGE granularity that is 4096B leaked PER CALL -- the 2081// defect that took 28.5GB of a 36GB host in nx_ts_lumadiff (2MB input, ~3.66M calls). 2082// nxi_* is MSB-first, allocates NOTHING, and emits identical bytes including the sign. 2083func jq_putn(v: i64) -> i64 { nxi_out(v); return 0 } 2084func jq_strlen(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} return n } 2085// MIGRATED to the shared emitter (debt 1785563586). The old body mmapped a scratch buffer 2086// per call and never freed it. At PAGE granularity that is 4096B leaked PER CALL -- the 2087// defect that took 28.5GB of a 36GB host in nx_ts_lumadiff (2MB input, ~3.66M calls). 2088// nxi_* is MSB-first, allocates NOTHING, and emits identical bytes including the sign. 2089func jq_fdn(fd: i64, v: i64) -> i64 { nxi_fd(fd, v); return 0 } 2090 2091// helper: parse a NUL-terminated source string, return ctx (via box) + prog index. 2092func jq_parse(s: *u8, ctxbox: *i64) -> i64 { 2093 return jp_parse_source(s, jq_strlen(s), ctxbox) 2094} 2095func jq_ctx(ctxbox: *i64) -> *i64 { return (ctxbox[0]) as *i64 } 2096func jq_haserr(ctxbox: *i64) -> i64 { let c: *i64 = jq_ctx(ctxbox); let p: *i64 = jp_pst(c); return p[PST_ERR] } 2097 2098// node index of statement-0's top expression (a-slot of the first EXPR_STMT). 2099func jq_expr0(ctxbox: *i64, prog: i64) -> i64 { 2100 let c: *i64 = jq_ctx(ctxbox) 2101 let st: i64 = jp_child_at(c, prog, 0) 2102 return jp_na(c, st) 2103} 2104// kind of statement-0's top expression. 2105func jq_expr0_kind(ctxbox: *i64, prog: i64) -> i64 { 2106 let c: *i64 = jq_ctx(ctxbox) 2107 return jp_nkind(c, jq_expr0(ctxbox, prog)) 2108} 2109// op-code (extra slot) of statement-0's top expression. 2110func jq_expr0_op(ctxbox: *i64, prog: i64) -> i64 { 2111 let c: *i64 = jq_ctx(ctxbox) 2112 return jp_nextra(c, jq_expr0(ctxbox, prog)) 2113} 2114// 1 iff `src` parses with NO error AND its statement-0 expression is a BINARY whose 2115// op-code == want. Used by KAT10 to sweep an operator family compactly. 2116func jq_binop_is(src: *u8, ctxbox: *i64, want: i64) -> i64 { 2117 let prog: i64 = jq_parse(src, ctxbox) 2118 if jq_haserr(ctxbox) == 1 { return 0 } 2119 if jq_expr0_kind(ctxbox, prog) != ND_BINARY { return 0 } 2120 if jq_expr0_op(ctxbox, prog) != want { return 0 } 2121 return 1 2122} 2123// 1 iff `src` parses with NO error AND its statement-0 expression is a UNARY whose 2124// op-code == want. 2125func jq_unop_is(src: *u8, ctxbox: *i64, want: i64) -> i64 { 2126 let prog: i64 = jq_parse(src, ctxbox) 2127 if jq_haserr(ctxbox) == 1 { return 0 } 2128 if jq_expr0_kind(ctxbox, prog) != ND_UNARY { return 0 } 2129 if jq_expr0_op(ctxbox, prog) != want { return 0 } 2130 return 1 2131} 2132 2133func main() -> i64 { 2134 let ctxbox: *i64 = sys_mmap(16) as *i64 2135 var pass: i64 = 0 2136 var tot: i64 = 0 2137 jq_puts("nx_js_parse gate (R-JS-PARSE, WB-JS-001 rung 1)\n" as *u8) 2138 2139 // ---- KAT 1 (precedence): 1+2*3 -> EXPR_STMT( BINARY(+) ) whose RIGHT child is 2140 // BINARY(*). Proves * binds tighter than + (NOT left-flat). ---- 2141 let s1: *u8 = "1+2*3\x00" as *u8 2142 let prog1: i64 = jq_parse(s1, ctxbox) 2143 let c1: *i64 = jq_ctx(ctxbox) 2144 var r1: i64 = 1 2145 if jq_haserr(ctxbox) == 1 { r1 = 0 } 2146 if jp_nkind(c1, prog1) != ND_PROGRAM { r1 = 0 } 2147 if jp_nb(c1, prog1) != 1 { r1 = 0 } // exactly 1 statement 2148 if r1 == 1 { 2149 let st: i64 = jp_child_at(c1, prog1, 0) 2150 if jp_nkind(c1, st) != ND_EXPR_STMT { r1 = 0 } 2151 if r1 == 1 { 2152 let add: i64 = jp_na(c1, st) 2153 if jp_nkind(c1, add) != ND_BINARY { r1 = 0 } 2154 if r1 == 1 { if jp_nextra(c1, add) != OP_ADD { r1 = 0 } } 2155 if r1 == 1 { 2156 let lhs: i64 = jp_na(c1, add) 2157 let rhs: i64 = jp_nb(c1, add) 2158 if jp_nkind(c1, lhs) != ND_NUMBER { r1 = 0 } // left = 1 (a NUMBER) 2159 if jp_nkind(c1, rhs) != ND_BINARY { r1 = 0 } // right = (2*3) 2160 if r1 == 1 { if jp_nextra(c1, rhs) != OP_MUL { r1 = 0 } } 2161 } 2162 } 2163 } 2164 if r1 == 1 { jq_puts(" PASS KAT1 precedence 1+2*3 -> +(1, *(2,3))\n" as *u8); pass=pass+1 } else { jq_puts(" FAIL KAT1 precedence\n" as *u8) } 2165 tot = tot + 1 2166 2167 // ---- KAT 2 (assoc): a=b=c -> ASSIGN(a, ASSIGN(b, c)) right-assoc; 2168 // AND 2-3-4 -> BINARY(-) whose LEFT is BINARY(-) (left-assoc). ---- 2169 let s2: *u8 = "a=b=c\x00" as *u8 2170 let prog2: i64 = jq_parse(s2, ctxbox) 2171 let c2: *i64 = jq_ctx(ctxbox) 2172 var r2: i64 = 1 2173 if jq_haserr(ctxbox) == 1 { r2 = 0 } 2174 if r2 == 1 { 2175 let st2: i64 = jp_child_at(c2, prog2, 0) 2176 let asn: i64 = jp_na(c2, st2) 2177 if jp_nkind(c2, asn) != ND_ASSIGN { r2 = 0 } 2178 if r2 == 1 { 2179 let lhs: i64 = jp_na(c2, asn) 2180 let rhs: i64 = jp_nb(c2, asn) 2181 if jp_nkind(c2, lhs) != ND_IDENT { r2 = 0 } // a 2182 if jp_nkind(c2, rhs) != ND_ASSIGN { r2 = 0 } // (b=c) nested RIGHT 2183 } 2184 } 2185 // left-assoc subtraction 2186 let s2b: *u8 = "2-3-4\x00" as *u8 2187 let prog2b: i64 = jq_parse(s2b, ctxbox) 2188 let c2b: *i64 = jq_ctx(ctxbox) 2189 if jq_haserr(ctxbox) == 1 { r2 = 0 } 2190 if r2 == 1 { 2191 let st: i64 = jp_child_at(c2b, prog2b, 0) 2192 let sub: i64 = jp_na(c2b, st) 2193 if jp_nkind(c2b, sub) != ND_BINARY { r2 = 0 } 2194 if r2 == 1 { if jp_nextra(c2b, sub) != OP_SUB { r2 = 0 } } 2195 if r2 == 1 { 2196 let lhs: i64 = jp_na(c2b, sub) 2197 let rhs: i64 = jp_nb(c2b, sub) 2198 if jp_nkind(c2b, lhs) != ND_BINARY { r2 = 0 } // (2-3) nested LEFT 2199 if jp_nkind(c2b, rhs) != ND_NUMBER { r2 = 0 } // 4 on the right 2200 } 2201 } 2202 if r2 == 1 { jq_puts(" PASS KAT2 assoc a=b=c right + 2-3-4 left\n" as *u8); pass=pass+1 } else { jq_puts(" FAIL KAT2 assoc\n" as *u8) } 2203 tot = tot + 1 2204 2205 // ---- KAT 3 (call/member chain): foo.bar(x) -> CALL of MEMBER(foo,bar), 1 arg. ---- 2206 let s3: *u8 = "foo.bar(x)\x00" as *u8 2207 let prog3: i64 = jq_parse(s3, ctxbox) 2208 let c3: *i64 = jq_ctx(ctxbox) 2209 var r3: i64 = 1 2210 if jq_haserr(ctxbox) == 1 { r3 = 0 } 2211 if r3 == 1 { 2212 let st: i64 = jp_child_at(c3, prog3, 0) 2213 let call: i64 = jp_na(c3, st) 2214 if jp_nkind(c3, call) != ND_CALL { r3 = 0 } 2215 if r3 == 1 { 2216 let callee: i64 = jp_na(c3, call) // a=callee 2217 let argc: i64 = jp_nc(c3, call) // c=arg count 2218 if jp_nkind(c3, callee) != ND_MEMBER { r3 = 0 } 2219 if argc != 1 { r3 = 0 } 2220 if r3 == 1 { 2221 let obj: i64 = jp_na(c3, callee) 2222 let prop: i64 = jp_nb(c3, callee) 2223 if jp_nkind(c3, obj) != ND_IDENT { r3 = 0 } // foo 2224 if jp_nkind(c3, prop) != ND_IDENT { r3 = 0 } // bar 2225 } 2226 } 2227 } 2228 if r3 == 1 { jq_puts(" PASS KAT3 chain foo.bar(x) -> CALL(MEMBER(foo,bar), [x])\n" as *u8); pass=pass+1 } else { jq_puts(" FAIL KAT3 chain\n" as *u8) } 2229 tot = tot + 1 2230 2231 // ---- KAT 4 (statements): var decl + if/else + while + function decl + return. ---- 2232 let s4: *u8 = "var x = 1; if (x) { return x; } else { x = 2; } while (x) { x = x - 1; } function f(a, b) { return a + b; }\x00" as *u8 2233 let prog4: i64 = jq_parse(s4, ctxbox) 2234 let c4: *i64 = jq_ctx(ctxbox) 2235 var r4: i64 = 1 2236 if jq_haserr(ctxbox) == 1 { r4 = 0 } 2237 if jp_nkind(c4, prog4) != ND_PROGRAM { r4 = 0 } 2238 if r4 == 1 { if jp_nb(c4, prog4) != 4 { r4 = 0 } } // 4 top-level statements 2239 if r4 == 1 { 2240 let s_var: i64 = jp_child_at(c4, prog4, 0) 2241 let s_if: i64 = jp_child_at(c4, prog4, 1) 2242 let s_wh: i64 = jp_child_at(c4, prog4, 2) 2243 let s_fn: i64 = jp_child_at(c4, prog4, 3) 2244 if jp_nkind(c4, s_var) != ND_VAR_DECL { r4 = 0 } 2245 if jp_nkind(c4, s_if) != ND_IF { r4 = 0 } 2246 if jp_nkind(c4, s_wh) != ND_WHILE { r4 = 0 } 2247 if jp_nkind(c4, s_fn) != ND_FUNC_DECL { r4 = 0 } 2248 // var decl has an initializer (b != -1) 2249 if r4 == 1 { if jp_nb(c4, s_var) == -1 { r4 = 0 } } 2250 // if has an else branch (c != -1) and both branches are BLOCKs 2251 if r4 == 1 { 2252 if jp_nc(c4, s_if) == -1 { r4 = 0 } 2253 if r4 == 1 { 2254 if jp_nkind(c4, jp_nb(c4, s_if)) != ND_BLOCK { r4 = 0 } // then 2255 if jp_nkind(c4, jp_nc(c4, s_if)) != ND_BLOCK { r4 = 0 } // else 2256 } 2257 } 2258 // then-block contains a RETURN 2259 if r4 == 1 { 2260 let then_b: i64 = jp_nb(c4, s_if) 2261 if jp_nb(c4, then_b) < 1 { r4 = 0 } 2262 if r4 == 1 { 2263 let ret: i64 = jp_child_at(c4, then_b, 0) 2264 if jp_nkind(c4, ret) != ND_RETURN { r4 = 0 } 2265 if r4 == 1 { if jp_na(c4, ret) == -1 { r4 = 0 } } // return HAS an expr 2266 } 2267 } 2268 // function f(a,b){...} -> 2 params, body BLOCK with a RETURN of a BINARY(+) 2269 if r4 == 1 { 2270 let params: i64 = jp_nb(c4, s_fn) 2271 let body: i64 = jp_nc(c4, s_fn) 2272 if jp_nkind(c4, params) != ND_PARAMS { r4 = 0 } 2273 if r4 == 1 { if jp_nb(c4, params) != 2 { r4 = 0 } } // 2 params 2274 if jp_nkind(c4, body) != ND_BLOCK { r4 = 0 } 2275 if r4 == 1 { 2276 let ret: i64 = jp_child_at(c4, body, 0) 2277 if jp_nkind(c4, ret) != ND_RETURN { r4 = 0 } 2278 if r4 == 1 { 2279 let plus: i64 = jp_na(c4, ret) 2280 if jp_nkind(c4, plus) != ND_BINARY { r4 = 0 } 2281 if r4 == 1 { if jp_nextra(c4, plus) != OP_ADD { r4 = 0 } } 2282 } 2283 } 2284 } 2285 } 2286 if r4 == 1 { jq_puts(" PASS KAT4 statements var/if-else/while/func+return\n" as *u8); pass=pass+1 } else { jq_puts(" FAIL KAT4 statements\n" as *u8) } 2287 tot = tot + 1 2288 2289 // ---- KAT 5 (TAMPER): malformed "1 + )" MUST be an error, never a fake AST. ---- 2290 let s5: *u8 = "1 + )\x00" as *u8 2291 let prog5: i64 = jq_parse(s5, ctxbox) 2292 var r5: i64 = 1 2293 if jq_haserr(ctxbox) != 1 { r5 = 0 } // must have flagged an error 2294 // second malformed sample: "var = ;" (decl with no name) 2295 let s5b: *u8 = "var = ;\x00" as *u8 2296 let prog5b: i64 = jq_parse(s5b, ctxbox) 2297 if jq_haserr(ctxbox) != 1 { r5 = 0 } 2298 // third: dangling operator "a *" (RHS missing) 2299 let s5c: *u8 = "a *\x00" as *u8 2300 let prog5c: i64 = jq_parse(s5c, ctxbox) 2301 if jq_haserr(ctxbox) != 1 { r5 = 0 } 2302 if r5 == 1 { jq_puts(" PASS KAT5 tamper: 3 malformed inputs all -> ERROR (no fake AST)\n" as *u8); pass=pass+1 } else { jq_puts(" FAIL KAT5 tamper (a malformed input was accepted!)\n" as *u8) } 2303 tot = tot + 1 2304 2305 // ---- KAT 6 (precedence ladder + unary): !a && b || c == d -> top is OR, whose 2306 // left is AND, the AND's left is UNARY(!a), and == binds tighter than ==. --- 2307 let s6: *u8 = "!a && b || c == d\x00" as *u8 2308 let prog6: i64 = jq_parse(s6, ctxbox) 2309 let c6: *i64 = jq_ctx(ctxbox) 2310 var r6: i64 = 1 2311 if jq_haserr(ctxbox) == 1 { r6 = 0 } 2312 if r6 == 1 { 2313 let st: i64 = jp_child_at(c6, prog6, 0) 2314 let or_n: i64 = jp_na(c6, st) 2315 if jp_nkind(c6, or_n) != ND_BINARY { r6 = 0 } 2316 if r6 == 1 { if jp_nextra(c6, or_n) != OP_OR { r6 = 0 } } // top = || 2317 if r6 == 1 { 2318 let and_n: i64 = jp_na(c6, or_n) // left of || 2319 let eq_n: i64 = jp_nb(c6, or_n) // right of || 2320 if jp_nextra(c6, and_n) != OP_AND { r6 = 0 } // && under || 2321 if r6 == 1 { if jp_nextra(c6, eq_n) != OP_EQ { r6 = 0 } } // == under || 2322 if r6 == 1 { 2323 let not_n: i64 = jp_na(c6, and_n) // left of && 2324 if jp_nkind(c6, not_n) != ND_UNARY { r6 = 0 } 2325 if r6 == 1 { if jp_nextra(c6, not_n) != OP_NOT { r6 = 0 } } 2326 } 2327 } 2328 } 2329 if r6 == 1 { jq_puts(" PASS KAT6 ladder !a && b || c==d -> ||(&&(!a,b), ==(c,d))\n" as *u8); pass=pass+1 } else { jq_puts(" FAIL KAT6 precedence ladder\n" as *u8) } 2330 tot = tot + 1 2331 2332 // ---- KAT 7 (parenthesized regrouping): (1+2)*3 -> BINARY(*) whose LEFT is 2333 // BINARY(+). Proves parens override precedence (distinct from KAT1). ---- 2334 let s7: *u8 = "(1+2)*3\x00" as *u8 2335 let prog7: i64 = jq_parse(s7, ctxbox) 2336 let c7: *i64 = jq_ctx(ctxbox) 2337 var r7: i64 = 1 2338 if jq_haserr(ctxbox) == 1 { r7 = 0 } 2339 if r7 == 1 { 2340 let st: i64 = jp_child_at(c7, prog7, 0) 2341 let mul: i64 = jp_na(c7, st) 2342 if jp_nkind(c7, mul) != ND_BINARY { r7 = 0 } 2343 if r7 == 1 { if jp_nextra(c7, mul) != OP_MUL { r7 = 0 } } 2344 if r7 == 1 { 2345 let lhs: i64 = jp_na(c7, mul) 2346 let rhs: i64 = jp_nb(c7, mul) 2347 if jp_nkind(c7, lhs) != ND_BINARY { r7 = 0 } // (1+2) on the LEFT now 2348 if r7 == 1 { if jp_nextra(c7, lhs) != OP_ADD { r7 = 0 } } 2349 if jp_nkind(c7, rhs) != ND_NUMBER { r7 = 0 } // 3 on the right 2350 } 2351 } 2352 if r7 == 1 { jq_puts(" PASS KAT7 parens (1+2)*3 -> *(+(1,2), 3)\n" as *u8); pass=pass+1 } else { jq_puts(" FAIL KAT7 parens\n" as *u8) } 2353 tot = tot + 1 2354 2355 // ---- KAT 8 (index + nested call chain): a[b].c(d, e) -> CALL(MEMBER(INDEX(a,b),c),[d,e]) ---- 2356 let s8: *u8 = "a[b].c(d, e)\x00" as *u8 2357 let prog8: i64 = jq_parse(s8, ctxbox) 2358 let c8: *i64 = jq_ctx(ctxbox) 2359 var r8: i64 = 1 2360 if jq_haserr(ctxbox) == 1 { r8 = 0 } 2361 if r8 == 1 { 2362 let st: i64 = jp_child_at(c8, prog8, 0) 2363 let call: i64 = jp_na(c8, st) 2364 if jp_nkind(c8, call) != ND_CALL { r8 = 0 } 2365 if r8 == 1 { if jp_nc(c8, call) != 2 { r8 = 0 } } // 2 args 2366 if r8 == 1 { 2367 let mem: i64 = jp_na(c8, call) 2368 if jp_nkind(c8, mem) != ND_MEMBER { r8 = 0 } 2369 if r8 == 1 { 2370 let idx: i64 = jp_na(c8, mem) 2371 if jp_nkind(c8, idx) != ND_INDEX { r8 = 0 } 2372 if r8 == 1 { 2373 if jp_nkind(c8, jp_na(c8, idx)) != ND_IDENT { r8 = 0 } // a 2374 if jp_nkind(c8, jp_nb(c8, idx)) != ND_IDENT { r8 = 0 } // b 2375 } 2376 } 2377 } 2378 } 2379 if r8 == 1 { jq_puts(" PASS KAT8 a[b].c(d,e) -> CALL(MEMBER(INDEX(a,b),c),[d,e])\n" as *u8); pass=pass+1 } else { jq_puts(" FAIL KAT8 index/call chain\n" as *u8) } 2380 tot = tot + 1 2381 2382 // ---- KAT 9 (literals + unary families that had ZERO coverage before): assert 2383 // STRING / null (NULL) / true,false (BOOL) primary nodes AND typeof / unary 2384 // '-' / unary '+' unary-op codes. Council BLOCKER fix: these code paths 2385 // existed but no KAT ever asserted their nodes -> 'ALL gated' was false. ---- 2386 var r9: i64 = 1 2387 // string literal "x" -- byte-built so the source escape is unambiguous. 2388 let s9str: *u8 = sys_mmap(8) 2389 s9str[0] = 34 as u8; s9str[1] = 120 as u8; s9str[2] = 34 as u8; s9str[3] = 0 as u8 // "x" 2390 let p9str: i64 = jq_parse(s9str, ctxbox) 2391 if jq_haserr(ctxbox) == 1 { r9 = 0 } 2392 if r9 == 1 { if jq_expr0_kind(ctxbox, p9str) != ND_STRING { r9 = 0 } } 2393 // null 2394 if r9 == 1 { 2395 let p: i64 = jq_parse("null\x00" as *u8, ctxbox) 2396 if jq_haserr(ctxbox) == 1 { r9 = 0 } 2397 if r9 == 1 { if jq_expr0_kind(ctxbox, p) != ND_NULL { r9 = 0 } } 2398 } 2399 // true -> BOOL extra=1 2400 if r9 == 1 { 2401 let p: i64 = jq_parse("true\x00" as *u8, ctxbox) 2402 if jq_haserr(ctxbox) == 1 { r9 = 0 } 2403 if r9 == 1 { if jq_expr0_kind(ctxbox, p) != ND_BOOL { r9 = 0 } } 2404 if r9 == 1 { if jq_expr0_op(ctxbox, p) != 1 { r9 = 0 } } 2405 } 2406 // false -> BOOL extra=0 2407 if r9 == 1 { 2408 let p: i64 = jq_parse("false\x00" as *u8, ctxbox) 2409 if jq_haserr(ctxbox) == 1 { r9 = 0 } 2410 if r9 == 1 { if jq_expr0_kind(ctxbox, p) != ND_BOOL { r9 = 0 } } 2411 if r9 == 1 { if jq_expr0_op(ctxbox, p) != 0 { r9 = 0 } } 2412 } 2413 if r9 == 1 { if jq_unop_is("typeof a\x00" as *u8, ctxbox, OP_TYPEOF) == 0 { r9 = 0 } } 2414 if r9 == 1 { if jq_unop_is("-a\x00" as *u8, ctxbox, OP_NEG) == 0 { r9 = 0 } } 2415 if r9 == 1 { if jq_unop_is("+a\x00" as *u8, ctxbox, OP_POS) == 0 { r9 = 0 } } 2416 if r9 == 1 { jq_puts(" PASS KAT9 literals STRING/null/true/false + typeof/-/+ unary\n" as *u8); pass=pass+1 } else { jq_puts(" FAIL KAT9 literals/unary coverage\n" as *u8) } 2417 tot = tot + 1 2418 2419 // ---- KAT 10 (operator families that had ZERO coverage): one BINARY node per 2420 // previously-untested operator -- '/' '%' (mul tier), '<' '<=' '>' '>=' 2421 // (rel tier), '!=' '===' '!==' (eq tier). Makes 'ALL gated' honest. ---- 2422 var r10: i64 = 1 2423 if jq_binop_is("a/b\x00" as *u8, ctxbox, OP_DIV) == 0 { r10 = 0 } 2424 if jq_binop_is("a%b\x00" as *u8, ctxbox, OP_MOD) == 0 { r10 = 0 } 2425 if jq_binop_is("a<b\x00" as *u8, ctxbox, OP_LT) == 0 { r10 = 0 } 2426 if jq_binop_is("a<=b\x00" as *u8, ctxbox, OP_LE) == 0 { r10 = 0 } 2427 if jq_binop_is("a>b\x00" as *u8, ctxbox, OP_GT) == 0 { r10 = 0 } 2428 if jq_binop_is("a>=b\x00" as *u8, ctxbox, OP_GE) == 0 { r10 = 0 } 2429 if jq_binop_is("a!=b\x00" as *u8, ctxbox, OP_NE) == 0 { r10 = 0 } 2430 if jq_binop_is("a===b\x00" as *u8, ctxbox, OP_SEQ) == 0 { r10 = 0 } 2431 if jq_binop_is("a!==b\x00" as *u8, ctxbox, OP_SNE) == 0 { r10 = 0 } 2432 if r10 == 1 { jq_puts(" PASS KAT10 op families / % < <= > >= != === !== all gated\n" as *u8); pass=pass+1 } else { jq_puts(" FAIL KAT10 op family coverage\n" as *u8) } 2433 tot = tot + 1 2434 2435 // ---- KAT 11 (ASI tamper -- the BLOCKER): adjacent statements on ONE LINE with 2436 // no separator MUST error ('a b', '1 2' are real-JS SyntaxErrors); but a 2437 // LINE BREAK between them is a valid ASI point and MUST parse as 2 stmts. 2438 // Proves we ENFORCE same-line separation yet still honor ASI (not just 2439 // reject-everything). ---- 2440 var r11: i64 = 1 2441 // 'a b' -> error 2442 let p11a: i64 = jq_parse("a b\x00" as *u8, ctxbox) 2443 if jq_haserr(ctxbox) != 1 { r11 = 0 } 2444 // '1 2' -> error 2445 let p11b: i64 = jq_parse("1 2\x00" as *u8, ctxbox) 2446 if jq_haserr(ctxbox) != 1 { r11 = 0 } 2447 // 'a\nb' (newline separator) -> OK, 2 statements, no error 2448 let s11c: *u8 = sys_mmap(8) 2449 s11c[0] = 97 as u8; s11c[1] = 10 as u8; s11c[2] = 98 as u8; s11c[3] = 0 as u8 // a<LF>b 2450 let p11c: i64 = jq_parse(s11c, ctxbox) 2451 let c11c: *i64 = jq_ctx(ctxbox) 2452 if jq_haserr(ctxbox) == 1 { r11 = 0 } 2453 if r11 == 1 { if jp_nb(c11c, p11c) != 2 { r11 = 0 } } // exactly 2 statements 2454 if r11 == 1 { jq_puts(" PASS KAT11 ASI: 'a b'/'1 2' -> ERROR, 'a<LF>b' -> 2 stmts OK\n" as *u8); pass=pass+1 } else { jq_puts(" FAIL KAT11 ASI (adjacent same-line accepted, or newline rejected!)\n" as *u8) } 2455 tot = tot + 1 2456 2457 // ---- KAT 12 (lvalue tamper -- the BLOCKER): assignment to a non-lvalue MUST 2458 // error. '1 = 2' (NUMBER lhs) and '(a+b) = c' (BINARY lhs) are real-JS 2459 // 'Invalid left-hand side in assignment'. Also confirm a VALID lvalue 2460 // 'a.b = c' (MEMBER) still parses, so we reject only the invalid ones. ---- 2461 var r12: i64 = 1 2462 let p12a: i64 = jq_parse("1 = 2\x00" as *u8, ctxbox) 2463 if jq_haserr(ctxbox) != 1 { r12 = 0 } 2464 let p12b: i64 = jq_parse("(a+b) = c\x00" as *u8, ctxbox) 2465 if jq_haserr(ctxbox) != 1 { r12 = 0 } 2466 // sanity: a valid lvalue target (member) still parses as ASSIGN, no error. 2467 let p12c: i64 = jq_parse("a.b = c\x00" as *u8, ctxbox) 2468 if jq_haserr(ctxbox) == 1 { r12 = 0 } 2469 if r12 == 1 { if jq_expr0_kind(ctxbox, p12c) != ND_ASSIGN { r12 = 0 } } 2470 if r12 == 1 { jq_puts(" PASS KAT12 lvalue: '1=2'/'(a+b)=c' -> ERROR, 'a.b=c' -> ASSIGN OK\n" as *u8); pass=pass+1 } else { jq_puts(" FAIL KAT12 lvalue (non-lvalue assignment accepted!)\n" as *u8) } 2471 tot = tot + 1 2472 2473 jq_puts("---- nx_js_parse gate: passed " as *u8); jq_putn(pass); jq_puts(" / " as *u8); jq_putn(tot); jq_puts("\n" as *u8) 2474 let lfd: i64 = sys_openat_append("knowledge/status/js_engine.log\x00" as *u8, 0x1a4) 2475 if lfd >= 0 { 2476 sys_write(lfd, "R-JS-PARSE organ=nx_js_parse kats=" as *u8, 34) 2477 jq_fdn(lfd, pass); sys_write(lfd, "/" as *u8, 1); jq_fdn(lfd, tot) 2478 if pass == tot { sys_write(lfd, " tamper=ok verdict=GREEN\n" as *u8, 25) } 2479 if pass != tot { sys_write(lfd, " tamper=?? verdict=RED\n" as *u8, 23) } 2480 sys_close(lfd) 2481 } 2482 if pass == tot { sys_exit(0); return 0 } 2483 sys_exit(1) 2484 return 1 2485}