code wiki / (root) / nx_js_parse.nx

nx_js_parse.nx source

↩ module page · 2531 lines · 127084 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 // SHORTHAND PROPERTY `{dev,gctx}` (ES6, ubiquitous): an IDENT key followed directly by 561 // `,` or `}` means {dev:dev} -- the value IS the same-named variable. Found by walking 562 // the real shipped page (byte 293,650: GPU={dev,gctx,t3,...}). Only IDENT keys qualify; 563 // {return,} stays an error, exactly as a browser treats it. 564 var jshort: i64 = 0 565 if jp_kind_at(ctx, jp_cur(ctx) - 1) == JS_TOK_IDENT { 566 if jp_is_punct(ctx, ",\x00" as *u8) == 1 { jshort = 1 } 567 if jp_is_punct(ctx, "}\x00" as *u8) == 1 { jshort = 1 } 568 } 569 if jshort == 1 { 570 val = jp_new_node(ctx, ND_IDENT, -1, -1, -1, keytok, 0) 571 } else { 572 if jp_eat_punct(ctx, ":\x00" as *u8) == 0 { jp_set_err(ctx); go = 0 } 573 if go == 1 { val = jp_parse_assign(ctx) } 574 } 575 } 576 if go == 1 { 577 let prop: i64 = jp_new_node(ctx, ND_PROP, val, -1, -1, keytok, 0) 578 if count < ND_MAGIC_1024 { scratch[count] = prop } 579 count = count + 1 580 if jp_err(ctx) == 1 { go = 0 } 581 if go == 1 { 582 if jp_eat_punct(ctx, ",\x00" as *u8) == 1 { 583 // trailing comma before '}' is allowed: {a:1,} 584 if jp_is_punct(ctx, "}\x00" as *u8) == 1 { jp_advance(ctx); go = 0 } 585 } else { 586 if jp_eat_punct(ctx, "}\x00" as *u8) == 1 { go = 0 } 587 else { jp_set_err(ctx); go = 0 } 588 } 589 } 590 } 591 } 592 } 593 if jp_err(ctx) == 1 { return jp_error_node(ctx) } 594 let start: i64 = jp_child_commit(ctx, scratch, count) 595 return jp_new_node(ctx, ND_OBJECT, start, count, -1, -1, 0) 596} 597 598// `new C(args)` -> ND_NEW. extra=0: a = the ND_CALL node (callee + args). extra=1: a = callee (bare `new C`). 599func jp_parse_new(ctx: *i64) -> i64 { 600 jp_advance(ctx) // consume 'new' 601 var callee: i64 = jp_parse_primary(ctx) 602 if jp_err(ctx) == 1 { return jp_error_node(ctx) } 603 // MemberExpression tail: `new A.b.c(...)` / `new A[k](...)` -- the constructor is the FULL member chain 604 // (`.prop` and `[expr]`), but NOT a call '(' (that begins the `new` Arguments). Mirrors jp_parse_postfix 605 // minus the call tail. (Guarded-if form: NishiLang has no `else if`.) 606 var go: i64 = 1 607 while go == 1 { 608 if jp_err(ctx) == 1 { go = 0 } 609 if go == 1 { 610 if jp_is_punct(ctx, ".\x00" as *u8) == 1 { 611 jp_advance(ctx) 612 if jp_tok_kind(ctx) != JS_TOK_IDENT { if jp_tok_kind(ctx) != JS_TOK_KEYWORD { callee = jp_error_node(ctx); go = 0 } } 613 if go == 1 { 614 let prop: i64 = jp_new_node(ctx, ND_IDENT, -1, -1, -1, jp_cur(ctx), 0) 615 jp_advance(ctx) 616 callee = jp_new_node(ctx, ND_MEMBER, callee, prop, -1, -1, 0) 617 } 618 } else { 619 if jp_is_punct(ctx, "[\x00" as *u8) == 1 { 620 jp_advance(ctx) 621 let ix: i64 = jp_parse_expr(ctx) 622 if jp_eat_punct(ctx, "]\x00" as *u8) == 0 { callee = jp_error_node(ctx); go = 0 } 623 if go == 1 { callee = jp_new_node(ctx, ND_INDEX, callee, ix, -1, -1, 0) } 624 } else { 625 go = 0 626 } 627 } 628 } 629 } 630 if jp_err(ctx) == 1 { return jp_error_node(ctx) } 631 if jp_is_punct(ctx, "(\x00" as *u8) == 1 { 632 let callnode: i64 = jp_parse_call_tail(ctx, callee, 0) 633 return jp_new_node(ctx, ND_NEW, callnode, -1, -1, -1, 0) 634 } 635 return jp_new_node(ctx, ND_NEW, callee, -1, -1, -1, 1) 636} 637// `class Name { constructor(params){body} m(params){body}... }` -> ND_CLASS (a=name, b=ctor ND_FUNC_DECL, 638// c=methods ND_BLOCK of method ND_FUNC_DECLs). Prototype methods are now KEPT (was: dropped). No extends/ 639// static/getters yet (those decline gracefully at parse or eval). Method-node = ND_FUNC_DECL(name,params,body). 640func jp_parse_class_decl(ctx: *i64) -> i64 { 641 jp_advance(ctx) // consume 'class' 642 if jp_tok_kind(ctx) != JS_TOK_IDENT { return jp_error_node(ctx) } 643 let nametok: i64 = jp_cur(ctx) 644 let name: i64 = jp_new_node(ctx, ND_IDENT, -1, -1, -1, nametok, 0) 645 jp_advance(ctx) 646 // optional `extends <expr>` -- superclass is usually a bare ident (`extends A`) but may be a 647 // member (`extends React.Component`); parse a postfix so both shapes work. Stored in ND_CLASS extra. 648 var superidx: i64 = 0 - 1 649 if jp_is_kw(ctx, "extends\x00" as *u8) == 1 { 650 jp_advance(ctx) 651 superidx = jp_parse_postfix(ctx) 652 if jp_err(ctx) == 1 { return jp_error_node(ctx) } 653 } 654 if jp_eat_punct(ctx, "{\x00" as *u8) == 0 { return jp_error_node(ctx) } 655 var cparams: i64 = 0 - 1 656 var cbody: i64 = 0 - 1 657 let mscratch: *i64 = sys_mmap(ND_MAGIC_1024 * 8) as *i64 // collected method func-decl nodes 658 var mcount: i64 = 0 659 var go: i64 = 1 660 while go == 1 { 661 if jp_is_punct(ctx, "}\x00" as *u8) == 1 { jp_advance(ctx); go = 0 } 662 else { 663 var isok: i64 = 0 664 if jp_tok_kind(ctx) == JS_TOK_IDENT { isok = 1 } 665 if jp_tok_kind(ctx) == JS_TOK_KEYWORD { isok = 1 } 666 if isok == 0 { jp_set_err(ctx); go = 0 } 667 if go == 1 { 668 let is_ctor: i64 = jp_is_lex(ctx, JS_TOK_IDENT, "constructor\x00" as *u8) 669 let mnametok: i64 = jp_cur(ctx) 670 jp_advance(ctx) // method name 671 let mparams: i64 = jp_parse_params(ctx) 672 let mbody: i64 = jp_parse_block(ctx) 673 if jp_err(ctx) == 1 { go = 0 } 674 if go == 1 { 675 if is_ctor == 1 { cparams = mparams; cbody = mbody } 676 else { 677 let mname: i64 = jp_new_node(ctx, ND_IDENT, -1, -1, -1, mnametok, 0) 678 let mfd: i64 = jp_new_node(ctx, ND_FUNC_DECL, mname, mparams, mbody, -1, 0) 679 if mcount < ND_MAGIC_1024 { mscratch[mcount] = mfd } 680 mcount = mcount + 1 681 } 682 } 683 } 684 } 685 } 686 if jp_err(ctx) == 1 { return jp_error_node(ctx) } 687 if cparams == (0 - 1) { 688 if superidx >= 0 { 689 // DERIVED class with no explicit ctor -> synthesize `constructor(...N){ super(...N) }` so 690 // `new B(args)` runs the PARENT ctor with the forwarded args (spec's implicit derived ctor). 691 // N reuses the class-name token (a harmless local rest param; the body only spreads it). 692 let restp: i64 = jp_new_node(ctx, ND_IDENT, -1, -1, -1, nametok, 1) // rest param (extra=1=rest) 693 let pscr: *i64 = sys_mmap(8) as *i64 694 pscr[0] = restp 695 let pstart: i64 = jp_child_commit(ctx, pscr, 1) 696 cparams = jp_new_node(ctx, ND_PARAMS, pstart, 1, -1, -1, 0) 697 let argid: i64 = jp_new_node(ctx, ND_IDENT, -1, -1, -1, nametok, 0) // reference to N 698 let spread: i64 = jp_new_node(ctx, ND_SPREAD, argid, -1, -1, -1, 0) // ...N 699 let ascr: *i64 = sys_mmap(8) as *i64 700 ascr[0] = spread 701 let astart: i64 = jp_child_commit(ctx, ascr, 1) 702 let supnode: i64 = jp_new_node(ctx, ND_SUPER, -1, -1, -1, nametok, 0) 703 let scall: i64 = jp_new_node(ctx, ND_CALL, supnode, astart, 1, -1, 0) // super(...N) 704 let estmt: i64 = jp_new_node(ctx, ND_EXPR_STMT, scall, -1, -1, -1, 0) 705 let bscr: *i64 = sys_mmap(8) as *i64 706 bscr[0] = estmt 707 let bstart: i64 = jp_child_commit(ctx, bscr, 1) 708 cbody = jp_new_node(ctx, ND_BLOCK, bstart, 1, -1, -1, 0) 709 } else { // non-derived class, no ctor -> empty function 710 let scratch: *i64 = sys_mmap(8) as *i64 711 let ps: i64 = jp_child_commit(ctx, scratch, 0) 712 cparams = jp_new_node(ctx, ND_PARAMS, ps, 0, -1, -1, 0) 713 let bs: i64 = jp_child_commit(ctx, scratch, 0) 714 cbody = jp_new_node(ctx, ND_BLOCK, bs, 0, -1, -1, 0) 715 } 716 } 717 let ctorfd: i64 = jp_new_node(ctx, ND_FUNC_DECL, name, cparams, cbody, -1, 0) 718 let mstart: i64 = jp_child_commit(ctx, mscratch, mcount) 719 let methods: i64 = jp_new_node(ctx, ND_BLOCK, mstart, mcount, -1, -1, 0) 720 return jp_new_node(ctx, ND_CLASS, name, ctorfd, methods, -1, superidx) 721} 722// primary: literal / ident / parenthesized expr 723func jp_parse_primary(ctx: *i64) -> i64 { 724 if jp_err(ctx) == 1 { return jp_error_node(ctx) } 725 let k: i64 = jp_tok_kind(ctx) 726 let tk: i64 = jp_cur(ctx) 727 728 if k == JS_TOK_NUMBER { 729 let n: i64 = jp_new_node(ctx, ND_NUMBER, -1, -1, -1, tk, 0) 730 jp_advance(ctx) 731 return n 732 } 733 if k == JS_TOK_STRING { 734 let n: i64 = jp_new_node(ctx, ND_STRING, -1, -1, -1, tk, 0) 735 jp_advance(ctx) 736 return n 737 } 738 if k == JS_TOK_TEMPLATE { 739 let n: i64 = jp_new_node(ctx, ND_TEMPLATE, -1, -1, -1, tk, 0) 740 jp_advance(ctx) 741 return n 742 } 743 if k == JS_TOK_REGEX { 744 let n: i64 = jp_new_node(ctx, ND_REGEX, -1, -1, -1, tk, 0) 745 jp_advance(ctx) 746 return n 747 } 748 if k == JS_TOK_IDENT { 749 let n: i64 = jp_new_node(ctx, ND_IDENT, -1, -1, -1, tk, 0) 750 jp_advance(ctx) 751 return n 752 } 753 if k == JS_TOK_KEYWORD { 754 if jp_is_kw(ctx, "true\x00" as *u8) == 1 { 755 let n: i64 = jp_new_node(ctx, ND_BOOL, -1, -1, -1, tk, 1) 756 jp_advance(ctx); return n 757 } 758 if jp_is_kw(ctx, "false\x00" as *u8) == 1 { 759 let n: i64 = jp_new_node(ctx, ND_BOOL, -1, -1, -1, tk, 0) 760 jp_advance(ctx); return n 761 } 762 if jp_is_kw(ctx, "this\x00" as *u8) == 1 { 763 let n: i64 = jp_new_node(ctx, ND_THIS, -1, -1, -1, tk, 0) // the this keyword; eval resolves via the frame binding 764 jp_advance(ctx); return n 765 } 766 if jp_is_kw(ctx, "super\x00" as *u8) == 1 { 767 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 768 jp_advance(ctx); return n 769 } 770 if jp_is_kw(ctx, "null\x00" as *u8) == 1 { 771 let n: i64 = jp_new_node(ctx, ND_NULL, -1, -1, -1, tk, 0) 772 jp_advance(ctx); return n 773 } 774 // FUNCTION EXPRESSION (R-JS-CLOSURE, rung 6): `function (params){body}` or named 775 // `function name(params){body}` in expression/primary position -- so `var f = 776 // function(x){...}` and `(function(){...})()` parse. Same ND_FUNC_DECL shape as a 777 // declaration (the evaluator builds a closure when it EVALUATES this node). 778 if jp_is_kw(ctx, "new\x00" as *u8) == 1 { return jp_parse_new(ctx) } 779 // `async function(){}` EXPRESSION -- the expression twin of the declaration case in 780 // jp_parse_stmt; same contextual consume-or-restore. 781 var easync: i64 = jp_is_lex(ctx, JS_TOK_IDENT, "async\x00" as *u8) 782 if jp_is_kw(ctx, "async\x00" as *u8) == 1 { easync = 1 } 783 if easync == 1 { 784 let esave: i64 = jp_cur(ctx) 785 jp_set_cur(ctx, esave + 1) 786 if jp_is_kw(ctx, "function\x00" as *u8) == 1 { return jp_parse_func_expr(ctx) } 787 jp_set_cur(ctx, esave) 788 } 789 if jp_is_kw(ctx, "function\x00" as *u8) == 1 { return jp_parse_func_expr(ctx) } 790 // any other keyword in expression position is a syntax error (rung 1) 791 return jp_error_node(ctx) 792 } 793 if k == JS_TOK_PUNCT { 794 if jp_is_punct(ctx, "(\x00" as *u8) == 1 { 795 jp_advance(ctx) 796 let inner: i64 = jp_parse_expr(ctx) 797 if jp_eat_punct(ctx, ")\x00" as *u8) == 0 { return jp_error_node(ctx) } 798 return inner 799 } 800 // array literal in expression position: [ ... ] 801 if jp_is_punct(ctx, "[\x00" as *u8) == 1 { return jp_parse_array(ctx) } 802 // object literal in expression position: { ... } (statement-leading '{' is a 803 // BLOCK and never reaches here -- jp_parse_stmt dispatches it before expr parsing). 804 if jp_is_punct(ctx, "{\x00" as *u8) == 1 { return jp_parse_object(ctx) } 805 return jp_error_node(ctx) 806 } 807 // EOF / ERROR token / anything else -> error 808 return jp_error_node(ctx) 809} 810 811// postfix: left-assoc chain of .ident [expr] (args...) applied to a primary 812func jp_parse_postfix(ctx: *i64) -> i64 { 813 var node: i64 = jp_parse_primary(ctx) 814 var go: i64 = 1 815 while go == 1 { 816 if jp_err(ctx) == 1 { go = 0 } 817 if go == 1 { 818 // OPTIONAL CHAINING ?. -- dispatch on what follows: ?.ident (member) / ?.( (call) / ?.[ (index). 819 // vk: `window.CSS?.supports?.(...)`. ND_MEMBER/ND_INDEX/ND_CALL carry extra=1 => short-circuit on nullish. 820 if jp_is_punct(ctx, "?.\x00" as *u8) == 1 { 821 jp_advance(ctx) // consume ?. 822 if jp_is_punct(ctx, "(\x00" as *u8) == 1 { 823 node = jp_parse_call_tail(ctx, node, 1) 824 if jp_err(ctx) == 1 { go = 0 } 825 } else { if jp_is_punct(ctx, "[\x00" as *u8) == 1 { 826 jp_advance(ctx) 827 let ix2: i64 = jp_parse_expr(ctx) 828 if jp_eat_punct(ctx, "]\x00" as *u8) == 0 { node = jp_error_node(ctx); go = 0 } 829 if go == 1 { node = jp_new_node(ctx, ND_INDEX, node, ix2, -1, -1, 1) } 830 } else { 831 if jp_tok_kind(ctx) != JS_TOK_IDENT { if jp_tok_kind(ctx) != JS_TOK_KEYWORD { node = jp_error_node(ctx); go = 0 } } 832 if go == 1 { 833 let prop2: i64 = jp_new_node(ctx, ND_IDENT, -1, -1, -1, jp_cur(ctx), 0) 834 jp_advance(ctx) 835 node = jp_new_node(ctx, ND_MEMBER, node, prop2, -1, -1, 1) 836 } 837 } } 838 } else { 839 // member: .ident 840 var isdot: i64 = 0 841 if jp_is_punct(ctx, ".\x00" as *u8) == 1 { isdot = 1 } 842 if isdot == 1 { 843 jp_advance(ctx) 844 if jp_tok_kind(ctx) != JS_TOK_IDENT { 845 if jp_tok_kind(ctx) != JS_TOK_KEYWORD { node = jp_error_node(ctx); go = 0 } 846 } 847 if go == 1 { 848 let prop: i64 = jp_new_node(ctx, ND_IDENT, -1, -1, -1, jp_cur(ctx), 0) 849 jp_advance(ctx) 850 node = jp_new_node(ctx, ND_MEMBER, node, prop, -1, -1, 0) 851 } 852 } else { 853 // computed index: [expr] 854 if jp_is_punct(ctx, "[\x00" as *u8) == 1 { 855 jp_advance(ctx) 856 let ix: i64 = jp_parse_expr(ctx) 857 if jp_eat_punct(ctx, "]\x00" as *u8) == 0 { node = jp_error_node(ctx); go = 0 } 858 if go == 1 { node = jp_new_node(ctx, ND_INDEX, node, ix, -1, -1, 0) } 859 } else { 860 // call: ( args... ) 861 if jp_is_punct(ctx, "(\x00" as *u8) == 1 { 862 node = jp_parse_call_tail(ctx, node, 0) 863 if jp_err(ctx) == 1 { go = 0 } 864 } else { 865 go = 0 866 } 867 } 868 } 869 } 870 } 871 } 872 // postfix increment/decrement: x++ / x-- (binds to the WHOLE member/index chain, one only). 873 // Valid only on a simple lvalue; `5++` stays un-consumed here and errors upstream. 874 // (ASI note: `a \n ++b` is treated as `a++ ... b` here -- line-agnostic; minified 875 // bundles have no newlines, named divergence from the spec's restricted production.) 876 if jp_err(ctx) == 0 { 877 if jp_is_lvalue(ctx, node) == 1 { 878 if jp_is_punct(ctx, "++\x00" as *u8) == 1 { jp_advance(ctx); node = jp_new_node(ctx, ND_UPDATE, node, -1, -1, -1, 3) } 879 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) } } 880 } 881 } 882 return node 883} 884 885// parse `( a , b , ... )` after the callee; build a CALL node whose 886// a=callee, b=child_start, c=child_count (args live in the child arena). 887// Args are buffered in local scratch then committed CONTIGUOUSLY (a nested call 888// inside an arg pushes into the shared arena mid-parse -- see jp_child_commit). 889func jp_parse_call_tail(ctx: *i64, callee: i64, opt: i64) -> i64 { // opt=1 -> optional call a?.(args) 890 jp_advance(ctx) // consume '(' 891 let scratch: *i64 = sys_mmap(256 * 8) as *i64 892 var count: i64 = 0 893 // empty arg list? 894 if jp_is_punct(ctx, ")\x00" as *u8) == 1 { 895 jp_advance(ctx) 896 let start0: i64 = jp_child_commit(ctx, scratch, 0) 897 return jp_new_node(ctx, ND_CALL, callee, start0, 0, -1, opt) 898 } 899 var go: i64 = 1 900 while go == 1 { 901 var arg: i64 = 0 902 if jp_is_punct(ctx, "...\x00" as *u8) == 1 { // spread argument f(...xs) 903 jp_advance(ctx) 904 let inner: i64 = jp_parse_assign(ctx) 905 arg = jp_new_node(ctx, ND_SPREAD, inner, -1, -1, -1, 0) 906 } else { 907 arg = jp_parse_assign(ctx) 908 } 909 if count < 256 { scratch[count] = arg } 910 count = count + 1 911 if jp_err(ctx) == 1 { go = 0 } 912 if go == 1 { 913 if jp_eat_punct(ctx, ",\x00" as *u8) == 1 { 914 // continue to next arg 915 } else { 916 if jp_eat_punct(ctx, ")\x00" as *u8) == 1 { go = 0 } 917 else { jp_set_err(ctx); go = 0 } 918 } 919 } 920 } 921 if jp_err(ctx) == 1 { return jp_error_node(ctx) } 922 let start: i64 = jp_child_commit(ctx, scratch, count) 923 return jp_new_node(ctx, ND_CALL, callee, start, count, -1, opt) 924} 925 926// is a node a valid update/assignment TARGET (simple lvalue)? 927// (jp_is_lvalue is defined ONCE, earlier in this file at :367. A byte-identical second 928// definition -- differing only in its parameter NAME (node vs idx) -- used to sit here, 929// the classic copy-paste/merge artefact. nx_cc accepted the redefinition silently and 930// picked a winner; removed 2026-07-31 with debt 1785447657.) 931// unary prefix: ! - + typeof ++ -- (right-recursive onto another unary) 932func jp_parse_unary(ctx: *i64) -> i64 { 933 if jp_err(ctx) == 1 { return jp_error_node(ctx) } 934 // `yield [expr]` / `yield* expr` -- expression-position, lowest-binding-ish; operand optional 935 // (bare `yield` before ; ) } , ] is valid). Parsed everywhere for bundle compatibility (strict 936 // contextual legality is the engine's later concern, not the frontier's). 937 // `await expr` -- phase 3 stub: evaluate the operand and use its VALUE (exact for already-settled 938 // and synchronous values; true suspension rides the existing event loop -- declared later rung). 939 // Contextual like `async`: only when an expression actually follows (a bare `await` identifier 940 // before ) , ; ] } = stays an identifier). 941 var awk: i64 = 0 942 if jp_is_kw(ctx, "await\x00" as *u8) == 1 { awk = 1 } 943 if jp_is_lex(ctx, JS_TOK_IDENT, "await\x00" as *u8) == 1 { awk = 1 } 944 if awk == 1 { 945 let nkw: i64 = jp_kind_at(ctx, jp_cur(ctx) + 1) 946 var starts: i64 = 0 947 if nkw == JS_TOK_IDENT { starts = 1 } 948 if nkw == JS_TOK_NUMBER { starts = 1 } 949 if nkw == JS_TOK_STRING { starts = 1 } 950 if nkw == JS_TOK_KEYWORD { starts = 1 } 951 if jp_punct_at(ctx, jp_cur(ctx) + 1, "(\x00" as *u8) == 1 { starts = 1 } 952 if jp_punct_at(ctx, jp_cur(ctx) + 1, "[\x00" as *u8) == 1 { starts = 1 } 953 if starts == 1 { jp_advance(ctx); return jp_parse_unary(ctx) } 954 } 955 if jp_is_kw(ctx, "yield\x00" as *u8) == 1 { 956 jp_advance(ctx) 957 if jp_is_punct(ctx, "*\x00" as *u8) == 1 { jp_advance(ctx) } 958 var yop: i64 = 0 - 1 959 var bare: i64 = 0 960 if jp_is_punct(ctx, ";\x00" as *u8) == 1 { bare = 1 } 961 if jp_is_punct(ctx, ")\x00" as *u8) == 1 { bare = 1 } 962 if jp_is_punct(ctx, "}\x00" as *u8) == 1 { bare = 1 } 963 if jp_is_punct(ctx, ",\x00" as *u8) == 1 { bare = 1 } 964 if jp_is_punct(ctx, "]\x00" as *u8) == 1 { bare = 1 } 965 if bare == 0 { yop = jp_parse_assign(ctx) } 966 return jp_new_node(ctx, ND_YIELD, yop, -1, -1, -1, 0) 967 } 968 // prefix increment/decrement: ++x / --x ("++"/"--" are single lexer tokens, so the 969 // "+"/"-" checks below can never shadow them). Target must be a simple lvalue 970 // (IDENT/MEMBER/INDEX) -- `++5` is a SyntaxError in real JS, ERROR node here. 971 if jp_is_punct(ctx, "++\x00" as *u8) == 1 { 972 jp_advance(ctx) 973 let operand: i64 = jp_parse_unary(ctx) 974 if jp_is_lvalue(ctx, operand) == 0 { jp_set_err(ctx); return jp_error_node(ctx) } 975 return jp_new_node(ctx, ND_UPDATE, operand, -1, -1, -1, 1) 976 } 977 if jp_is_punct(ctx, "--\x00" as *u8) == 1 { 978 jp_advance(ctx) 979 let operand: i64 = jp_parse_unary(ctx) 980 if jp_is_lvalue(ctx, operand) == 0 { jp_set_err(ctx); return jp_error_node(ctx) } 981 return jp_new_node(ctx, ND_UPDATE, operand, -1, -1, -1, 2) 982 } 983 if jp_is_punct(ctx, "!\x00" as *u8) == 1 { 984 jp_advance(ctx) 985 let operand: i64 = jp_parse_unary(ctx) 986 return jp_new_node(ctx, ND_UNARY, operand, -1, -1, -1, OP_NOT) 987 } 988 if jp_is_punct(ctx, "~\x00" as *u8) == 1 { 989 jp_advance(ctx) 990 let operand: i64 = jp_parse_unary(ctx) 991 return jp_new_node(ctx, ND_UNARY, operand, -1, -1, -1, OP_BNOT) 992 } 993 if jp_is_punct(ctx, "-\x00" as *u8) == 1 { 994 jp_advance(ctx) 995 let operand: i64 = jp_parse_unary(ctx) 996 return jp_new_node(ctx, ND_UNARY, operand, -1, -1, -1, OP_NEG) 997 } 998 if jp_is_punct(ctx, "+\x00" as *u8) == 1 { 999 jp_advance(ctx) 1000 let operand: i64 = jp_parse_unary(ctx) 1001 return jp_new_node(ctx, ND_UNARY, operand, -1, -1, -1, OP_POS) 1002 } 1003 if jp_is_kw(ctx, "typeof\x00" as *u8) == 1 { 1004 jp_advance(ctx) 1005 let operand: i64 = jp_parse_unary(ctx) 1006 return jp_new_node(ctx, ND_UNARY, operand, -1, -1, -1, OP_TYPEOF) 1007 } 1008 if jp_is_kw(ctx, "void\x00" as *u8) == 1 { 1009 jp_advance(ctx) 1010 let operand: i64 = jp_parse_unary(ctx) 1011 return jp_new_node(ctx, ND_UNARY, operand, -1, -1, -1, OP_VOID) 1012 } 1013 if jp_is_kw(ctx, "delete\x00" as *u8) == 1 { 1014 jp_advance(ctx) 1015 let operand: i64 = jp_parse_unary(ctx) // a MEMBER/INDEX ref; the evaluator inspects the node, not its value 1016 return jp_new_node(ctx, ND_UNARY, operand, -1, -1, -1, OP_DELETE) 1017 } 1018 return jp_parse_postfix(ctx) 1019} 1020 1021// ---- binary operator tier helper: returns the op-code for the current token at a 1022// given precedence tier, or 0 if the current token is not an op of that tier. 1023// Tiers are checked by dedicated functions to keep else-if nesting shallow. ---- 1024 1025// tier 1 (highest binary): * / % 1026func jp_mul_op(ctx: *i64) -> i64 { 1027 if jp_tok_kind(ctx) != JS_TOK_PUNCT { return 0 } 1028 if jp_is_punct(ctx, "*\x00" as *u8) == 1 { return OP_MUL } 1029 if jp_is_punct(ctx, "/\x00" as *u8) == 1 { return OP_DIV } 1030 if jp_is_punct(ctx, "%\x00" as *u8) == 1 { return OP_MOD } 1031 return 0 1032} 1033// tier 2: + - 1034func jp_add_op(ctx: *i64) -> i64 { 1035 if jp_tok_kind(ctx) != JS_TOK_PUNCT { return 0 } 1036 if jp_is_punct(ctx, "+\x00" as *u8) == 1 { return OP_ADD } 1037 if jp_is_punct(ctx, "-\x00" as *u8) == 1 { return OP_SUB } 1038 return 0 1039} 1040// tier 3: < <= > >= (NOTE: must check the 2-char forms first via lexeme compare; 1041// the lexer already produced "<=" as ONE punct token, so exact-match is safe.) 1042static jp_noin: i64 // 1 while parsing a for-init EXPRESSION: `in` is the for-in separator, NOT the operator 1043func jp_rel_op(ctx: *i64) -> i64 { 1044 // `instanceof`/`in` are KEYWORD tokens (not punct) at the SAME precedence tier as < > (real JS RelationalExpr). 1045 if jp_is_kw(ctx, "instanceof\x00" as *u8) == 1 { return OP_INSTANCEOF } 1046 if jp_is_kw(ctx, "in\x00" as *u8) == 1 { if jp_noin == 0 { return OP_IN } return 0 } 1047 if jp_tok_kind(ctx) != JS_TOK_PUNCT { return 0 } 1048 if jp_is_punct(ctx, "<=\x00" as *u8) == 1 { return OP_LE } 1049 if jp_is_punct(ctx, ">=\x00" as *u8) == 1 { return OP_GE } 1050 if jp_is_punct(ctx, "<\x00" as *u8) == 1 { return OP_LT } 1051 if jp_is_punct(ctx, ">\x00" as *u8) == 1 { return OP_GT } 1052 return 0 1053} 1054// tier 4: == != === !== 1055func jp_eq_op(ctx: *i64) -> i64 { 1056 if jp_tok_kind(ctx) != JS_TOK_PUNCT { return 0 } 1057 if jp_is_punct(ctx, "===\x00" as *u8) == 1 { return OP_SEQ } 1058 if jp_is_punct(ctx, "!==\x00" as *u8) == 1 { return OP_SNE } 1059 if jp_is_punct(ctx, "==\x00" as *u8) == 1 { return OP_EQ } 1060 if jp_is_punct(ctx, "!=\x00" as *u8) == 1 { return OP_NE } 1061 return 0 1062} 1063 1064// Generic left-assoc binary builder is awkward without function pointers, so each 1065// tier is its own small function that loops while its op matches, recursing to the 1066// NEXT-HIGHER tier for each operand. Correct precedence = the recursion order; 1067// correct (left) associativity = the while-loop reassigning `left`. 1068 1069func jp_parse_mul(ctx: *i64) -> i64 { 1070 var left: i64 = jp_parse_unary(ctx) 1071 var go: i64 = 1 1072 while go == 1 { 1073 if jp_err(ctx) == 1 { go = 0 } 1074 if go == 1 { 1075 let op: i64 = jp_mul_op(ctx) 1076 if op == 0 { go = 0 } else { 1077 jp_advance(ctx) 1078 let right: i64 = jp_parse_unary(ctx) 1079 left = jp_new_node(ctx, ND_BINARY, left, right, -1, -1, op) 1080 } 1081 } 1082 } 1083 return left 1084} 1085func jp_parse_add(ctx: *i64) -> i64 { 1086 var left: i64 = jp_parse_mul(ctx) 1087 var go: i64 = 1 1088 while go == 1 { 1089 if jp_err(ctx) == 1 { go = 0 } 1090 if go == 1 { 1091 let op: i64 = jp_add_op(ctx) 1092 if op == 0 { go = 0 } else { 1093 jp_advance(ctx) 1094 let right: i64 = jp_parse_mul(ctx) 1095 left = jp_new_node(ctx, ND_BINARY, left, right, -1, -1, op) 1096 } 1097 } 1098 } 1099 return left 1100} 1101// SHIFT: << >> >>> -- precedence between additive and relational (JS). Distinct lexer tokens, so no 1102// confusion with < / > (relational). Uses variable shift in eval (immediate-shift lexer gotcha). 1103func jp_parse_shift(ctx: *i64) -> i64 { 1104 var left: i64 = jp_parse_add(ctx) 1105 var go: i64 = 1 1106 while go == 1 { 1107 if jp_err(ctx) == 1 { go = 0 } 1108 if go == 1 { 1109 var op: i64 = 0 1110 if jp_is_punct(ctx, "<<\x00" as *u8) == 1 { op = OP_SHL } 1111 if jp_is_punct(ctx, ">>>\x00" as *u8) == 1 { op = OP_USHR } 1112 if op == 0 { if jp_is_punct(ctx, ">>\x00" as *u8) == 1 { op = OP_SHR } } 1113 if op == 0 { go = 0 } else { 1114 jp_advance(ctx) 1115 let right: i64 = jp_parse_add(ctx) 1116 left = jp_new_node(ctx, ND_BINARY, left, right, -1, -1, op) 1117 } 1118 } 1119 } 1120 return left 1121} 1122func jp_parse_rel(ctx: *i64) -> i64 { 1123 var left: i64 = jp_parse_shift(ctx) 1124 var go: i64 = 1 1125 while go == 1 { 1126 if jp_err(ctx) == 1 { go = 0 } 1127 if go == 1 { 1128 let op: i64 = jp_rel_op(ctx) 1129 if op == 0 { go = 0 } else { 1130 jp_advance(ctx) 1131 let right: i64 = jp_parse_shift(ctx) 1132 left = jp_new_node(ctx, ND_BINARY, left, right, -1, -1, op) 1133 } 1134 } 1135 } 1136 return left 1137} 1138func jp_parse_eq(ctx: *i64) -> i64 { 1139 var left: i64 = jp_parse_rel(ctx) 1140 var go: i64 = 1 1141 while go == 1 { 1142 if jp_err(ctx) == 1 { go = 0 } 1143 if go == 1 { 1144 let op: i64 = jp_eq_op(ctx) 1145 if op == 0 { go = 0 } else { 1146 jp_advance(ctx) 1147 let right: i64 = jp_parse_rel(ctx) 1148 left = jp_new_node(ctx, ND_BINARY, left, right, -1, -1, op) 1149 } 1150 } 1151 } 1152 return left 1153} 1154// BITWISE AND / XOR / OR -- precedence & > ^ > | , all below equality and above && (JS). Single-char 1155// lexer tokens, never confused with && / || (2-char tokens). 1156func jp_parse_bitand(ctx: *i64) -> i64 { 1157 var left: i64 = jp_parse_eq(ctx) 1158 var go: i64 = 1 1159 while go == 1 { 1160 if jp_err(ctx) == 1 { go = 0 } 1161 if go == 1 { 1162 if jp_is_punct(ctx, "&\x00" as *u8) == 1 { 1163 jp_advance(ctx) 1164 let right: i64 = jp_parse_eq(ctx) 1165 left = jp_new_node(ctx, ND_BINARY, left, right, -1, -1, OP_BAND) 1166 } else { go = 0 } 1167 } 1168 } 1169 return left 1170} 1171func jp_parse_bitxor(ctx: *i64) -> i64 { 1172 var left: i64 = jp_parse_bitand(ctx) 1173 var go: i64 = 1 1174 while go == 1 { 1175 if jp_err(ctx) == 1 { go = 0 } 1176 if go == 1 { 1177 if jp_is_punct(ctx, "^\x00" as *u8) == 1 { 1178 jp_advance(ctx) 1179 let right: i64 = jp_parse_bitand(ctx) 1180 left = jp_new_node(ctx, ND_BINARY, left, right, -1, -1, OP_BXOR) 1181 } else { go = 0 } 1182 } 1183 } 1184 return left 1185} 1186func jp_parse_bitor(ctx: *i64) -> i64 { 1187 var left: i64 = jp_parse_bitxor(ctx) 1188 var go: i64 = 1 1189 while go == 1 { 1190 if jp_err(ctx) == 1 { go = 0 } 1191 if go == 1 { 1192 if jp_is_punct(ctx, "|\x00" as *u8) == 1 { 1193 jp_advance(ctx) 1194 let right: i64 = jp_parse_bitxor(ctx) 1195 left = jp_new_node(ctx, ND_BINARY, left, right, -1, -1, OP_BOR) 1196 } else { go = 0 } 1197 } 1198 } 1199 return left 1200} 1201func jp_parse_and(ctx: *i64) -> i64 { 1202 var left: i64 = jp_parse_bitor(ctx) 1203 var go: i64 = 1 1204 while go == 1 { 1205 if jp_err(ctx) == 1 { go = 0 } 1206 if go == 1 { 1207 if jp_is_punct(ctx, "&&\x00" as *u8) == 1 { 1208 jp_advance(ctx) 1209 let right: i64 = jp_parse_bitor(ctx) 1210 left = jp_new_node(ctx, ND_BINARY, left, right, -1, -1, OP_AND) 1211 } else { go = 0 } 1212 } 1213 } 1214 return left 1215} 1216func jp_parse_or(ctx: *i64) -> i64 { 1217 var left: i64 = jp_parse_and(ctx) 1218 var go: i64 = 1 1219 while go == 1 { 1220 if jp_err(ctx) == 1 { go = 0 } 1221 if go == 1 { 1222 if jp_is_punct(ctx, "||\x00" as *u8) == 1 { 1223 jp_advance(ctx) 1224 let right: i64 = jp_parse_and(ctx) 1225 left = jp_new_node(ctx, ND_BINARY, left, right, -1, -1, OP_OR) 1226 } else { go = 0 } 1227 } 1228 } 1229 return left 1230} 1231 1232// compound-assignment operator code at the cursor (the BINARY op to fold in), or 0 if 1233// the current token is not a compound assign. The lexer produced "+=" "-=" etc. as 1234// SINGLE multi-char punct tokens (js_punct_len), so an exact-lexeme compare is safe and 1235// can never be confused with bare '=' / '==' / '==='. 1236func jp_compound_op(ctx: *i64) -> i64 { 1237 if jp_tok_kind(ctx) != JS_TOK_PUNCT { return 0 } 1238 if jp_is_punct(ctx, "+=\x00" as *u8) == 1 { return OP_ADD } 1239 if jp_is_punct(ctx, "-=\x00" as *u8) == 1 { return OP_SUB } 1240 if jp_is_punct(ctx, "*=\x00" as *u8) == 1 { return OP_MUL } 1241 if jp_is_punct(ctx, "/=\x00" as *u8) == 1 { return OP_DIV } 1242 if jp_is_punct(ctx, "%=\x00" as *u8) == 1 { return OP_MOD } 1243 // bitwise/shift compounds (Octane Crypto/jsbn: `c >>= ...`). The lexer already emits these as single 1244 // puncts (js_punct_len), and the fold rides the SAME aop path as += (ev_apply_binop / BC_BINOP). 1245 if jp_is_punct(ctx, "&=\x00" as *u8) == 1 { return OP_BAND } 1246 if jp_is_punct(ctx, "|=\x00" as *u8) == 1 { return OP_BOR } 1247 if jp_is_punct(ctx, "^=\x00" as *u8) == 1 { return OP_BXOR } 1248 if jp_is_punct(ctx, "<<=\x00" as *u8) == 1 { return OP_SHL } 1249 if jp_is_punct(ctx, ">>=\x00" as *u8) == 1 { return OP_SHR } 1250 if jp_is_punct(ctx, ">>>=\x00" as *u8) == 1 { return OP_USHR } 1251 return 0 1252} 1253 1254// ternary `cond ? then : else` -- RIGHT-assoc, just ABOVE assignment, just BELOW the 1255// OR tier (real JS: ConditionalExpression). The branches are assignment-expressions 1256// (so `a ? b : c = d` parses the false branch as an assignment, real JS). Returns the 1257// OR-level node unchanged when no '?' follows, so all lower KATs are untouched. 1258// nullish coalescing `a ?? b` -- short-circuit; tier just BELOW ternary, ABOVE ||. Operands are 1259// OR-level expressions (the common `a ?? b` parses correctly; ?? mixing with ||/&& needs parens in real JS). 1260func jp_parse_nullish(ctx: *i64) -> i64 { 1261 var left: i64 = jp_parse_or(ctx) 1262 var go: i64 = 1 1263 while go == 1 { 1264 if jp_err(ctx) == 1 { go = 0 } 1265 if go == 1 { 1266 if jp_is_punct(ctx, "??\x00" as *u8) == 1 { 1267 jp_advance(ctx) 1268 let right: i64 = jp_parse_or(ctx) 1269 left = jp_new_node(ctx, ND_BINARY, left, right, -1, -1, OP_NULLISH) 1270 } else { go = 0 } 1271 } 1272 } 1273 return left 1274} 1275func jp_parse_ternary(ctx: *i64) -> i64 { 1276 let cond: i64 = jp_parse_nullish(ctx) 1277 if jp_err(ctx) == 1 { return cond } 1278 if jp_is_punct(ctx, "?\x00" as *u8) == 1 { 1279 jp_advance(ctx) 1280 let then_e: i64 = jp_parse_assign(ctx) // then-branch is an assignment-expr 1281 if jp_err(ctx) == 1 { return jp_error_node(ctx) } 1282 if jp_eat_punct(ctx, ":\x00" as *u8) == 0 { return jp_error_node(ctx) } 1283 let else_e: i64 = jp_parse_assign(ctx) // else-branch right-recurses (right-assoc) 1284 if jp_err(ctx) == 1 { return jp_error_node(ctx) } 1285 return jp_new_node(ctx, ND_TERNARY, cond, then_e, else_e, -1, 0) 1286 } 1287 return cond 1288} 1289 1290// ===================== arrow functions (R-JS-CLOSURE, rung 6) ===================== 1291// An arrow desugars to the SAME node shape as a function decl/expression -- ND_FUNC_DECL 1292// (a=name|-1, b=params, c=body BLOCK) -- so the evaluator's ONE js_call_userfn path runs 1293// both. Arrows are anonymous (name = -1). The lexical capture happens in the evaluator 1294// (the closure records the env where the literal is evaluated); the parser only produces 1295// the same params+body skeleton, so an arrow is lexically identical to `function(){}`. 1296 1297// wrap a single statement node into a synthetic BLOCK list-node (a=child_start, b=1). 1298// Used for an arrow EXPRESSION body: `x => expr` desugars to a body `{ return expr; }`. 1299func jp_wrap_block1(ctx: *i64, stmt: i64) -> i64 { 1300 let start: i64 = jp_child_push(ctx, stmt) 1301 return jp_new_node(ctx, ND_BLOCK, start, 1, -1, -1, 0) 1302} 1303 1304// parse a PARENTHESIZED arrow param list `( a , b , ... )` (idents only; empty () ok) 1305// -> ND_PARAMS. Identical shape to jp_parse_params (reused), kept as its own call so the 1306// arrow path reads clearly. Default/rest/destructured params are a NAMED OPEN (idents only). 1307func jp_parse_arrow_params(ctx: *i64) -> i64 { return jp_parse_params(ctx) } 1308 1309// parse an arrow BODY after the `=>` has been consumed: a BLOCK body `{ stmts }` runs as-is; 1310// an EXPRESSION body `expr` desugars to `{ return expr; }` (the implicit-return rule). The 1311// expression body is an ASSIGNMENT-expr (so `x => y = 1` parses `y=1` as the body, real JS). 1312func jp_parse_arrow_body(ctx: *i64) -> i64 { 1313 if jp_is_punct(ctx, "{\x00" as *u8) == 1 { return jp_parse_block(ctx) } 1314 let e: i64 = jp_parse_assign(ctx) 1315 if jp_err(ctx) == 1 { return jp_error_node(ctx) } 1316 let ret: i64 = jp_new_node(ctx, ND_RETURN, e, -1, -1, -1, 0) 1317 return jp_wrap_block1(ctx, ret) 1318} 1319 1320// SINGLE-PARAM arrow `x => body`. Cursor is on the IDENT (its next token is '=>'). 1321func jp_parse_arrow_single(ctx: *i64) -> i64 { 1322 let pn: i64 = jp_new_node(ctx, ND_IDENT, -1, -1, -1, jp_cur(ctx), 0) 1323 jp_advance(ctx) // consume the param ident 1324 let start: i64 = jp_child_push(ctx, pn) 1325 let params: i64 = jp_new_node(ctx, ND_PARAMS, start, 1, -1, -1, 0) 1326 if jp_eat_punct(ctx, "=>\x00" as *u8) == 0 { return jp_error_node(ctx) } 1327 let body: i64 = jp_parse_arrow_body(ctx) 1328 if jp_err(ctx) == 1 { return jp_error_node(ctx) } 1329 return jp_new_node(ctx, ND_FUNC_DECL, -1, params, body, -1, 0) // name=-1 (anonymous) 1330} 1331 1332// PAREN-LIST arrow `( a, b ) => body`. Cursor is on '(' (jp_arrow_paren_ahead confirmed 1333// a following '=>'). Parse the param idents, eat '=>', parse the body. 1334func jp_parse_arrow_paren(ctx: *i64) -> i64 { 1335 let params: i64 = jp_parse_arrow_params(ctx) 1336 if jp_err(ctx) == 1 { return jp_error_node(ctx) } 1337 if jp_eat_punct(ctx, "=>\x00" as *u8) == 0 { return jp_error_node(ctx) } 1338 let body: i64 = jp_parse_arrow_body(ctx) 1339 if jp_err(ctx) == 1 { return jp_error_node(ctx) } 1340 return jp_new_node(ctx, ND_FUNC_DECL, -1, params, body, -1, 0) 1341} 1342 1343// 1 iff an ARROW function begins at the cursor: `x =>` (IDENT then '=>') or `( ... ) =>` 1344// (the cover-grammar paren case resolved by jp_arrow_paren_ahead). Used to fork at the 1345// TOP of jp_parse_assign so the arrow's params are not first mis-parsed as an expression. 1346func jp_arrow_ahead(ctx: *i64) -> i64 { 1347 if jp_tok_kind(ctx) == JS_TOK_IDENT { 1348 if jp_punct_at(ctx, jp_cur(ctx) + 1, "=>\x00" as *u8) == 1 { return 1 } 1349 } 1350 if jp_is_punct(ctx, "(\x00" as *u8) == 1 { 1351 if jp_arrow_paren_ahead(ctx) == 1 { return 1 } 1352 } 1353 return 0 1354} 1355 1356// assignment: RIGHT-associative, LOWEST precedence. `lhs = rhs` (or `lhs += rhs` etc.) 1357// where rhs is itself an assignment (so a=b=c nests right). lhs is whatever the ternary 1358// tier parsed. Compound assigns store the BINARY op-code in the ND_ASSIGN `extra` slot 1359// (0 = plain '='); the evaluator reads lhs, folds the op with rhs, writes back. 1360func jp_parse_assign(ctx: *i64) -> i64 { 1361 // ASYNC ARROWS `async (a,b)=>{…}` / `async x=>…` (JS-SOTA phase 3; the netflix frontier after 1362 // object spread: `a=e=>async(t,n)=>{…}`). `async` is CONTEXTUAL -- `async(1)` is a CALL to a 1363 // function named async -- so only consume it when a real arrow follows; otherwise RESTORE the 1364 // cursor and let it parse as a plain identifier. Async-ness itself is not yet modelled (the 1365 // body runs synchronously and returns its value, not a promise) -- an honest, declared stub. 1366 // `async` lexes as a KEYWORD (nx_js_lex reserves it), so an IDENT-only test here NEVER fired -- 1367 // async arrows were unparseable the whole time this branch existed. Same both-kinds fix as the 1368 // `async function` forms (2026-08-25), found by walking the real shipped page byte 254,078: 1369 // WebAssembly.instantiate(bytes).then(async r=>{...}). 1370 var aasync: i64 = jp_is_lex(ctx, JS_TOK_IDENT, "async\x00" as *u8) 1371 if jp_is_kw(ctx, "async\x00" as *u8) == 1 { aasync = 1 } 1372 if aasync == 1 { 1373 let asave: i64 = jp_cur(ctx) 1374 jp_set_cur(ctx, asave + 1) 1375 var aok: i64 = 0 1376 if jp_is_punct(ctx, "(\x00" as *u8) == 1 { if jp_arrow_paren_ahead(ctx) == 1 { aok = 1 } } 1377 if jp_kind_at(ctx, asave + 1) == JS_TOK_IDENT { if jp_punct_at(ctx, asave + 2, "=>\x00" as *u8) == 1 { aok = 1 } } 1378 if aok == 0 { jp_set_cur(ctx, asave) } 1379 } 1380 if jp_err(ctx) == 1 { return jp_error_node(ctx) } 1381 // ARROW FUNCTION fork (lowest precedence, right-assoc): detect `x =>` / `(...) =>` 1382 // BEFORE treating the head as an expression. The body recurses through assignment 1383 // (jp_parse_arrow_body), so `x => y => x+y` (curried) right-nests naturally. 1384 if jp_arrow_ahead(ctx) == 1 { 1385 if jp_tok_kind(ctx) == JS_TOK_IDENT { return jp_parse_arrow_single(ctx) } 1386 return jp_parse_arrow_paren(ctx) 1387 } 1388 let lhs: i64 = jp_parse_ternary(ctx) 1389 if jp_err(ctx) == 1 { return lhs } 1390 // bare '=' -- must NOT match == or === (DISTINCT multi-char punct tokens), so an 1391 // exact-lexeme compare against "=" is unambiguous. 1392 if jp_is_punct(ctx, "=\x00" as *u8) == 1 { 1393 // Real JS: the assignment target MUST be an lvalue (IDENT / MEMBER / INDEX). 1394 // '1 = 2' or '(a+b) = c' -> SyntaxError: Invalid left-hand side in assignment. 1395 if jp_is_lvalue(ctx, lhs) == 0 { return jp_error_node(ctx) } 1396 jp_advance(ctx) 1397 let rhs: i64 = jp_parse_assign(ctx) // right-recursion = right associativity 1398 return jp_new_node(ctx, ND_ASSIGN, lhs, rhs, -1, -1, 0) 1399 } 1400 // compound assign lhs (+= -= *= /= %=) rhs -> ND_ASSIGN with op in extra slot. 1401 let cop: i64 = jp_compound_op(ctx) 1402 if cop != 0 { 1403 if jp_is_lvalue(ctx, lhs) == 0 { return jp_error_node(ctx) } 1404 jp_advance(ctx) 1405 let rhs: i64 = jp_parse_assign(ctx) 1406 return jp_new_node(ctx, ND_ASSIGN, lhs, rhs, -1, -1, cop) 1407 } 1408 return lhs 1409} 1410 1411// top-level expression = assignment (which descends through all binary tiers). 1412func jp_parse_expr(ctx: *i64) -> i64 { 1413 let first: i64 = jp_parse_assign(ctx) 1414 if jp_err(ctx) == 1 { return first } 1415 if jp_is_punct(ctx, ",\x00" as *u8) == 0 { return first } // no comma = single expr (the common case, 0 overhead) 1416 // sequence operator `a, b, c` -> ND_SEQ (eval each in order, value = last). All jp_parse_expr callers are 1417 // sequence-valid contexts (for-header, (), stmt, cond, return, switch); list-commas use jp_parse_assign. 1418 let scratch: *i64 = sys_mmap(256 * 8) as *i64 1419 scratch[0] = first 1420 var count: i64 = 1 1421 while jp_is_punct(ctx, ",\x00" as *u8) == 1 { 1422 jp_advance(ctx) 1423 let e: i64 = jp_parse_assign(ctx) 1424 if jp_err(ctx) == 1 { return jp_error_node(ctx) } 1425 if count < 256 { scratch[count] = e } 1426 count = count + 1 1427 } 1428 let cs: i64 = jp_child_commit(ctx, scratch, count) 1429 return jp_new_node(ctx, ND_SEQ, cs, count, -1, -1, 0) 1430} 1431 1432// ===================== statement parsing ===================== 1433// forward refs: jp_parse_stmt is mutually recursive with block/if/while. 1434 1435// var/let/const decl: KEYWORD IDENT [ = expr ] [ ; ] 1436// array/object destructuring PATTERNS for a var-decl target. Shorthand names only ([a,b] / {a,b}); 1437// renaming {a:b}, defaults in patterns, and nested patterns are a NAMED OPEN. 1438func jp_parse_array_pattern(ctx: *i64) -> i64 { 1439 jp_advance(ctx) // '[' 1440 let scratch: *i64 = sys_mmap(256 * 8) as *i64 1441 var count: i64 = 0 1442 var go: i64 = 1 1443 if jp_is_punct(ctx, "]\x00" as *u8) == 1 { jp_advance(ctx); go = 0 } 1444 while go == 1 { 1445 var nm: i64 = 0 - 1 1446 // NESTED patterns: [e, [t,n], {x}] -- vk uses `for(let [e,[t,n]] of ...)`. 1447 if jp_is_punct(ctx, "[\x00" as *u8) == 1 { nm = jp_parse_array_pattern(ctx); if jp_err(ctx) == 1 { go = 0 } } 1448 else { if jp_is_punct(ctx, "{\x00" as *u8) == 1 { nm = jp_parse_object_pattern(ctx); if jp_err(ctx) == 1 { go = 0 } } 1449 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 } } 1450 else { 1451 if jp_tok_kind(ctx) != JS_TOK_IDENT { jp_set_err(ctx); go = 0 } 1452 if go == 1 { nm = jp_new_node(ctx, ND_IDENT, -1, -1, -1, jp_cur(ctx), 0); jp_advance(ctx) } 1453 } } } 1454 if go == 1 { 1455 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) 1456 if go == 1 { 1457 if count < 256 { scratch[count] = nm } 1458 count = count + 1 1459 if jp_eat_punct(ctx, ",\x00" as *u8) == 1 { } 1460 else { if jp_eat_punct(ctx, "]\x00" as *u8) == 1 { go = 0 } else { jp_set_err(ctx); go = 0 } } 1461 } 1462 } 1463 } 1464 if jp_err(ctx) == 1 { return jp_error_node(ctx) } 1465 let start: i64 = jp_child_commit(ctx, scratch, count) 1466 return jp_new_node(ctx, ND_ARRAY_PAT, start, count, -1, -1, 0) 1467} 1468func jp_parse_object_pattern(ctx: *i64) -> i64 { 1469 jp_advance(ctx) // '{' 1470 let scratch: *i64 = sys_mmap(256 * 8) as *i64 1471 var count: i64 = 0 1472 var go: i64 = 1 1473 if jp_is_punct(ctx, "}\x00" as *u8) == 1 { jp_advance(ctx); go = 0 } 1474 while go == 1 { 1475 if jp_tok_kind(ctx) != JS_TOK_IDENT { jp_set_err(ctx); go = 0 } 1476 if go == 1 { 1477 let keytok: i64 = jp_cur(ctx) // source property KEY 1478 jp_advance(ctx) 1479 var alias: i64 = 0 - 1 1480 if jp_is_punct(ctx, ":\x00" as *u8) == 1 { // {key: localName} or {key: [a,b]} / {key: {x}} (nested) 1481 jp_advance(ctx) 1482 if jp_is_punct(ctx, "[\x00" as *u8) == 1 { alias = jp_parse_array_pattern(ctx); if jp_err(ctx) == 1 { go = 0 } } 1483 else { if jp_is_punct(ctx, "{\x00" as *u8) == 1 { alias = jp_parse_object_pattern(ctx); if jp_err(ctx) == 1 { go = 0 } } 1484 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) } 1485 else { jp_set_err(ctx); go = 0 } } } 1486 } 1487 if go == 1 { if jp_is_punct(ctx, "=\x00" as *u8) == 1 { // {x = default} -- parse+ignore default (rare) 1488 jp_advance(ctx) 1489 jp_parse_assign(ctx) 1490 if jp_err(ctx) == 1 { go = 0 } 1491 } } 1492 if go == 1 { 1493 let nm: i64 = jp_new_node(ctx, ND_IDENT, alias, -1, -1, keytok, 0) // tok=KEY; slot-a=ALIAS node (-1 if bare) 1494 if count < 256 { scratch[count] = nm } 1495 count = count + 1 1496 if jp_eat_punct(ctx, ",\x00" as *u8) == 1 { } 1497 else { if jp_eat_punct(ctx, "}\x00" as *u8) == 1 { go = 0 } else { jp_set_err(ctx); go = 0 } } 1498 } 1499 } 1500 } 1501 if jp_err(ctx) == 1 { return jp_error_node(ctx) } 1502 let start: i64 = jp_child_commit(ctx, scratch, count) 1503 return jp_new_node(ctx, ND_OBJ_PAT, start, count, -1, -1, 0) 1504} 1505// parse ONE declarator `name [= init]` (no var keyword, no terminator) -> an ND_VAR_DECL node. 1506func jp_parse_one_declarator(ctx: *i64) -> i64 { 1507 var name: i64 = 0 1508 if jp_is_punct(ctx, "[\x00" as *u8) == 1 { name = jp_parse_array_pattern(ctx) } 1509 else { if jp_is_punct(ctx, "{\x00" as *u8) == 1 { name = jp_parse_object_pattern(ctx) } 1510 else { 1511 if jp_tok_kind(ctx) != JS_TOK_IDENT { return jp_error_node(ctx) } 1512 name = jp_new_node(ctx, ND_IDENT, -1, -1, -1, jp_cur(ctx), 0) 1513 jp_advance(ctx) 1514 } } 1515 if jp_err(ctx) == 1 { return jp_error_node(ctx) } 1516 var init: i64 = -1 1517 if jp_is_punct(ctx, "=\x00" as *u8) == 1 { 1518 jp_advance(ctx) 1519 init = jp_parse_assign(ctx) 1520 if jp_err(ctx) == 1 { return jp_error_node(ctx) } 1521 } 1522 return jp_new_node(ctx, ND_VAR_DECL, name, init, -1, -1, 0) 1523} 1524func jp_parse_var_decl(ctx: *i64) -> i64 { 1525 jp_advance(ctx) // consume var/let/const 1526 let first: i64 = jp_parse_one_declarator(ctx) 1527 if jp_err(ctx) == 1 { return jp_error_node(ctx) } 1528 // MULTI-declarator: `var a, b=c, d;` -> ND_VAR_LIST of ND_VAR_DECLs (real JS; very common). 1529 if jp_is_punct(ctx, ",\x00" as *u8) == 1 { 1530 let scratch: *i64 = sys_mmap(256 * 8) as *i64 1531 scratch[0] = first 1532 var count: i64 = 1 1533 while jp_is_punct(ctx, ",\x00" as *u8) == 1 { 1534 jp_advance(ctx) 1535 let d: i64 = jp_parse_one_declarator(ctx) 1536 if jp_err(ctx) == 1 { return jp_error_node(ctx) } 1537 if count < 256 { scratch[count] = d } 1538 count = count + 1 1539 } 1540 if jp_eat_punct(ctx, ";\x00" as *u8) == 0 { if jp_can_asi(ctx) == 0 { return jp_error_node(ctx) } } 1541 let cs: i64 = jp_child_commit(ctx, scratch, count) 1542 return jp_new_node(ctx, ND_VAR_LIST, cs, count, -1, -1, 0) 1543 } 1544 // single declarator: ASI-lite termination (explicit ';' OR line break / '}' / EOF). 1545 if jp_eat_punct(ctx, ";\x00" as *u8) == 0 { 1546 if jp_can_asi(ctx) == 0 { return jp_error_node(ctx) } 1547 } 1548 return first 1549} 1550 1551// return [ expr ] ; 1552func jp_parse_return(ctx: *i64) -> i64 { 1553 jp_advance(ctx) // consume `return` 1554 var expr: i64 = -1 1555 // bare `return;` or `return}` -> no expression 1556 var has_expr: i64 = 1 1557 if jp_is_punct(ctx, ";\x00" as *u8) == 1 { has_expr = 0 } 1558 if jp_is_punct(ctx, "}\x00" as *u8) == 1 { has_expr = 0 } 1559 if jp_tok_kind(ctx) == JS_TOK_EOF { has_expr = 0 } 1560 if has_expr == 1 { 1561 expr = jp_parse_expr(ctx) 1562 if jp_err(ctx) == 1 { return jp_error_node(ctx) } 1563 } 1564 // ASI-lite termination: explicit ';' OR a line break / '}' / EOF. 1565 if jp_eat_punct(ctx, ";\x00" as *u8) == 0 { 1566 if jp_can_asi(ctx) == 0 { return jp_error_node(ctx) } 1567 } 1568 return jp_new_node(ctx, ND_RETURN, expr, -1, -1, -1, 0) 1569} 1570 1571// block: { stmt* } -> list-node a=child_start, b=child_count 1572func jp_parse_block(ctx: *i64) -> i64 { 1573 jp_advance(ctx) // consume '{' 1574 let scratch: *i64 = sys_mmap(ND_MAGIC_2048 * 8) as *i64 1575 var count: i64 = 0 1576 var go: i64 = 1 1577 while go == 1 { 1578 if jp_err(ctx) == 1 { go = 0 } 1579 if go == 1 { 1580 if jp_is_punct(ctx, "}\x00" as *u8) == 1 { jp_advance(ctx); go = 0 } 1581 else { 1582 if jp_tok_kind(ctx) == JS_TOK_EOF { jp_set_err(ctx); go = 0 } 1583 else { 1584 let st: i64 = jp_parse_stmt(ctx) 1585 if count < ND_MAGIC_2048 { scratch[count] = st } 1586 count = count + 1 1587 } 1588 } 1589 } 1590 } 1591 if jp_err(ctx) == 1 { return jp_error_node(ctx) } 1592 let start: i64 = jp_child_commit(ctx, scratch, count) 1593 return jp_new_node(ctx, ND_BLOCK, start, count, -1, -1, 0) 1594} 1595 1596// if ( cond ) stmt [ else stmt ] 1597func jp_parse_if(ctx: *i64) -> i64 { 1598 jp_advance(ctx) // consume `if` 1599 if jp_eat_punct(ctx, "(\x00" as *u8) == 0 { return jp_error_node(ctx) } 1600 let cond: i64 = jp_parse_expr(ctx) 1601 if jp_eat_punct(ctx, ")\x00" as *u8) == 0 { return jp_error_node(ctx) } 1602 let then_s: i64 = jp_parse_stmt(ctx) 1603 var else_s: i64 = -1 1604 if jp_is_kw(ctx, "else\x00" as *u8) == 1 { 1605 jp_advance(ctx) 1606 else_s = jp_parse_stmt(ctx) 1607 } 1608 if jp_err(ctx) == 1 { return jp_error_node(ctx) } 1609 return jp_new_node(ctx, ND_IF, cond, then_s, else_s, -1, 0) 1610} 1611 1612// while ( cond ) stmt 1613func jp_parse_while(ctx: *i64) -> i64 { 1614 jp_advance(ctx) // consume `while` 1615 if jp_eat_punct(ctx, "(\x00" as *u8) == 0 { return jp_error_node(ctx) } 1616 let cond: i64 = jp_parse_expr(ctx) 1617 if jp_eat_punct(ctx, ")\x00" as *u8) == 0 { return jp_error_node(ctx) } 1618 let body: i64 = jp_parse_stmt(ctx) 1619 if jp_err(ctx) == 1 { return jp_error_node(ctx) } 1620 return jp_new_node(ctx, ND_WHILE, cond, body, -1, -1, 0) 1621} 1622 1623// for ( init ; cond ; update ) body -> ND_FOR. Slots: a=init, b=cond, c=update, 1624// tokidx-slot[4]=body. init may be a var-decl OR an expression OR empty; cond/update may 1625// be empty (empty cond = perpetually true, handled in the evaluator). We parse init WITHOUT 1626// consuming the ';' inside the init-decl/expr path so the for-header ';' separators stay 1627// uniform here. (for-in / for-of need iterator protocol -> HONEST OPEN, not parsed here: 1628// after the init clause we REQUIRE a ';'.) 1629func jp_parse_for(ctx: *i64) -> i64 { 1630 jp_advance(ctx) // consume `for` 1631 if jp_eat_punct(ctx, "(\x00" as *u8) == 0 { return jp_error_node(ctx) } 1632 // ---- init clause ---- (empty if the next token is ';') 1633 var init: i64 = -1 1634 if jp_is_punct(ctx, ";\x00" as *u8) == 1 { 1635 jp_advance(ctx) 1636 } else { 1637 var is_decl: i64 = 0 1638 if jp_is_kw(ctx, "var\x00" as *u8) == 1 { is_decl = 1 } 1639 if jp_is_kw(ctx, "let\x00" as *u8) == 1 { is_decl = 1 } 1640 if jp_is_kw(ctx, "const\x00" as *u8) == 1 { is_decl = 1 } 1641 if is_decl == 1 { init = jp_parse_for_decl(ctx) } 1642 else { jp_noin = 1; init = jp_parse_expr(ctx); jp_noin = 0 } // `in` here = for-in separator, not operator 1643 if jp_err(ctx) == 1 { return jp_error_node(ctx) } 1644 // ---- R-JS-SYNTAX2 (rung 8): for-of / for-in disambiguation. After the loop var 1645 // (a decl `var x` or a bare lvalue expr `x`), a contextual `of`/`in` keyword forks 1646 // to the iterator forms; a `;` continues the classic C-style for(init;cond;update). 1647 if jp_is_kw(ctx, "of\x00" as *u8) == 1 { return jp_parse_for_in_of(ctx, init, ND_FOR_OF) } 1648 if jp_is_kw(ctx, "in\x00" as *u8) == 1 { return jp_parse_for_in_of(ctx, init, ND_FOR_IN) } 1649 // the for-header requires an explicit ';' after the init clause (no ASI inside ()). 1650 if jp_eat_punct(ctx, ";\x00" as *u8) == 0 { return jp_error_node(ctx) } 1651 } 1652 // ---- cond clause ---- (empty = true) 1653 var cond: i64 = -1 1654 if jp_is_punct(ctx, ";\x00" as *u8) == 1 { 1655 jp_advance(ctx) 1656 } else { 1657 cond = jp_parse_expr(ctx) 1658 if jp_err(ctx) == 1 { return jp_error_node(ctx) } 1659 if jp_eat_punct(ctx, ";\x00" as *u8) == 0 { return jp_error_node(ctx) } 1660 } 1661 // ---- update clause ---- (empty if next is ')') 1662 var update: i64 = -1 1663 if jp_is_punct(ctx, ")\x00" as *u8) == 1 { 1664 jp_advance(ctx) 1665 } else { 1666 update = jp_parse_expr(ctx) 1667 if jp_err(ctx) == 1 { return jp_error_node(ctx) } 1668 if jp_eat_punct(ctx, ")\x00" as *u8) == 0 { return jp_error_node(ctx) } 1669 } 1670 let body: i64 = jp_parse_stmt(ctx) 1671 if jp_err(ctx) == 1 { return jp_error_node(ctx) } 1672 // store body in the tokidx slot (FOR has 4 children; a/b/c hold init/cond/update). 1673 return jp_new_node(ctx, ND_FOR, init, cond, update, body, 0) 1674} 1675 1676// for-header var-decl WITHOUT the terminating ';' (the for-loop header owns the ';' 1677// separators, so the decl must NOT consume one or run ASI). Mirrors jp_parse_var_decl 1678// minus the termination clause. Produces an ND_VAR_DECL (a=name, b=init|-1). 1679func jp_parse_for_decl(ctx: *i64) -> i64 { 1680 jp_advance(ctx) // consume var/let/const 1681 let first: i64 = jp_parse_one_declarator(ctx) 1682 if jp_err(ctx) == 1 { return jp_error_node(ctx) } 1683 // MULTI-declarator for-init `for(var i=0, j=n; ...)` -> ND_VAR_LIST (common; scheme2js Boyer uses it). 1684 // NO terminator here -- the for-header owns the ';'. `in`/`of` after a MULTI list = SyntaxError anyway. 1685 if jp_is_punct(ctx, ",\x00" as *u8) == 1 { 1686 let scratch: *i64 = sys_mmap(256 * 8) as *i64 1687 scratch[0] = first 1688 var count: i64 = 1 1689 while jp_is_punct(ctx, ",\x00" as *u8) == 1 { 1690 jp_advance(ctx) 1691 let d: i64 = jp_parse_one_declarator(ctx) 1692 if jp_err(ctx) == 1 { return jp_error_node(ctx) } 1693 if count < 256 { scratch[count] = d } 1694 count = count + 1 1695 } 1696 let cs: i64 = jp_child_commit(ctx, scratch, count) 1697 return jp_new_node(ctx, ND_VAR_LIST, cs, count, -1, -1, 0) 1698 } 1699 return first 1700} 1701 1702// R-JS-SYNTAX2 (rung 8): finish a for-of / for-in header after the contextual `of`/`in` 1703// keyword. `target` is the already-parsed loop variable (an ND_VAR_DECL `var x` carrying its 1704// name IDENT, or a bare lvalue expr) and `nkind` is ND_FOR_OF or ND_FOR_IN. Cursor sits on 1705// the `of`/`in` keyword. Shape: a=target, b=iterable/object-expr, c=body. The right-hand side 1706// is an ASSIGNMENT-expr (real JS: `for(x of a)`); then ')' and the loop body statement. 1707func jp_parse_for_in_of(ctx: *i64, target: i64, nkind: i64) -> i64 { 1708 jp_advance(ctx) // consume `of` / `in` 1709 // for-IN head is `Expression` (comma-inclusive) per the grammar -- minified jQuery ships `for(e in a,b,...)`; 1710 // for-OF head is `AssignmentExpression` (a comma there is a real SyntaxError), so keep it single. 1711 var rhs: i64 = 0 1712 if nkind == ND_FOR_IN { rhs = jp_parse_expr(ctx) } else { rhs = jp_parse_assign(ctx) } 1713 if jp_err(ctx) == 1 { return jp_error_node(ctx) } 1714 if jp_eat_punct(ctx, ")\x00" as *u8) == 0 { return jp_error_node(ctx) } 1715 let body: i64 = jp_parse_stmt(ctx) 1716 if jp_err(ctx) == 1 { return jp_error_node(ctx) } 1717 return jp_new_node(ctx, nkind, target, rhs, body, -1, 0) 1718} 1719 1720// R-JS-SYNTAX2 (rung 8): switch ( disc ) { case E: stmts... case E2: stmts... default: stmts... } 1721// -> ND_SWITCH (a=disc-expr, b=case_list_child_start, c=case_count). Each clause is an ND_CASE 1722// node (a=case-expr or -1 for default, b=stmt_list_child_start, c=stmt_count) committed 1723// CONTIGUOUSLY in the child arena, and the ND_CASE list itself is committed contiguously after. 1724// A clause's statement list runs until the next `case`/`default`/`}` (NOT terminated by a 1725// keyword), so fall-through is preserved by the EVALUATOR (the parser just records clause order). 1726// Multiple `default` clauses are an honest OPEN; the first `default` wins at eval. 1727func jp_parse_switch(ctx: *i64) -> i64 { 1728 jp_advance(ctx) // consume `switch` 1729 if jp_eat_punct(ctx, "(\x00" as *u8) == 0 { return jp_error_node(ctx) } 1730 let disc: i64 = jp_parse_expr(ctx) 1731 if jp_err(ctx) == 1 { return jp_error_node(ctx) } 1732 if jp_eat_punct(ctx, ")\x00" as *u8) == 0 { return jp_error_node(ctx) } 1733 if jp_eat_punct(ctx, "{\x00" as *u8) == 0 { return jp_error_node(ctx) } 1734 let cases: *i64 = sys_mmap(256 * 8) as *i64 // ND_CASE node indices (clause order) 1735 var ccount: i64 = 0 1736 var go: i64 = 1 1737 while go == 1 { 1738 if jp_err(ctx) == 1 { go = 0 } 1739 if go == 1 { 1740 if jp_is_punct(ctx, "}\x00" as *u8) == 1 { jp_advance(ctx); go = 0 } 1741 else { 1742 if jp_tok_kind(ctx) == JS_TOK_EOF { jp_set_err(ctx); go = 0 } 1743 else { 1744 // clause head: `case EXPR :` or `default :` 1745 var case_expr: i64 = -1 1746 var headok: i64 = 0 1747 if jp_is_kw(ctx, "case\x00" as *u8) == 1 { 1748 jp_advance(ctx) 1749 case_expr = jp_parse_expr(ctx) 1750 headok = 1 1751 } 1752 if headok == 0 { if jp_is_kw(ctx, "default\x00" as *u8) == 1 { jp_advance(ctx); headok = 1 } } 1753 if headok == 0 { jp_set_err(ctx); go = 0 } 1754 if go == 1 { 1755 if jp_eat_punct(ctx, ":\x00" as *u8) == 0 { jp_set_err(ctx); go = 0 } 1756 } 1757 if go == 1 { 1758 // clause statement list: parse statements until the next clause head or '}'. 1759 let cnode: i64 = jp_parse_case_body(ctx, case_expr) 1760 if jp_err(ctx) == 1 { go = 0 } 1761 else { 1762 if ccount < 256 { cases[ccount] = cnode } 1763 ccount = ccount + 1 1764 } 1765 } 1766 } 1767 } 1768 } 1769 } 1770 if jp_err(ctx) == 1 { return jp_error_node(ctx) } 1771 let cstart: i64 = jp_child_commit(ctx, cases, ccount) 1772 // LIST-node layout (a=child_start, b=child_count) so jp_child_at reads the ND_CASE list; 1773 // the discriminant rides slot c. 1774 return jp_new_node(ctx, ND_SWITCH, cstart, ccount, disc, -1, 0) 1775} 1776 1777// parse ONE switch clause's statement list (the head `case E:`/`default:` is already consumed; 1778// `case_expr` = the case test expr or -1 for default). Statements run until the next `case`/ 1779// `default`/`}` boundary. Returns an ND_CASE node (a=case_expr, b=stmt_child_start, c=stmt_count). 1780func jp_parse_case_body(ctx: *i64, case_expr: i64) -> i64 { 1781 let scratch: *i64 = sys_mmap(ND_MAGIC_1024 * 8) as *i64 1782 var count: i64 = 0 1783 var go: i64 = 1 1784 while go == 1 { 1785 if jp_err(ctx) == 1 { go = 0 } 1786 if go == 1 { 1787 if jp_is_punct(ctx, "}\x00" as *u8) == 1 { go = 0 } // end of switch (do NOT consume) 1788 else { if jp_is_kw(ctx, "case\x00" as *u8) == 1 { go = 0 } // next clause (do NOT consume) 1789 else { if jp_is_kw(ctx, "default\x00" as *u8) == 1 { go = 0 } // default clause (do NOT consume) 1790 else { 1791 if jp_tok_kind(ctx) == JS_TOK_EOF { jp_set_err(ctx); go = 0 } 1792 else { 1793 let st: i64 = jp_parse_stmt(ctx) 1794 if count < ND_MAGIC_1024 { scratch[count] = st } 1795 count = count + 1 1796 } 1797 } } } 1798 } 1799 } 1800 if jp_err(ctx) == 1 { return jp_error_node(ctx) } 1801 let start: i64 = jp_child_commit(ctx, scratch, count) 1802 // LIST-node layout (a=child_start, b=child_count) so jp_child_at reads the clause's stmts; 1803 // the case-test expr (-1 for default) rides slot c. 1804 return jp_new_node(ctx, ND_CASE, start, count, case_expr, -1, 0) 1805} 1806 1807// do body while ( cond ) ; -> ND_DOWHILE (a=cond, b=body). Body runs at least once. 1808func jp_parse_dowhile(ctx: *i64) -> i64 { 1809 jp_advance(ctx) // consume `do` 1810 let body: i64 = jp_parse_stmt(ctx) 1811 if jp_err(ctx) == 1 { return jp_error_node(ctx) } 1812 if jp_is_kw(ctx, "while\x00" as *u8) == 0 { return jp_error_node(ctx) } 1813 jp_advance(ctx) // consume `while` 1814 if jp_eat_punct(ctx, "(\x00" as *u8) == 0 { return jp_error_node(ctx) } 1815 let cond: i64 = jp_parse_expr(ctx) 1816 if jp_err(ctx) == 1 { return jp_error_node(ctx) } 1817 if jp_eat_punct(ctx, ")\x00" as *u8) == 0 { return jp_error_node(ctx) } 1818 // optional trailing ';' (ASI applies); real JS allows `do{}while(c)` then a break. 1819 if jp_eat_punct(ctx, ";\x00" as *u8) == 0 { 1820 if jp_can_asi(ctx) == 0 { return jp_error_node(ctx) } 1821 } 1822 return jp_new_node(ctx, ND_DOWHILE, cond, body, -1, -1, 0) 1823} 1824 1825// break ; / continue ; -> ND_BREAK / ND_CONTINUE (no children). ASI-lite termination. 1826// (labeled break/continue need a label table -> HONEST OPEN, not parsed: a label after 1827// the keyword is not consumed, so `break outer;` would fall to the ';' check and error.) 1828func jp_parse_break(ctx: *i64) -> i64 { 1829 jp_advance(ctx) // consume `break` 1830 if jp_tok_kind(ctx) == JS_TOK_IDENT { jp_advance(ctx) } // optional label -> consumed (targets nearest loop; see labeled-stmt note) 1831 if jp_eat_punct(ctx, ";\x00" as *u8) == 0 { 1832 if jp_can_asi(ctx) == 0 { return jp_error_node(ctx) } 1833 } 1834 return jp_new_node(ctx, ND_BREAK, -1, -1, -1, -1, 0) 1835} 1836func jp_parse_continue(ctx: *i64) -> i64 { 1837 jp_advance(ctx) // consume `continue` 1838 if jp_tok_kind(ctx) == JS_TOK_IDENT { jp_advance(ctx) } // optional label -> consumed (targets nearest loop) 1839 if jp_eat_punct(ctx, ";\x00" as *u8) == 0 { 1840 if jp_can_asi(ctx) == 0 { return jp_error_node(ctx) } 1841 } 1842 return jp_new_node(ctx, ND_CONTINUE, -1, -1, -1, -1, 0) 1843} 1844 1845// param list `( a , b , ... )` -> PARAMS list-node a=child_start, b=child_count 1846func jp_parse_params(ctx: *i64) -> i64 { 1847 if jp_eat_punct(ctx, "(\x00" as *u8) == 0 { return jp_error_node(ctx) } 1848 let scratch: *i64 = sys_mmap(256 * 8) as *i64 1849 var count: i64 = 0 1850 if jp_is_punct(ctx, ")\x00" as *u8) == 1 { 1851 jp_advance(ctx) 1852 let start0: i64 = jp_child_commit(ctx, scratch, 0) 1853 return jp_new_node(ctx, ND_PARAMS, start0, 0, -1, -1, 0) 1854 } 1855 var go: i64 = 1 1856 while go == 1 { 1857 var isrest: i64 = 0 1858 if jp_is_punct(ctx, "...\x00" as *u8) == 1 { isrest = 1; jp_advance(ctx) } // rest param ...r 1859 var pn: i64 = 0 - 1 1860 var handled: i64 = 0 1861 // DESTRUCTURING params (ES6): `function f({a, b: c}, [x, y])` -- vk's framework uses these. 1862 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 } } } 1863 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 } } } } 1864 if handled == 0 { 1865 if jp_tok_kind(ctx) != JS_TOK_IDENT { jp_set_err(ctx); go = 0 } 1866 if go == 1 { 1867 let ptok: i64 = jp_cur(ctx) 1868 jp_advance(ctx) 1869 var defexpr: i64 = 0 - 1 1870 if isrest == 0 { if jp_is_punct(ctx, "=\x00" as *u8) == 1 { // default param: name = expr 1871 jp_advance(ctx) 1872 defexpr = jp_parse_assign(ctx) 1873 if jp_err(ctx) == 1 { go = 0 } 1874 } } 1875 pn = jp_new_node(ctx, ND_IDENT, defexpr, -1, -1, ptok, isrest) // extra=isrest; slot a=default/-1 1876 } 1877 } 1878 if go == 1 { 1879 if count < 256 { scratch[count] = pn } 1880 count = count + 1 1881 if jp_eat_punct(ctx, ",\x00" as *u8) == 1 { 1882 // next param 1883 } else { 1884 if jp_eat_punct(ctx, ")\x00" as *u8) == 1 { go = 0 } 1885 else { jp_set_err(ctx); go = 0 } 1886 } 1887 } 1888 } 1889 if jp_err(ctx) == 1 { return jp_error_node(ctx) } 1890 let start: i64 = jp_child_commit(ctx, scratch, count) 1891 return jp_new_node(ctx, ND_PARAMS, start, count, -1, -1, 0) 1892} 1893 1894// function NAME ( params ) { body } 1895func jp_parse_func_decl(ctx: *i64) -> i64 { 1896 jp_advance(ctx) // consume `function` 1897 if jp_is_punct(ctx, "*\x00" as *u8) == 1 { jp_advance(ctx) } // generator marker (stub semantics; ND_YIELD) 1898 if jp_tok_kind(ctx) != JS_TOK_IDENT { return jp_error_node(ctx) } 1899 let name: i64 = jp_new_node(ctx, ND_IDENT, -1, -1, -1, jp_cur(ctx), 0) 1900 jp_advance(ctx) 1901 let params: i64 = jp_parse_params(ctx) 1902 if jp_err(ctx) == 1 { return jp_error_node(ctx) } 1903 if jp_is_punct(ctx, "{\x00" as *u8) == 0 { return jp_error_node(ctx) } 1904 let body: i64 = jp_parse_block(ctx) 1905 if jp_err(ctx) == 1 { return jp_error_node(ctx) } 1906 return jp_new_node(ctx, ND_FUNC_DECL, name, params, body, -1, 0) 1907} 1908 1909// FUNCTION EXPRESSION (R-JS-CLOSURE, rung 6): `function (params){body}` (anonymous) or 1910// `function name(params){body}` (named) in EXPRESSION position. Same ND_FUNC_DECL node 1911// shape as a declaration (a=name|-1, b=params, c=body); name = -1 when anonymous. The 1912// expression-vs-statement distinction is purely WHERE it is parsed -- a statement-leading 1913// `function` is a declaration (jp_parse_stmt), here it is a value. Cursor is on `function`. 1914func jp_parse_func_expr(ctx: *i64) -> i64 { 1915 jp_advance(ctx) // consume `function` 1916 if jp_is_punct(ctx, "*\x00" as *u8) == 1 { jp_advance(ctx) } // generator marker (stub semantics; ND_YIELD) 1917 var name: i64 = -1 1918 if jp_tok_kind(ctx) == JS_TOK_IDENT { // optional name (named function expression) 1919 name = jp_new_node(ctx, ND_IDENT, -1, -1, -1, jp_cur(ctx), 0) 1920 jp_advance(ctx) 1921 } 1922 let params: i64 = jp_parse_params(ctx) 1923 if jp_err(ctx) == 1 { return jp_error_node(ctx) } 1924 if jp_is_punct(ctx, "{\x00" as *u8) == 0 { return jp_error_node(ctx) } 1925 let body: i64 = jp_parse_block(ctx) 1926 if jp_err(ctx) == 1 { return jp_error_node(ctx) } 1927 return jp_new_node(ctx, ND_FUNC_DECL, name, params, body, -1, 0) 1928} 1929 1930// expression statement: expr [ ; | ASI ] 1931// Termination is REQUIRED -- but the semicolon may be inserted automatically (ASI): 1932// after a complete expression, accept an explicit ';', else require an ASI point 1933// (line break, '}', or EOF). Adjacent statements on ONE line with no separator 1934// (`a b`, `1 2`) are a SyntaxError -- jp_can_asi returns 0 there, so we error. 1935func jp_parse_expr_stmt(ctx: *i64) -> i64 { 1936 let e: i64 = jp_parse_expr(ctx) 1937 if jp_err(ctx) == 1 { return jp_error_node(ctx) } 1938 if jp_eat_punct(ctx, ";\x00" as *u8) == 1 { 1939 return jp_new_node(ctx, ND_EXPR_STMT, e, -1, -1, -1, 0) 1940 } 1941 if jp_can_asi(ctx) == 0 { return jp_error_node(ctx) } // no ';', no ASI point 1942 return jp_new_node(ctx, ND_EXPR_STMT, e, -1, -1, -1, 0) 1943} 1944 1945// one statement (dispatch on leading keyword / punct). Shallow if-chain by design. 1946// try { ... } catch (e) { ... } finally { ... } -- catch param optional (`catch {`), 1947// at least one of catch/finally required (real JS). Finally block rides the tokidx slot. 1948func jp_parse_try(ctx: *i64) -> i64 { 1949 jp_advance(ctx) // consume `try` 1950 if jp_is_punct(ctx, "{\x00" as *u8) == 0 { jp_set_err(ctx); return jp_error_node(ctx) } 1951 let tryb: i64 = jp_parse_block(ctx) 1952 if jp_err(ctx) == 1 { return jp_error_node(ctx) } 1953 var param: i64 = 0 - 1 1954 var catchb: i64 = 0 - 1 1955 var finb: i64 = 0 - 1 1956 if jp_is_kw(ctx, "catch\x00" as *u8) == 1 { 1957 jp_advance(ctx) 1958 if jp_is_punct(ctx, "(\x00" as *u8) == 1 { 1959 jp_advance(ctx) 1960 if jp_tok_kind(ctx) != JS_TOK_IDENT { jp_set_err(ctx); return jp_error_node(ctx) } 1961 param = jp_new_node(ctx, ND_IDENT, -1, -1, -1, jp_cur(ctx), 0) 1962 jp_advance(ctx) 1963 if jp_eat_punct(ctx, ")\x00" as *u8) == 0 { jp_set_err(ctx); return jp_error_node(ctx) } 1964 } 1965 if jp_is_punct(ctx, "{\x00" as *u8) == 0 { jp_set_err(ctx); return jp_error_node(ctx) } 1966 catchb = jp_parse_block(ctx) 1967 if jp_err(ctx) == 1 { return jp_error_node(ctx) } 1968 } 1969 if jp_is_kw(ctx, "finally\x00" as *u8) == 1 { 1970 jp_advance(ctx) 1971 if jp_is_punct(ctx, "{\x00" as *u8) == 0 { jp_set_err(ctx); return jp_error_node(ctx) } 1972 finb = jp_parse_block(ctx) 1973 if jp_err(ctx) == 1 { return jp_error_node(ctx) } 1974 } 1975 if catchb < 0 { if finb < 0 { jp_set_err(ctx); return jp_error_node(ctx) } } 1976 return jp_new_node(ctx, ND_TRY, tryb, param, catchb, finb, 0) 1977} 1978// throw expr ; 1979func jp_parse_throw(ctx: *i64) -> i64 { 1980 jp_advance(ctx) // consume `throw` 1981 let e: i64 = jp_parse_expr(ctx) 1982 if jp_err(ctx) == 1 { return jp_error_node(ctx) } 1983 if jp_eat_punct(ctx, ";\x00" as *u8) == 0 { 1984 if jp_can_asi(ctx) == 0 { return jp_error_node(ctx) } 1985 } 1986 return jp_new_node(ctx, ND_THROW, e, -1, -1, -1, 0) 1987} 1988func jp_parse_stmt(ctx: *i64) -> i64 { 1989 if jp_err(ctx) == 1 { return jp_error_node(ctx) } 1990 // lone semicolon = empty statement -> treat as empty EXPR_STMT? keep strict: 1991 // we just consume a stray ';' as an empty block of work by recursing once. 1992 if jp_is_punct(ctx, ";\x00" as *u8) == 1 { 1993 jp_advance(ctx) 1994 return jp_new_node(ctx, ND_BLOCK, jp_pst(ctx)[PST_NCHILD], 0, -1, -1, 0) 1995 } 1996 if jp_is_punct(ctx, "{\x00" as *u8) == 1 { return jp_parse_block(ctx) } 1997 if jp_is_kw(ctx, "var\x00" as *u8) == 1 { return jp_parse_var_decl(ctx) } 1998 if jp_is_kw(ctx, "let\x00" as *u8) == 1 { return jp_parse_var_decl(ctx) } 1999 if jp_is_kw(ctx, "const\x00" as *u8) == 1 { return jp_parse_var_decl(ctx) } 2000 if jp_is_kw(ctx, "return\x00" as *u8) == 1 { return jp_parse_return(ctx) } 2001 if jp_is_kw(ctx, "if\x00" as *u8) == 1 { return jp_parse_if(ctx) } 2002 if jp_is_kw(ctx, "while\x00" as *u8) == 1 { return jp_parse_while(ctx) } 2003 if jp_is_kw(ctx, "for\x00" as *u8) == 1 { return jp_parse_for(ctx) } 2004 if jp_is_kw(ctx, "do\x00" as *u8) == 1 { return jp_parse_dowhile(ctx) } 2005 if jp_is_kw(ctx, "switch\x00" as *u8) == 1 { return jp_parse_switch(ctx) } 2006 if jp_is_kw(ctx, "break\x00" as *u8) == 1 { return jp_parse_break(ctx) } 2007 if jp_is_kw(ctx, "continue\x00" as *u8) == 1 { return jp_parse_continue(ctx) } 2008 // `async function NAME(){}` DECLARATION (2026-08-25). Same contextual idiom as the async arrow in 2009 // jp_parse_assign: `async` is an IDENT, so consume it only when `function` genuinely follows, 2010 // else restore the cursor and let it parse as a plain name. Async-ness itself is not modelled 2011 // (declared stub, as for arrows). Found because the shipped /world pages declare 2012 // `async function loadNPC` and the parser refused them at token 0 -- which made every static 2013 // referee over the page (nx_jsscope) unable to judge the real subject. 2014 // `async` may lex as IDENT or as KEYWORD depending on the lexer's reserved list -- accept both, 2015 // because a guard keyed on one kind silently never fires under the other (measured: the IDENT-only 2016 // form left the parse error at token 0 exactly as before). 2017 var fasync: i64 = jp_is_lex(ctx, JS_TOK_IDENT, "async\x00" as *u8) 2018 if jp_is_kw(ctx, "async\x00" as *u8) == 1 { fasync = 1 } 2019 if fasync == 1 { 2020 let fsave: i64 = jp_cur(ctx) 2021 jp_set_cur(ctx, fsave + 1) 2022 if jp_is_kw(ctx, "function\x00" as *u8) == 1 { return jp_parse_func_decl(ctx) } 2023 jp_set_cur(ctx, fsave) 2024 } 2025 if jp_is_kw(ctx, "function\x00" as *u8) == 1 { return jp_parse_func_decl(ctx) } 2026 if jp_is_kw(ctx, "class\x00" as *u8) == 1 { return jp_parse_class_decl(ctx) } 2027 if jp_is_kw(ctx, "try\x00" as *u8) == 1 { return jp_parse_try(ctx) } 2028 if jp_is_kw(ctx, "throw\x00" as *u8) == 1 { return jp_parse_throw(ctx) } 2029 // labeled statement: IDENT ':' stmt (minified jQuery/Sizzle use labeled loops). Pragmatic support: the label 2030 // is a TRANSPARENT wrapper -> parse+discard it, return the inner statement. break/continue [label] consume the 2031 // label but target the NEAREST loop (correct when the label is on the enclosing loop; a labeled break to a 2032 // NON-nearest outer loop is the known approximation -> a full label table is the follow-on). 2033 if jp_tok_kind(ctx) == JS_TOK_IDENT { if jp_punct_at(ctx, jp_cur(ctx) + 1, ":\x00" as *u8) == 1 { 2034 jp_advance(ctx) // label IDENT 2035 jp_advance(ctx) // ':' 2036 return jp_parse_stmt(ctx) 2037 } } 2038 // default: expression statement 2039 return jp_parse_expr_stmt(ctx) 2040} 2041 2042// PROGRAM = stmt* until EOF. Returns the PROGRAM node index (list-node). 2043func jp_parse_program(ctx: *i64) -> i64 { 2044 let scratch: *i64 = sys_mmap(ND_MAGIC_4096 * 8) as *i64 2045 var count: i64 = 0 2046 var go: i64 = 1 2047 while go == 1 { 2048 if jp_tok_kind(ctx) == JS_TOK_EOF { go = 0 } 2049 if go == 1 { if jp_cur(ctx) >= ctx[CTX_NTOK] { go = 0 } } 2050 if go == 1 { if jp_err(ctx) == 1 { go = 0 } } 2051 if go == 1 { 2052 let st: i64 = jp_parse_stmt(ctx) 2053 if count < ND_MAGIC_4096 { scratch[count] = st } 2054 count = count + 1 2055 } 2056 } 2057 let start: i64 = jp_child_commit(ctx, scratch, count) 2058 return jp_new_node(ctx, ND_PROGRAM, start, count, -1, -1, 0) 2059} 2060 2061// ===================== top-level driver ===================== 2062// Build a parse context over freshly-mmap'd arenas, lex `src`, then parse a PROGRAM. 2063// Returns the PROGRAM node index. Writes the ctx pointer to *ctx_out so the caller 2064// (gate) can inspect the AST. Sets the error flag inside pst on any syntax error. 2065func jp_parse_source(src: *u8, srclen: i64, ctx_out: *i64) -> i64 { 2066 // Budgets: js_lex TRUNCATES SILENTLY at maxtoks (parse then dies mid-file with a misleading cursor) -- 2067 // 4096 tokens capped real sources at ~16-26KB (Octane RayTrace 28KB / Crypto 48KB both hit it). mmap is 2068 // virtual (only touched pages cost), so size for real-page bundles. Dynamic growth = a follow-on. 2069 // ADAPTIVE budgets sized to the input: real pages (vk = ~250KB of concatenated inline JS across 70 scripts) 2070 // blew the fixed 65536-token cap -> js_lex truncated -> parse died mid-file (rc=1). ~1 token per 2 bytes 2071 // covers dense minified JS; nodes/children scale with tokens. mmap is virtual (only touched pages cost). 2072 var maxtoks: i64 = ND_MAGIC_65536 2073 if srclen / 2 > maxtoks { maxtoks = srclen / 2 } 2074 var maxnodes: i64 = maxtoks * 2 2075 var maxchild: i64 = maxtoks * 2 2076 if maxnodes < ND_MAGIC_131072 { maxnodes = ND_MAGIC_131072 } 2077 if maxchild < ND_MAGIC_131072 { maxchild = ND_MAGIC_131072 } 2078 let toks: *i64 = sys_mmap(maxtoks * 3 * 8) as *i64 2079 let ntb: *i64 = sys_mmap(16) as *i64 2080 let nodes: *i64 = sys_mmap(maxnodes * NODE_SLOTS * 8) as *i64 2081 let children: *i64 = sys_mmap(maxchild * 8) as *i64 2082 let pst: *i64 = sys_mmap(64) as *i64 2083 let ctx: *i64 = sys_mmap(CTX_MAXCHILD * 8 + 64) as *i64 2084 2085 let ntok: i64 = js_lex(src, srclen, toks, maxtoks, ntb) 2086 2087 pst[PST_CUR] = 0 2088 pst[PST_ERR] = 0 2089 pst[PST_NNODE] = 0 2090 pst[PST_NCHILD] = 0 2091 pst[PST_ERR_POS] = 0 - 1 2092 2093 ctx[CTX_SRC] = src as i64 2094 ctx[CTX_TOKS] = toks as i64 2095 ctx[CTX_NTOK] = ntok 2096 ctx[CTX_NODES] = nodes as i64 2097 ctx[CTX_CHILDREN] = children as i64 2098 ctx[CTX_PST] = pst as i64 2099 ctx[CTX_MAXNODE] = maxnodes 2100 ctx[CTX_MAXCHILD] = maxchild 2101 2102 // If the lexer itself emitted an ERROR token, the source is malformed. 2103 var li: i64 = 0 2104 while li < ntok { 2105 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 } 2106 li = li + 1 2107 } 2108 2109 let prog: i64 = jp_parse_program(ctx) 2110 2111 // Trailing unconsumed tokens (after a complete parse) = syntax error too: 2112 // e.g. "1 + )" leaves the ')' dangling / errors mid-expr. Already covered by 2113 // err flag, but guard the case where the program loop stopped early WITHOUT 2114 // consuming everything and WITHOUT an error (shouldn't happen, but honest). 2115 if pst[PST_ERR] == 0 { 2116 if pst[PST_CUR] < ntok { if pst[PST_CUR] < ntok { pst[PST_ERR_POS] = toks[pst[PST_CUR] * 3 + 1] } pst[PST_ERR] = 1 } 2117 } 2118 2119 ctx_out[0] = ctx as i64 2120 return prog 2121} 2122 2123// ===================== GATE ===================== 2124func 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 } 2125// MIGRATED to the shared emitter (debt 1785563586). The old body mmapped a scratch buffer 2126// per call and never freed it. At PAGE granularity that is 4096B leaked PER CALL -- the 2127// defect that took 28.5GB of a 36GB host in nx_ts_lumadiff (2MB input, ~3.66M calls). 2128// nxi_* is MSB-first, allocates NOTHING, and emits identical bytes including the sign. 2129func jq_putn(v: i64) -> i64 { nxi_out(v); return 0 } 2130func jq_strlen(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} return n } 2131// MIGRATED to the shared emitter (debt 1785563586). The old body mmapped a scratch buffer 2132// per call and never freed it. At PAGE granularity that is 4096B leaked PER CALL -- the 2133// defect that took 28.5GB of a 36GB host in nx_ts_lumadiff (2MB input, ~3.66M calls). 2134// nxi_* is MSB-first, allocates NOTHING, and emits identical bytes including the sign. 2135func jq_fdn(fd: i64, v: i64) -> i64 { nxi_fd(fd, v); return 0 } 2136 2137// helper: parse a NUL-terminated source string, return ctx (via box) + prog index. 2138func jq_parse(s: *u8, ctxbox: *i64) -> i64 { 2139 return jp_parse_source(s, jq_strlen(s), ctxbox) 2140} 2141func jq_ctx(ctxbox: *i64) -> *i64 { return (ctxbox[0]) as *i64 } 2142func jq_haserr(ctxbox: *i64) -> i64 { let c: *i64 = jq_ctx(ctxbox); let p: *i64 = jp_pst(c); return p[PST_ERR] } 2143 2144// node index of statement-0's top expression (a-slot of the first EXPR_STMT). 2145func jq_expr0(ctxbox: *i64, prog: i64) -> i64 { 2146 let c: *i64 = jq_ctx(ctxbox) 2147 let st: i64 = jp_child_at(c, prog, 0) 2148 return jp_na(c, st) 2149} 2150// kind of statement-0's top expression. 2151func jq_expr0_kind(ctxbox: *i64, prog: i64) -> i64 { 2152 let c: *i64 = jq_ctx(ctxbox) 2153 return jp_nkind(c, jq_expr0(ctxbox, prog)) 2154} 2155// op-code (extra slot) of statement-0's top expression. 2156func jq_expr0_op(ctxbox: *i64, prog: i64) -> i64 { 2157 let c: *i64 = jq_ctx(ctxbox) 2158 return jp_nextra(c, jq_expr0(ctxbox, prog)) 2159} 2160// 1 iff `src` parses with NO error AND its statement-0 expression is a BINARY whose 2161// op-code == want. Used by KAT10 to sweep an operator family compactly. 2162func jq_binop_is(src: *u8, ctxbox: *i64, want: i64) -> i64 { 2163 let prog: i64 = jq_parse(src, ctxbox) 2164 if jq_haserr(ctxbox) == 1 { return 0 } 2165 if jq_expr0_kind(ctxbox, prog) != ND_BINARY { return 0 } 2166 if jq_expr0_op(ctxbox, prog) != want { return 0 } 2167 return 1 2168} 2169// 1 iff `src` parses with NO error AND its statement-0 expression is a UNARY whose 2170// op-code == want. 2171func jq_unop_is(src: *u8, ctxbox: *i64, want: i64) -> i64 { 2172 let prog: i64 = jq_parse(src, ctxbox) 2173 if jq_haserr(ctxbox) == 1 { return 0 } 2174 if jq_expr0_kind(ctxbox, prog) != ND_UNARY { return 0 } 2175 if jq_expr0_op(ctxbox, prog) != want { return 0 } 2176 return 1 2177} 2178 2179func main() -> i64 { 2180 let ctxbox: *i64 = sys_mmap(16) as *i64 2181 var pass: i64 = 0 2182 var tot: i64 = 0 2183 jq_puts("nx_js_parse gate (R-JS-PARSE, WB-JS-001 rung 1)\n" as *u8) 2184 2185 // ---- KAT 1 (precedence): 1+2*3 -> EXPR_STMT( BINARY(+) ) whose RIGHT child is 2186 // BINARY(*). Proves * binds tighter than + (NOT left-flat). ---- 2187 let s1: *u8 = "1+2*3\x00" as *u8 2188 let prog1: i64 = jq_parse(s1, ctxbox) 2189 let c1: *i64 = jq_ctx(ctxbox) 2190 var r1: i64 = 1 2191 if jq_haserr(ctxbox) == 1 { r1 = 0 } 2192 if jp_nkind(c1, prog1) != ND_PROGRAM { r1 = 0 } 2193 if jp_nb(c1, prog1) != 1 { r1 = 0 } // exactly 1 statement 2194 if r1 == 1 { 2195 let st: i64 = jp_child_at(c1, prog1, 0) 2196 if jp_nkind(c1, st) != ND_EXPR_STMT { r1 = 0 } 2197 if r1 == 1 { 2198 let add: i64 = jp_na(c1, st) 2199 if jp_nkind(c1, add) != ND_BINARY { r1 = 0 } 2200 if r1 == 1 { if jp_nextra(c1, add) != OP_ADD { r1 = 0 } } 2201 if r1 == 1 { 2202 let lhs: i64 = jp_na(c1, add) 2203 let rhs: i64 = jp_nb(c1, add) 2204 if jp_nkind(c1, lhs) != ND_NUMBER { r1 = 0 } // left = 1 (a NUMBER) 2205 if jp_nkind(c1, rhs) != ND_BINARY { r1 = 0 } // right = (2*3) 2206 if r1 == 1 { if jp_nextra(c1, rhs) != OP_MUL { r1 = 0 } } 2207 } 2208 } 2209 } 2210 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) } 2211 tot = tot + 1 2212 2213 // ---- KAT 2 (assoc): a=b=c -> ASSIGN(a, ASSIGN(b, c)) right-assoc; 2214 // AND 2-3-4 -> BINARY(-) whose LEFT is BINARY(-) (left-assoc). ---- 2215 let s2: *u8 = "a=b=c\x00" as *u8 2216 let prog2: i64 = jq_parse(s2, ctxbox) 2217 let c2: *i64 = jq_ctx(ctxbox) 2218 var r2: i64 = 1 2219 if jq_haserr(ctxbox) == 1 { r2 = 0 } 2220 if r2 == 1 { 2221 let st2: i64 = jp_child_at(c2, prog2, 0) 2222 let asn: i64 = jp_na(c2, st2) 2223 if jp_nkind(c2, asn) != ND_ASSIGN { r2 = 0 } 2224 if r2 == 1 { 2225 let lhs: i64 = jp_na(c2, asn) 2226 let rhs: i64 = jp_nb(c2, asn) 2227 if jp_nkind(c2, lhs) != ND_IDENT { r2 = 0 } // a 2228 if jp_nkind(c2, rhs) != ND_ASSIGN { r2 = 0 } // (b=c) nested RIGHT 2229 } 2230 } 2231 // left-assoc subtraction 2232 let s2b: *u8 = "2-3-4\x00" as *u8 2233 let prog2b: i64 = jq_parse(s2b, ctxbox) 2234 let c2b: *i64 = jq_ctx(ctxbox) 2235 if jq_haserr(ctxbox) == 1 { r2 = 0 } 2236 if r2 == 1 { 2237 let st: i64 = jp_child_at(c2b, prog2b, 0) 2238 let sub: i64 = jp_na(c2b, st) 2239 if jp_nkind(c2b, sub) != ND_BINARY { r2 = 0 } 2240 if r2 == 1 { if jp_nextra(c2b, sub) != OP_SUB { r2 = 0 } } 2241 if r2 == 1 { 2242 let lhs: i64 = jp_na(c2b, sub) 2243 let rhs: i64 = jp_nb(c2b, sub) 2244 if jp_nkind(c2b, lhs) != ND_BINARY { r2 = 0 } // (2-3) nested LEFT 2245 if jp_nkind(c2b, rhs) != ND_NUMBER { r2 = 0 } // 4 on the right 2246 } 2247 } 2248 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) } 2249 tot = tot + 1 2250 2251 // ---- KAT 3 (call/member chain): foo.bar(x) -> CALL of MEMBER(foo,bar), 1 arg. ---- 2252 let s3: *u8 = "foo.bar(x)\x00" as *u8 2253 let prog3: i64 = jq_parse(s3, ctxbox) 2254 let c3: *i64 = jq_ctx(ctxbox) 2255 var r3: i64 = 1 2256 if jq_haserr(ctxbox) == 1 { r3 = 0 } 2257 if r3 == 1 { 2258 let st: i64 = jp_child_at(c3, prog3, 0) 2259 let call: i64 = jp_na(c3, st) 2260 if jp_nkind(c3, call) != ND_CALL { r3 = 0 } 2261 if r3 == 1 { 2262 let callee: i64 = jp_na(c3, call) // a=callee 2263 let argc: i64 = jp_nc(c3, call) // c=arg count 2264 if jp_nkind(c3, callee) != ND_MEMBER { r3 = 0 } 2265 if argc != 1 { r3 = 0 } 2266 if r3 == 1 { 2267 let obj: i64 = jp_na(c3, callee) 2268 let prop: i64 = jp_nb(c3, callee) 2269 if jp_nkind(c3, obj) != ND_IDENT { r3 = 0 } // foo 2270 if jp_nkind(c3, prop) != ND_IDENT { r3 = 0 } // bar 2271 } 2272 } 2273 } 2274 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) } 2275 tot = tot + 1 2276 2277 // ---- KAT 4 (statements): var decl + if/else + while + function decl + return. ---- 2278 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 2279 let prog4: i64 = jq_parse(s4, ctxbox) 2280 let c4: *i64 = jq_ctx(ctxbox) 2281 var r4: i64 = 1 2282 if jq_haserr(ctxbox) == 1 { r4 = 0 } 2283 if jp_nkind(c4, prog4) != ND_PROGRAM { r4 = 0 } 2284 if r4 == 1 { if jp_nb(c4, prog4) != 4 { r4 = 0 } } // 4 top-level statements 2285 if r4 == 1 { 2286 let s_var: i64 = jp_child_at(c4, prog4, 0) 2287 let s_if: i64 = jp_child_at(c4, prog4, 1) 2288 let s_wh: i64 = jp_child_at(c4, prog4, 2) 2289 let s_fn: i64 = jp_child_at(c4, prog4, 3) 2290 if jp_nkind(c4, s_var) != ND_VAR_DECL { r4 = 0 } 2291 if jp_nkind(c4, s_if) != ND_IF { r4 = 0 } 2292 if jp_nkind(c4, s_wh) != ND_WHILE { r4 = 0 } 2293 if jp_nkind(c4, s_fn) != ND_FUNC_DECL { r4 = 0 } 2294 // var decl has an initializer (b != -1) 2295 if r4 == 1 { if jp_nb(c4, s_var) == -1 { r4 = 0 } } 2296 // if has an else branch (c != -1) and both branches are BLOCKs 2297 if r4 == 1 { 2298 if jp_nc(c4, s_if) == -1 { r4 = 0 } 2299 if r4 == 1 { 2300 if jp_nkind(c4, jp_nb(c4, s_if)) != ND_BLOCK { r4 = 0 } // then 2301 if jp_nkind(c4, jp_nc(c4, s_if)) != ND_BLOCK { r4 = 0 } // else 2302 } 2303 } 2304 // then-block contains a RETURN 2305 if r4 == 1 { 2306 let then_b: i64 = jp_nb(c4, s_if) 2307 if jp_nb(c4, then_b) < 1 { r4 = 0 } 2308 if r4 == 1 { 2309 let ret: i64 = jp_child_at(c4, then_b, 0) 2310 if jp_nkind(c4, ret) != ND_RETURN { r4 = 0 } 2311 if r4 == 1 { if jp_na(c4, ret) == -1 { r4 = 0 } } // return HAS an expr 2312 } 2313 } 2314 // function f(a,b){...} -> 2 params, body BLOCK with a RETURN of a BINARY(+) 2315 if r4 == 1 { 2316 let params: i64 = jp_nb(c4, s_fn) 2317 let body: i64 = jp_nc(c4, s_fn) 2318 if jp_nkind(c4, params) != ND_PARAMS { r4 = 0 } 2319 if r4 == 1 { if jp_nb(c4, params) != 2 { r4 = 0 } } // 2 params 2320 if jp_nkind(c4, body) != ND_BLOCK { r4 = 0 } 2321 if r4 == 1 { 2322 let ret: i64 = jp_child_at(c4, body, 0) 2323 if jp_nkind(c4, ret) != ND_RETURN { r4 = 0 } 2324 if r4 == 1 { 2325 let plus: i64 = jp_na(c4, ret) 2326 if jp_nkind(c4, plus) != ND_BINARY { r4 = 0 } 2327 if r4 == 1 { if jp_nextra(c4, plus) != OP_ADD { r4 = 0 } } 2328 } 2329 } 2330 } 2331 } 2332 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) } 2333 tot = tot + 1 2334 2335 // ---- KAT 5 (TAMPER): malformed "1 + )" MUST be an error, never a fake AST. ---- 2336 let s5: *u8 = "1 + )\x00" as *u8 2337 let prog5: i64 = jq_parse(s5, ctxbox) 2338 var r5: i64 = 1 2339 if jq_haserr(ctxbox) != 1 { r5 = 0 } // must have flagged an error 2340 // second malformed sample: "var = ;" (decl with no name) 2341 let s5b: *u8 = "var = ;\x00" as *u8 2342 let prog5b: i64 = jq_parse(s5b, ctxbox) 2343 if jq_haserr(ctxbox) != 1 { r5 = 0 } 2344 // third: dangling operator "a *" (RHS missing) 2345 let s5c: *u8 = "a *\x00" as *u8 2346 let prog5c: i64 = jq_parse(s5c, ctxbox) 2347 if jq_haserr(ctxbox) != 1 { r5 = 0 } 2348 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) } 2349 tot = tot + 1 2350 2351 // ---- KAT 6 (precedence ladder + unary): !a && b || c == d -> top is OR, whose 2352 // left is AND, the AND's left is UNARY(!a), and == binds tighter than ==. --- 2353 let s6: *u8 = "!a && b || c == d\x00" as *u8 2354 let prog6: i64 = jq_parse(s6, ctxbox) 2355 let c6: *i64 = jq_ctx(ctxbox) 2356 var r6: i64 = 1 2357 if jq_haserr(ctxbox) == 1 { r6 = 0 } 2358 if r6 == 1 { 2359 let st: i64 = jp_child_at(c6, prog6, 0) 2360 let or_n: i64 = jp_na(c6, st) 2361 if jp_nkind(c6, or_n) != ND_BINARY { r6 = 0 } 2362 if r6 == 1 { if jp_nextra(c6, or_n) != OP_OR { r6 = 0 } } // top = || 2363 if r6 == 1 { 2364 let and_n: i64 = jp_na(c6, or_n) // left of || 2365 let eq_n: i64 = jp_nb(c6, or_n) // right of || 2366 if jp_nextra(c6, and_n) != OP_AND { r6 = 0 } // && under || 2367 if r6 == 1 { if jp_nextra(c6, eq_n) != OP_EQ { r6 = 0 } } // == under || 2368 if r6 == 1 { 2369 let not_n: i64 = jp_na(c6, and_n) // left of && 2370 if jp_nkind(c6, not_n) != ND_UNARY { r6 = 0 } 2371 if r6 == 1 { if jp_nextra(c6, not_n) != OP_NOT { r6 = 0 } } 2372 } 2373 } 2374 } 2375 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) } 2376 tot = tot + 1 2377 2378 // ---- KAT 7 (parenthesized regrouping): (1+2)*3 -> BINARY(*) whose LEFT is 2379 // BINARY(+). Proves parens override precedence (distinct from KAT1). ---- 2380 let s7: *u8 = "(1+2)*3\x00" as *u8 2381 let prog7: i64 = jq_parse(s7, ctxbox) 2382 let c7: *i64 = jq_ctx(ctxbox) 2383 var r7: i64 = 1 2384 if jq_haserr(ctxbox) == 1 { r7 = 0 } 2385 if r7 == 1 { 2386 let st: i64 = jp_child_at(c7, prog7, 0) 2387 let mul: i64 = jp_na(c7, st) 2388 if jp_nkind(c7, mul) != ND_BINARY { r7 = 0 } 2389 if r7 == 1 { if jp_nextra(c7, mul) != OP_MUL { r7 = 0 } } 2390 if r7 == 1 { 2391 let lhs: i64 = jp_na(c7, mul) 2392 let rhs: i64 = jp_nb(c7, mul) 2393 if jp_nkind(c7, lhs) != ND_BINARY { r7 = 0 } // (1+2) on the LEFT now 2394 if r7 == 1 { if jp_nextra(c7, lhs) != OP_ADD { r7 = 0 } } 2395 if jp_nkind(c7, rhs) != ND_NUMBER { r7 = 0 } // 3 on the right 2396 } 2397 } 2398 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) } 2399 tot = tot + 1 2400 2401 // ---- KAT 8 (index + nested call chain): a[b].c(d, e) -> CALL(MEMBER(INDEX(a,b),c),[d,e]) ---- 2402 let s8: *u8 = "a[b].c(d, e)\x00" as *u8 2403 let prog8: i64 = jq_parse(s8, ctxbox) 2404 let c8: *i64 = jq_ctx(ctxbox) 2405 var r8: i64 = 1 2406 if jq_haserr(ctxbox) == 1 { r8 = 0 } 2407 if r8 == 1 { 2408 let st: i64 = jp_child_at(c8, prog8, 0) 2409 let call: i64 = jp_na(c8, st) 2410 if jp_nkind(c8, call) != ND_CALL { r8 = 0 } 2411 if r8 == 1 { if jp_nc(c8, call) != 2 { r8 = 0 } } // 2 args 2412 if r8 == 1 { 2413 let mem: i64 = jp_na(c8, call) 2414 if jp_nkind(c8, mem) != ND_MEMBER { r8 = 0 } 2415 if r8 == 1 { 2416 let idx: i64 = jp_na(c8, mem) 2417 if jp_nkind(c8, idx) != ND_INDEX { r8 = 0 } 2418 if r8 == 1 { 2419 if jp_nkind(c8, jp_na(c8, idx)) != ND_IDENT { r8 = 0 } // a 2420 if jp_nkind(c8, jp_nb(c8, idx)) != ND_IDENT { r8 = 0 } // b 2421 } 2422 } 2423 } 2424 } 2425 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) } 2426 tot = tot + 1 2427 2428 // ---- KAT 9 (literals + unary families that had ZERO coverage before): assert 2429 // STRING / null (NULL) / true,false (BOOL) primary nodes AND typeof / unary 2430 // '-' / unary '+' unary-op codes. Council BLOCKER fix: these code paths 2431 // existed but no KAT ever asserted their nodes -> 'ALL gated' was false. ---- 2432 var r9: i64 = 1 2433 // string literal "x" -- byte-built so the source escape is unambiguous. 2434 let s9str: *u8 = sys_mmap(8) 2435 s9str[0] = 34 as u8; s9str[1] = 120 as u8; s9str[2] = 34 as u8; s9str[3] = 0 as u8 // "x" 2436 let p9str: i64 = jq_parse(s9str, ctxbox) 2437 if jq_haserr(ctxbox) == 1 { r9 = 0 } 2438 if r9 == 1 { if jq_expr0_kind(ctxbox, p9str) != ND_STRING { r9 = 0 } } 2439 // null 2440 if r9 == 1 { 2441 let p: i64 = jq_parse("null\x00" as *u8, ctxbox) 2442 if jq_haserr(ctxbox) == 1 { r9 = 0 } 2443 if r9 == 1 { if jq_expr0_kind(ctxbox, p) != ND_NULL { r9 = 0 } } 2444 } 2445 // true -> BOOL extra=1 2446 if r9 == 1 { 2447 let p: i64 = jq_parse("true\x00" as *u8, ctxbox) 2448 if jq_haserr(ctxbox) == 1 { r9 = 0 } 2449 if r9 == 1 { if jq_expr0_kind(ctxbox, p) != ND_BOOL { r9 = 0 } } 2450 if r9 == 1 { if jq_expr0_op(ctxbox, p) != 1 { r9 = 0 } } 2451 } 2452 // false -> BOOL extra=0 2453 if r9 == 1 { 2454 let p: i64 = jq_parse("false\x00" as *u8, ctxbox) 2455 if jq_haserr(ctxbox) == 1 { r9 = 0 } 2456 if r9 == 1 { if jq_expr0_kind(ctxbox, p) != ND_BOOL { r9 = 0 } } 2457 if r9 == 1 { if jq_expr0_op(ctxbox, p) != 0 { r9 = 0 } } 2458 } 2459 if r9 == 1 { if jq_unop_is("typeof a\x00" as *u8, ctxbox, OP_TYPEOF) == 0 { r9 = 0 } } 2460 if r9 == 1 { if jq_unop_is("-a\x00" as *u8, ctxbox, OP_NEG) == 0 { r9 = 0 } } 2461 if r9 == 1 { if jq_unop_is("+a\x00" as *u8, ctxbox, OP_POS) == 0 { r9 = 0 } } 2462 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) } 2463 tot = tot + 1 2464 2465 // ---- KAT 10 (operator families that had ZERO coverage): one BINARY node per 2466 // previously-untested operator -- '/' '%' (mul tier), '<' '<=' '>' '>=' 2467 // (rel tier), '!=' '===' '!==' (eq tier). Makes 'ALL gated' honest. ---- 2468 var r10: i64 = 1 2469 if jq_binop_is("a/b\x00" as *u8, ctxbox, OP_DIV) == 0 { r10 = 0 } 2470 if jq_binop_is("a%b\x00" as *u8, ctxbox, OP_MOD) == 0 { r10 = 0 } 2471 if jq_binop_is("a<b\x00" as *u8, ctxbox, OP_LT) == 0 { r10 = 0 } 2472 if jq_binop_is("a<=b\x00" as *u8, ctxbox, OP_LE) == 0 { r10 = 0 } 2473 if jq_binop_is("a>b\x00" as *u8, ctxbox, OP_GT) == 0 { r10 = 0 } 2474 if jq_binop_is("a>=b\x00" as *u8, ctxbox, OP_GE) == 0 { r10 = 0 } 2475 if jq_binop_is("a!=b\x00" as *u8, ctxbox, OP_NE) == 0 { r10 = 0 } 2476 if jq_binop_is("a===b\x00" as *u8, ctxbox, OP_SEQ) == 0 { r10 = 0 } 2477 if jq_binop_is("a!==b\x00" as *u8, ctxbox, OP_SNE) == 0 { r10 = 0 } 2478 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) } 2479 tot = tot + 1 2480 2481 // ---- KAT 11 (ASI tamper -- the BLOCKER): adjacent statements on ONE LINE with 2482 // no separator MUST error ('a b', '1 2' are real-JS SyntaxErrors); but a 2483 // LINE BREAK between them is a valid ASI point and MUST parse as 2 stmts. 2484 // Proves we ENFORCE same-line separation yet still honor ASI (not just 2485 // reject-everything). ---- 2486 var r11: i64 = 1 2487 // 'a b' -> error 2488 let p11a: i64 = jq_parse("a b\x00" as *u8, ctxbox) 2489 if jq_haserr(ctxbox) != 1 { r11 = 0 } 2490 // '1 2' -> error 2491 let p11b: i64 = jq_parse("1 2\x00" as *u8, ctxbox) 2492 if jq_haserr(ctxbox) != 1 { r11 = 0 } 2493 // 'a\nb' (newline separator) -> OK, 2 statements, no error 2494 let s11c: *u8 = sys_mmap(8) 2495 s11c[0] = 97 as u8; s11c[1] = 10 as u8; s11c[2] = 98 as u8; s11c[3] = 0 as u8 // a<LF>b 2496 let p11c: i64 = jq_parse(s11c, ctxbox) 2497 let c11c: *i64 = jq_ctx(ctxbox) 2498 if jq_haserr(ctxbox) == 1 { r11 = 0 } 2499 if r11 == 1 { if jp_nb(c11c, p11c) != 2 { r11 = 0 } } // exactly 2 statements 2500 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) } 2501 tot = tot + 1 2502 2503 // ---- KAT 12 (lvalue tamper -- the BLOCKER): assignment to a non-lvalue MUST 2504 // error. '1 = 2' (NUMBER lhs) and '(a+b) = c' (BINARY lhs) are real-JS 2505 // 'Invalid left-hand side in assignment'. Also confirm a VALID lvalue 2506 // 'a.b = c' (MEMBER) still parses, so we reject only the invalid ones. ---- 2507 var r12: i64 = 1 2508 let p12a: i64 = jq_parse("1 = 2\x00" as *u8, ctxbox) 2509 if jq_haserr(ctxbox) != 1 { r12 = 0 } 2510 let p12b: i64 = jq_parse("(a+b) = c\x00" as *u8, ctxbox) 2511 if jq_haserr(ctxbox) != 1 { r12 = 0 } 2512 // sanity: a valid lvalue target (member) still parses as ASSIGN, no error. 2513 let p12c: i64 = jq_parse("a.b = c\x00" as *u8, ctxbox) 2514 if jq_haserr(ctxbox) == 1 { r12 = 0 } 2515 if r12 == 1 { if jq_expr0_kind(ctxbox, p12c) != ND_ASSIGN { r12 = 0 } } 2516 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) } 2517 tot = tot + 1 2518 2519 jq_puts("---- nx_js_parse gate: passed " as *u8); jq_putn(pass); jq_puts(" / " as *u8); jq_putn(tot); jq_puts("\n" as *u8) 2520 let lfd: i64 = sys_openat_append("knowledge/status/js_engine.log\x00" as *u8, 0x1a4) 2521 if lfd >= 0 { 2522 sys_write(lfd, "R-JS-PARSE organ=nx_js_parse kats=" as *u8, 34) 2523 jq_fdn(lfd, pass); sys_write(lfd, "/" as *u8, 1); jq_fdn(lfd, tot) 2524 if pass == tot { sys_write(lfd, " tamper=ok verdict=GREEN\n" as *u8, 25) } 2525 if pass != tot { sys_write(lfd, " tamper=?? verdict=RED\n" as *u8, 23) } 2526 sys_close(lfd) 2527 } 2528 if pass == tot { sys_exit(0); return 0 } 2529 sys_exit(1) 2530 return 1 2531}