code wiki / (root) / nx_js_eval.nx

nx_js_eval.nx source

↩ module page · 4641 lines · 262144 B

1// nx_js_eval.nx -- R-JS-EVAL (WB-JS-001 rung 2): the ECMAScript tree-walking 2// EVALUATOR with a variable ENVIRONMENT (scope chain) + USER FUNCTIONS, riding the 3// gated parser (nx_js_parse) per the no-floating law. Shape = recursive tree-walk 4// over the parser's flat node arena. Founds R-JS-RUNTIME (objects/builtins) and the 5// DOM bindings that render JS-walled pages (google image results are JS-walled). 6// 7// VALUE = 2 i64 slots [type, payload]: 8// VAL_UNDEF / VAL_NULL ; VAL_BOOL: 0|1 ; VAL_NUM: integer ; 9// VAL_STR: pointer (as i64) to a string-heap RECORD [len:i64][bytes...] -- so '+' 10// can produce real, comparable byte strings (concat) ; VAL_FUNC: function-decl 11// node index (params+body live in the AST; the call frame parents the global env). 12// 13// ERROR is signalled by an eval return code of 1 (NOT a tag): a ReferenceError -- 14// reading or assigning an UNDECLARED name -- is an ERROR, never a silent undefined 15// (`y+1` with y undeclared yields an error result, matching real JS "y is not defined"). 16// 17// RUNG-2 (ALL gated): 18// expressions: literals (number/string/bool/null), identifier lookup with 19// ReferenceError, unary ! - + typeof, binary + - * / % (integer; + does STRING 20// CONCAT when either operand is a string, coercing the other), comparison 21// < <= > >= (numeric; AND byte-lexicographic when BOTH operands are strings: 22// 'a'<'b', 'apple'<'banana', 'ab'<'abc'), equality == != === !== (strings 23// compared by bytes), logical && || 24// SHORT-CIRCUIT (returns the deciding OPERAND value = real JS), assignment (incl 25// chained a=b=c -- updates the DECLARING scope, never auto-creates a global). 26// statements: var/let/const decl, assignment, expr stmt, { blocks }, if/else, 27// while (iteration cap), return. 28// FUNCTIONS: `function f(params){body}` is a VAL_FUNC; CALL f(args) opens a fresh 29// call env (parent = global env -> globals + recursion resolve), binds params 30// (a param SHADOWS an outer same-named var), runs the body, and RETURN unwinds 31// (completion status 2) out of any nested if/while to the call boundary. 32// Recursion works (fact(5)=120, fib(10)=55). Function declarations are HOISTED both at 33// the TOP LEVEL (js_run_source) and INTRA-BODY (js_call_core pre-pass over the body 34// block's DIRECT children) -- so a nested `function inner(){}` is callable BEFORE its 35// textual position within the same function body (rung 7). Decls inside inner blocks 36// (if/for) are bound when reached, not hoisted across block boundaries (real-JS `var`- 37// style function hoisting to the enclosing function scope is a NAMED OPEN -- only the 38// body's DIRECT children are pre-bound). 39// 40// RUNG-6 (R-JS-CLOSURE, NOW DONE + gated): FIRST-CLASS FUNCTIONS with PROPER LEXICAL 41// CLOSURES. A function VALUE is now a CLOSURE record [fnode, defenv] (clos_new): when a 42// function/arrow LITERAL or DECL is evaluated it captures the env in scope at THAT point, 43// and js_call_userfn parents the call frame to the captured DEFENV (not genv) -- so an 44// inner function returned from an outer one resolves free vars up the LEXICAL chain to the 45// enclosing function's locals (makeAdder(5)->add5(3)=8; a returned counter mutates its 46// captured `c` across calls; two closures from one factory have INDEPENDENT frames). Also: 47// FUNCTION EXPRESSIONS (`var f=function(x){...}`, named fn-exprs, IIFE `(function(){})()`), 48// and ARROW functions (`x=>x+1`, `(a,b)=>a+b`, `()=>e`, block-body `(x)=>{...}`) -- arrows 49// desugar to the same ND_FUNC_DECL node and are CLOSURES too. Lexical (NOT dynamic) scope is 50// gated by a discriminating case (a closure reads its DEFINING binding, never the caller's), 51// and a free var absent in chain+global is still a ReferenceError (rc=1). 52// 53// RUNG-7 (R-JS-CALLBACK, NOW DONE + gated): ARRAY ITERATION methods that CALL BACK into a 54// user CLOSURE, plus INTRA-BODY function-decl hoisting. A reusable closure-invoke core 55// (js_call_core) takes a closure value + an array of PRE-EVALUATED arg value-cells (the 56// inverse of normal ND_CALL where args live in the AST), builds the call frame parented to 57// the closure's captured defenv, binds params positionally, hoists the body's nested decls, 58// runs the body, and returns the value. Both ND_CALL (js_call_userfn) and the array methods 59// route through this ONE core. Methods (this = the array, arg0 = the callback closure): 60// .forEach(fn) -> call fn(el, i) in order; returns undefined. 61// .map(fn) -> a NEW array of fn(el, i) (same length). 62// .filter(fn) -> a NEW array of els where fn(el, i) is truthy. 63// .reduce(fn[,init]) -> acc = fn(acc, el, i) from init (no-init: start at el0; empty+ 64// no-init = honest ERROR rc=1). 65// .some(fn) -> true if fn(el,i) truthy for ANY (short-circuit); .every -> ALL. 66// A NON-FUNCTION callback (`[1,2].map(5)`) is an HONEST ERROR (rc=1), never silent. The 67// callback may be a CLOSURE over outer vars (forEach side-effect into an outer `s`) or an 68// arrow. INTRA-BODY HOISTING: js_call_core pre-binds each ND_FUNC_DECL among the body 69// block's DIRECT children before executing, so `inner()` is callable before its decl. 70// HONEST OPEN (rung 7, NAMED not faked): the callback's 3rd argument (the ARRAY ITSELF) is 71// not passed (only element + index); thisArg (the 2nd .map/.forEach argument) is not bound; 72// .sort() / .sort(cmp), .reduceRight, .flat / .flatMap, .find / .findIndex are NOT 73// implemented and NOT gated. Decls inside an inner block (if/for) hoist only to that block 74// when reached (function-scope `var`-style hoisting across blocks remains a NAMED OPEN). 75// 76// HONEST OPEN (named, NOT faked/stubbed-as-done): 77// - f64 / IEEE-754 floating point: NishiLang has NO native float, so JS numbers are 78// i64 INTEGERS this rung; non-integer `/` truncates, float literals (3.14, 1e3), 79// NaN/Infinity are rung-2b. Division by zero -> ERROR (no Infinity). 80// - `this` binding for user functions does NOT exist yet (user fns have no receiver this 81// rung), so `this` INSIDE AN ARROW (which should lexically inherit the enclosing `this`) 82// is a NAMED OPEN -- there is no `this` for it to capture until the `this`/`new`/method 83// rung lands. Default/rest params and destructured arrow params are also NAMED OPEN 84// (arrow + fn-expr params are plain idents only). The cover-grammar disambiguation handles 85// the common `(x)=>`, `(x,y)=>`, `x=>`, `()=>` cases; deeply ambiguous covers (e.g. an 86// arrow head buried after a comma in a larger expression) remain a NAMED OPEN. 87// - RUNG-3 (R-JS-OBJ, NOW DONE + gated): a HEAP object model -- VAL_OBJECT (a string 88// keyed property table) and VAL_ARRAY (length + indexed cells, `.length`), allocated 89// via sys_mmap. Object {k:v} / array [e0,e1] LITERALS, MEMBER read (o.x -> value or 90// UNDEFINED when absent, real JS), INDEX read (o[i]/arr[i], out-of-range arr -> undefined, 91// object key coerced to a string name), and MEMBER/INDEX WRITE (o.x=v, arr[i]=v create/ 92// update; array write past length EXTENDS length). typeof object/array -> "object". 93// Member/index on a PRIMITIVE (number/bool) is a NAMED OPEN -> ERROR (rc=1), never a 94// fabricated value (KAT16 tamper). `str.length` + string indexing = R-JS-RUNTIME open. 95// - STILL OPEN (R-JS-RUNTIME / later rungs, NAMED not faked): prototype methods 96// (Array.push/pop, Object.keys, String methods), `this`/`new`/classes, getters/setters, 97// computed object keys {[k]:v}, shorthand/method props, spread/rest, for-in over keys, 98// property delete. These are NOT claimed and NOT gated as working. 99// - relational < <= > >= with MIXED string/number operands now COERCES NUMERICALLY for 100// the integer subset (council fix): a string that is a clean (optionally negative) integer 101// run parses via ToNumber, so '10'<5 = false, '5'<6 = true, 6>'5' = true -- NOT the old 102// silent str->0. A NON-numeric string is NaN in JS, which needs the float ladder, so it 103// ERRORs (rc=1) instead of the banned coerce-to-0. Full float/whitespace ToNumber + mixed 104// ARITHMETIC ('5'-1) string coercion remain rung-2b opens (arithmetic still str->0/concat). 105// - RUNG-4 (R-JS-CTRL, NOW DONE + gated): C-style `for(init;cond;update)body` (init = var- 106// decl/expr/empty, cond/update may be empty), `do body while(cond)`, ternary `cond?a:b` 107// (only the taken branch evaluated -- short-circuit proven), compound assign `+= -= *= /= %=` 108// (read-op-write; works for IDENT, MEMBER `o.n+=6`, and INDEX `a[i]+=10` lvalues; `+=` does 109// STRING CONCAT like `+`), and `break`/`continue` (completion signals CS_BREAK/CS_CONTINUE 110// consumed by the nearest enclosing while/for/do-while; a stray break/continue OUTSIDE any 111// loop -- at a function boundary or program top -- is a NAMED ERROR, real JS Illegal break). 112// - STILL OPEN (R-JS-CTRL successors, NAMED not faked): for-in (key iteration) / for-of 113// (iterator protocol), switch/case, LABELED break/continue (need a label table), the comma 114// operator -- these are later rungs; the gate does NOT assert them. try/catch/throw, 115// arguments object = 2d. String<->number coercion beyond '+' and loose == = 2b. 116// - Inherits the parser's rung-1b opens (template literals, arrows, classes, destructuring, 117// bitwise & | ^ << >> >>>, ?? / ?., bitwise/shift compound assigns &= |= **= etc.). 118// - DECLARATION-KEYWORD SEMANTICS (council minor): var/let/const all PARSE + bind, but 119// `const` immutability (`const k=1; k=2` is a no-op here; JS throws TypeError) and 120// `let`/`const` BLOCK-scoping (`{let y=5} y` leaks; JS = ReferenceError) are rung-2b -- 121// the parser maps all three to ND_VAR_DECL with no keyword code, so the var/let/const 122// distinction is not yet enforced. NB: scoping is BLOCK-based for every declarator incl 123// a for-loop `var` init (`var i=99; for(var i=0;...){} i` => 99, NOT real-JS hoisted 3) -- 124// real-JS `var` function-hoisting/leak is the rung-2b open, the OPPOSITE direction. 125// 126// GATE (main): 51 KATs evaluate whole programs and ASSERT the COMPUTED result value -- 127// (KAT43-51 add R-JS-CLOSURE: closure capture, mutable-local counters, independent frames, 128// function expressions + IIFE, arrow expr/block bodies, arrow lexical capture, lexical-not- 129// dynamic scope, and the unresolvable-free-var ReferenceError tamper) -- 130// precedence-through-eval (2+3*4=14), recursion (fact(5)=120), while-accumulation 131// (sum 1..5=15), assignment mutation, if/else branch selection, logical short-circuit, 132// STRING CONCAT ('a'+'b'="ab"), typeof, functions+lexical scope, STRING-RELATIONAL 133// ordering by bytes ('a'<'b'=true, 'apple'<'banana'=true, 'ab'<'abc'=true, 'b'>'a'=true, 134// 'a'<='a'=true, 'b'<'a'=false), object/array model, control-flow, and the R-JS-RUNTIME 135// builtins -- PLUS TAMPER KATs (an UNDECLARED identifier MUST be an ERROR, never a silent 136// 0/undefined; an OPEN node kind / missing method MUST surface as ERROR/rc=1, never silent- 137// undefined-success; a DETACHED string/array method value called with no receiver MUST 138// error rc=1 and NOT crash -- KAT41; a MIXED string/number relational MUST coerce 139// numerically, NOT silently treat the string as 0 -- KAT42). 140// Self-validating; exit 0 iff all pass; appends knowledge/status/js_engine.log. 141// license_tier: ORIGINAL (tutor-bootstrap; team re-authors from the eval spec). 142import "nx_js_parse.nx" 143import "nx_itoa_lib.nx" // shared MSB-first emitter (zero-alloc) 144import "nx_dom_query.nx" // DOM binding: document.getElementById composes nx_dom_find_attr_eq 145import "nx_html_tokenizer.nx" // querySelectorAll drives the tokenizer directly to collect all matches 146import "nx_html_entities.nx" // textContent/getAttribute decode &amp;/&#39;/&nbsp; via the gated table 147import "nx_js_f64.nx" // R-JS-F64-2: f64 JS numbers (VAL_FLOAT) -- int/string converters + soft-float 148import "nx_f64_sqrt.nx" // Math.sqrt (soft-float sqrt; shares nx_f64.nx with nx_js_f64) 149import "_pe_f64pow.nx" // Math.pow over REALS (fdlibm-shape, <=1 ULP; was int-truncating base+exp) 150import "nx_domtree.nx" // R-JS-DOMWRITE: mutable DOM so page scripts can BUILD content (rung B/C) 151import "nx_json.nx" // JSON.parse: REUSE the sovereign JSON tokenizer (don't duplicate a parser) 152import "nx_rxfull.nx" // R-JS-REGEX: the sovereign regex engine (RegExp value + .test / String.search) 153 154// ---- EVAL-CELL ARENA (2026-07-27) ------------------------------------------------- 155// Every eval step allocates a 2-slot VALUE box. Those were 204 separate `sys_mmap(16)` 156// call sites: the kernel rounds EACH to a 4096-byte page and, once written, each page is 157// RESIDENT -- 256x memory amplification. MEASURED: a 20M-step script OOM-killed the whole 158// WSL VM, which is why the page fuel had to be capped at 1M steps. Nothing in the engine 159// ever FREES a value box (they live to process exit), so a bump arena is lifetime-identical 160// by construction -- it only stops paying a page per cell. Cells are ZEROED to match 161// MAP_ANONYMOUS semantics exactly, and 16-byte aligned. Chunk exhaustion mmaps another 162// chunk (never fails closed); `ev_cell_bytes()` reports arena footprint for the gate. 163// Statics live HERE, above every reader, per the forward-static-ref miscompile rule. 164const EV_ARENA_CHUNK: i64 = 1048576 // 256 chunks = 1 GiB ceiling at the raised fuel 165static ev_arena_base: i64 = 0 166static ev_arena_off: i64 = 0 167static ev_arena_bytes: i64 = 0 168 169func ev_cell() -> *i64 { 170 if ev_arena_base == 0 { 171 ev_arena_base = sys_mmap(EV_ARENA_CHUNK) as i64 172 ev_arena_off = 0 173 ev_arena_bytes = ev_arena_bytes + EV_ARENA_CHUNK 174 } 175 if (ev_arena_off + 16) > EV_ARENA_CHUNK { 176 ev_arena_base = sys_mmap(EV_ARENA_CHUNK) as i64 177 ev_arena_off = 0 178 ev_arena_bytes = ev_arena_bytes + EV_ARENA_CHUNK 179 } 180 let p: *i64 = (ev_arena_base + ev_arena_off) as *i64 181 ev_arena_off = ev_arena_off + 16 182 p[0] = 0 183 p[1] = 0 184 return p 185} 186func ev_cell_bytes() -> i64 { return ev_arena_bytes } 187 188const VAL_UNDEF: i64 = 0 189const VAL_NULL: i64 = 1 190const VAL_BOOL: i64 = 2 191const VAL_NUM: i64 = 3 192const VAL_STR: i64 = 4 193const VAL_FUNC: i64 = 5 194const VAL_OBJECT: i64 = 6 // payload = ptr(as i64) to an object record (property table) 195const VAL_ARRAY: i64 = 7 // payload = ptr(as i64) to an array record (length + cells) 196// R-JS-RUNTIME (rung 5): builtin library values. 197const VAL_NATIVE: i64 = 8 // payload = a builtin id (BI_*). A native function value. 198const VAL_GLOBALNS: i64 = 9 // payload = a namespace id (NS_*). The Math/Object/console/JSON globals. 199const VAL_FLOAT: i64 = 10 // payload = an f64 BIT-PATTERN (software-IEEE). A JS Number with a fraction. 200const VAL_REGEX: i64 = 15 // payload = ptr to a regex record [*Regex(nx_rxfull), patptr, patlen, flagbits] (moved up: read before its old decl) 201const VAL_PROMISE: i64 = 11 // payload = ptr(as i64) to a promise record (moved up: forward-const fix) 202const VAL_RESOLVE: i64 = 12 // internal handler value: promise ptr to FULFILL (moved up: forward-const fix) 203const VAL_REJECT: i64 = 13 // internal handler value: promise ptr to REJECT (moved up: forward-const fix) 204const VAL_RESPONSE: i64 = 14 // fetch Response record tag (moved up: forward-const fix) 205 206// ---- global-namespace ids (VAL_GLOBALNS payload) -- the bound global objects. ---- 207const NS_MATH: i64 = 1 208const NS_OBJECT: i64 = 2 209const NS_CONSOLE: i64 = 3 210const NS_JSON: i64 = 4 211const NS_DOCUMENT: i64 = 5 // the `document` host global (DOM binding) 212 213// ---- builtin ids (VAL_NATIVE payload) -- one per method/free-function. The dispatch 214// tables (ev_native_str/ev_native_arr/ev_native_global) map a method NAME on a given 215// receiver type to one of these; ev_invoke_native runs it with `this` + arg values. ---- 216// String methods (this = a string value): 217// Gated runtime diagnostics (CALL-ERR/REF-ERR/PROP-ERR/STMT-ERR raise-site prints). MUST live at the TOP: 218// readers span the whole file and a static defined BELOW a reader is a forward-static-ref MISCOMPILE (reads 219// garbage -> the gates were silently dead when this sat at the bottom; see gotchas). Zero-init = off. 220static js_rt_dbg: i64 221func js_set_rt_dbg(v: i64) -> i64 { js_rt_dbg = v; return 0 } 222// Runtime error-POSITION recorder (the runtime analog of the parser's PST_ERR_POS). A silently-erroring 223// node (member/index assign to a non-object, `in`/binop type errors, `new` of a non-ctor) returns 1 with 224// no leaf print, so an unwinding STMT-ERR only shows the top statement. js_rt_mark records the FIRST such 225// offset; typeof/try SWALLOW their operand errors, so they save+restore this around the swallowed eval. 226static js_rt_errpos: i64 // token offset of the innermost UNSWALLOWED runtime error; -1 = none recorded 227func js_rt_errpos_reset() -> i64 { js_rt_errpos = 0 - 1; return 0 } 228func js_rt_errpos_get() -> i64 { return js_rt_errpos } 229func js_rt_mark(ctx: *i64, idx: i64) -> i64 { 230 if js_rt_errpos < 0 { 231 js_rt_errpos = ev_tok_start(ctx, idx) 232 if js_rt_dbg == 2 { sys_write(2, "MARK @" as *u8, 6); nx_dbg_num(js_rt_errpos); sys_write(2, " '" as *u8, 2); let sp: *u8 = jp_src(ctx); var st: i64 = js_rt_errpos - 20; if st < 0 { st = 0 } var d: i64 = 0; while d < 64 { if sp[st + d] == (0 as u8) { d = 64 } else { sys_write(2, ((sp as i64) + st + d) as *u8, 1); d = d + 1 } } sys_write(2, "'\n" as *u8, 2) } 233 } 234 return 0 235} 236const BI_STR_CHARAT: i64 = 1 237const BI_STR_INDEXOF: i64 = 2 238const BI_STR_SLICE: i64 = 3 239const BI_STR_UPPER: i64 = 4 240const BI_STR_LOWER: i64 = 5 241const BI_STR_INCLUDES:i64 = 6 242const BI_STR_SEARCH: i64 = 145 // str.search(re) -> match index or -1 243const BI_RE_TEST: i64 = 146 // re.test(str) -> bool (unique in js_native_apply; NOT 103/110-range) 244const BI_STR_MATCH: i64 = 147 // str.match(re) -> array (g: full matches; else [full,g1,..]) or null 245const BI_STR_REPLACE: i64 = 148 // str.replace(re,repl) -> string (g: all; $& $1..$9 $$ substitution) 246const BI_REGEX_CTOR: i64 = 149 // new RegExp(pat,flags) / RegExp(pat,flags) -> a VAL_REGEX 247const BI_STR_SPLIT: i64 = 150 // str.split(re) -> array of pieces 248const BI_RE_EXEC: i64 = 151 // re.exec(str) -> [full,g1,..] or null; g-flag advances re record's lastIndex (slot 4) 249const BI_STR_CHARCODEAT: i64 = 152 // "s".charCodeAt(i) -> byte value (ASCII code unit); OOR -> NaN 250const BI_STR_FROMCHARCODE: i64 = 153 // String.fromCharCode(a,b,..) -> string (static on the String native) 251const BI_PARSEINT: i64 = 154 // parseInt(str, radix?) -> int (leading-ws/sign/prefix-digits; none -> NaN) 252const BI_STR_SUBSTRING: i64 = 155 // s.substring(a,b?) -> clamp+swap slice 253const BI_STR_SUBSTR: i64 = 156 // s.substr(a,len?) -> start+length slice 254// Array methods (this = an array value): 255const BI_ARR_PUSH: i64 = 10 256const BI_ARR_POP: i64 = 11 257const BI_ARR_INDEXOF: i64 = 12 258const BI_ARR_JOIN: i64 = 13 259const BI_ARR_SLICE: i64 = 14 260const BI_ARR_REVERSE: i64 = 15 // arr.reverse() -- in-place, returns the array (YouTube sig transform) 261const BI_ARR_SPLICE: i64 = 16 // arr.splice(start,deleteCount,...items) -- mutates, returns removed (YouTube sig transform) 262const BI_ARR_SORT: i64 = 17 // arr.sort([cmp]) -- STABLE in-place sort (Sizzle sortStable needs stability); cmp is a user closure 263const BI_ARR_SHIFT: i64 = 18 // arr.shift() -- remove+return arr[0], shift rest down (jQuery Callbacks/queue) 264const BI_ARR_UNSHIFT: i64 = 19 // arr.unshift(...items) -- prepend items, return new length 265const BI_STUB_NULL: i64 = 200 // headless element-stub method -> null (getAttribute/closest/querySelector) 266const BI_STUB_RETARG: i64 = 201 // headless element-stub method -> arg0 (appendChild/insertBefore are chainable) 267const BI_EL_CLONE: i64 = 202 // element-stub cloneNode -> a fresh element stub 268const BI_STUB_EMPTYARR: i64 = 203 // -> a fresh empty array (getElementsByTagName/querySelectorAll) 269const BI_STUB_FALSE: i64 = 204 // -> false (hasAttribute/contains/matches) 270const BI_EL_CLONE_TREE: i64 = 205 // tree Element.cloneNode(deep) -> wrap dt_clone_subtree of this @node 271const BI_EL_CMPDOCPOS: i64 = 206 // Element.compareDocumentPosition(other) -> DOM-order bitmask (Sizzle sort) 272const BI_ARR_CONCAT: i64 = 207 // arr.concat(...items) -> NEW array (array args spread one level); jQuery flat helper 273const BI_STR_CONCAT: i64 = 208 // str.concat(...args) -> this + args coerced to strings 274const BI_ARR_ISARRAY: i64 = 209 // Array.isArray(x) -> x is a VAL_ARRAY (static on the Array constructor) 275const BI_DOC_CREATE_FRAGMENT: i64 = 210 // document.createDocumentFragment() -> REAL dt container node (nodeType 11) 276const BI_DOC_CREATE_TEXT: i64 = 211 // document.createTextNode(s) -> REAL dt text node (nodeType 3) 277const BI_EL_GET_BY_TAG: i64 = 212 // element.getElementsByTagName(tag) -> descendant elements (Sizzle .find) 278const BI_EL_QSA: i64 = 213 // element.querySelectorAll(sel) -> descendant matches (#id/.class/tag) 279const BI_EL_QS: i64 = 214 // element.querySelector(sel) -> first descendant match or null 280const BI_EL_GET_BY_CLASS: i64 = 215 // element.getElementsByClassName(cls) -> descendant elements w/ class 281const BI_EL_REMOVE_CHILD: i64 = 216 // element.removeChild(child) -> unlink child from tree, return it (.remove()) 282const BI_DOC_GET_BY_TAG: i64 = 217 // document.getElementsByTagName(tag) -> whole-tree tag query (Sizzle $('a')) 283const BI_DOC_GET_BY_CLASS: i64 = 218 // document.getElementsByClassName(cls) -> whole-tree class query 284const BI_EL_MATCHES: i64 = 219 // element.matches(sel) -> CSS complex-selector predicate (Sizzle .is/.filter/.closest) 285const BI_EL_GET_ATTR_NODE: i64 = 220 // element.getAttributeNode(name) -> {value,name} or null (Sizzle ID seed-filter + delegation) 286const BI_WIN_ADD_LISTENER: i64 = 221 // window.addEventListener(type,fn) -> STORE + fire DOMContentLoaded/load at render end 287const WIN_SENTINEL: i64 = 0 - 999 // listener-table `node` value marking a window-level handler (tree=0) 288// Array ITERATION methods (R-JS-CALLBACK rung 7) -- this = an array, arg0 = a callback 289// CLOSURE (VAL_FUNC) or arrow. Each calls back into the user closure per element. 290// Distinct 50-range so they never collide with Object(20/21)/Math(30-35)/console(40) ids. 291const BI_ARR_FOREACH: i64 = 50 292const BI_ARR_MAP: i64 = 51 293const BI_ARR_FILTER: i64 = 52 294const BI_ARR_REDUCE: i64 = 53 295const BI_ARR_SOME: i64 = 54 296const BI_ARR_EVERY: i64 = 55 297// Object.* free functions (this ignored; arg0 = the operand object): 298const BI_OBJ_KEYS: i64 = 20 299const BI_OBJ_VALUES: i64 = 21 300const BI_OBJ_DEFINEPROP: i64 = 22 // Object.defineProperty(target, key, {value: v}) -> target[key]=v 301// Object.prototype methods (inherited by every VAL_OBJECT/VAL_FUNC) + Object.getPrototypeOf. jQuery's very 302// first module statements read these off a plain `{}` (class2type.toString / .hasOwnProperty) and dispatch 303// them via `.call(obj)` -- the R-JS-RUNTIME stdlib floor. `this` = the receiver (thisv), not an arg. 304const BI_OBJ_TOSTRING: i64 = 23 // ({}).toString() / toString.call(x) -> "[object Type]" (the toType tag) 305const BI_OBJ_HASOWN: i64 = 24 // ({}).hasOwnProperty(k) / hasOwn.call(o,k) -> bool (OWN props only) 306const BI_OBJ_VALUEOF: i64 = 25 // ({}).valueOf() -> the receiver itself (identity for objects) 307const BI_OBJ_ISPROTOTYPEOF: i64 = 26 // proto.isPrototypeOf(o) -> is `this` on o's [[Prototype]] chain 308const BI_FN_TOSTRING: i64 = 27 // Function.prototype.toString -> a source-ish string (jQuery fnToString) 309const BI_OBJ_GETPROTO: i64 = 28 // Object.getPrototypeOf(o) -> o's [[Prototype]] object or null 310const BI_OBJ_CREATE: i64 = 29 // Object.create(proto) -> new object with [[Prototype]]=proto (jQuery event storage: Object.create(null)) 311const BI_OBJ_ASSIGN: i64 = 222 // Object.assign(target, ...sources) -> copy own props of sources into target, return target (ES6, ubiquitous) 312// Math.* free functions (this ignored): 313const BI_MATH_MAX: i64 = 30 314const BI_MATH_MIN: i64 = 31 315const BI_MATH_ABS: i64 = 32 316const BI_MATH_FLOOR: i64 = 33 317const BI_MATH_CEIL: i64 = 34 318const BI_MATH_POW: i64 = 35 319const BI_MATH_RANDOM: i64 = 36 // xorshift64 PRNG -> VAL_FLOAT in [0,1) 320const BI_MATH_SQRT: i64 = 37 // Math.sqrt -> VAL_FLOAT (soft-float sqrt) 321// console.* (this ignored): 322const BI_CONSOLE_LOG: i64 = 40 323 324// DOM builtins (document.*) -- compose nx_dom_query over the page HTML seeded into the global env. 325const BI_DOC_GET_BY_ID: i64 = 60 326const BI_DOC_QUERY_SELECTOR: i64 = 61 327const BI_EL_GET_ATTR: i64 = 62 // element.getAttribute(name) (method on a snapshot element) 328const BI_DOC_QUERY_SELECTOR_ALL: i64 = 63 // document.querySelectorAll(sel) -> array of elements 329// DOM-WRITE mode: when genv[3]==DOM_TREE_SENTINEL, genv[2] is a *DomTree (mutable), not html bytes. 330const DOM_TREE_SENTINEL: i64 = 0 - 424242 331const BI_DOC_CREATE_ELEMENT: i64 = 64 // document.createElement(tag) (tree mode) 332const BI_EL_APPEND_CHILD: i64 = 65 // element.appendChild(child) 333const BI_EL_SET_ATTR: i64 = 66 // element.setAttribute(name, value) 334const BI_JSON_PARSE: i64 = 67 // JSON.parse(text) 335const BI_EL_ADD_LISTENER: i64 = 68 // element.addEventListener(type, fn) -- R-JS-EVENT (execution verify) 336const BI_EL_CLICK: i64 = 69 // element.click() -- fire the element's click listeners (test driver) 337const BI_CLS_TOGGLE: i64 = 70 // element.classList.toggle(name[, force]) -- R-JS-CLASSLIST 338const BI_CLS_ADD: i64 = 71 // element.classList.add(name) 339const BI_CLS_REMOVE: i64 = 72 // element.classList.remove(name) 340const BI_CLS_CONTAINS: i64 = 73 // element.classList.contains(name) 341 342// statement completion status 343const CS_NORMAL: i64 = 0 344const CS_ERROR: i64 = 1 345const CS_RETURN: i64 = 2 346const CS_BREAK: i64 = 3 // R-JS-CTRL: nearest enclosing loop stops 347const CS_CONTINUE: i64 = 4 // R-JS-CTRL: nearest enclosing loop skips to its update/next iter 348 349const EV_LOOP_CAP: i64 = 10000000 350 351// GLOBAL EXECUTION FUEL (2026-07-27, JS-lane T1 prerequisite): EV_LOOP_CAP is PER 352// LOOP -- two nested capped loops = 10^14 potential steps, and recursion has no cap 353// at all, so a hostile/heavy real-page script could still wall-hang the fetcher. 354// Fuel counts EVERY js_eval dispatch globally; exhaustion surfaces as an eval error 355// (rc=1) that unwinds like any ReferenceError -- fail-safe, page renders script-less. 356// 0 = disarmed (gates/tests keep exact old behavior); page renders ARM it. 357// Statics live HERE (top, before all readers) per the fwd-static-ref miscompile rule. 358// SIZED BY MEASUREMENT, not taste (rule 11). With the eval-cell arena live, footprint is 359// a MEASURED 12.5 bytes/step and ~32 ms per million steps (nx_jsfuel_probe at 200k/1M/5M 360// = 2048/12288/62464 KiB, linear). 10M steps => ~125 MiB arena, ~320 ms worst case: a 361// sane per-page ceiling for a browser tab. BEFORE the arena each step cost a RESIDENT 362// 4096-byte page (328x more) and a 20M-step burn OOM-killed the whole VM -- that is what 363// held this at 1M. Re-measure with nx_jsfuel_probe <budget> before changing it. 364const EV_FUEL_PAGE_DEFAULT: i64 = 10000000 365static ev_fuel_max: i64 = 0 366static ev_fuel_used: i64 = 0 367static ev_fuel_hit: i64 = 0 368 369func ev_fuel_arm(n: i64) -> i64 { ev_fuel_max = n; ev_fuel_used = 0; ev_fuel_hit = 0; return 0 } 370func ev_fuel_spent() -> i64 { return ev_fuel_used } 371func ev_fuel_exhausted() -> i64 { return ev_fuel_hit } 372// Per-run reset at every run entry (debt 1785032369): keep a caller's custom armed budget, else arm the 373// measured page default -- DISARMED-BY-DEFAULT left every consumer unbounded in practice, which is the 374// hostile-input hole the debt measured (the crawler at minutes-per-page). A run entry re-arms, so a spent 375// budget never poisons the next program. 376func ev_fuel_run_reset() -> i64 { 377 var b: i64 = ev_fuel_max 378 if b == 0 { b = EV_FUEL_PAGE_DEFAULT } 379 return ev_fuel_arm(b) 380} 381 382// read the FOR body node (stored in the tokidx slot[4] of an ND_FOR; a/b/c hold 383// init/cond/update). Kept as a named accessor so the loop reader stays readable. 384func ev_for_body(ctx: *i64, idx: i64) -> i64 { let nodes: *i64 = jp_nodes(ctx); return nodes[idx * NODE_SLOTS + 4] } 385// ND_TRY's finally BLOCK rides the same slot-4 (tokidx) position ev_for_body reads for ND_FOR. 386func ev_try_finally(ctx: *i64, idx: i64) -> i64 { let nodes: *i64 = jp_nodes(ctx); return nodes[idx * NODE_SLOTS + 4] } 387// pending-throw channel on the GLOBAL env (see ENV_HDR note): set by ND_THROW, taken by ND_TRY. 388func ev_throw_set(genv: *i64, t: i64, p: i64) -> i64 { genv[6] = 1; genv[7] = t; genv[8] = p; return 0 } 389func ev_throw_take(genv: *i64, out: *i64) -> i64 { 390 if genv[6] == 0 { return 0 } 391 genv[6] = 0 392 ev_set(out, genv[7], genv[8]) 393 return 1 394} 395// non-consuming: is a thrown VALUE pending? (diagnostics only -- a catching ND_TRY must use take.) 396func ev_throw_peek(genv: *i64) -> i64 { return genv[6] } 397 398func ev_set(out: *i64, t: i64, p: i64) -> i64 { out[0] = t; out[1] = p; return 0 } 399func ev_copy(out: *i64, src: *i64) -> i64 { out[0] = src[0]; out[1] = src[1]; return 0 } 400func ev_b2(cond: i64) -> i64 { if cond == 1 { return 1 } return 0 } 401func ev_isnull(e: *i64) -> i64 { if (e as i64) == 0 { return 1 } return 0 } 402 403func ev_atoi(src: *u8, start: i64, len: i64) -> i64 { 404 var n: i64 = 0 405 var i: i64 = 0 406 while i < len { let c: i64 = src[start + i] & 0xff; if c >= 48 { if c <= 57 { n = n * 10 + (c - 48) } } i = i + 1 } 407 return n 408} 409func ev_tok(ctx: *i64, idx: i64) -> i64 { let nodes: *i64 = jp_nodes(ctx); return nodes[idx * NODE_SLOTS + 4] } 410func ev_tok_kind(ctx: *i64, idx: i64) -> i64 { let toks: *i64 = jp_toks(ctx); return toks[ev_tok(ctx, idx) * 3 + 0] } 411func ev_tok_start(ctx: *i64, idx: i64) -> i64 { let toks: *i64 = jp_toks(ctx); return toks[ev_tok(ctx, idx) * 3 + 1] } 412func ev_tok_len(ctx: *i64, idx: i64) -> i64 { let toks: *i64 = jp_toks(ctx); return toks[ev_tok(ctx, idx) * 3 + 2] } 413func ev_num_of(ctx: *i64, idx: i64) -> i64 { return ev_atoi(jp_src(ctx), ev_tok_start(ctx, idx), ev_tok_len(ctx, idx)) } 414 415// ===================== string heap ===================== 416// A VAL_STR payload is a pointer (cast to i64) to a RECORD: [len: i64][bytes...]. 417// String literals copy their INNER bytes (quotes stripped) into a fresh record; '+' 418// concat allocates a new record. This replaces the old "payload = AST node index" 419// model so concatenation produces real, comparable byte strings. 420// BUMP POOL: objects/arrays/strings were each a separate sys_mmap syscall (churn-heavy code = one syscall 421// PER allocation -- 50k objects = 50k mmaps; the dominant object-churn cost vs V8's bump allocator). Pool 422// them: bump-allocate from 16MB chunks, one mmap per chunk. Callers already init every field they read 423// (obj_new sets the header, obj_get bounds-checks count, arr_set fills gaps, ev_str_new's caller writes all 424// len bytes) so the pool NOT re-zeroing per alloc is safe. Pool never frees (same as before; object GC = P7). 425static nx_pool: i64 426static nx_pool_hp: i64 427static nx_pool_cap: i64 428static nx_pool_total: i64 429static nx_pool_count: i64 430// MIGRATED to the shared emitter (debt 1785563586). The old body mmapped a scratch buffer 431// per call and never freed it. At PAGE granularity that is 4096B leaked PER CALL -- the 432// defect that took 28.5GB of a 36GB host in nx_ts_lumadiff (2MB input, ~3.66M calls). 433// nxi_* is MSB-first, allocates NOTHING, and emits identical bytes including the sign. 434func nx_dbg_num(v: i64) -> i64 { nxi_out(v); return 0 } 435// MIGRATED to the shared emitter (debt 1785563586). The old body mmapped a scratch buffer 436// per call and never freed it. At PAGE granularity that is 4096B leaked PER CALL -- the 437// defect that took 28.5GB of a 36GB host in nx_ts_lumadiff (2MB input, ~3.66M calls). 438// nxi_* is MSB-first, allocates NOTHING, and emits identical bytes including the sign. 439func nx_dbg_num1(v: i64) -> i64 { nxi_out(v); return 0 } 440// ---- GC HEAP: CONSERVATIVE mark-sweep over the object/array/string pool, made SAFE by a BLOCK-START BITMAP. 441// Each block = [ i64 meta ][ payload... ], meta = (payload_bytes << 3) | (free<<1) | mark; nx_pool_alloc returns 442// the PAYLOAD ptr. Per chunk a BITMAP marks every block-START word, so a candidate pointer is validated EXACTLY 443// (interior/false pointers are rejected -> no corruption; the ONLY hazard of conservative GC is eliminated). 444// The tracer scans every word of every reachable block + root region and follows only words the bitmap confirms 445// are real payloads -> never under-retains (safe); may over-retain a little (a leak, acceptable). Chunks are 446// RETAINED [base,used,bitmap] so the sweep walks all blocks linearly; freed blocks go to size-class free-lists. 447// gc_enabled gates collection; the VM triggers it at a SAFE instruction boundary (precise root regions). 448const GC_NCLASS: i64 = 8192 // free-list size classes by payload_words (nb/8); >= this = no reuse 449const GC_CST: i64 = 3 // chunk-table stride: [base, used_bytes, bitmap_ptr] 450static gc_freelist: i64 // *i64[GC_NCLASS] heads (payload ptr of first free block; 0=none) 451static gc_chunks: i64 // *i64 retired-chunk table (GC_CST per entry) 452static gc_chunk_n: i64 453static gc_chunk_cap: i64 454static nx_pool_bitmap: i64 // *u8 block-start bitmap for the CURRENT chunk (1 bit / 8 bytes) 455static gc_enabled: i64 // 0 = bump+headers only; 1 = collect 456static gc_since: i64 // bytes bump-allocated since the last collection 457static gc_collections: i64 458static gc_work: i64 // *i64 mark worklist (block HEADER addrs to trace) 459static gc_work_n: i64 460static gc_work_cap: i64 461static gc_threshold: i64 // collect once gc_since bytes bump-allocated since the last cycle (0=use default) 462static gc_nosweep: i64 // DIAGNOSTIC: 1 = mark/trace but never free (isolates scan bugs from sweep bugs) 463const GC_THRESHOLD_DEFAULT: i64 = 16777216 // 16MB = one chunk; small programs never cross it (0 collections) 464// GC is ON BY DEFAULT (a SOTA engine collects): the JIT stays enabled (roots on the vs-stack, synced at calls + 465// inline loop-back-edge safepoints = ~0 overhead), so default-on costs small programs nothing (they never cross 466// the threshold) and makes allocation-heavy programs sustainable. A gate that must measure UNCOLLECTED behavior 467// (e.g. nx_js_octane_memcensus) calls gc_enable(0). gc_configure(N) overrides the threshold. 468func gc_init() -> i64 { 469 if gc_freelist == 0 { 470 gc_freelist = sys_mmap(GC_NCLASS * 8) as i64 471 gc_chunk_cap = 8192 472 gc_chunks = sys_mmap(gc_chunk_cap * GC_CST * 8) as i64 473 gc_chunk_n = 0 474 gc_work_cap = 1048576 475 gc_work = sys_mmap(gc_work_cap * 8) as i64 476 gc_work_n = 0 477 gc_enabled = 1 // DEFAULT-ON 478 gc_threshold = GC_THRESHOLD_DEFAULT 479 } 480 return 0 481} 482func gc_setbit(bm: i64, wordidx: i64) -> i64 { let b: *u8 = bm as *u8; let byi: i64 = wordidx / 8; b[byi] = (b[byi] | (1 << (wordidx % 8))) as u8; return 0 } 483func gc_getbit(bm: i64, wordidx: i64) -> i64 { let b: *u8 = bm as *u8; return (b[wordidx / 8] >> (wordidx % 8)) & 1 } 484// record the CURRENT chunk (about to be replaced) so the sweep can still walk it. 485func gc_retire_chunk() -> i64 { 486 if nx_pool == 0 { return 0 } 487 gc_init() 488 if gc_chunk_n >= gc_chunk_cap { 489 let ncap: i64 = gc_chunk_cap * 2 490 let nt: *i64 = sys_mmap(ncap * GC_CST * 8) as *i64 491 let ot: *i64 = gc_chunks as *i64 492 var i: i64 = 0 493 while i < (gc_chunk_n * GC_CST) { nt[i] = ot[i]; i = i + 1 } 494 gc_chunks = nt as i64 495 gc_chunk_cap = ncap 496 } 497 let t: *i64 = gc_chunks as *i64 498 t[gc_chunk_n * GC_CST] = nx_pool 499 t[gc_chunk_n * GC_CST + 1] = nx_pool_hp 500 t[gc_chunk_n * GC_CST + 2] = nx_pool_bitmap 501 gc_chunk_n = gc_chunk_n + 1 502 return 0 503} 504func nx_pool_alloc(bytes: i64) -> i64 { 505 let nb: i64 = (bytes + 7) / 8 * 8 506 nx_pool_count = nx_pool_count + 1 507 // reuse a freed block of the SAME size class (nb/8) if one is on the free-list. 508 let cls: i64 = nb / 8 509 if cls < GC_NCLASS { 510 gc_init() 511 let fl: *i64 = gc_freelist as *i64 512 let head: i64 = fl[cls] 513 if head != 0 { 514 let hpp: *i64 = head as *i64 // payload; payload[0] links to the next free block 515 fl[cls] = hpp[0] 516 let meta: *i64 = (head - 8) as *i64 517 meta[0] = (nb << 3) // in-use: free=0 mark=0 518 var z: i64 = 0 519 while z < cls { hpp[z] = 0; z = z + 1 } // restore the mmap zero-fill invariant 520 return head 521 } 522 } 523 let need: i64 = nb + 8 524 nx_pool_total = nx_pool_total + need 525 gc_since = gc_since + need 526 if (nx_pool_hp + need) > nx_pool_cap { 527 gc_retire_chunk() 528 var cs: i64 = 16777216 // 16MB chunk 529 if need > cs { cs = need } 530 gc_init() 531 nx_pool = sys_mmap(cs) as i64 532 nx_pool_bitmap = sys_mmap(cs / 64 + 8) as i64 // 1 bit per 8-byte word = cs/8 bits = cs/64 bytes 533 nx_pool_hp = 0 534 nx_pool_cap = cs 535 } 536 let base: i64 = nx_pool + nx_pool_hp 537 gc_setbit(nx_pool_bitmap as i64, nx_pool_hp / 8) // mark this block START word for exact pointer validation 538 nx_pool_hp = nx_pool_hp + need 539 let m: *i64 = base as *i64 540 m[0] = (nb << 3) // size, free=0 mark=0 541 return base + 8 542} 543// validate a candidate payload pointer: return its HEADER addr if `w` is a real in-pool block start, else 0. 544func gc_is_start(w: i64) -> i64 { 545 let h: i64 = w - 8 546 if nx_pool != 0 { if h >= nx_pool { if h < (nx_pool + nx_pool_hp) { 547 if gc_getbit(nx_pool_bitmap as i64, (h - nx_pool) / 8) == 1 { return h } 548 return 0 549 } } } 550 let t: *i64 = gc_chunks as *i64 551 var c: i64 = 0 552 while c < gc_chunk_n { 553 let cb: i64 = t[c * GC_CST] 554 if h >= cb { if h < (cb + t[c * GC_CST + 1]) { 555 if gc_getbit(t[c * GC_CST + 2] as i64, (h - cb) / 8) == 1 { return h } 556 return 0 557 } } 558 c = c + 1 559 } 560 return 0 561} 562// mark the block at header `h` (if in-use + not already marked) and enqueue it for tracing. 563func gc_mark_header(h: i64) -> i64 { 564 let meta: *i64 = h as *i64 565 let m: i64 = meta[0] 566 if (m & 1) == 1 { return 0 } // already marked 567 if (m & 2) == 2 { return 0 } // free block -> never mark (a live ptr never points here) 568 meta[0] = m | 1 569 if gc_work_n >= gc_work_cap { 570 let ncap: i64 = gc_work_cap * 2 571 let nw: *i64 = sys_mmap(ncap * 8) as *i64 572 let ow: *i64 = gc_work as *i64 573 var i: i64 = 0 574 while i < gc_work_n { nw[i] = ow[i]; i = i + 1 } 575 gc_work = nw as i64 576 gc_work_cap = ncap 577 } 578 let wl: *i64 = gc_work as *i64 579 wl[gc_work_n] = h 580 gc_work_n = gc_work_n + 1 581 return 0 582} 583// scan `nwords` i64 words at `base_addr`, marking every one that is a valid pool payload pointer. 584func gc_scan_region(base_addr: i64, nwords: i64) -> i64 { 585 if base_addr == 0 { return 0 } 586 let p: *i64 = base_addr as *i64 587 var i: i64 = 0 588 while i < nwords { 589 let w: i64 = p[i] 590 if w != 0 { let h: i64 = gc_is_start(w as i64); if h != 0 { gc_mark_header(h as i64) } } 591 i = i + 1 592 } 593 return 0 594} 595// drain the worklist: trace each marked block by scanning its payload words (conservative: the bitmap keeps it safe). 596func gc_trace_all() -> i64 { 597 while gc_work_n > 0 { 598 gc_work_n = gc_work_n - 1 599 let wl: *i64 = gc_work as *i64 600 let h: i64 = wl[gc_work_n] 601 let hp: *i64 = h as *i64 // hoist: (cast)[i] >> n desyncs the parser; a let-bound ptr is safe 602 let size: i64 = hp[0] >> 3 603 gc_scan_region(h + 8, size / 8) 604 } 605 return 0 606} 607// gc_mark_globals is defined LATER (after fproto_*/js_gthis/js_objproto/js_nsstat_tab/sh_* are declared): nx_cc 608// miscompiles a FORWARD reference to a module static (reads garbage, not the static's address), so any function 609// touching those statics MUST be positioned after their declarations. 610// SWEEP: walk every retained + current chunk linearly; free unmarked in-use blocks to the free-lists, clear marks. 611func gc_sweep_range(base: i64, used: i64) -> i64 { 612 var off: i64 = 0 613 let fl: *i64 = gc_freelist as *i64 614 while off < used { 615 let hp2: i64 = base + off 616 let meta: *i64 = hp2 as *i64 617 let m: i64 = meta[0] 618 let size: i64 = m >> 3 619 let total: i64 = size + 8 620 if (m & 2) == 0 { // not already free 621 if (m & 1) == 1 { meta[0] = (size << 3) } // live: clear mark 622 else { if gc_nosweep == 1 { meta[0] = (size << 3) } // DIAGNOSTIC: leave dead blocks in-use (no free) 623 else { // dead: reclaim 624 let cls: i64 = size / 8 625 if cls < GC_NCLASS { if cls > 0 { 626 let payload: i64 = hp2 + 8 627 let pl: *i64 = payload as *i64 // hoist cast: nx_cc rejects an inline (cast)[i] LVALUE 628 pl[0] = fl[cls] // free-list link stored in the dead block's first word 629 fl[cls] = payload 630 } } 631 meta[0] = (size << 3) | 2 // free=1 632 } } 633 } 634 if total <= 0 { off = used } else { off = off + total } // guard against a corrupt zero-size header 635 } 636 return 0 637} 638func gc_sweep_all() -> i64 { 639 let t: *i64 = gc_chunks as *i64 640 var c: i64 = 0 641 while c < gc_chunk_n { gc_sweep_range(t[c * GC_CST] as i64, t[c * GC_CST + 1] as i64); c = c + 1 } 642 if nx_pool != 0 { gc_sweep_range(nx_pool as i64, nx_pool_hp as i64) } 643 gc_collections = gc_collections + 1 644 gc_since = 0 645 return 0 646} 647func gc_enable(v: i64) -> i64 { gc_init(); gc_enabled = v; if gc_threshold == 0 { gc_threshold = GC_THRESHOLD_DEFAULT }; return 0 } 648func gc_configure(threshold: i64) -> i64 { gc_init(); gc_threshold = threshold; return 0 } 649func gc_set_nosweep(v: i64) -> i64 { gc_nosweep = v; return 0 } // DIAGNOSTIC toggle 650func gc_is_enabled() -> i64 { return gc_enabled } 651func gc_since_bytes() -> i64 { return gc_since } 652func gc_collection_count() -> i64 { return gc_collections } 653// loop-top poll: 1 = a collection is due (GC on + threshold crossed). Cheap: two loads + a compare. 654func gc_poll() -> i64 { if gc_enabled == 0 { return 0 } if gc_since >= gc_threshold { return 1 } return 0 } 655// absolute addresses of the two poll statics, so the JIT can bake an INLINE `gc_since >= gc_threshold` compare 656// at loop back-edges (no per-iteration call in the common no-collect case). Stable: statics never relocate. 657func gc_since_addr() -> i64 { return (&gc_since) as i64 } 658func gc_threshold_addr() -> i64 { return (&gc_threshold) as i64 } 659func nx_pool_total_mb() -> i64 { return nx_pool_total / 1048576 } 660func nx_pool_total_kb() -> i64 { return nx_pool_total / 1024 } 661func nx_pool_alloc_count() -> i64 { return nx_pool_count } 662func nx_pool_hp_bytes() -> i64 { return nx_pool_hp } // current-chunk bump offset = RESIDENT proxy (plateaus under GC) 663func nx_pool_hp_kb() -> i64 { return nx_pool_hp / 1024 } 664func gc_chunk_count() -> i64 { return gc_chunk_n } // # retired 16MB chunks (stays low if GC reuses freed blocks) 665func ev_str_new(len: i64) -> *i64 { let rec: *i64 = (nx_pool_alloc(16 + len)) as *i64; rec[0] = len; return rec } 666// ROPE / CONS-STRING (V8-style): `a + b` builds an O(1) CONS node instead of copying (str-build was O(n^2) 667// for the s=s+x loop = the 2311x-vs-V8 gap). A cons rec = [CONS_MARK, left, right, total_len, flat_cache, 668// leaf_count], told apart from a flat rec ([len>=0, bytes...]) by the NEGATIVE marker in slot 0. ev_str_bytes 669// FLATTENS on demand (iterative in-order, O(total_len) ONCE, cached in slot 4) so all ~70 call sites 670// transparently get flat bytes; ev_str_len returns the cached total (no flatten). [[project-nishi-bytecode-vm-2026-07-06]] 671const CONS_MARK: i64 = 0 - 700701 672func str_leaves(rec: *i64) -> i64 { if rec[0] == CONS_MARK { return rec[5] } return 1 } 673func str_flatten(rec: *i64) -> *i64 { 674 if rec[4] != 0 { return (rec[4]) as *i64 } 675 let flat: *i64 = ev_str_new(rec[3]) 676 let dst: *u8 = (flat as i64 + 8) as *u8 677 let stack: *i64 = (nx_pool_alloc(8 * (rec[5] + 4))) as *i64 // stack depth <= leaf count (left-leaning worst case) 678 var sp: i64 = 0 679 stack[0] = rec as i64 680 sp = 1 681 var off: i64 = 0 682 while sp > 0 { 683 sp = sp - 1 684 let r: *i64 = (stack[sp]) as *i64 685 if r[0] == CONS_MARK { 686 stack[sp] = r[2]; sp = sp + 1 // push right, then left -> left processed first (in order) 687 stack[sp] = r[1]; sp = sp + 1 688 } else { 689 let n: i64 = r[0] 690 let src: *u8 = (r as i64 + 8) as *u8 691 var i: i64 = 0 692 while i < n { dst[off + i] = src[i]; i = i + 1 } 693 off = off + n 694 } 695 } 696 rec[4] = flat as i64 697 return flat 698} 699func ev_str_len(rec: *i64) -> i64 { if rec[0] == CONS_MARK { return rec[3] } return rec[0] } 700func ev_str_bytes(rec: *i64) -> *u8 { if rec[0] == CONS_MARK { let f: *i64 = str_flatten(rec); return (f as i64 + 8) as *u8 } return (rec as i64 + 8) as *u8 } 701 702// build a string VALUE from a STRING literal node (lexer keeps the quotes -> strip). 703// hex digit -> value (0-15), or -1. 704func ev_hexval(c: i64) -> i64 { 705 let x: i64 = c & 0xff 706 if x >= 48 { if x <= 57 { return x - 48 } } 707 if x >= 97 { if x <= 102 { return x - 87 } } 708 if x >= 65 { if x <= 70 { return x - 55 } } 709 return 0 - 1 710} 711// write code point cp (<= 0xFFFF) as UTF-8 at dst[at]; returns byte count (1-3). NAMED DIVERGENCE: 712// our strings are BYTE strings, so "ẜ".length == 3 (UTF-8 bytes) where V8 counts 1 UTF-16 unit; 713// equality/ordering/concat stay self-consistent, which is what algorithms depend on. 714func ev_utf8_put(dst: *u8, at: i64, cp: i64) -> i64 { 715 if cp < 128 { dst[at] = cp as u8; return 1 } 716 if cp < 2048 { 717 dst[at] = (192 + (cp / 64)) as u8 718 dst[at + 1] = (128 + (cp % 64)) as u8 719 return 2 720 } 721 dst[at] = (224 + (cp / 4096)) as u8 722 dst[at + 1] = (128 + ((cp / 64) % 64)) as u8 723 dst[at + 2] = (128 + (cp % 64)) as u8 724 return 3 725} 726// string literal -> value record with JS ESCAPE DECODING (was a raw byte copy -- "A" compared 727// UNEQUAL to "A" and "\n" was 2 chars; scheme2js/Octane bundles use \uXXXX symbol prefixes heavily). 728// Handles \n \t \r \b \f \v \0, \xHH, \uXXXX (UTF-8 encoded), line continuation \<LF>, and the spec 729// identity fallback (\" \' \\ \/ + any unknown escape -> the char itself). Decoded length is always 730// <= raw length, so the raw-sized allocation is safe; the record length is set to the decoded size. 731func ev_str_from_lit(ctx: *i64, idx: i64, out: *i64) -> i64 { 732 let src: *u8 = jp_src(ctx) 733 let s: i64 = ev_tok_start(ctx, idx) 734 let l: i64 = ev_tok_len(ctx, idx) 735 var inner: i64 = l - 2 736 if inner < 0 { inner = 0 } 737 let rec: *i64 = ev_str_new(inner) 738 let dst: *u8 = ev_str_bytes(rec) 739 var di: i64 = 0 740 var i: i64 = 0 741 while i < inner { 742 let ch: i64 = (src[s + 1 + i]) & 0xff 743 if ch != 92 { 744 dst[di] = ch as u8 745 di = di + 1 746 i = i + 1 747 } else { 748 if i + 1 >= inner { // trailing lone backslash: keep it 749 dst[di] = 92 as u8 750 di = di + 1 751 i = i + 1 752 } else { 753 let e: i64 = (src[s + 1 + i + 1]) & 0xff 754 var adv: i64 = 2 755 var outc: i64 = 0 - 1 // -1 = identity(e); -2 = emit nothing; -3 = already emitted 756 if e == 110 { outc = 10 } // \n 757 if e == 116 { outc = 9 } // \t 758 if e == 114 { outc = 13 } // \r 759 if e == 98 { outc = 8 } // \b 760 if e == 102 { outc = 12 } // \f 761 if e == 118 { outc = 11 } // \v 762 if e == 48 { outc = 0 } // \0 763 if e == 10 { outc = 0 - 2 } // line continuation: swallow 764 if e == 13 { outc = 0 - 2; if i + 2 < inner { if ((src[s + 1 + i + 2]) & 0xff) == 10 { adv = 3 } } } // \<CR>[LF] 765 if e == 120 { // \xHH -> a SINGLE byte 0x00-0xFF (V8 length==1; keeps 766 if i + 3 < inner { // string `\xNN` byte-consistent with regex `\xNN`). 767 let h1: i64 = ev_hexval(src[s + 1 + i + 2]) 768 let h2: i64 = ev_hexval(src[s + 1 + i + 3]) 769 if h1 >= 0 { if h2 >= 0 { 770 dst[di] = (h1 * 16 + h2) as u8 771 di = di + 1 772 adv = 4 773 outc = 0 - 3 774 } } 775 } 776 } 777 if e == 117 { // \uHHHH -> a SINGLE byte (cp & 0xff), consistent with \xNN 778 if i + 5 < inner { // + String.fromCharCode: every char = 1 byte, so `.length` 779 let u1: i64 = ev_hexval(src[s + 1 + i + 2]) // counts 1 per \u = V8's UTF-16 code-unit 780 let u2: i64 = ev_hexval(src[s + 1 + i + 3]) // count for the BMP (EXACT incl charCodeAt 781 let u3: i64 = ev_hexval(src[s + 1 + i + 4]) // for cp<=0xFF; low-byte for cp>=0x100 which 782 let u4: i64 = ev_hexval(src[s + 1 + i + 5]) // a byte engine cannot hold as one char). 783 if u1 >= 0 { if u2 >= 0 { if u3 >= 0 { if u4 >= 0 { 784 let ucp: i64 = ((u1 * 16 + u2) * 16 + u3) * 16 + u4 785 dst[di] = (ucp & 0xff) as u8 786 di = di + 1 787 adv = 6 788 outc = 0 - 3 789 } } } } 790 } 791 } 792 if outc >= 0 { dst[di] = outc as u8; di = di + 1 } 793 if outc == (0 - 1) { dst[di] = e as u8; di = di + 1 } 794 i = i + adv 795 } 796 } 797 } 798 rec[0] = di 799 ev_set(out, VAL_STR, rec as i64) 800 return 0 801} 802func ev_str_eq(a: *i64, b: *i64) -> i64 { 803 let la: i64 = ev_str_len(a) 804 if la != ev_str_len(b) { return 0 } 805 let sa: *u8 = ev_str_bytes(a) 806 let sb: *u8 = ev_str_bytes(b) 807 var i: i64 = 0 808 while i < la { if (sa[i] & 0xff) != (sb[i] & 0xff) { return 0 } i = i + 1 } 809 return 1 810} 811// byte-lexicographic compare of two string records: -1 if a<b, 0 if a==b, 1 if a>b. 812// Matches JS relational string ordering (which compares UTF-16 code units; for the 813// ASCII/byte corpus this rung handles, byte order == code-unit order). Shorter string 814// is "less" when it is a prefix of the longer (real JS: "ab" < "abc"). 815func ev_str_cmp(a: *i64, b: *i64) -> i64 { 816 let la: i64 = ev_str_len(a) 817 let lb: i64 = ev_str_len(b) 818 let sa: *u8 = ev_str_bytes(a) 819 let sb: *u8 = ev_str_bytes(b) 820 var n: i64 = la 821 if lb < n { n = lb } 822 var i: i64 = 0 823 while i < n { 824 let ca: i64 = sa[i] & 0xff 825 let cb: i64 = sb[i] & 0xff 826 if ca < cb { return 0 - 1 } 827 if ca > cb { return 1 } 828 i = i + 1 829 } 830 if la < lb { return 0 - 1 } 831 if la > lb { return 1 } 832 return 0 833} 834// strict NUMERIC-STRING parse for MIXED relational coercion (rung-2b ladder, integer 835// subset): a string record that is a pure (optionally one leading '-') run of ASCII 836// digits parses to its integer value -- written to *vout -- and returns 1. ANY other 837// shape (empty, non-digit byte, embedded sign, float/exponent) returns 0 = NOT a clean 838// integer string. We deliberately do NOT coerce a non-numeric string to 0 (that was the 839// silent-wrong bug KAT12 eliminated); the caller errors instead. Real-JS leading/trailing 840// whitespace + float/NaN coercion needs the full float ladder and stays HONEST OPEN. 841func ev_str_numval(rec: *i64, vout: *i64) -> i64 { 842 let l: i64 = ev_str_len(rec) 843 if l == 0 { return 0 } 844 let by: *u8 = ev_str_bytes(rec) 845 var i: i64 = 0 846 var neg: i64 = 0 847 if (by[0] & 0xff) == 45 { neg = 1; i = 1 } // a single leading '-' 848 if i >= l { return 0 } // "-" alone is not a number 849 var n: i64 = 0 850 while i < l { 851 let c: i64 = by[i] & 0xff 852 if c < 48 { return 0 } 853 if c > 57 { return 0 } 854 n = n * 10 + (c - 48) 855 i = i + 1 856 } 857 if neg == 1 { n = 0 - n } 858 vout[0] = n 859 return 1 860} 861// true iff `op` is a relational operator (< <= > >=) -- used to scope the MIXED 862// string<->number coercion fix to ordering comparisons only (NOT arithmetic). 863func ev_is_relop(op: i64) -> i64 { 864 if op == OP_LT { return 1 } 865 if op == OP_LE { return 1 } 866 if op == OP_GT { return 1 } 867 if op == OP_GE { return 1 } 868 return 0 869} 870// a value is "cleanly numeric" for relational coercion iff it is a number/bool/null 871// (each has an unambiguous integer ToNumber this rung). Strings/objects/arrays/undefined 872// are NOT (string needs ev_str_numval; the rest are NaN/open). 873func ev_is_clean_num(v: *i64) -> i64 { 874 if v[0] == VAL_NUM { return 1 } 875 if v[0] == VAL_BOOL { return 1 } 876 if v[0] == VAL_NULL { return 1 } 877 return 0 878} 879// true iff EXACTLY ONE of (lb,rb) is a string and the OTHER is a clean numeric value -- 880// the mixed string<->number relational shape we coerce (e.g. '10' < 5, 6 > '5'). 881func ev_one_str_one_num(lb: *i64, rb: *i64) -> i64 { 882 if lb[0] == VAL_STR { if ev_is_clean_num(rb) == 1 { return 1 } } 883 if rb[0] == VAL_STR { if ev_is_clean_num(lb) == 1 { return 1 } } 884 return 0 885} 886// coerce one operand of a mixed relational to its integer value into *vout. A clean 887// numeric uses ev_tonum; a STRING must parse as a clean integer (ev_str_numval) else we 888// return 0 = cannot coerce (caller errors -- never silent 0). Returns 1 on success. 889func ev_mixed_relnum(v: *i64, vout: *i64) -> i64 { 890 if v[0] == VAL_STR { return ev_str_numval((v[1]) as *i64, vout) } 891 if ev_is_clean_num(v) == 1 { vout[0] = ev_tonum(v); return 1 } 892 return 0 893} 894func ev_str_concat(a: *i64, b: *i64, out: *i64) -> i64 { 895 // ROPE: O(1) cons node instead of copying both operands (was O(n^2) for s=s+x loops). Flattened lazily 896 // by ev_str_bytes when the bytes are actually needed. 897 let c: *i64 = (nx_pool_alloc(8 * 6)) as *i64 898 c[0] = CONS_MARK 899 c[1] = a as i64 900 c[2] = b as i64 901 c[3] = ev_str_len(a) + ev_str_len(b) 902 c[4] = 0 903 c[5] = str_leaves(a) + str_leaves(b) 904 ev_set(out, VAL_STR, c as i64) 905 return 0 906} 907// string record from a NUL-terminated C-string (for typeof + coercions). 908func ev_cstr(s: *u8) -> *i64 { 909 var n: i64 = 0 910 while s[n] != (0 as u8) { n = n + 1 } 911 let rec: *i64 = ev_str_new(n) 912 let dst: *u8 = ev_str_bytes(rec) 913 var i: i64 = 0 914 while i < n { dst[i] = s[i]; i = i + 1 } 915 return rec 916} 917// number -> decimal-digit string record (for '+' concat coercion). The digit scratch is a REUSED static 918// (built once) -- it was sys_mmap(32) [a SYSCALL] per call, making `''+n` coercion ~115x slower than V8 919// (num->str is everywhere in real JS). Non-reentrant (ev_str_new/ev_str_bytes never recurse into here). 920static ev_n2s_tmp: i64 921func ev_num_to_str(n: i64) -> *i64 { 922 var m: i64 = n 923 var neg: i64 = 0 924 if m < 0 { neg = 1; m = 0 - m } 925 if ev_n2s_tmp == 0 { ev_n2s_tmp = (sys_mmap(32)) as i64 } 926 let tmp: *u8 = ev_n2s_tmp as *u8 927 var k: i64 = 0 928 if m == 0 { tmp[0] = 48 as u8; k = 1 } 929 while m > 0 { tmp[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 } 930 var total: i64 = k 931 if neg == 1 { total = total + 1 } 932 let rec: *i64 = ev_str_new(total) 933 let dst: *u8 = ev_str_bytes(rec) 934 var off: i64 = 0 935 if neg == 1 { dst[0] = 45 as u8; off = 1 } 936 var i: i64 = 0 937 while i < k { dst[off + i] = tmp[k - 1 - i]; i = i + 1 } 938 return rec 939} 940// coerce any value to a string record (for '+' with a string operand). 941func ev_coerce_str(v: *i64) -> *i64 { 942 let t: i64 = v[0] 943 if t == VAL_STR { return (v[1]) as *i64 } 944 if t == VAL_NUM { return ev_num_to_str(v[1]) } 945 if t == VAL_FLOAT { return ev_f64_str(v[1]) } 946 if t == VAL_BOOL { if v[1] == 1 { return ev_cstr("true\x00" as *u8) } return ev_cstr("false\x00" as *u8) } 947 if t == VAL_NULL { return ev_cstr("null\x00" as *u8) } 948 if t == VAL_FUNC { return ev_cstr("function\x00" as *u8) } 949 if t == VAL_OBJECT { return ev_cstr("[object Object]\x00" as *u8) } // real JS Object.prototype.toString 950 if t == VAL_ARRAY { return ev_cstr("[object Array]\x00" as *u8) } // (Array join is R-JS-RUNTIME; placeholder, never "undefined") 951 if t == VAL_NATIVE { return ev_cstr("function\x00" as *u8) } // a native builtin stringifies as a function 952 if t == VAL_GLOBALNS { return ev_cstr("[object Object]\x00" as *u8) } // Math/Object/console are objects 953 return ev_cstr("undefined\x00" as *u8) 954} 955// build a STRING RECORD from an IDENT/PROP-KEY token's bytes (a property name like `x`). 956// Used for MEMBER access keys: o.x -> key "x". The token is NOT quoted (it is an ident). 957func ev_key_from_ident(ctx: *i64, idx: i64) -> *i64 { 958 let src: *u8 = jp_src(ctx) 959 let s: i64 = ev_tok_start(ctx, idx) 960 let l: i64 = ev_tok_len(ctx, idx) 961 let rec: *i64 = ev_str_new(l) 962 let dst: *u8 = ev_str_bytes(rec) 963 var i: i64 = 0 964 while i < l { dst[i] = src[s + i]; i = i + 1 } 965 return rec 966} 967// build a STRING RECORD key from a STRING-literal token (quotes stripped), via the IDENT 968// raw form when the prop-key token is a STRING (object literal {'a':1}). Reuses the 969// quote-stripping of ev_str_from_lit by going through a temp value. 970func ev_key_from_strtok(ctx: *i64, idx: i64) -> *i64 { 971 let tmp: *i64 = ev_cell() 972 ev_str_from_lit(ctx, idx, tmp) 973 return (tmp[1]) as *i64 974} 975// PROP-key string record for a node whose tokidx names the key: STRING token -> strip 976// quotes, IDENT token -> raw bytes. Used by ND_PROP (object literal) and ND_MEMBER (o.x, 977// where the property child is an IDENT node). 978func ev_prop_key(ctx: *i64, idx: i64) -> *i64 { 979 if ev_tok_kind(ctx, idx) == JS_TOK_STRING { return ev_key_from_strtok(ctx, idx) } 980 return ev_key_from_ident(ctx, idx) 981} 982// typeof tag -> a string record value (real-JS strings; typeof null === "object"). 983func ev_typeof_str(tag: i64) -> *i64 { 984 if tag == VAL_NUM { return ev_cstr("number\x00" as *u8) } 985 if tag == VAL_FLOAT { return ev_cstr("number\x00" as *u8) } // typeof 0.5 === "number" 986 if tag == VAL_BOOL { return ev_cstr("boolean\x00" as *u8) } 987 if tag == VAL_STR { return ev_cstr("string\x00" as *u8) } 988 if tag == VAL_FUNC { return ev_cstr("function\x00" as *u8) } 989 if tag == VAL_NULL { return ev_cstr("object\x00" as *u8) } 990 if tag == VAL_OBJECT { return ev_cstr("object\x00" as *u8) } // typeof {} === "object" 991 if tag == VAL_ARRAY { return ev_cstr("object\x00" as *u8) } // typeof [] === "object" (JS) 992 if tag == VAL_NATIVE { return ev_cstr("function\x00" as *u8) } // typeof Math.max === "function" 993 if tag == VAL_GLOBALNS { return ev_cstr("object\x00" as *u8) } // typeof Math === "object" 994 return ev_cstr("undefined\x00" as *u8) 995} 996 997func ev_truthy(v: *i64) -> i64 { 998 let t: i64 = v[0] 999 if t == VAL_UNDEF { return 0 } 1000 if t == VAL_NULL { return 0 } 1001 if t == VAL_BOOL { return v[1] } 1002 if t == VAL_NUM { if v[1] == 0 { return 0 } return 1 } 1003 if t == VAL_FLOAT { if nx_f64_is_nan(v[1]) == 1 { return 0 } if nx_f64_is_zero(v[1]) == 1 { return 0 } return 1 } 1004 if t == VAL_STR { let rec: *i64 = (v[1]) as *i64; if ev_str_len(rec) == 0 { return 0 } return 1 } 1005 return 1 1006} 1007func ev_tonum(v: *i64) -> i64 { 1008 let t: i64 = v[0] 1009 if t == VAL_NUM { return v[1] } 1010 if t == VAL_FLOAT { return jf2i(v[1]) } // truncate toward zero (ToInt32/array-index coercion) 1011 if t == VAL_BOOL { return v[1] } 1012 if t == VAL_NULL { return 0 } 1013 return 0 1014} 1015func ev_is_numlike(v: *i64) -> i64 { if v[0] == VAL_NUM { return 1 } if v[0] == VAL_FLOAT { return 1 } return 0 } 1016func ev_strict_eq(l: *i64, r: *i64) -> i64 { 1017 // both numeric (int or float) -> NUMERIC equality (1.0 === 1 is true; JS has one number type). 1018 if ev_is_numlike(l) == 1 { if ev_is_numlike(r) == 1 { 1019 if l[0] == VAL_FLOAT { return nx_f64_eq(ev_tof64(l), ev_tof64(r)) } 1020 if r[0] == VAL_FLOAT { return nx_f64_eq(ev_tof64(l), ev_tof64(r)) } 1021 if l[1] == r[1] { return 1 } // both integers -> exact (no f64 precision loss) 1022 return 0 1023 } } 1024 if l[0] != r[0] { return 0 } 1025 if l[0] == VAL_NULL { return 1 } 1026 if l[0] == VAL_UNDEF { return 1 } 1027 if l[0] == VAL_STR { return ev_str_eq((l[1]) as *i64, (r[1]) as *i64) } 1028 if l[1] == r[1] { return 1 } 1029 return 0 1030} 1031func ev_loose_eq(l: *i64, r: *i64) -> i64 { 1032 // ECMAScript Abstract Equality (council fix): null/undefined loosely-equal ONLY 1033 // each other -- NEVER a number/bool/string. If EITHER side is null/undefined the 1034 // result is true IFF BOTH are; NO numeric coercion (was the null==0 -> true bug). 1035 var lnu: i64 = 0 1036 if l[0] == VAL_NULL { lnu = 1 } 1037 if l[0] == VAL_UNDEF { lnu = 1 } 1038 var rnu: i64 = 0 1039 if r[0] == VAL_NULL { rnu = 1 } 1040 if r[0] == VAL_UNDEF { rnu = 1 } 1041 if lnu == 1 { if rnu == 1 { return 1 } return 0 } 1042 if rnu == 1 { return 0 } 1043 if l[0] == VAL_STR { if r[0] == VAL_STR { return ev_str_eq((l[1]) as *i64, (r[1]) as *i64) } } 1044 if l[0] == VAL_STR { return 0 } // str<->number coercion = rung-2b open 1045 if r[0] == VAL_STR { return 0 } 1046 // REFERENCE types (object/array/function/regex/...) compare by IDENTITY (payload ptr). Was falling 1047 // through to ev_tonum (0 for every object) -> ANY two objects compared == (bit DeltaBlue's solver, 1048 // whose logic lives on identity: out.determinedBy == c). Ref-vs-number ToPrimitive = named open (0). 1049 var lrf: i64 = 0 1050 if l[0] == VAL_OBJECT { lrf = 1 } 1051 if l[0] == VAL_ARRAY { lrf = 1 } 1052 if l[0] == VAL_FUNC { lrf = 1 } 1053 if l[0] == VAL_NATIVE { lrf = 1 } 1054 if l[0] == VAL_GLOBALNS { lrf = 1 } 1055 if l[0] == VAL_REGEX { lrf = 1 } 1056 if l[0] == VAL_PROMISE { lrf = 1 } 1057 if l[0] == VAL_RESPONSE { lrf = 1 } 1058 var rrf: i64 = 0 1059 if r[0] == VAL_OBJECT { rrf = 1 } 1060 if r[0] == VAL_ARRAY { rrf = 1 } 1061 if r[0] == VAL_FUNC { rrf = 1 } 1062 if r[0] == VAL_NATIVE { rrf = 1 } 1063 if r[0] == VAL_GLOBALNS { rrf = 1 } 1064 if r[0] == VAL_REGEX { rrf = 1 } 1065 if r[0] == VAL_PROMISE { rrf = 1 } 1066 if r[0] == VAL_RESPONSE { rrf = 1 } 1067 if lrf == 1 { if rrf == 1 { if l[0] == r[0] { if l[1] == r[1] { return 1 } } return 0 } return 0 } 1068 if rrf == 1 { return 0 } 1069 if l[0] == VAL_FLOAT { return nx_f64_eq(ev_tof64(l), ev_tof64(r)) } // float == : numeric (not int-trunc) 1070 if r[0] == VAL_FLOAT { return nx_f64_eq(ev_tof64(l), ev_tof64(r)) } 1071 if ev_tonum(l) == ev_tonum(r) { return 1 } 1072 return 0 1073} 1074 1075// apply an ARITHMETIC binary op (OP_ADD/SUB/MUL/DIV/MOD) to two already-evaluated 1076// VALUES, writing the result to out. Returns 0 ok, 1 ERROR (div/mod by zero -- no float 1077// Infinity this rung). '+' does STRING CONCAT when EITHER operand is a string (coercing 1078// the other), exactly mirroring the ND_BINARY '+' path -- so `s += 5` and `s = s + 5` 1079// agree. Used by COMPOUND ASSIGNMENT (a += b folds a and b through this). 1080// ---- f64 JS numbers (R-JS-F64-2) ---- 1081// a NUMBER literal node -> VAL_FLOAT iff its token contains a '.', else VAL_NUM (integer). Keeps the 1082// integer fast-path EXACTLY as before (2+3 stays integer 5); 0.5 becomes a float. 1083func ev_hexdigit(c: i64) -> i64 { 1084 if c >= 48 { if c <= 57 { return c - 48 } } // 0-9 1085 if c >= 97 { if c <= 102 { return c - 87 } } // a-f 1086 if c >= 65 { if c <= 70 { return c - 55 } } // A-F 1087 return 0 - 1 1088} 1089func ev_parse_radix(src: *u8, s: i64, l: i64, base: i64) -> i64 { 1090 var v: i64 = 0 1091 var i: i64 = 0 1092 var go: i64 = 1 1093 while go == 1 { 1094 if i >= l { go = 0 } else { 1095 let d: i64 = ev_hexdigit(src[s + i] & 0xff) 1096 if d < 0 { go = 0 } else { if d >= base { go = 0 } else { v = v * base + d; i = i + 1 } } 1097 } 1098 } 1099 return v 1100} 1101func ev_num_node(ctx: *i64, idx: i64, out: *i64) -> i64 { 1102 let src: *u8 = jp_src(ctx) 1103 let s: i64 = ev_tok_start(ctx, idx) 1104 let l: i64 = ev_tok_len(ctx, idx) 1105 // RADIX prefixes 0x/0o/0b (real JS) -- must precede the decimal/'.' path. 1106 if l >= 2 { if (src[s] & 0xff) == 48 { 1107 let c1: i64 = src[s + 1] & 0xff 1108 if c1 == 120 { ev_set(out, VAL_NUM, ev_parse_radix(src, s + 2, l - 2, 16)); return 0 } 1109 if c1 == 88 { ev_set(out, VAL_NUM, ev_parse_radix(src, s + 2, l - 2, 16)); return 0 } 1110 if c1 == 111 { ev_set(out, VAL_NUM, ev_parse_radix(src, s + 2, l - 2, 8)); return 0 } 1111 if c1 == 79 { ev_set(out, VAL_NUM, ev_parse_radix(src, s + 2, l - 2, 8)); return 0 } 1112 if c1 == 98 { ev_set(out, VAL_NUM, ev_parse_radix(src, s + 2, l - 2, 2)); return 0 } 1113 if c1 == 66 { ev_set(out, VAL_NUM, ev_parse_radix(src, s + 2, l - 2, 2)); return 0 } 1114 } } 1115 var isf: i64 = 0 1116 var i: i64 = 0 1117 while i < l { if (src[s + i] & 0xff) == 46 { isf = 1 } i = i + 1 } // '.' -> float 1118 if isf == 1 { ev_set(out, VAL_FLOAT, jparse_f64(((src as i64) + s) as *u8, l)); return 0 } 1119 ev_set(out, VAL_NUM, ev_atoi(src, s, l)); return 0 1120} 1121// coerce a value to f64 bits (VAL_FLOAT -> raw; VAL_NUM/other -> integer-as-f64). 1122func ev_tof64(v: *i64) -> i64 { 1123 if v[0] == VAL_FLOAT { return v[1] } 1124 return ji2f(ev_tonum(v)) 1125} 1126// f64 bits -> a JS string record (composes jf64_to_str). 1127static ev_f2s_tmp: i64 // reused float->string scratch (was sys_mmap(40) per call -- same syscall bug) 1128func ev_f64_str(bits: i64) -> *i64 { 1129 if ev_f2s_tmp == 0 { ev_f2s_tmp = (sys_mmap(40)) as i64 } 1130 let buf: *u8 = ev_f2s_tmp as *u8 1131 let n: i64 = jf64_to_str(bits, buf) 1132 let rec: *i64 = ev_str_new(n) 1133 let d: *u8 = ev_str_bytes(rec) 1134 var i: i64 = 0 1135 while i < n { d[i] = buf[i]; i = i + 1 } 1136 return rec 1137} 1138// f64 arithmetic for the float-promotion path. JS x/0 = Infinity (nx_f64_div handles); % = trunc-fmod. 1139func ev_binop_f64(op: i64, fa: i64, fb: i64, out: *i64) -> i64 { 1140 if op == OP_ADD { ev_set(out, VAL_FLOAT, nx_f64_add(fa, fb)); return 0 } 1141 if op == OP_SUB { ev_set(out, VAL_FLOAT, nx_f64_add(fa, nx_f64_neg(fb))); return 0 } 1142 if op == OP_MUL { ev_set(out, VAL_FLOAT, nx_f64_mul(fa, fb)); return 0 } 1143 if op == OP_DIV { ev_set(out, VAL_FLOAT, nx_f64_div(fa, fb)); return 0 } 1144 if op == OP_MOD { 1145 let q: i64 = ji2f(jf2i(nx_f64_div(fa, fb))) // trunc(a/b) 1146 ev_set(out, VAL_FLOAT, nx_f64_add(fa, nx_f64_neg(nx_f64_mul(q, fb)))); return 0 1147 } 1148 // relational (R-JS-F64-3): float ordering via the gated nx_f64 comparisons -> VAL_BOOL. 1149 if op == OP_LT { ev_set(out, VAL_BOOL, nx_f64_lt(fa, fb)); return 0 } 1150 if op == OP_GT { ev_set(out, VAL_BOOL, nx_f64_gt(fa, fb)); return 0 } 1151 if op == OP_LE { if nx_f64_lt(fa, fb) == 1 { ev_set(out, VAL_BOOL, 1); return 0 } ev_set(out, VAL_BOOL, nx_f64_eq(fa, fb)); return 0 } 1152 if op == OP_GE { if nx_f64_gt(fa, fb) == 1 { ev_set(out, VAL_BOOL, 1); return 0 } ev_set(out, VAL_BOOL, nx_f64_eq(fa, fb)); return 0 } 1153 ev_set(out, VAL_UNDEF, 0); return 1 1154} 1155// P8 (real-JS IEEE-double number model): integer DIV/MOD no longer int-truncates or errors. An EXACT 1156// quotient stays a fast VAL_NUM (6/2 -> 3, JS value-equal); a NON-EXACT or /0 result promotes to a 1157// VAL_FLOAT via the soft-IEEE divider (7/2 -> 3.5, 1/0 -> Infinity, -1/0 -> -Infinity, 0/0 -> NaN, 1158// a%0 -> NaN). This is V8's Smi-with-double-fallback: ints for speed, doubles when the result needs one. 1159func ev_num_div(a: i64, b: i64, out: *i64) -> i64 { 1160 if b == 0 { return ev_binop_f64(OP_DIV, ji2f(a), ji2f(b), out) } 1161 let q: i64 = a / b 1162 if q * b == a { ev_set(out, VAL_NUM, q); return 0 } 1163 return ev_binop_f64(OP_DIV, ji2f(a), ji2f(b), out) 1164} 1165func ev_num_mod(a: i64, b: i64, out: *i64) -> i64 { 1166 if b == 0 { ev_set(out, VAL_FLOAT, nx_f64_div(0, 0)); return 0 } // a % 0 = NaN (0.0/0.0 bits) 1167 ev_set(out, VAL_NUM, a - (a / b) * b) 1168 return 0 1169} 1170 1171// JS ToInt32: take the low 32 bits and interpret as a signed 32-bit integer (bitwise ops operate on these). 1172func js_toint32(v: i64) -> i64 { 1173 let m: i64 = v & 0xffffffff 1174 if m >= 2147483648 { return m - 4294967296 } 1175 return m 1176} 1177// `a instanceof B` -> true iff B.prototype appears on a's [[Prototype]] chain. B must be callable 1178// (VAL_FUNC/VAL_NATIVE); a non-object left operand is simply false (real JS). Shared by both engines. 1179func ev_instanceof(lb: *i64, rb: *i64, out: *i64) -> i64 { 1180 if rb[0] != VAL_FUNC { if rb[0] != VAL_NATIVE { ev_set(out, VAL_UNDEF, 0); return 1 } } // RHS not callable -> TypeError 1181 if lb[0] != VAL_OBJECT { ev_set(out, VAL_BOOL, 0); return 0 } // primitive LHS -> false 1182 let target: i64 = func_prototype(rb[1]) 1183 var o: *i64 = (lb[1]) as *i64 1184 var guard: i64 = 0 1185 while guard < 200 { 1186 let p: i64 = obj_proto(o) 1187 if p == 0 { ev_set(out, VAL_BOOL, 0); return 0 } 1188 if p == target { ev_set(out, VAL_BOOL, 1); return 0 } 1189 o = p as *i64 1190 guard = guard + 1 1191 } 1192 ev_set(out, VAL_BOOL, 0); return 0 1193} 1194// `k in obj` -> true if obj has property named ToString(k), OWN or INHERITED. Objects: obj_get + proto chain. 1195// Arrays: a numeric index within bounds (methods/length = follow-on; scheme2js uses the object form). key from 1196// ev_key_of_val (defined later; forward function call is fine). 1197func ev_in(lb: *i64, rb: *i64, out: *i64) -> i64 { 1198 if rb[0] == VAL_OBJECT { 1199 let key: *i64 = ev_key_of_val(lb) 1200 let tmp: *i64 = ev_cell() 1201 let o: *i64 = (rb[1]) as *i64 1202 if obj_get(o, key, tmp) == 1 { ev_set(out, VAL_BOOL, 1); return 0 } 1203 if obj_proto_lookup(o, key, tmp) == 1 { ev_set(out, VAL_BOOL, 1); return 0 } 1204 ev_set(out, VAL_BOOL, 0); return 0 1205 } 1206 if rb[0] == VAL_ARRAY { 1207 let a: *i64 = (rb[1]) as *i64 1208 // `"length" in arr` === true (arrays own a length property -- jQuery's isArrayLike hinges on this; 1209 // without it every array misclassifies as a plain object -> ce.each takes the for-in branch and the 1210 // class2type population throws). A numeric key in bounds is also present. Anything else -> false. 1211 let key: *i64 = ev_key_of_val(lb) 1212 if ev_is_length_key(key) == 1 { ev_set(out, VAL_BOOL, 1); return 0 } 1213 let ki: i64 = ev_str_to_index(key) 1214 if ki >= 0 { if ki < arr_len(a) { ev_set(out, VAL_BOOL, 1); return 0 } } 1215 ev_set(out, VAL_BOOL, 0); return 0 1216 } 1217 ev_set(out, VAL_BOOL, 0); return 0 // primitive RHS: real JS TypeErrors; lenient false (scheme2js never hits) 1218} 1219func ev_ws_byte(c: i64) -> i64 { if c == 32 { return 1 } if c == 9 { return 1 } if c == 10 { return 1 } if c == 13 { return 1 } return 0 } 1220// JS ToNumber(v) -> a numeric value cell (VAL_NUM int / VAL_FLOAT incl NaN). Strings parse 1221// [ws][sign]digits[.digits][ws] (exponent/hex/Infinity = follow-on): non-numeric -> NaN, ""/all-ws -> 0. 1222// bool->0/1, null->0, undefined/object -> NaN. Fixes `100/"5"`=20 (was "5"->0 -> 100/0 -> Infinity). 1223func ev_coerce_num(v: *i64, out: *i64) -> i64 { 1224 let t: i64 = v[0] 1225 if t == VAL_NUM { out[0] = VAL_NUM; out[1] = v[1]; return 0 } 1226 if t == VAL_FLOAT { out[0] = VAL_FLOAT; out[1] = v[1]; return 0 } 1227 if t == VAL_BOOL { out[0] = VAL_NUM; out[1] = v[1]; return 0 } 1228 if t == VAL_NULL { out[0] = VAL_NUM; out[1] = 0; return 0 } 1229 if t != VAL_STR { out[0] = VAL_FLOAT; out[1] = NX_F64_NAN_RAW; return 0 } 1230 let rec: *i64 = (v[1]) as *i64 1231 let l: i64 = ev_str_len(rec) 1232 let by: *u8 = ev_str_bytes(rec) 1233 var a: i64 = 0 1234 var go: i64 = 1 1235 while go == 1 { if a < l { if ev_ws_byte(by[a] & 0xff) == 1 { a = a + 1 } else { go = 0 } } else { go = 0 } } 1236 var b: i64 = l 1237 go = 1 1238 while go == 1 { if b > a { if ev_ws_byte(by[b - 1] & 0xff) == 1 { b = b - 1 } else { go = 0 } } else { go = 0 } } 1239 if b <= a { out[0] = VAL_NUM; out[1] = 0; return 0 } 1240 var j: i64 = a 1241 let c0: i64 = by[a] & 0xff 1242 if c0 == 43 { j = j + 1 } 1243 if c0 == 45 { j = j + 1 } 1244 var dots: i64 = 0 1245 var digits: i64 = 0 1246 var valid: i64 = 1 1247 while j < b { 1248 let c: i64 = by[j] & 0xff 1249 if c == 46 { dots = dots + 1 } else { if c >= 48 { if c <= 57 { digits = digits + 1 } else { valid = 0 } } else { valid = 0 } } 1250 j = j + 1 1251 } 1252 if valid == 0 { out[0] = VAL_FLOAT; out[1] = NX_F64_NAN_RAW; return 0 } 1253 if digits == 0 { out[0] = VAL_FLOAT; out[1] = NX_F64_NAN_RAW; return 0 } 1254 if dots > 1 { out[0] = VAL_FLOAT; out[1] = NX_F64_NAN_RAW; return 0 } 1255 if dots == 0 { 1256 var neg: i64 = 0 1257 var k: i64 = a 1258 if c0 == 45 { neg = 1; k = a + 1 } 1259 if c0 == 43 { k = a + 1 } 1260 var n: i64 = 0 1261 while k < b { n = n * 10 + ((by[k] & 0xff) - 48); k = k + 1 } 1262 if neg == 1 { n = 0 - n } 1263 out[0] = VAL_NUM; out[1] = n 1264 return 0 1265 } 1266 out[0] = VAL_FLOAT 1267 out[1] = jparse_f64(((by as i64) + a) as *u8, b - a) 1268 return 0 1269} 1270func ev_apply_binop(op: i64, lb: *i64, rb: *i64, out: *i64) -> i64 { 1271 if op == OP_INSTANCEOF { return ev_instanceof(lb, rb, out) } 1272 if op == OP_IN { return ev_in(lb, rb, out) } 1273 if op == OP_ADD { 1274 if lb[0] == VAL_STR { return ev_str_concat(ev_coerce_str(lb), ev_coerce_str(rb), out) } 1275 if rb[0] == VAL_STR { return ev_str_concat(ev_coerce_str(lb), ev_coerce_str(rb), out) } 1276 } 1277 // ToNumber coercion for the non-`+` numeric/bitwise ops: a string/bool/null operand becomes a number 1278 // (JS ToNumber). Without this `100 / "5"` did "5"->0 -> 100/0 -> Infinity, spinning RayTrace's pixel 1279 // loop into 4M+ allocations. `+` with a string already concatenated above, so it never reaches here. 1280 var lc: *i64 = lb 1281 var rc: *i64 = rb 1282 if lb[0] == VAL_STR { let lt: *i64 = ev_cell(); ev_coerce_num(lb, lt); lc = lt } 1283 if rb[0] == VAL_STR { let rt: *i64 = ev_cell(); ev_coerce_num(rb, rt); rc = rt } 1284 // BITWISE (&|^ << >> >>>) -- real JS ToInt32 both sides (never float-promote); result is a 32-bit int. 1285 if op == OP_BAND { ev_set(out, VAL_NUM, js_toint32(ev_tonum(lc)) & js_toint32(ev_tonum(rc))); return 0 } 1286 if op == OP_BOR { ev_set(out, VAL_NUM, js_toint32(ev_tonum(lc)) | js_toint32(ev_tonum(rc))); return 0 } 1287 if op == OP_BXOR { ev_set(out, VAL_NUM, js_toint32(ev_tonum(lc)) ^ js_toint32(ev_tonum(rc))); return 0 } 1288 if op == OP_SHL { let sa: i64 = js_toint32(ev_tonum(lc)); let sb: i64 = ev_tonum(rc) & 31; ev_set(out, VAL_NUM, js_toint32(sa << sb)); return 0 } 1289 if op == OP_SHR { let sa: i64 = js_toint32(ev_tonum(lc)); let sb: i64 = ev_tonum(rc) & 31; ev_set(out, VAL_NUM, sa >> sb); return 0 } 1290 if op == OP_USHR { let ua: i64 = ev_tonum(lc) & 0xffffffff; let sb: i64 = ev_tonum(rc) & 31; ev_set(out, VAL_NUM, ua >> sb); return 0 } 1291 // FLOAT promotion: if either operand is a float, do f64 arithmetic (result VAL_FLOAT). 1292 if lc[0] == VAL_FLOAT { return ev_binop_f64(op, ev_tof64(lc), ev_tof64(rc), out) } 1293 if rc[0] == VAL_FLOAT { return ev_binop_f64(op, ev_tof64(lc), ev_tof64(rc), out) } 1294 let a: i64 = ev_tonum(lc) 1295 let b: i64 = ev_tonum(rc) 1296 if op == OP_ADD { ev_set(out, VAL_NUM, a + b); return 0 } 1297 if op == OP_SUB { ev_set(out, VAL_NUM, a - b); return 0 } 1298 if op == OP_MUL { ev_set(out, VAL_NUM, a * b); return 0 } 1299 if op == OP_DIV { return ev_num_div(a, b, out) } 1300 if op == OP_MOD { return ev_num_mod(a, b, out) } 1301 ev_set(out, VAL_UNDEF, 0); return 1 1302} 1303 1304// ===================== heap object model (R-JS-OBJ, rung 3) ===================== 1305// OBJECT record (VAL_OBJECT payload = ptr as i64): 1306// [0]=count, then count props at OBJ_HDR + i*OBJ_ENT = [key-strrec(*i64 as i64), vt, vp]. 1307// Keys are STRING RECORDS (ev_str_*), so any property name -- ident OR computed string 1308// key -- is a real comparable byte string. Linear scan (objects are small in practice; 1309// a hash table is a later perf rung, NOT a correctness gap). 1310// ARRAY record (VAL_ARRAY payload = ptr as i64): 1311// [0]=length, then ARR_HDR + i*ARR_ENT = [vt, vp] per element cell up to ARR_CAP. 1312// GROWABLE OBJECT: STABLE 5-word header [0]=count [1]=SHAPE id [2]=[[Prototype]] [3]=capacity [4]=backing-ptr. 1313// The header ptr is the VAL_OBJECT payload and never moves; props live in the backing block at 1314// backing[i*OBJ_ENT + 0/1/2] = key,vt,vp, which DOUBLES on demand. Was a fixed OBJ_CAP=256 inline block = 1315// ~6KB PER OBJECT (used ~48B) -> obj_new page-faults dominated churn (measured 2/3 of cost). Now ~small. 1316const OBJ_PROTO: i64 = 2 // [[Prototype]] link (set by `new`; walked on property miss) 1317const OBJ_CAPF: i64 = 3 // backing capacity (elements) 1318const OBJ_BACK: i64 = 4 // backing-block ptr (i64) 1319const OBJ_HDRW: i64 = 5 // header word count 1320const OBJ_ENT: i64 = 3 1321const OBJ_INITCAP: i64 = 4 // most objects have <=4 props (Vector{x,y,z}, Node{key,value,left,right}); 8 was 1322 // 2-4x oversized -> ~40% less object memory (Splay 249MB->~150MB). Grows if >4. 1323const OBJ_MAXCAP: i64 = 1048576 // 1M props -- runaway guard, not a real-workload limit 1324const ARR_HDR: i64 = 1 1325const ARR_ENT: i64 = 2 1326const ARR_CAP: i64 = 4096 1327 1328// ===================== SHAPES / hidden classes (P3, SOTA ladder) ===================== 1329// A SHAPE is the interned identity of an object's property LAYOUT (V8 hidden class / map). Because our 1330// objects are ADDITIVE-ONLY and slots are insertion-ordered, an object's layout is fully determined by 1331// the ORDERED sequence of keys it has added -- so a shape = a node in a TRANSITION TREE: shape 0 = 1332// EMPTY; adding key K to shape S transitions to a child shape (created once, then SHARED by every object 1333// that adds K after the same prefix). Objects built the same way share a shape id, so an inline cache 1334// keyed on (shape,offset) hits across ALL of them -- the SOTA property model, vs a per-object cache that 1335// misses on every distinct object. Object stores its shape id at o[1]; the key->offset map is just 1336// insertion order, so obj_find on an IC miss yields the offset and the shape makes it reusable. Pure 1337// fast-path: any miss falls back to the linear obj_find, so shapes NEVER change correctness. 1338const SH_ENT: i64 = 5 // [parent, added_key(strrec as i64), nprops, first_child, next_sibling] (0 = none) 1339const SH_MAX: i64 = 262144 1340static sh_arena: i64 1341static sh_count: i64 1342// shape 0 is RESERVED-INVALID so a cold inline-cache slot (mmap-zero) can NEVER false-hit a real 1343// shape; the EMPTY object shape (the root of the transition tree) is id SH_EMPTY = 1. 1344const SH_EMPTY: i64 = 1 1345func sh_init() -> i64 { 1346 if sh_count == 0 { 1347 let a: *i64 = sys_mmap(SH_MAX * SH_ENT * 8) as *i64 1348 sh_arena = a as i64 1349 a[0] = 0; a[1] = 0; a[2] = 0; a[3] = 0; a[4] = 0 // shape 0 = reserved / invalid (cold-IC sentinel) 1350 a[5] = 0; a[6] = 0; a[7] = 0; a[8] = 0; a[9] = 0 // shape 1 = EMPTY (every object starts here) 1351 sh_count = 2 1352 } 1353 return 0 1354} 1355func sh_rec(id: i64) -> *i64 { return ((sh_arena) + id * SH_ENT * 8) as *i64 } 1356// shape reached by adding `key` to shape `from`; interned (shared child) so same construction path -> 1357// same shape id. Returns `from` unchanged if the registry is full (fast-path just degrades). 1358func sh_transition(from: i64, key: *i64) -> i64 { 1359 sh_init() 1360 let fr: *i64 = sh_rec(from as i64) 1361 var c: i64 = fr[3] 1362 while c != 0 { 1363 let cr: *i64 = sh_rec(c as i64) 1364 if ev_str_eq((cr[1]) as *i64, key) == 1 { return c } 1365 c = cr[4] 1366 } 1367 if sh_count >= SH_MAX { return from } 1368 let nid: i64 = sh_count 1369 sh_count = nid + 1 1370 let nr: *i64 = sh_rec(nid as i64) 1371 let oldhead: i64 = fr[3] 1372 nr[0] = from 1373 nr[1] = key as i64 1374 nr[2] = fr[2] + 1 1375 nr[3] = 0 1376 nr[4] = oldhead 1377 fr[3] = nid 1378 return nid 1379} 1380func obj_shape(o: *i64) -> i64 { return o[1] } 1381// GC root: shape records intern property-NAME key strings BY POINTER (word 1 of each SH_ENT record). An 1382// interned shape can outlive every object that carried that property, becoming the SOLE holder of a runtime 1383// key string -> retain them, or a later sh_transition ev_str_eq would read freed memory. Other record words 1384// are small ids (gc_is_start rejects them). Defined here so SH_ENT/sh_arena/sh_count are in lexical scope. 1385func gc_scan_shapes() -> i64 { if sh_arena != 0 { gc_scan_region(sh_arena as i64, sh_count * SH_ENT) } return 0 } 1386 1387func obj_new() -> *i64 { 1388 let o: *i64 = (nx_pool_alloc(8 * OBJ_HDRW)) as *i64 1389 o[0] = 0; o[1] = SH_EMPTY; o[OBJ_PROTO] = 0; o[OBJ_CAPF] = OBJ_INITCAP 1390 o[OBJ_BACK] = nx_pool_alloc(8 * OBJ_INITCAP * OBJ_ENT) 1391 return o 1392} 1393func obj_proto(o: *i64) -> i64 { return o[OBJ_PROTO] } 1394func obj_set_proto(o: *i64, p: i64) -> i64 { o[OBJ_PROTO] = p; return 0 } 1395// walk the [[Prototype]] chain from `o` (NOT checking o's own props -- caller already did) for `key`. 1396// 1 = found (into out), 0 = absent. Depth-bounded so a proto cycle can't hang the engine. 1397func obj_proto_lookup(o: *i64, key: *i64, out: *i64) -> i64 { 1398 var p: i64 = obj_proto(o) 1399 var depth: i64 = 0 1400 while depth < 200 { 1401 if p == 0 { return 0 } 1402 let po: *i64 = p as *i64 1403 if obj_get(po, key, out) == 1 { return 1 } 1404 p = obj_proto(po) 1405 depth = depth + 1 1406 } 1407 return 0 1408} 1409// ===== function .prototype objects: `Foo.prototype` is a STABLE object created lazily; methods added to it 1410// are inherited by every `new Foo()` instance (via the instance's OBJ_PROTO link). Uniform across tree & 1411// VM closures because it is keyed by the function VALUE-PAYLOAD (closure ptr). Growable side table. ===== 1412static fproto_keys: i64 1413static fproto_vals: i64 1414static fproto_n: i64 1415static fproto_cap: i64 1416func fproto_init() -> i64 { 1417 if fproto_cap == 0 { fproto_cap = 64; fproto_keys = sys_mmap(8 * fproto_cap) as i64; fproto_vals = sys_mmap(8 * fproto_cap) as i64; fproto_n = 0 } 1418 return 0 1419} 1420func fproto_find(fnpay: i64) -> i64 { 1421 fproto_init() 1422 let k: *i64 = fproto_keys as *i64 1423 var i: i64 = 0 1424 while i < fproto_n { if k[i] == fnpay { return i } i = i + 1 } 1425 return 0 - 1 1426} 1427func fproto_put(fnpay: i64, protopay: i64) -> i64 { 1428 fproto_init() 1429 let fi: i64 = fproto_find(fnpay) 1430 if fi >= 0 { let v: *i64 = fproto_vals as *i64; v[fi] = protopay; return 0 } 1431 if fproto_n >= fproto_cap { 1432 let ncap: i64 = fproto_cap * 2 1433 let nk: *i64 = sys_mmap(8 * ncap) as *i64 1434 let nv: *i64 = sys_mmap(8 * ncap) as *i64 1435 let ok: *i64 = fproto_keys as *i64 1436 let ov: *i64 = fproto_vals as *i64 1437 var j: i64 = 0 1438 while j < fproto_n { nk[j] = ok[j]; nv[j] = ov[j]; j = j + 1 } 1439 fproto_keys = nk as i64; fproto_vals = nv as i64; fproto_cap = ncap 1440 } 1441 let k: *i64 = fproto_keys as *i64 1442 let v: *i64 = fproto_vals as *i64 1443 k[fproto_n] = fnpay 1444 v[fproto_n] = protopay 1445 fproto_n = fproto_n + 1 1446 return 0 1447} 1448// globalThis: the object bound to `this` in a plain (non-method, non-new) call -- NON-STRICT semantics 1449// (Octane's mode; e.g. NavierStokes checkResult uses `this.result` as scratch). Lazily created + PERSISTENT 1450// so a top-level `this.x` accumulates across calls. Strict-mode undefined-this is a follow-on rung. 1451static js_gthis: i64 1452func js_globalthis() -> i64 { 1453 if js_gthis == 0 { js_gthis = (obj_new()) as i64 } 1454 return js_gthis 1455} 1456// Object.prototype: the shared root object every VAL_OBJECT/VAL_FUNC inherits from on a property miss 1457// (real JS's implicit top of the [[Prototype]] chain). Methods installed here via Object.defineProperty 1458// (jsbn/DeltaBlue `inheritsFrom`) become visible on all objects + constructor functions. Persistent. 1459static js_objproto: i64 1460func js_object_prototype() -> i64 { 1461 if js_objproto == 0 { js_objproto = (obj_new()) as i64 } 1462 return js_objproto 1463} 1464// String.prototype / Array.prototype: persistent WRITABLE objects so user code can extend the built-ins 1465// (`String.prototype.m = fn`; common in scheme2js/prototype.js). A method call on a string/array primitive 1466// that ISN'T a hardcoded builtin falls back to these (js_eval_call). GC roots (gc_mark_globals scans them). 1467static js_strproto: i64 1468func js_string_prototype() -> i64 { 1469 if js_strproto == 0 { js_strproto = (obj_new()) as i64 } 1470 return js_strproto 1471} 1472static js_arrproto: i64 1473func js_array_prototype() -> i64 { 1474 if js_arrproto == 0 { js_arrproto = (obj_new()) as i64 } 1475 return js_arrproto 1476} 1477// FAST-TIER bridge for user-defined String/Array.prototype methods. The VM/JIT (nx_js_vm.nx) can't read 1478// these file-scoped statics directly, so it resolves a proto method through this accessor -- mirroring the 1479// tree-tier js_eval_call fallback so all three tiers behave identically. Reads the RAW static (no lazy 1480// obj_new) so a dispatch miss never allocates mid-call; returns the method's VAL_FUNC payload (the closure 1481// record ptr, as i64) when the key resolves to a function, else 0. `out` is caller scratch [tag,pay]. 1482func js_userproto_method(tag: i64, key: *i64, out: *i64) -> i64 { 1483 var proto: i64 = 0 1484 if tag == VAL_STR { proto = js_strproto } 1485 if tag == VAL_ARRAY { proto = js_arrproto } 1486 if proto == 0 { return 0 } 1487 if obj_get((proto) as *i64, key, out) == 1 { 1488 if out[0] == VAL_FUNC { return out[1] } 1489 } 1490 return 0 1491} 1492// USER STATICS on a global NAMESPACE (`Object.extend = fn` -- prototype.js/RayTrace idiom): a per-ns 1493// property store, read before the fixed natives so user overrides win. Lazily created, persistent. Backed 1494// by a single mmap'd 16-slot table (a static array-of-i64 type miscompiles; a lone i64 ptr is safe). 1495static js_nsstat_tab: i64 1496func js_ns_statics(ns: i64) -> *i64 { 1497 if js_nsstat_tab == 0 { js_nsstat_tab = sys_mmap(16 * 8) as i64 } 1498 let tab: *i64 = js_nsstat_tab as *i64 1499 var k: i64 = ns 1500 if k < 0 { k = 0 } 1501 if k > 15 { k = 15 } 1502 if tab[k] == 0 { tab[k] = (obj_new()) as i64 } 1503 return (tab[k]) as *i64 1504} 1505// GC: mark the PERSISTENT global roots that hold pool objects (prototypes, ns-statics, globalThis, shape keys). 1506// MUST be defined HERE, after all the statics it reads -- nx_cc miscompiles forward static references. 1507func gc_mark_globals() -> i64 { 1508 if js_gthis != 0 { let h: i64 = gc_is_start(js_gthis as i64); if h != 0 { gc_mark_header(h as i64) } } 1509 if js_objproto != 0 { let h2: i64 = gc_is_start(js_objproto as i64); if h2 != 0 { gc_mark_header(h2 as i64) } } 1510 if js_strproto != 0 { let h3: i64 = gc_is_start(js_strproto as i64); if h3 != 0 { gc_mark_header(h3 as i64) } } 1511 if js_arrproto != 0 { let h4: i64 = gc_is_start(js_arrproto as i64); if h4 != 0 { gc_mark_header(h4 as i64) } } 1512 if js_nsstat_tab != 0 { gc_scan_region(js_nsstat_tab as i64, 16) } 1513 if fproto_vals != 0 { gc_scan_region(fproto_vals as i64, fproto_n as i64) } // function own-property objects (protos+methods) 1514 gc_scan_shapes() // shape records intern property-name key strings 1515 return 0 1516} 1517// A function is also an OBJECT: func_own(fnpay) is its own-property store (holds static props like 1518// `Foo.Node`, `Foo.count`, AND `prototype`), created empty on first access. Keyed by closure ptr in the 1519// fproto side-table (which now maps fnpay -> this own-object, not the prototype directly). 1520func func_own(fnpay: i64) -> *i64 { 1521 let fi: i64 = fproto_find(fnpay) 1522 if fi >= 0 { let v: *i64 = fproto_vals as *i64; return (v[fi]) as *i64 } 1523 let o: *i64 = obj_new() 1524 fproto_put(fnpay, o as i64) 1525 return o 1526} 1527// Foo.prototype (GET): the [[Prototype]] object shared by `new Foo()` instances -- stored as the "prototype" 1528// own-property, created empty on first access (so methods added to Foo.prototype are inherited). 1529// PERF: this runs on EVERY `new Foo()`. The "prototype" key string + the out-scratch are cached in statics 1530// (built ONCE) -- previously it did sys_mmap(16) [a SYSCALL] + ev_cstr("prototype") [a string alloc] PER call, 1531// which was the dominant ~1.7us/object cost of object churn (K4). Non-reentrant: obj_get/obj_new never recurse 1532// into func_prototype, so the single shared scratch is safe. 1533static fproto_key: i64 1534static fproto_scratch: i64 1535func func_prototype(fnpay: i64) -> i64 { 1536 let own: *i64 = func_own(fnpay) 1537 if fproto_key == 0 { fproto_key = (ev_cstr("prototype\x00" as *u8)) as i64 } 1538 if fproto_scratch == 0 { fproto_scratch = sys_mmap(16) as i64 } 1539 let pk: *i64 = fproto_key as *i64 1540 let pb: *i64 = fproto_scratch as *i64 1541 if obj_get(own, pk, pb) == 1 { if pb[0] == VAL_OBJECT { return pb[1] } } 1542 let p: *i64 = obj_new() 1543 obj_set(own, pk, VAL_OBJECT, p as i64) 1544 return p as i64 1545} 1546func obj_count(o: *i64) -> i64 { return o[0] } 1547func obj_key(o: *i64, i: i64) -> *i64 { let d: *i64 = (o[OBJ_BACK]) as *i64; return (d[i * OBJ_ENT]) as *i64 } 1548// find property index by string-record key, or -1 if absent. Skips TOMBSTONED slots (key==0, from delete). 1549func obj_find(o: *i64, key: *i64) -> i64 { 1550 let c: i64 = o[0] 1551 var i: i64 = 0 1552 while i < c { let kp: *i64 = obj_key(o, i); if (kp as i64) != 0 { if ev_str_eq(kp, key) == 1 { return i } } i = i + 1 } 1553 return 0 - 1 1554} 1555// DELETE a property: TOMBSTONE the slot (key=0, value=undefined) rather than remove+shift. This keeps the 1556// object's SHAPE + slot layout STABLE, so the inline cache stays valid -- a later IC read of a deleted prop's 1557// offset returns the undefined we stored, which IS the correct JS semantics. obj_find/for-in skip key==0. 1558// Returns 1 always (JS `delete` on a configurable/absent prop is true). Re-adding the key appends a fresh slot. 1559func obj_delete(o: *i64, key: *i64) -> i64 { 1560 let fi: i64 = obj_find(o, key) 1561 if fi < 0 { return 1 } 1562 let d: *i64 = (o[OBJ_BACK]) as *i64 1563 let b: i64 = fi * OBJ_ENT 1564 d[b + 0] = 0 // tombstone key 1565 d[b + 1] = VAL_UNDEF // value -> undefined 1566 d[b + 2] = 0 1567 return 1 1568} 1569// grow the backing store (doubling) + copy the live props. 1 = cap-exceeded, 0 ok. 1570func obj_grow(o: *i64) -> i64 { 1571 let nc: i64 = o[OBJ_CAPF] * 2 1572 if nc > OBJ_MAXCAP { return 1 } 1573 let nd: *i64 = (nx_pool_alloc(8 * nc * OBJ_ENT)) as *i64 1574 let od: *i64 = (o[OBJ_BACK]) as *i64 1575 let n: i64 = o[0] * OBJ_ENT 1576 var k: i64 = 0 1577 while k < n { nd[k] = od[k]; k = k + 1 } 1578 o[OBJ_BACK] = nd as i64 1579 o[OBJ_CAPF] = nc 1580 return 0 1581} 1582// SET property (create or update) by string-record key. Idempotent on the key. 1583func obj_set(o: *i64, key: *i64, vt: i64, vp: i64) -> i64 { 1584 let fi: i64 = obj_find(o, key) 1585 if fi >= 0 { let d0: *i64 = (o[OBJ_BACK]) as *i64; let b: i64 = fi * OBJ_ENT; d0[b + 1] = vt; d0[b + 2] = vp; return 0 } 1586 let c: i64 = o[0] 1587 if c >= o[OBJ_CAPF] { if obj_grow(o) == 1 { return 1 } } 1588 let d: *i64 = (o[OBJ_BACK]) as *i64 1589 let b: i64 = c * OBJ_ENT 1590 d[b + 0] = key as i64; d[b + 1] = vt; d[b + 2] = vp 1591 o[0] = c + 1 1592 let ns: i64 = sh_transition(o[1], key) // P3: advance the object's shape (call hoisted -- array-store gotcha) 1593 o[1] = ns 1594 return 0 1595} 1596// GET property into out; returns 1 if FOUND, 0 if absent (caller sets undefined on 0). 1597func obj_get(o: *i64, key: *i64, out: *i64) -> i64 { 1598 let fi: i64 = obj_find(o, key) 1599 if fi < 0 { return 0 } 1600 let d: *i64 = (o[OBJ_BACK]) as *i64 1601 let b: i64 = fi * OBJ_ENT 1602 ev_set(out, d[b + 1], d[b + 2]) 1603 return 1 1604} 1605 1606// GROWABLE array: STABLE 3-word header [0]=length [1]=capacity [2]=backing-data-ptr. The header pointer 1607// is the VAL_ARRAY payload and never moves; only the backing store is reallocated (doubling) as the array 1608// grows -- so all references stay valid while a[5000], a[1e6] etc. now work (was a hard 4096 cap). Cells 1609// live in the backing block at data[i*ARR_ENT + 0/1]. All array access goes through arr_get/arr_set/arr_len. 1610const ARR_INITCAP: i64 = 8 1611const ARR_MAXCAP: i64 = 67108864 // 64M elements -- runaway guard, NOT a real-workload limit 1612func arr_new() -> *i64 { 1613 let a: *i64 = (nx_pool_alloc(8 * 4)) as *i64 // [0]len [1]cap [2]data [3]expando-obj (lazy, named props) 1614 a[0] = 0 1615 a[1] = ARR_INITCAP 1616 a[2] = nx_pool_alloc(8 * ARR_INITCAP * ARR_ENT) 1617 a[3] = 0 1618 return a 1619} 1620// arrays ARE objects in JS: they carry arbitrary NAMED (non-index) properties (jQuery event storage sets 1621// `handlers.delegateCount` on a handlers array). a[3] lazily holds an expando object for those props. 1622func arr_expando(a: *i64) -> *i64 { 1623 if a[3] == 0 { a[3] = (obj_new()) as i64 } 1624 return (a[3]) as *i64 1625} 1626func arr_len(a: *i64) -> i64 { return a[0] } 1627// GET element i into out; returns 1 if in-range, 0 if out-of-range (undefined). 1628func arr_get(a: *i64, i: i64, out: *i64) -> i64 { 1629 if i < 0 { return 0 } 1630 if i >= a[0] { return 0 } 1631 let d: *i64 = (a[2]) as *i64 1632 let b: i64 = i * ARR_ENT 1633 ev_set(out, d[b + 0], d[b + 1]) 1634 return 1 1635} 1636// grow the backing store (doubling) so index `need` fits + copy the live prefix. 1 = cap-exceeded, 0 ok. 1637func arr_grow(a: *i64, need: i64) -> i64 { 1638 if need >= ARR_MAXCAP { return 1 } 1639 var nc: i64 = a[1] 1640 while nc <= need { nc = nc * 2 } 1641 if nc > ARR_MAXCAP { nc = ARR_MAXCAP } 1642 let nd: *i64 = (nx_pool_alloc(8 * nc * ARR_ENT)) as *i64 1643 let od: *i64 = (a[2]) as *i64 1644 let n: i64 = a[0] * ARR_ENT 1645 var k: i64 = 0 1646 while k < n { nd[k] = od[k]; k = k + 1 } 1647 a[2] = nd as i64 1648 a[1] = nc 1649 return 0 1650} 1651// SET element i; write past current length EXTENDS length (real JS), filling the gap with undefined. 1652// Grows the backing store on demand. Returns 1 only on the 64M runaway guard. 1653func arr_set(a: *i64, i: i64, vt: i64, vp: i64) -> i64 { 1654 if i < 0 { return 1 } 1655 if i >= a[1] { if arr_grow(a, i) == 1 { return 1 } } 1656 let d: *i64 = (a[2]) as *i64 1657 if i >= a[0] { 1658 var j: i64 = a[0] 1659 while j < i { let bb: i64 = j * ARR_ENT; d[bb + 0] = VAL_UNDEF; d[bb + 1] = 0; j = j + 1 } 1660 a[0] = i + 1 1661 } 1662 let b: i64 = i * ARR_ENT 1663 d[b + 0] = vt; d[b + 1] = vp 1664 return 0 1665} 1666 1667// ===================== environment with scope chain ===================== 1668// env layout: [0]=count, [1]=parent(*i64 as i64; 0=none), [2]=doc_ptr, [3]=doc_len (DOM document, 1669// set only on the GLOBAL env by js_run_source_doc; 0 on all others -- mmap zero-fill), entries at 1670// ENV_HDR + i*4 = [ns,nl,vt,vp]. The document.* builtins read genv[2]/genv[3]. 1671// ENV_HDR bumped 4->5 (R-JS-EVENT): genv[4] = the DOM event-listener table (lazy). 5->6 (R-JS-EVENTLOOP): 1672// genv[5] = the event-loop queue state (lazy, 0=never used). genv[0]=count, genv[1]=parent, genv[2]/[3]= 1673// DOM doc ptr/len (unchanged). All variable slots are ENV_HDR + i*ENV_ENTRY so they shift uniformly -- 1674// self-consistent. Only genv (global) roots the table/queues; child envs waste slots 4/5. 1675// ENV_HDR 6->9 (R-JS-TRY): genv[6]=pending-throw flag, genv[7]=thrown tag, genv[8]=thrown pay -- 1676// the `throw` VALUE channel (CS_ERROR carries no value; a catching ND_TRY reads+clears these; 1677// a plain runtime error leaves flag 0 so catch binds undefined). Only genv uses them. 1678const ENV_HDR: i64 = 9 1679const ENV_ENTRY: i64 = 5 // [ns, nl, vt, vp, namesrc] -- namesrc lets names compare ACROSS parse contexts 1680const ENV_MAX: i64 = 512 1681const THIS_NS: i64 = 0 - 999999 // sentinel name-offset for the `this` binding (matches only another THIS_NS) 1682const SUPER_NS: i64 = 0 - 999998 // sentinel name-offset for the `@super` binding (the parent constructor in an `extends` class body; matches only another SUPER_NS) 1683// R-JS-EVENTLOOP builtin ids (VAL_NATIVE payload -- bare global functions, resolved by ev_global_native). 1684const BI_SETTIMEOUT: i64 = 80 1685const BI_QUEUEMICROTASK: i64 = 81 1686const BI_RAF: i64 = 82 // requestAnimationFrame 1687const BI_CLEARTIMEOUT: i64 = 83 // no-op (timers run to completion in the drain; nothing to cancel) 1688// event-loop queue state (genv[5]): [0]=micro_next [1]=micro_end [2]=macro_next [3]=macro_end 1689const EL_HDR: i64 = 4 1690const EL_MAXQ: i64 = 8192 // bounded per-queue depth (named cap; overflow -> drop + honest 1 from el_push) 1691// R-JS-PROMISE (rung 2): Promises ride the microtask queue -- .then reactions schedule as microtasks. 1692// VAL_PROMISE/RESOLVE/REJECT (11/12/13) moved up to the VAL_ block near VAL_FLOAT (forward-const fix) 1693const NS_PROMISE: i64 = 6 1694const BI_PROM_RESOLVE: i64 = 90 1695const BI_PROM_REJECT: i64 = 91 1696const BI_PROM_THEN: i64 = 92 1697const BI_PROM_CATCH: i64 = 93 1698// promise record: [0]=state (0 pending / 1 fulfilled / 2 rejected) [1]=value tag [2]=value payload 1699// [3]=reaction count ; reactions at PROM_HDR + i*PROM_REACT = [onFtag onFpay onRtag onRpay resultprom]. 1700const PROM_HDR: i64 = 4 1701const PROM_REACT: i64 = 5 1702const PROM_MAXR: i64 = 64 1703// micro-queue TYPED job (8 i64): [0]=kind (0 plain closure / 1 promise reaction). kind0: [1]=clos ptr. 1704// kind1: [1]=handler tag [2]=handler pay [3]=value tag [4]=value pay [5]=result promise ptr [6]=pass-through state. 1705const JOB_SZ: i64 = 8 1706// R-JS-PENDING-FETCH: a fetch/xhr to a NON-data URL records a PENDING request in the EL buffer, PAST the 1707// two queues (PEND_BASE = EL_HDR + 2*EL_MAXQ = 4 + 16384). The engine stays TLS-free; the CONSUMER (headless 1708// render) enumerates pending, fetches each over sovereign TLS, services it, and re-drains -- the real-web loop. 1709const PEND_BASE: i64 = 16388 // EL_HDR(4) + 2*EL_MAXQ(16384); pending count at [PEND_BASE], entries after 1710const PEND_MAX: i64 = 128 1711const PEND_ENT: i64 = 3 // [url-str-rec ptr, promise ptr, serviced flag] 1712// R-JS-FETCH (rung 3): fetch() -> Promise<Response>. Engine stays TLS-free -- a data: URL resolves inline; 1713// the CONSUMER (headless renderer, rung 5) drives real network. Response record = [status, body tag, body pay]. 1714// VAL_RESPONSE (=14) and VAL_REGEX (=15) moved up to the VAL_ block near VAL_FLOAT (forward-const fix) 1715const RESP_HDR: i64 = 3 1716const BI_FETCH: i64 = 96 1717const BI_RESP_TEXT: i64 = 94 1718const BI_RESP_JSON: i64 = 95 1719// R-JS-BOM (rung 4): the browser object model as host namespaces (window/location/navigator/history). 1720const NS_WINDOW: i64 = 7 1721const NS_LOCATION: i64 = 8 1722const NS_NAVIGATOR: i64 = 9 1723const NS_HISTORY: i64 = 10 1724const NS_DATE: i64 = 11 // the Date global (Date.now() -> ms timestamp; new Date()/getTime = follow-on) 1725const BI_BOM_NOOP: i64 = 97 // location.reload / history.pushState / window.addEventListener ... -> undefined 1726const BI_DATE_NOW: i64 = 98 // Date.now() -> current time in ms (jQuery guid/expando) 1727// R-JS-XHR: XMLHttpRequest (older $.ajax / jQuery sites). Rides the same data: resolution as fetch; the XHR 1728// value is a plain VAL_OBJECT whose open/send are VAL_NATIVE props (dispatched by the existing object path). 1729const BI_XHR_NEW: i64 = 100 1730const BI_STRING_CTOR: i64 = 110 // String(x) -- coerce x to a primitive string ("" if no arg). NOT 103 (=BI_XHR_NOOP collision, dispatched in js_native_apply before us). 1731// Global constructors get bids UNIQUE across the whole called-native space (js_native_apply dispatches 1732// plain no-`new` calls by bid, so ARRAY_CTOR=101/ERROR_CTOR=102 silently hit BI_XHR_OPEN/BI_XHR_SEND -- 1733// `Array(5)` errored, `Error(m)` returned undefined). Error subtypes get DISTINCT bids so `.name` is right. 1734const BI_ARRAY_CTOR: i64 = 111 // new Array(n) / new Array(a,b,c) / Array(...) -- native array constructor 1735const BI_ERROR_CTOR: i64 = 112 // new Error(msg) / Error(msg) -- object with .message + .name="Error" 1736const BI_TYPEERR_CTOR: i64 = 113 // TypeError -> .name="TypeError" 1737const BI_RANGEERR_CTOR: i64 = 114 // RangeError -> .name="RangeError" 1738const BI_NUM_TOSTRING: i64 = 115 // (n).toString([radix]) -- number/float/bool receiver (Crypto uses radix 16) 1739const BI_XHR_OPEN: i64 = 101 1740const BI_XHR_SEND: i64 = 102 1741const BI_XHR_NOOP: i64 = 103 // setRequestHeader / getResponseHeader / abort -> undefined 1742func env_new() -> *i64 { let e: *i64 = sys_mmap(8 * (ENV_HDR + ENV_MAX * ENV_ENTRY)) as *i64; e[0] = 0; e[1] = 0; return e } 1743// GC root: scan a tree-style env_new structure (the VM's genv) as one flat conservative region -- its whole 1744// fixed mmap extent (unwritten slots read 0 = harmless). Defined here so the ENV_ constants are in scope. 1745func gc_scan_env(envp: i64) -> i64 { if envp != 0 { gc_scan_region(envp as i64, ENV_HDR + ENV_MAX * ENV_ENTRY) } return 0 } 1746func env_child(parent: *i64) -> *i64 { let e: *i64 = sys_mmap(8 * (ENV_HDR + ENV_MAX * ENV_ENTRY)) as *i64; e[0] = 0; e[1] = parent as i64; return e } 1747func env_name_eq(s1src: *u8, s1: i64, l1: i64, s2src: *u8, s2: i64, l2: i64) -> i64 { 1748 if s1 == THIS_NS { if s2 == THIS_NS { return 1 } return 0 } // `this` sentinel matches only another `this` 1749 if s2 == THIS_NS { return 0 } 1750 if s1 == SUPER_NS { if s2 == SUPER_NS { return 1 } return 0 } // `@super` sentinel matches only another `@super` 1751 if s2 == SUPER_NS { return 0 } 1752 if l1 != l2 { return 0 } 1753 var i: i64 = 0 1754 while i < l1 { if (s1src[s1 + i] & 0xff) != (s2src[s2 + i] & 0xff) { return 0 } i = i + 1 } 1755 return 1 1756} 1757func env_find_local(env: *i64, src: *u8, ns: i64, nl: i64) -> i64 { 1758 let c: i64 = env[0] 1759 var i: i64 = 0 1760 while i < c { let b: i64 = ENV_HDR + i * ENV_ENTRY; if env_name_eq((env[b + 4]) as *u8, env[b], env[b + 1], src, ns, nl) == 1 { return i } i = i + 1 } 1761 return 0 - 1 1762} 1763func env_define(env: *i64, src: *u8, ns: i64, nl: i64, vt: i64, vp: i64) -> i64 { 1764 let fi: i64 = env_find_local(env, src, ns, nl) 1765 if fi >= 0 { let b: i64 = ENV_HDR + fi * ENV_ENTRY; env[b + 2] = vt; env[b + 3] = vp; return 0 } 1766 let c: i64 = env[0] 1767 if c >= ENV_MAX { return 1 } 1768 let b: i64 = ENV_HDR + c * ENV_ENTRY 1769 env[b] = ns; env[b + 1] = nl; env[b + 2] = vt; env[b + 3] = vp; env[b + 4] = src as i64 1770 env[0] = c + 1 1771 return 0 1772} 1773// LOOKUP: walk the scope chain. Returns 0 if FOUND (value in out), 1 if the name was 1774// never declared anywhere on the chain -- a ReferenceError. The caller MUST treat a 1 1775// as an eval error and NOT silently substitute undefined (real JS: `y+1` with y never 1776// declared throws "y is not defined"). `typeof undeclared` is handled separately. 1777func env_get(env: *i64, src: *u8, ns: i64, nl: i64, out: *i64) -> i64 { 1778 var e: *i64 = env 1779 while ev_isnull(e) == 0 { 1780 let fi: i64 = env_find_local(e, src, ns, nl) 1781 if fi >= 0 { let b: i64 = ENV_HDR + fi * ENV_ENTRY; ev_set(out, e[b + 2], e[b + 3]); return 0 } 1782 e = (e[1]) as *i64 1783 } 1784 ev_set(out, VAL_UNDEF, 0) 1785 return 1 1786} 1787// ASSIGN to an EXISTING binding (walk up to where it was declared). Returns 0 on 1788// success, 1 if the name was never declared on the chain. We do NOT auto-create a 1789// global -- assigning to an undeclared name is a ReferenceError (the spec asks the 1790// evaluator to update the declaring scope, not invent one), so the caller errors. 1791func env_assign(env: *i64, src: *u8, ns: i64, nl: i64, vt: i64, vp: i64) -> i64 { 1792 var e: *i64 = env 1793 while ev_isnull(e) == 0 { 1794 let fi: i64 = env_find_local(e, src, ns, nl) 1795 if fi >= 0 { let b: i64 = ENV_HDR + fi * ENV_ENTRY; e[b + 2] = vt; e[b + 3] = vp; return 0 } 1796 e = (e[1]) as *i64 1797 } 1798 return 1 1799} 1800 1801// ===================== closure records (R-JS-CLOSURE, rung 6) ===================== 1802// A FUNCTION VALUE is a CLOSURE: VAL_FUNC's payload is no longer the bare function AST 1803// node -- it is a pointer (cast to i64) to a 2-slot heap record [fnode, defenv]: 1804// [0] = fnode -- the ND_FUNC_DECL AST node (params in slot b, body BLOCK in slot c) 1805// [1] = defenv -- the ENVIRONMENT in scope where the function literal/decl was EVALUATED 1806// The call frame parents `defenv` (NOT the global env), so the body resolves free variables 1807// up the LEXICAL chain to the enclosing function's locals -- the rung-2c closure fix. Envs 1808// are heap and never freed (see header allocate-generously note), so capturing one is just 1809// storing the pointer; a returned inner function keeps its defining frame alive forever. 1810const CLOS_SLOTS: i64 = 2 1811// ---- BYTECODE-VM RE-ENTRY SEAM (rung 5) ---- 1812// A closure record whose slot 0 is VMF_MAGIC is a BYTECODE-VM closure [magic, fidx, vmenv], 1813// NOT a tree-walker [fnode, defenv]. When one reaches js_call_core (callback natives, the 1814// event loop, promise reactions, DOM listeners -- ALL closure invocation routes through 1815// js_call_core), the call re-enters the bound VM through a function-pointer hook. The hook 1816// + its VM-state pointer are module statics set by the VM (js_vm_bind) before it runs -- 1817// a fn-ptr indirection because the VM imports THIS module (no circular import). 1818// Hook signature: fn(vsaddr, closaddr, argbufaddr, argc, thisvaladdr(0=none), outaddr) -> rc. 1819const VMF_MAGIC: i64 = 780301 1820static js_vm_hook_addr: i64 1821static js_vm_vs_addr: i64 1822func js_vm_bind(hook: i64, vs: i64) -> i64 { js_vm_hook_addr = hook; js_vm_vs_addr = vs; return 0 } 1823// CONSUMER-SWAP (the payoff wiring): a bound DOC HOOK lets js_run_source_doc route a page script through 1824// the bytecode VM/JIT (nx_js_vm's compile_run_doc, registered via js_vm_doc_bind) instead of the tree-walk, 1825// FALLING BACK to the tree-walker when the hook returns DECLINE (-2) for a feature the VM can't compile 1826// (template ${} etc). Default 0 = unbound = tree-walker (unchanged); a consumer opts in. Hook signature: 1827// fn(srcaddr, srclen, dochtml, doclen, outaddr) -> rc (0 ok / 1 error / -2 DECLINE-use-tree-walker). 1828const JS_VM_DOC_DECLINE: i64 = 0 - 2 1829static js_vm_doc_hook: i64 1830func js_vm_doc_bind(hook: i64) -> i64 { js_vm_doc_hook = hook; return 0 } 1831 1832func clos_new(fnode: i64, defenv: *i64) -> *i64 { 1833 let c: *i64 = sys_mmap(8 * CLOS_SLOTS) as *i64 1834 c[0] = fnode 1835 c[1] = defenv as i64 1836 return c 1837} 1838func clos_fnode(c: *i64) -> i64 { return c[0] } 1839func clos_defenv(c: *i64) -> *i64 { return (c[1]) as *i64 } 1840 1841// ===================== expression evaluation ===================== 1842// js_eval(ctx, idx, env, genv, out) -> 0 ok, 1 error. genv = global env (call frames parent it). 1843func js_eval(ctx: *i64, idx: i64, env: *i64, genv: *i64, out: *i64) -> i64 { 1844 if idx < 0 { ev_set(out, VAL_UNDEF, 0); return 1 } 1845 if ev_fuel_max > 0 { 1846 ev_fuel_used = ev_fuel_used + 1 1847 if ev_fuel_used > ev_fuel_max { ev_fuel_hit = 1; ev_set(out, VAL_UNDEF, 0); return 1 } 1848 } 1849 let k: i64 = jp_nkind(ctx, idx) 1850 1851 if k == ND_NUMBER { return ev_num_node(ctx, idx, out) } 1852 if k == ND_BOOL { ev_set(out, VAL_BOOL, ev_b2(jp_nextra(ctx, idx))); return 0 } 1853 if k == ND_NULL { ev_set(out, VAL_NULL, 0); return 0 } 1854 // ARRAY ELISION `[0,4,,5]`: an omitted element reads as undefined (never an error). 1855 if k == ND_HOLE { ev_set(out, VAL_UNDEF, 0); return 0 } 1856 if k == ND_YIELD { 1857 // generator STUB (JS-SOTA phase 1): evaluate the operand for its side effects, yield 1858 // expression value = undefined. True suspension/iteration semantics = a later rung. 1859 let yo: i64 = jp_na(ctx, idx) 1860 if yo >= 0 { let ytmp: *i64 = ev_cell(); if js_eval(ctx, yo, env, genv, ytmp) == 1 { ev_set(out, VAL_UNDEF, 0); return 1 } } 1861 ev_set(out, VAL_UNDEF, 0) 1862 return 0 1863 } 1864 if k == ND_STRING { ev_str_from_lit(ctx, idx, out); return 0 } 1865 if k == ND_IDENT { 1866 // declared name (incl a local shadow of `undefined`) -> its bound value. 1867 if env_get(env, jp_src(ctx), ev_tok_start(ctx, idx), ev_tok_len(ctx, idx), out) == 0 { return 0 } 1868 // `undefined` is a global VALUE, not a ReferenceError -- real pages guard with 1869 // `x === undefined` / `x == undefined` everywhere (council fix). A local 1870 // declaration named `undefined` would have been found by env_get above. 1871 if js_lexeme_eq(jp_src(ctx), ev_tok_start(ctx, idx), ev_tok_len(ctx, idx), "undefined\x00" as *u8) == 1 { ev_set(out, VAL_UNDEF, 0); return 0 } 1872 // GLOBAL BUILTIN namespaces (Math/Object/console/JSON) resolve as global objects so 1873 // `Math.max(...)`, `Object.keys(o)`, `console.log(x)` go through the SAME member-call 1874 // path. A user `var Math = ...` would have been found by env_get above (shadows it). 1875 let gns: i64 = ev_global_ns(jp_src(ctx), ev_tok_start(ctx, idx), ev_tok_len(ctx, idx)) 1876 if gns != 0 { ev_set(out, VAL_GLOBALNS, gns); return 0 } 1877 // R-JS-EVENTLOOP: bare global FUNCTIONS (setTimeout/queueMicrotask/requestAnimationFrame/ 1878 // clearTimeout) resolve to a native value so `setTimeout(fn,0)` goes through the plain-call path. 1879 let gnat: i64 = ev_global_native(jp_src(ctx), ev_tok_start(ctx, idx), ev_tok_len(ctx, idx)) 1880 if gnat != 0 { ev_set(out, VAL_NATIVE, gnat); return 0 } 1881 // BROWSER GLOBAL-OBJECT SEMANTICS: `window` IS the global object, so a bare name a script 1882 // attached via `window.$ = jQuery` (jQuery's own exposure line) must resolve. Dynamic window 1883 // props live in the NS_WINDOW statics object (same store ev_get_prop reads for `window.x`); on 1884 // a full scope-chain miss consult it before erroring -- read-side window<->global unification 1885 // (rare path: only on an otherwise-undeclared name, so no hot-path cost). 1886 if obj_get(js_ns_statics(NS_WINDOW), ev_key_from_ident(ctx, idx), out) == 1 { return 0 } 1887 // a truly undeclared name is a ReferenceError, never a silent undefined. 1888 if js_rt_dbg == 1 { sys_write(2, "REF-ERR undeclared '" as *u8, 20); sys_write(2, ((jp_src(ctx) as i64) + ev_tok_start(ctx, idx)) as *u8, ev_tok_len(ctx, idx)); sys_write(2, "'\n" as *u8, 2) } 1889 js_rt_mark(ctx, idx) 1890 ev_set(out, VAL_UNDEF, 0); return 1 1891 } 1892 if k == ND_ERROR { js_rt_mark(ctx, idx); ev_set(out, VAL_UNDEF, 0); return 1 } 1893 1894 if k == ND_ASSIGN { 1895 let lhs: i64 = jp_na(ctx, idx) 1896 let aop: i64 = jp_nextra(ctx, idx) // 0 = plain '='; OP_ADD..OP_MOD = compound (+= etc.) 1897 let lk: i64 = jp_nkind(ctx, lhs) 1898 let rb: *i64 = ev_cell() 1899 // JS-SPEC ORDER (= V8; was rhs-first, which broke Octane NavierStokes' Gauss-Seidel idiom 1900 // `x[cur] = (... x[++cur] ...)`): the target's base+index evaluate ONCE, BEFORE the rhs; 1901 // a compound reads the current value via that same base/index, folds AFTER the rhs, and 1902 // writes back to the SAME slot (`a[++i] -= e` increments i exactly once). 1903 // IDENT target: assign to the DECLARING scope; assign-to-undeclared is a ReferenceError. 1904 if lk == ND_IDENT { 1905 if aop != 0 { 1906 let cur: *i64 = ev_cell() 1907 if js_eval(ctx, lhs, env, genv, cur) == 1 { ev_set(out, VAL_UNDEF, 0); return 1 } 1908 if js_eval(ctx, jp_nb(ctx, idx), env, genv, rb) == 1 { ev_set(out, VAL_UNDEF, 0); return 1 } 1909 let folded: *i64 = ev_cell() 1910 if ev_apply_binop(aop, cur, rb, folded) == 1 { ev_set(out, VAL_UNDEF, 0); return 1 } 1911 ev_copy(rb, folded) 1912 } else { 1913 if js_eval(ctx, jp_nb(ctx, idx), env, genv, rb) == 1 { ev_set(out, VAL_UNDEF, 0); return 1 } 1914 } 1915 if env_assign(env, jp_src(ctx), ev_tok_start(ctx, lhs), ev_tok_len(ctx, lhs), rb[0], rb[1]) == 1 { 1916 // Undeclared target. NON-STRICT: a PLAIN `x = v` auto-creates a GLOBAL binding (real JS); 1917 // a COMPOUND `x += v` on undeclared is a ReferenceError. (VM declines plain-auto-global to us.) 1918 if aop != 0 { ev_set(out, VAL_UNDEF, 0); return 1 } 1919 env_define(genv, jp_src(ctx), ev_tok_start(ctx, lhs), ev_tok_len(ctx, lhs), rb[0], rb[1]) 1920 } 1921 ev_copy(out, rb) 1922 return 0 1923 } 1924 // MEMBER target: o.x = v -> base evaluated ONCE (before rhs); compound reads via ev_get_prop. 1925 if lk == ND_MEMBER { 1926 let ob: *i64 = ev_cell() 1927 if js_eval(ctx, jp_na(ctx, lhs), env, genv, ob) == 1 { ev_set(out, VAL_UNDEF, 0); return 1 } 1928 let key: *i64 = ev_prop_key(ctx, jp_nb(ctx, lhs)) 1929 if aop != 0 { 1930 let cur: *i64 = ev_cell() 1931 if ev_get_prop(ob, key, cur) == 1 { ev_set(out, VAL_UNDEF, 0); return 1 } 1932 if js_eval(ctx, jp_nb(ctx, idx), env, genv, rb) == 1 { ev_set(out, VAL_UNDEF, 0); return 1 } 1933 let folded: *i64 = ev_cell() 1934 if ev_apply_binop(aop, cur, rb, folded) == 1 { ev_set(out, VAL_UNDEF, 0); return 1 } 1935 ev_copy(rb, folded) 1936 } else { 1937 if js_eval(ctx, jp_nb(ctx, idx), env, genv, rb) == 1 { ev_set(out, VAL_UNDEF, 0); return 1 } 1938 } 1939 if ev_set_prop(ob, key, rb[0], rb[1]) == 1 { js_rt_mark(ctx, idx); ev_set(out, VAL_UNDEF, 0); return 1 } 1940 js_dom_write_hook(ob, key, rb) // R-JS-DOMWRITE: mirror el.textContent=/innerHTML= into the live tree 1941 ev_copy(out, rb) 1942 return 0 1943 } 1944 // INDEX target: o[k] = v -> base+index evaluated ONCE (before rhs); compound reads the element. 1945 if lk == ND_INDEX { 1946 let ob: *i64 = ev_cell() 1947 if js_eval(ctx, jp_na(ctx, lhs), env, genv, ob) == 1 { ev_set(out, VAL_UNDEF, 0); return 1 } 1948 let kb: *i64 = ev_cell() 1949 if js_eval(ctx, jp_nb(ctx, lhs), env, genv, kb) == 1 { ev_set(out, VAL_UNDEF, 0); return 1 } 1950 if aop != 0 { 1951 let cur: *i64 = ev_cell() 1952 var have: i64 = 0 1953 if ob[0] == VAL_ARRAY { if kb[0] == VAL_NUM { 1954 let a0: *i64 = (ob[1]) as *i64 1955 if arr_get(a0, kb[1], cur) == 0 { ev_set(cur, VAL_UNDEF, 0) } 1956 have = 1 1957 } } 1958 if have == 0 { 1959 let key0: *i64 = ev_key_of_val(kb) 1960 if ev_get_prop(ob, key0, cur) == 1 { ev_set(out, VAL_UNDEF, 0); return 1 } 1961 } 1962 if js_eval(ctx, jp_nb(ctx, idx), env, genv, rb) == 1 { ev_set(out, VAL_UNDEF, 0); return 1 } 1963 let folded: *i64 = ev_cell() 1964 if ev_apply_binop(aop, cur, rb, folded) == 1 { ev_set(out, VAL_UNDEF, 0); return 1 } 1965 ev_copy(rb, folded) 1966 } else { 1967 if js_eval(ctx, jp_nb(ctx, idx), env, genv, rb) == 1 { ev_set(out, VAL_UNDEF, 0); return 1 } 1968 } 1969 // array + numeric key -> direct integer write (matches the read fast path). 1970 if ob[0] == VAL_ARRAY { 1971 if kb[0] == VAL_NUM { 1972 let a: *i64 = (ob[1]) as *i64 1973 if arr_set(a, kb[1], rb[0], rb[1]) == 1 { ev_set(out, VAL_UNDEF, 0); return 1 } 1974 ev_copy(out, rb) 1975 return 0 1976 } 1977 } 1978 let key: *i64 = ev_key_of_val(kb) 1979 if ev_set_prop(ob, key, rb[0], rb[1]) == 1 { js_rt_mark(ctx, idx); ev_set(out, VAL_UNDEF, 0); return 1 } 1980 ev_copy(out, rb) 1981 return 0 1982 } 1983 // any other lhs kind is not assignable (parser already rejects literals etc.). 1984 ev_set(out, VAL_UNDEF, 0); return 1 1985 } 1986 1987 // UPDATE ++x/--x/x++/x-- (extra: 1=++pre 2=--pre 3=++post 4=--post). Read the target's 1988 // CURRENT value, fold cur(+/-)1 via ev_apply_binop (same numeric/float ladder as compound 1989 // assign), write back through the SAME ident/member/index paths as ND_ASSIGN (member/index 1990 // re-evaluate the base for the write, matching compound-assign order). Result = the NEW 1991 // value for prefix, the OLD value for postfix. 1992 if k == ND_UPDATE { 1993 let tgt: i64 = jp_na(ctx, idx) 1994 let mode: i64 = jp_nextra(ctx, idx) 1995 let lk: i64 = jp_nkind(ctx, tgt) 1996 let cur: *i64 = ev_cell() 1997 let one: *i64 = ev_cell() 1998 ev_set(one, VAL_NUM, 1) 1999 var uop: i64 = OP_ADD 2000 if mode == 2 { uop = OP_SUB } 2001 if mode == 4 { uop = OP_SUB } 2002 let nw: *i64 = ev_cell() 2003 // SINGLE-EVAL of the reference (JS spec): the target base (and index) evaluate EXACTLY ONCE, then the 2004 // read + write both go through those already-evaluated values (mirrors the compound-assign path above). 2005 // The OLD code re-evaluated base/key for the write -> `a[k()]++` ran k() twice (spec violation). 2006 if lk == ND_IDENT { 2007 if js_eval(ctx, tgt, env, genv, cur) == 1 { ev_set(out, VAL_UNDEF, 0); return 1 } 2008 if ev_apply_binop(uop, cur, one, nw) == 1 { ev_set(out, VAL_UNDEF, 0); return 1 } 2009 if env_assign(env, jp_src(ctx), ev_tok_start(ctx, tgt), ev_tok_len(ctx, tgt), nw[0], nw[1]) == 1 { ev_set(out, VAL_UNDEF, 0); return 1 } 2010 if mode <= 2 { ev_copy(out, nw) } else { ev_copy(out, cur) } 2011 return 0 2012 } 2013 if lk == ND_MEMBER { 2014 let ob: *i64 = ev_cell() 2015 if js_eval(ctx, jp_na(ctx, tgt), env, genv, ob) == 1 { ev_set(out, VAL_UNDEF, 0); return 1 } // base ONCE 2016 let key: *i64 = ev_prop_key(ctx, jp_nb(ctx, tgt)) 2017 if ev_get_prop(ob, key, cur) == 1 { ev_set(out, VAL_UNDEF, 0); return 1 } // read via same base 2018 if ev_apply_binop(uop, cur, one, nw) == 1 { ev_set(out, VAL_UNDEF, 0); return 1 } 2019 if ev_set_prop(ob, key, nw[0], nw[1]) == 1 { ev_set(out, VAL_UNDEF, 0); return 1 } // write via same base 2020 js_dom_write_hook(ob, key, nw) 2021 if mode <= 2 { ev_copy(out, nw) } else { ev_copy(out, cur) } 2022 return 0 2023 } 2024 if lk == ND_INDEX { 2025 let ob: *i64 = ev_cell() 2026 if js_eval(ctx, jp_na(ctx, tgt), env, genv, ob) == 1 { ev_set(out, VAL_UNDEF, 0); return 1 } // base ONCE 2027 let kb: *i64 = ev_cell() 2028 if js_eval(ctx, jp_nb(ctx, tgt), env, genv, kb) == 1 { ev_set(out, VAL_UNDEF, 0); return 1 } // index ONCE 2029 var have: i64 = 0 2030 if ob[0] == VAL_ARRAY { if kb[0] == VAL_NUM { 2031 let a0: *i64 = (ob[1]) as *i64 2032 if arr_get(a0, kb[1], cur) == 0 { ev_set(cur, VAL_UNDEF, 0) } 2033 have = 1 2034 } } 2035 if have == 0 { 2036 let key0: *i64 = ev_key_of_val(kb) 2037 if ev_get_prop(ob, key0, cur) == 1 { ev_set(out, VAL_UNDEF, 0); return 1 } 2038 } 2039 if ev_apply_binop(uop, cur, one, nw) == 1 { ev_set(out, VAL_UNDEF, 0); return 1 } 2040 var wdone: i64 = 0 2041 if ob[0] == VAL_ARRAY { if kb[0] == VAL_NUM { 2042 let a: *i64 = (ob[1]) as *i64 2043 if arr_set(a, kb[1], nw[0], nw[1]) == 1 { ev_set(out, VAL_UNDEF, 0); return 1 } 2044 wdone = 1 2045 } } 2046 if wdone == 0 { 2047 let key2: *i64 = ev_key_of_val(kb) 2048 if ev_set_prop(ob, key2, nw[0], nw[1]) == 1 { ev_set(out, VAL_UNDEF, 0); return 1 } 2049 } 2050 if mode <= 2 { ev_copy(out, nw) } else { ev_copy(out, cur) } 2051 return 0 2052 } 2053 ev_set(out, VAL_UNDEF, 0) 2054 return 1 2055 } 2056 2057 if k == ND_SEQ { // comma operator: eval each child in order; value = the LAST 2058 let sn: i64 = jp_nb(ctx, idx) 2059 var si: i64 = 0 2060 while si < sn { 2061 let sch: i64 = jp_child_at(ctx, idx, si) 2062 if js_eval(ctx, sch, env, genv, out) == 1 { 2063 if js_rt_dbg == 2 { var scb: i64 = ev_tok_start(ctx, sch); var dch: i64 = sch; var dl: i64 = 0; while dl < 5 { let na: i64 = jp_na(ctx, dch); if na < 0 { dl = 5 } else { let noff: i64 = ev_tok_start(ctx, na); if noff > 0 { scb = noff; dl = 5 } else { dch = na; dl = dl + 1 } } } sys_write(2, "SEQ-ERR part " as *u8, 13); nx_dbg_num(si); sys_write(2, "/" as *u8, 1); nx_dbg_num(sn); sys_write(2, " kind=" as *u8, 6); nx_dbg_num(jp_nkind(ctx, sch)); sys_write(2, " @" as *u8, 2); nx_dbg_num(scb); sys_write(2, " '" as *u8, 2); let spq: *u8 = jp_src(ctx); var dq: i64 = 0; while dq < 64 { if spq[scb+dq]==(0 as u8){dq=64} else { sys_write(2, ((spq as i64)+scb+dq) as *u8, 1); dq=dq+1 } } sys_write(2, "'\n" as *u8, 2) } 2064 return 1 2065 } 2066 si = si + 1 2067 } 2068 return 0 2069 } 2070 if k == ND_UNARY { 2071 let op: i64 = jp_nextra(ctx, idx) 2072 // typeof tolerates an UNDECLARED identifier (legal JS -> "undefined"); evaluate 2073 // its operand WITHOUT letting a ReferenceError escape, then map the tag. 2074 if op == OP_TYPEOF { 2075 let tb: *i64 = ev_cell() 2076 var tag: i64 = VAL_UNDEF 2077 let saved_ep: i64 = js_rt_errpos 2078 if js_eval(ctx, jp_na(ctx, idx), env, genv, tb) == 0 { tag = tb[0] } 2079 js_rt_errpos = saved_ep // typeof SWALLOWS a ReferenceError on its operand -> it is NOT the fatal 2080 let rec: *i64 = ev_typeof_str(tag) 2081 ev_set(out, VAL_STR, rec as i64) 2082 return 0 2083 } 2084 // delete obj.k / obj[k] -- operates on the REFERENCE (eval base + key, remove the property), not a value. 2085 if op == OP_DELETE { 2086 let opnd: i64 = jp_na(ctx, idx) 2087 let ok: i64 = jp_nkind(ctx, opnd) 2088 if ok == ND_MEMBER { 2089 let db: *i64 = ev_cell() 2090 if js_eval(ctx, jp_na(ctx, opnd), env, genv, db) == 1 { ev_set(out, VAL_UNDEF, 0); return 1 } 2091 if db[0] == VAL_OBJECT { let key: *i64 = ev_prop_key(ctx, jp_nb(ctx, opnd)); obj_delete((db[1]) as *i64, key) } 2092 ev_set(out, VAL_BOOL, 1); return 0 2093 } 2094 if ok == ND_INDEX { 2095 let db: *i64 = ev_cell() 2096 if js_eval(ctx, jp_na(ctx, opnd), env, genv, db) == 1 { ev_set(out, VAL_UNDEF, 0); return 1 } 2097 let kb: *i64 = ev_cell() 2098 if js_eval(ctx, jp_nb(ctx, opnd), env, genv, kb) == 1 { ev_set(out, VAL_UNDEF, 0); return 1 } 2099 if db[0] == VAL_OBJECT { let key: *i64 = ev_key_of_val(kb); obj_delete((db[1]) as *i64, key) } 2100 ev_set(out, VAL_BOOL, 1); return 0 2101 } 2102 ev_set(out, VAL_BOOL, 1); return 0 // delete of a non-reference (`delete x`) -> true, no effect 2103 } 2104 let ob: *i64 = ev_cell() 2105 if js_eval(ctx, jp_na(ctx, idx), env, genv, ob) == 1 { ev_set(out, VAL_UNDEF, 0); return 1 } 2106 if op == OP_NOT { if ev_truthy(ob) == 1 { ev_set(out, VAL_BOOL, 0) } else { ev_set(out, VAL_BOOL, 1) } return 0 } 2107 // -x / +x on a FLOAT stay float (was int-truncating: -0.5 -> 0, which zeroed NavierStokes' h). 2108 if op == OP_NEG { if ob[0] == VAL_FLOAT { ev_set(out, VAL_FLOAT, nx_f64_neg(ob[1])); return 0 } ev_set(out, VAL_NUM, 0 - ev_tonum(ob)); return 0 } 2109 if op == OP_POS { if ob[0] == VAL_FLOAT { ev_set(out, VAL_FLOAT, ob[1]); return 0 } ev_set(out, VAL_NUM, ev_tonum(ob)); return 0 } 2110 if op == OP_BNOT { ev_set(out, VAL_NUM, 0 - js_toint32(ev_tonum(ob)) - 1); return 0 } // ~x = -(ToInt32 x)-1 2111 if op == OP_VOID { ev_set(out, VAL_UNDEF, 0); return 0 } // void x -> operand evaluated (above) for side effects; yields undefined 2112 ev_set(out, VAL_UNDEF, 0); return 0 2113 } 2114 2115 if k == ND_BINARY { 2116 let op: i64 = jp_nextra(ctx, idx) 2117 let lb: *i64 = ev_cell() 2118 if js_eval(ctx, jp_na(ctx, idx), env, genv, lb) == 1 { ev_set(out, VAL_UNDEF, 0); return 1 } 2119 if op == OP_AND { 2120 if ev_truthy(lb) == 0 { ev_copy(out, lb); return 0 } 2121 return js_eval(ctx, jp_nb(ctx, idx), env, genv, out) 2122 } 2123 if op == OP_OR { 2124 if ev_truthy(lb) == 1 { ev_copy(out, lb); return 0 } 2125 return js_eval(ctx, jp_nb(ctx, idx), env, genv, out) 2126 } 2127 if op == OP_NULLISH { 2128 var nul: i64 = 0 2129 if lb[0] == VAL_NULL { nul = 1 } 2130 if lb[0] == VAL_UNDEF { nul = 1 } 2131 if nul == 0 { ev_copy(out, lb); return 0 } 2132 return js_eval(ctx, jp_nb(ctx, idx), env, genv, out) 2133 } 2134 let rb: *i64 = ev_cell() 2135 if js_eval(ctx, jp_nb(ctx, idx), env, genv, rb) == 1 { ev_set(out, VAL_UNDEF, 0); return 1 } 2136 if op == OP_INSTANCEOF { return ev_instanceof(lb, rb, out) } 2137 if op == OP_IN { return ev_in(lb, rb, out) } 2138 if op == OP_EQ { ev_set(out, VAL_BOOL, ev_loose_eq(lb, rb)); return 0 } 2139 if op == OP_NE { if ev_loose_eq(lb, rb) == 1 { ev_set(out, VAL_BOOL, 0) } else { ev_set(out, VAL_BOOL, 1) } return 0 } 2140 if op == OP_SEQ { ev_set(out, VAL_BOOL, ev_strict_eq(lb, rb)); return 0 } 2141 if op == OP_SNE { if ev_strict_eq(lb, rb) == 1 { ev_set(out, VAL_BOOL, 0) } else { ev_set(out, VAL_BOOL, 1) } return 0 } 2142 // '+' is STRING CONCAT when EITHER operand is a string (coerce the other). 2143 if op == OP_ADD { 2144 if lb[0] == VAL_STR { return ev_str_concat(ev_coerce_str(lb), ev_coerce_str(rb), out) } 2145 if rb[0] == VAL_STR { return ev_str_concat(ev_coerce_str(lb), ev_coerce_str(rb), out) } 2146 } 2147 // Relational < <= > >= on TWO strings = BYTE-lexicographic order (real JS: 2148 // 'a'<'b'=true, 'apple'<'banana'=true, 'ab'<'abc'=true). MIXED str/number relational 2149 // is handled by the next block (numeric coercion); only the str-vs-str case lands here. 2150 if lb[0] == VAL_STR { if rb[0] == VAL_STR { 2151 let c: i64 = ev_str_cmp((lb[1]) as *i64, (rb[1]) as *i64) 2152 if op == OP_LT { ev_set(out, VAL_BOOL, ev_b2(c < 0)); return 0 } 2153 if op == OP_LE { ev_set(out, VAL_BOOL, ev_b2(c <= 0)); return 0 } 2154 if op == OP_GT { ev_set(out, VAL_BOOL, ev_b2(c > 0)); return 0 } 2155 if op == OP_GE { ev_set(out, VAL_BOOL, ev_b2(c >= 0)); return 0 } 2156 } } 2157 // MIXED string<->number relational (council MAJOR fix): ONE operand is a string, 2158 // the other a clean numeric (number/bool/null). Real JS coerces the string with 2159 // ToNumber, so '10'<5 is 10<5=false and '5'<6=true -- NOT both-coerced-to-0. We 2160 // parse the string as an INTEGER (the only numeric form this rung); a NON-numeric 2161 // string (e.g. 'abc'<5) is ToNumber=NaN in JS -> we ERROR (rc=1) rather than the 2162 // banned silent coerce-to-0, since NaN ordering needs the float ladder (rung-2b). 2163 if ev_is_relop(op) == 1 { 2164 if ev_one_str_one_num(lb, rb) == 1 { 2165 let mb: *i64 = ev_cell() 2166 if ev_mixed_relnum(lb, mb) == 0 { js_rt_mark(ctx, idx); ev_set(out, VAL_UNDEF, 0); return 1 } 2167 let nb: *i64 = ev_cell() 2168 if ev_mixed_relnum(rb, nb) == 0 { js_rt_mark(ctx, idx); ev_set(out, VAL_UNDEF, 0); return 1 } 2169 let x: i64 = mb[0] 2170 let y: i64 = nb[0] 2171 if op == OP_LT { ev_set(out, VAL_BOOL, ev_b2(x < y)); return 0 } 2172 if op == OP_LE { ev_set(out, VAL_BOOL, ev_b2(x <= y)); return 0 } 2173 if op == OP_GT { ev_set(out, VAL_BOOL, ev_b2(x > y)); return 0 } 2174 if op == OP_GE { ev_set(out, VAL_BOOL, ev_b2(x >= y)); return 0 } 2175 } 2176 } 2177 // ToNumber coercion for non-`+` ops: string/bool/null operand -> number (JS ToNumber). `100/"5"`=20. 2178 var lc: *i64 = lb 2179 var rc: *i64 = rb 2180 if lb[0] == VAL_STR { let lt: *i64 = ev_cell(); ev_coerce_num(lb, lt); lc = lt } 2181 if rb[0] == VAL_STR { let rt: *i64 = ev_cell(); ev_coerce_num(rb, rt); rc = rt } 2182 // BITWISE (&|^ << >> >>>) -- real JS ToInt32 both sides (never float-promote); 32-bit int result. 2183 if op == OP_BAND { ev_set(out, VAL_NUM, js_toint32(ev_tonum(lc)) & js_toint32(ev_tonum(rc))); return 0 } 2184 if op == OP_BOR { ev_set(out, VAL_NUM, js_toint32(ev_tonum(lc)) | js_toint32(ev_tonum(rc))); return 0 } 2185 if op == OP_BXOR { ev_set(out, VAL_NUM, js_toint32(ev_tonum(lc)) ^ js_toint32(ev_tonum(rc))); return 0 } 2186 if op == OP_SHL { let sa: i64 = js_toint32(ev_tonum(lc)); let sh: i64 = ev_tonum(rc) & 31; ev_set(out, VAL_NUM, js_toint32(sa << sh)); return 0 } 2187 if op == OP_SHR { let sa: i64 = js_toint32(ev_tonum(lc)); let sh: i64 = ev_tonum(rc) & 31; ev_set(out, VAL_NUM, sa >> sh); return 0 } 2188 if op == OP_USHR { let ua: i64 = ev_tonum(lc) & 0xffffffff; let sh: i64 = ev_tonum(rc) & 31; ev_set(out, VAL_NUM, ua >> sh); return 0 } 2189 // FLOAT promotion (R-JS-F64-2): either operand a float -> f64 arithmetic (result VAL_FLOAT). 2190 if lc[0] == VAL_FLOAT { return ev_binop_f64(op, ev_tof64(lc), ev_tof64(rc), out) } 2191 if rc[0] == VAL_FLOAT { return ev_binop_f64(op, ev_tof64(lc), ev_tof64(rc), out) } 2192 let a: i64 = ev_tonum(lc) 2193 let b: i64 = ev_tonum(rc) 2194 if op == OP_ADD { ev_set(out, VAL_NUM, a + b); return 0 } 2195 if op == OP_SUB { ev_set(out, VAL_NUM, a - b); return 0 } 2196 if op == OP_MUL { ev_set(out, VAL_NUM, a * b); return 0 } 2197 // div/mod by zero is an ERROR here (no float Infinity/NaN this rung), not a value. 2198 if op == OP_DIV { let dr: i64 = ev_num_div(a, b, out); if dr == 1 { js_rt_mark(ctx, idx) } return dr } 2199 if op == OP_MOD { let mr: i64 = ev_num_mod(a, b, out); if mr == 1 { js_rt_mark(ctx, idx) } return mr } 2200 if op == OP_LT { ev_set(out, VAL_BOOL, ev_b2(a < b)); return 0 } 2201 if op == OP_LE { ev_set(out, VAL_BOOL, ev_b2(a <= b)); return 0 } 2202 if op == OP_GT { ev_set(out, VAL_BOOL, ev_b2(a > b)); return 0 } 2203 if op == OP_GE { ev_set(out, VAL_BOOL, ev_b2(a >= b)); return 0 } 2204 ev_set(out, VAL_UNDEF, 0); return 0 2205 } 2206 2207 // FUNCTION EXPRESSION / ARROW (R-JS-CLOSURE, rung 6): a function literal in EXPRESSION 2208 // position evaluates to a CLOSURE VALUE capturing the CURRENT env as its defining scope 2209 // (so `var add5 = makeAdder(5)` returns an inner fn that still sees makeAdder's `x`, and 2210 // `(x)=>x+1` / `(function(){...})()` work). The node is ND_FUNC_DECL (function exprs + arrows 2211 // share the decl shape). 2212 if k == ND_FUNC_DECL { 2213 let fnm: i64 = jp_na(ctx, idx) 2214 if fnm >= 0 { 2215 // NAMED function EXPRESSION (`var f = function fact(n){...fact(n-1)...}`): the name is bound 2216 // in an INTERMEDIATE scope visible ONLY to the function's own body (self-recursion), NOT the 2217 // enclosing scope (real JS). Recursive memoizers + some Sizzle helpers depend on this. 2218 let fnenv: *i64 = env_child(env) 2219 let cn: *i64 = clos_new(idx, fnenv) 2220 env_define(fnenv, jp_src(ctx), ev_tok_start(ctx, fnm), ev_tok_len(ctx, fnm), VAL_FUNC, cn as i64) 2221 ev_set(out, VAL_FUNC, cn as i64) 2222 return 0 2223 } 2224 let c: *i64 = clos_new(idx, env) 2225 ev_set(out, VAL_FUNC, c as i64) 2226 return 0 2227 } 2228 2229 // class Name{constructor(){} m(){}...}: ctor closure bound to Name; each method installed on the 2230 // constructor's .prototype (so `new Name()` instances inherit it via the [[Prototype]] chain). a=name 2231 // IDENT, b=ctor ND_FUNC_DECL, c=methods ND_BLOCK (each child = a method ND_FUNC_DECL). 2232 if k == ND_CLASS { 2233 let ctornode: i64 = jp_nb(ctx, idx) 2234 // `extends <expr>`: the superclass node is stored in the ND_CLASS extra slot (-1 = none). 2235 // Evaluate it to the parent constructor, then (a) run ctor+methods in a class-body child 2236 // env that binds `@super` = parent ctor (so `super(...)`/`super.m()` resolve lexically), 2237 // and (b) chain B.prototype -> A.prototype so `new B()` inherits A's methods. 2238 let supidx: i64 = jp_nextra(ctx, idx) 2239 var cenv: *i64 = env 2240 var suppay: i64 = 0 2241 var hassuper: i64 = 0 2242 if supidx >= 0 { 2243 let sv: *i64 = ev_cell() 2244 if js_eval(ctx, supidx, env, genv, sv) == 1 { ev_set(out, VAL_UNDEF, 0); return 1 } 2245 if sv[0] != VAL_FUNC { ev_set(out, VAL_UNDEF, 0); return 1 } // `extends <non-function>` -> error 2246 suppay = sv[1] 2247 hassuper = 1 2248 cenv = env_child(env) 2249 env_define(cenv, jp_src(ctx), SUPER_NS, 5, VAL_FUNC, suppay) 2250 } 2251 let cc: *i64 = clos_new(ctornode, cenv) 2252 let ctorpay: i64 = cc as i64 2253 let nameid: i64 = jp_na(ctx, idx) 2254 env_define(env, jp_src(ctx), ev_tok_start(ctx, nameid), ev_tok_len(ctx, nameid), VAL_FUNC, ctorpay) 2255 let proto: i64 = func_prototype(ctorpay) 2256 if hassuper == 1 { obj_set_proto(proto as *i64, func_prototype(suppay)) } // B.prototype -> A.prototype 2257 let methods: i64 = jp_nc(ctx, idx) 2258 let mcount: i64 = jp_nb(ctx, methods) 2259 var mi: i64 = 0 2260 while mi < mcount { 2261 let mnode: i64 = jp_child_at(ctx, methods, mi) 2262 let mkey: *i64 = ev_prop_key(ctx, jp_na(ctx, mnode)) 2263 let mclos: *i64 = clos_new(mnode, cenv) 2264 obj_set(proto as *i64, mkey, VAL_FUNC, mclos as i64) 2265 mi = mi + 1 2266 } 2267 ev_set(out, VAL_FUNC, ctorpay) 2268 return 0 2269 } 2270 2271 if k == ND_CALL { return js_eval_call(ctx, idx, env, genv, out) } 2272 2273 // TERNARY cond ? then : else -- eval cond, then eval ONLY the taken branch (real JS 2274 // short-circuit: the untaken branch is NEVER evaluated, so `1 ? 2 : zzz` is 2 with zzz 2275 // undeclared and no ReferenceError). a=cond, b=then-expr, c=else-expr. 2276 if k == ND_TERNARY { 2277 let cb: *i64 = ev_cell() 2278 if js_eval(ctx, jp_na(ctx, idx), env, genv, cb) == 1 { ev_set(out, VAL_UNDEF, 0); return 1 } 2279 if ev_truthy(cb) == 1 { return js_eval(ctx, jp_nb(ctx, idx), env, genv, out) } 2280 return js_eval(ctx, jp_nc(ctx, idx), env, genv, out) 2281 } 2282 2283 // OBJECT literal { k:v, ... } -> allocate a heap object, eval each prop value, store. 2284 if k == ND_OBJECT { 2285 let o: *i64 = obj_new() 2286 let cnt: i64 = jp_nb(ctx, idx) 2287 var i: i64 = 0 2288 while i < cnt { 2289 let prop: i64 = jp_child_at(ctx, idx, i) 2290 let key: *i64 = ev_prop_key(ctx, prop) // tokidx names the key 2291 let vb: *i64 = ev_cell() 2292 if js_eval(ctx, jp_na(ctx, prop), env, genv, vb) == 1 { if js_rt_dbg == 2 { sys_write(2, "OBJLIT-ERR key='" as *u8, 16); sys_write(2, ev_str_bytes(key), ev_str_len(key)); sys_write(2, "'\n" as *u8, 2) } js_rt_mark(ctx, prop); ev_set(out, VAL_UNDEF, 0); return 1 } 2293 obj_set(o, key, vb[0], vb[1]) 2294 i = i + 1 2295 } 2296 ev_set(out, VAL_OBJECT, o as i64) 2297 return 0 2298 } 2299 2300 // ARRAY literal [ e0, e1, ... ] -> allocate a heap array, eval elements in order. 2301 if k == ND_ARRAY { 2302 let a: *i64 = arr_new() 2303 let cnt: i64 = jp_nb(ctx, idx) 2304 var i: i64 = 0 2305 var oi: i64 = 0 2306 while i < cnt { 2307 let el: i64 = jp_child_at(ctx, idx, i) 2308 if jp_nkind(ctx, el) == ND_SPREAD { // [...a] : splice a's elements in 2309 let sb: *i64 = ev_cell() 2310 if js_eval(ctx, jp_na(ctx, el), env, genv, sb) == 1 { ev_set(out, VAL_UNDEF, 0); return 1 } 2311 if sb[0] == VAL_ARRAY { 2312 let src2: *i64 = (sb[1]) as *i64 2313 let slen: i64 = arr_len(src2) 2314 var si: i64 = 0 2315 while si < slen { 2316 let eb: *i64 = ev_cell() 2317 arr_get(src2, si, eb) 2318 arr_set(a, oi, eb[0], eb[1]) 2319 oi = oi + 1 2320 si = si + 1 2321 } 2322 } 2323 } else { 2324 let vb: *i64 = ev_cell() 2325 if js_eval(ctx, el, env, genv, vb) == 1 { ev_set(out, VAL_UNDEF, 0); return 1 } 2326 arr_set(a, oi, vb[0], vb[1]) 2327 oi = oi + 1 2328 } 2329 i = i + 1 2330 } 2331 ev_set(out, VAL_ARRAY, a as i64) 2332 return 0 2333 } 2334 2335 if k == ND_NEW { 2336 let obj: *i64 = obj_new() 2337 let thisv: *i64 = ev_cell() 2338 ev_set(thisv, VAL_OBJECT, obj as i64) 2339 var calleenode: i64 = 0 2340 var argsnode: i64 = 0 - 1 2341 if jp_nextra(ctx, idx) == 1 { calleenode = jp_na(ctx, idx) } 2342 else { let cn: i64 = jp_na(ctx, idx); calleenode = jp_na(ctx, cn); argsnode = cn } 2343 let cv: *i64 = ev_cell() 2344 if js_eval(ctx, calleenode, env, genv, cv) == 1 { ev_set(out, VAL_UNDEF, 0); return 1 } 2345 if cv[0] == VAL_GLOBALNS { if cv[1] == NS_OBJECT { ev_set(out, VAL_OBJECT, (obj_new()) as i64); return 0 } } // new Object() -> {} 2346 if cv[0] != VAL_NATIVE { if cv[0] != VAL_FUNC { ev_set(out, VAL_UNDEF, 0); return 1 } } 2347 var argc: i64 = 0 2348 let argbuf: *i64 = sys_mmap(8 * 2 * 256) as *i64 2349 if argsnode != (0 - 1) { 2350 let n: i64 = js_eval_args(ctx, argsnode, env, genv, argbuf) // post-spread-expansion count 2351 if n < 0 { ev_set(out, VAL_UNDEF, 0); return 1 } 2352 argc = n 2353 } 2354 if cv[0] == VAL_NATIVE { return js_construct_native_args(cv[1], argbuf, argc, out) } // new Array(n)/XHR 2355 obj_set_proto(obj, func_prototype(cv[1])) // link instance to Foo.prototype BEFORE the ctor runs 2356 let ret: *i64 = ev_cell() 2357 if js_call_core(ctx, (cv[1]) as *i64, argbuf, argc, thisv, genv, ret) == 1 { ev_set(out, VAL_UNDEF, 0); return 1 } 2358 if ret[0] == VAL_OBJECT { ev_copy(out, ret) } else { ev_set(out, VAL_OBJECT, obj as i64) } 2359 return 0 2360 } 2361 if k == ND_THIS { 2362 if env_get(env, jp_src(ctx), THIS_NS, 4, out) == 1 { ev_set(out, VAL_OBJECT, js_globalthis()) } // non-strict: unbound this -> globalThis 2363 return 0 2364 } 2365 if k == ND_TEMPLATE { return js_eval_template(ctx, idx, env, genv, out) } 2366 if k == ND_REGEX { return js_make_regex(ctx, idx, out) } 2367 if k == ND_SUPER { ev_set(out, VAL_UNDEF, 0); return 1 } // bare `super` (not a call/member) is a syntax error at eval 2368 2369 // MEMBER read: o.x (property child is an IDENT node; arr.length is special). 2370 if k == ND_MEMBER { return js_eval_member(ctx, idx, env, genv, out) } 2371 // INDEX read: o[i] / arr[i] (key is evaluated; array uses int index, object string key). 2372 if k == ND_INDEX { return js_eval_index(ctx, idx, env, genv, out) } 2373 2374 // Any OTHER node kind reaching eval is unrecognized/unimplemented -> ERROR, never a 2375 // silent success. (No blanket fall-through to undefined.) 2376 ev_set(out, VAL_UNDEF, 0); return 1 2377} 2378 2379// evaluate a CALL node. TWO call shapes: 2380// (1) METHOD CALL obj.method(args) -- callee is ND_MEMBER. Evaluate the object as 2381// `this`, resolve the method NAME against the receiver's type's builtin table, eval 2382// the args, and invoke the native (passing `this`). A user-FUNCTION stored as a 2383// property (o.f where o.f is a VAL_FUNC) is also called here, bound to its receiver's 2384// scope rules. A MISSING method = honest ERROR (rc=1), NEVER a silent undefined. 2385// (2) PLAIN CALL f(args) -- callee is an IDENT/closure resolving to a VAL_FUNC (or a 2386// VAL_NATIVE value, e.g. `var m = Math.max; m(...)`, with `this` = undefined). 2387// super(args) -- inside an `extends` class constructor: call the PARENT constructor with the 2388// SAME `this` (the instance under construction), so inherited fields get initialized on it. 2389// The `@super` binding (parent ctor VAL_FUNC) was installed by ND_CLASS in the class-body env; 2390// `this` is bound in the current call frame. super() itself evaluates to undefined. 2391func js_eval_super_ctor(ctx: *i64, idx: i64, env: *i64, genv: *i64, out: *i64) -> i64 { 2392 let sv: *i64 = ev_cell() 2393 if env_get(env, jp_src(ctx), SUPER_NS, 5, sv) == 1 { ev_set(out, VAL_UNDEF, 0); return 1 } // super outside an extends class 2394 if sv[0] != VAL_FUNC { ev_set(out, VAL_UNDEF, 0); return 1 } 2395 let thisv: *i64 = ev_cell() 2396 if env_get(env, jp_src(ctx), THIS_NS, 4, thisv) == 1 { ev_set(out, VAL_UNDEF, 0); return 1 } 2397 let r: i64 = js_call_userfn(ctx, idx, env, genv, (sv[1]) as *i64, thisv, out) 2398 if r == 1 { return 1 } 2399 ev_set(out, VAL_UNDEF, 0) 2400 return 0 2401} 2402// super.method(args) -- invoke the PARENT prototype's `method` with the CURRENT `this`, so an 2403// overriding B.method can delegate up to A.method on the same instance. Resolves the method off 2404// the parent ctor's .prototype (walking its chain for grand-parent methods). 2405func js_eval_super_method(ctx: *i64, idx: i64, callee: i64, env: *i64, genv: *i64, out: *i64) -> i64 { 2406 let sv: *i64 = ev_cell() 2407 if env_get(env, jp_src(ctx), SUPER_NS, 5, sv) == 1 { ev_set(out, VAL_UNDEF, 0); return 1 } 2408 if sv[0] != VAL_FUNC { ev_set(out, VAL_UNDEF, 0); return 1 } 2409 let proto: *i64 = (func_prototype(sv[1])) as *i64 2410 let key: *i64 = ev_prop_key(ctx, jp_nb(ctx, callee)) 2411 let mv: *i64 = ev_cell() 2412 var found: i64 = obj_get(proto, key, mv) 2413 if found == 0 { found = obj_proto_lookup(proto, key, mv) } // method defined on a grand-parent prototype 2414 if found == 0 { ev_set(out, VAL_UNDEF, 0); return 1 } 2415 if mv[0] != VAL_FUNC { ev_set(out, VAL_UNDEF, 0); return 1 } 2416 let thisv: *i64 = ev_cell() 2417 if env_get(env, jp_src(ctx), THIS_NS, 4, thisv) == 1 { ev_set(out, VAL_UNDEF, 0); return 1 } 2418 return js_call_userfn(ctx, idx, env, genv, (mv[1]) as *i64, thisv, out) 2419} 2420// Function.prototype.call / .apply: re-dispatch the receiver function with an EXPLICIT `this` (arg0) and 2421// arguments (call: arg1.. ; apply: the elements of arg1's array). fnclos = the receiver VAL_FUNC's closure 2422// record. Both are pervasive in real JS (inheritance via superConstructor.call, arguments-forwarding). 2423func js_fn_call_apply(ctx: *i64, idx: i64, env: *i64, genv: *i64, fnclos: *i64, is_apply: i64, out: *i64) -> i64 { 2424 let argbuf: *i64 = sys_mmap(8 * 2 * 256) as *i64 2425 let n: i64 = js_eval_args(ctx, idx, env, genv, argbuf) 2426 if n < 0 { ev_set(out, VAL_UNDEF, 0); return 1 } 2427 let thisv: *i64 = ev_cell() 2428 if n >= 1 { thisv[0] = ja_t(argbuf, 0); thisv[1] = ja_p(argbuf, 0) } else { ev_set(thisv, VAL_UNDEF, 0) } 2429 // SIZE the spread buffer to the ACTUAL arg count. fn.apply(this, arr) with a large arr must not 2430 // overflow a fixed 256-slot argbuf: jQuery's Sizzle does push.apply(results, getElementsByTagName(...)) 2431 // spreading HUNDREDS of nodes -> a 256-cap silently wrote past the mmap page, corrupting the count so 2432 // $('div') on a 292-div page collapsed to 0. cap2 = spread length (apply) or n (call), + slack. 2433 var cap2: i64 = 4 2434 if is_apply == 1 { if n >= 2 { if ja_t(argbuf, 1) == VAL_ARRAY { cap2 = arr_len((ja_p(argbuf, 1)) as *i64) + 4 } } } else { cap2 = n + 4 } 2435 let argbuf2: *i64 = sys_mmap(8 * 2 * cap2) as *i64 2436 var argc2: i64 = 0 2437 if is_apply == 1 { 2438 if n >= 2 { if ja_t(argbuf, 1) == VAL_ARRAY { 2439 let arr: *i64 = (ja_p(argbuf, 1)) as *i64 2440 let al: i64 = arr_len(arr) 2441 var i: i64 = 0 2442 while i < al { 2443 let eb: *i64 = ev_cell() 2444 if arr_get(arr, i, eb) == 0 { ev_set(eb, VAL_UNDEF, 0) } 2445 argbuf2[argc2 * 2] = eb[0]; argbuf2[argc2 * 2 + 1] = eb[1]; argc2 = argc2 + 1; i = i + 1 2446 } 2447 } } 2448 } else { 2449 var i: i64 = 1 2450 while i < n { argbuf2[argc2 * 2] = ja_t(argbuf, i); argbuf2[argc2 * 2 + 1] = ja_p(argbuf, i); argc2 = argc2 + 1; i = i + 1 } 2451 } 2452 return js_call_core(ctx, fnclos, argbuf2, argc2, thisv, genv, out) 2453} 2454// Function.prototype.call / .apply on a NATIVE receiver (bid = the builtin id, e.g. BI_OBJ_TOSTRING). 2455// jQuery drives its whole type system through this: `toString.call(obj)`, `hasOwn.call(obj,key)`, 2456// `fnToString.call(Ctor)`. arg0 becomes the native's `this`; the rest are its arguments (apply spreads 2457// arg1's array). Mirrors js_fn_call_apply but re-enters js_native_apply instead of js_call_core. 2458func js_native_call_apply(ctx: *i64, idx: i64, env: *i64, genv: *i64, bid: i64, is_apply: i64, out: *i64) -> i64 { 2459 let argbuf: *i64 = sys_mmap(8 * 2 * 256) as *i64 2460 let n: i64 = js_eval_args(ctx, idx, env, genv, argbuf) 2461 if n < 0 { ev_set(out, VAL_UNDEF, 0); return 1 } 2462 let thisv: *i64 = ev_cell() 2463 if n >= 1 { thisv[0] = ja_t(argbuf, 0); thisv[1] = ja_p(argbuf, 0) } else { ev_set(thisv, VAL_UNDEF, 0) } 2464 // SIZE the spread buffer to the ACTUAL arg count (see js_fn_call_apply) -- the native path is exactly 2465 // what jQuery's push.apply(results, getElementsByTagName(...)) hits, so a fixed 256-cap here is the 2466 // one that collapsed $('div')/$('a') on large real pages. cap2 = spread length (apply) or n (call). 2467 var cap2: i64 = 4 2468 if is_apply == 1 { if n >= 2 { if ja_t(argbuf, 1) == VAL_ARRAY { cap2 = arr_len((ja_p(argbuf, 1)) as *i64) + 4 } } } else { cap2 = n + 4 } 2469 let argbuf2: *i64 = sys_mmap(8 * 2 * cap2) as *i64 2470 var argc2: i64 = 0 2471 if is_apply == 1 { 2472 if n >= 2 { if ja_t(argbuf, 1) == VAL_ARRAY { 2473 let arr: *i64 = (ja_p(argbuf, 1)) as *i64 2474 let al: i64 = arr_len(arr) 2475 var i: i64 = 0 2476 while i < al { 2477 let eb: *i64 = ev_cell() 2478 if arr_get(arr, i, eb) == 0 { ev_set(eb, VAL_UNDEF, 0) } 2479 argbuf2[argc2 * 2] = eb[0]; argbuf2[argc2 * 2 + 1] = eb[1]; argc2 = argc2 + 1; i = i + 1 2480 } 2481 } } 2482 } else { 2483 var i: i64 = 1 2484 while i < n { argbuf2[argc2 * 2] = ja_t(argbuf, i); argbuf2[argc2 * 2 + 1] = ja_p(argbuf, i); argc2 = argc2 + 1; i = i + 1 } 2485 } 2486 return js_native_apply(ctx, genv, bid, thisv, argbuf2, argc2, out) 2487} 2488func js_eval_call(ctx: *i64, idx: i64, env: *i64, genv: *i64, out: *i64) -> i64 { 2489 let callee: i64 = jp_na(ctx, idx) 2490 // OPTIONAL CALL a?.(args) (ND_CALL extra=1): if the callee is null/undefined, short-circuit to undefined 2491 // (no throw). vk: `window.CSS?.supports?.(...)` -> CSS undefined -> whole chain undefined. The callee-eval 2492 // itself short-circuits cleanly for `a?.b` optional members, so this is a safe pre-check. 2493 if jp_nextra(ctx, idx) == 1 { 2494 let ocv: *i64 = ev_cell() 2495 if js_eval(ctx, callee, env, genv, ocv) == 1 { ev_set(out, VAL_UNDEF, 0); return 0 } 2496 if ocv[0] == VAL_NULL { ev_set(out, VAL_UNDEF, 0); return 0 } 2497 if ocv[0] == VAL_UNDEF { ev_set(out, VAL_UNDEF, 0); return 0 } 2498 } 2499 // ---- (0) super calls (extends classes): super(...) chains the parent ctor onto the current 2500 // `this`; super.m(...) invokes the parent's prototype method with the current `this`. Both 2501 // resolve the `@super` binding (parent ctor) that ND_CLASS installed in the class-body env. ---- 2502 if jp_nkind(ctx, callee) == ND_SUPER { return js_eval_super_ctor(ctx, idx, env, genv, out) } 2503 if jp_nkind(ctx, callee) == ND_MEMBER { 2504 if jp_nkind(ctx, jp_na(ctx, callee)) == ND_SUPER { return js_eval_super_method(ctx, idx, callee, env, genv, out) } 2505 } 2506 // ---- (1) method call: callee is a MEMBER access ---- 2507 if jp_nkind(ctx, callee) == ND_MEMBER { 2508 let thisv: *i64 = ev_cell() 2509 if js_eval(ctx, jp_na(ctx, callee), env, genv, thisv) == 1 { ev_set(out, VAL_UNDEF, 0); return 1 } 2510 let key: *i64 = ev_prop_key(ctx, jp_nb(ctx, callee)) 2511 // resolve a native method against the receiver's type. 2512 var bid: i64 = 0 2513 if thisv[0] == VAL_STR { bid = ev_native_str(key) } 2514 if thisv[0] == VAL_ARRAY { bid = ev_native_arr(key) } 2515 if thisv[0] == VAL_NUM { bid = ev_native_num(key) } 2516 if thisv[0] == VAL_FLOAT { bid = ev_native_num(key) } 2517 if thisv[0] == VAL_BOOL { bid = ev_native_num(key) } 2518 // user String.prototype / Array.prototype method (not a hardcoded builtin) -> call with this = receiver. 2519 if bid == 0 { if thisv[0] == VAL_STR { if js_strproto != 0 { let pm: *i64 = ev_cell(); if obj_get((js_strproto) as *i64, key, pm) == 1 { if pm[0] == VAL_FUNC { return js_call_userfn(ctx, idx, env, genv, (pm[1]) as *i64, thisv, out) } } } } } 2520 if bid == 0 { if thisv[0] == VAL_ARRAY { if js_arrproto != 0 { let pa: *i64 = ev_cell(); if obj_get((js_arrproto) as *i64, key, pa) == 1 { if pa[0] == VAL_FUNC { return js_call_userfn(ctx, idx, env, genv, (pa[1]) as *i64, thisv, out) } } } } } 2521 if thisv[0] == VAL_GLOBALNS { bid = ev_native_global(thisv[1], key) } 2522 if thisv[0] == VAL_PROMISE { bid = ev_native_promise(key) } // R-JS-PROMISE: p.then / p.catch 2523 if thisv[0] == VAL_RESPONSE { bid = ev_native_response(key) } // R-JS-FETCH: response.text() 2524 if thisv[0] == VAL_REGEX { bid = ev_native_regex(key) } // R-JS-REGEX: re.test(str) 2525 if thisv[0] == VAL_NATIVE { if thisv[1] == BI_STRING_CTOR { if ev_key_is(key, "fromCharCode\x00" as *u8) == 1 { bid = BI_STR_FROMCHARCODE } } } // String.fromCharCode static 2526 if thisv[0] == VAL_NATIVE { if thisv[1] == BI_ARRAY_CTOR { if ev_key_is(key, "isArray\x00" as *u8) == 1 { bid = BI_ARR_ISARRAY } } } // Array.isArray static 2527 // Function.prototype.call / .apply on a user function receiver. 2528 if thisv[0] == VAL_FUNC { 2529 if ev_key_is(key, "call\x00" as *u8) == 1 { return js_fn_call_apply(ctx, idx, env, genv, (thisv[1]) as *i64, 0, out) } 2530 if ev_key_is(key, "apply\x00" as *u8) == 1 { return js_fn_call_apply(ctx, idx, env, genv, (thisv[1]) as *i64, 1, out) } 2531 // a method on the function object itself (func_own) or inherited from Object.prototype 2532 // (e.g. `Ctor.inheritsFrom(shuper)`), called with `this` = the function. 2533 let pvf: *i64 = ev_cell() 2534 if ev_get_prop(thisv, key, pvf) == 0 { 2535 if pvf[0] == VAL_FUNC { return js_call_userfn(ctx, idx, env, genv, (pvf[1]) as *i64, thisv, out) } 2536 if pvf[0] == VAL_NATIVE { return js_invoke_native(ctx, idx, env, genv, pvf[1], thisv, out) } 2537 } 2538 } 2539 // Function.prototype.call / .apply on a NATIVE receiver (jQuery's type system: toString.call(obj), 2540 // hasOwn.call(o,k), fnToString.call(Ctor)). The native's OWN builtin id is thisv[1]; .call/.apply 2541 // re-dispatch it with arg0 as the new `this`. Other method names (.toString) resolve via ev_get_prop. 2542 if thisv[0] == VAL_NATIVE { 2543 if ev_key_is(key, "call\x00" as *u8) == 1 { return js_native_call_apply(ctx, idx, env, genv, thisv[1], 0, out) } 2544 if ev_key_is(key, "apply\x00" as *u8) == 1 { return js_native_call_apply(ctx, idx, env, genv, thisv[1], 1, out) } 2545 if bid == 0 { 2546 let pvn: *i64 = ev_cell() 2547 if ev_get_prop(thisv, key, pvn) == 0 { 2548 if pvn[0] == VAL_NATIVE { return js_invoke_native(ctx, idx, env, genv, pvn[1], thisv, out) } 2549 if pvn[0] == VAL_FUNC { return js_call_userfn(ctx, idx, env, genv, (pvn[1]) as *i64, thisv, out) } 2550 } 2551 } 2552 } 2553 // user statics on a NAMESPACE receiver (Object.extend(...)) -- win over the fixed natives. 2554 if thisv[0] == VAL_GLOBALNS { 2555 let pvg: *i64 = ev_cell() 2556 if obj_get(js_ns_statics(thisv[1]), key, pvg) == 1 { 2557 if pvg[0] == VAL_FUNC { return js_call_userfn(ctx, idx, env, genv, (pvg[1]) as *i64, thisv, out) } 2558 if pvg[0] == VAL_NATIVE { return js_invoke_native(ctx, idx, env, genv, pvg[1], thisv, out) } 2559 } 2560 } 2561 if bid != 0 { return js_invoke_native(ctx, idx, env, genv, bid, thisv, out) } 2562 // a user FUNCTION stored as an object/array property: o.f() / a.f(). 2563 if thisv[0] == VAL_OBJECT { 2564 let pv: *i64 = ev_cell() 2565 if ev_get_prop(thisv, key, pv) == 0 { 2566 if pv[0] == VAL_FUNC { return js_call_userfn(ctx, idx, env, genv, (pv[1]) as *i64, thisv, out) } 2567 if pv[0] == VAL_NATIVE { return js_invoke_native(ctx, idx, env, genv, pv[1], thisv, out) } 2568 } 2569 } 2570 // missing method on the receiver -> honest ERROR (NOT silent undefined). This is the 2571 // tamper contract: `var o={}; o.nope()` and `(5).nope()` BOTH error (rc=1). 2572 if js_rt_dbg == 1 { sys_write(2, "CALL-ERR missing-method '" as *u8, 25); sys_write(2, ev_str_bytes(key), ev_str_len(key)); sys_write(2, "' recv-tag=" as *u8, 11); nx_dbg_num(thisv[0]); sys_write(2, "\n" as *u8, 1) } 2573 js_rt_mark(ctx, callee) 2574 ev_set(out, VAL_UNDEF, 0); return 1 2575 } 2576 // ---- (2) plain call: evaluate the callee to a function value ---- 2577 let cv: *i64 = ev_cell() 2578 if js_eval(ctx, callee, env, genv, cv) == 1 { ev_set(out, VAL_UNDEF, 0); return 1 } 2579 if cv[0] == VAL_FUNC { return js_call_userfn(ctx, idx, env, genv, (cv[1]) as *i64, 0 as *i64, out) } 2580 if cv[0] == VAL_NATIVE { 2581 let undv: *i64 = ev_cell() 2582 ev_set(undv, VAL_UNDEF, 0) 2583 return js_invoke_native(ctx, idx, env, genv, cv[1], undv, out) 2584 } 2585 if js_rt_dbg == 1 { 2586 sys_write(2, "CALL-ERR non-fn callee cv-tag=" as *u8, 30); nx_dbg_num(cv[0]) 2587 if jp_nkind(ctx, callee) == ND_IDENT { sys_write(2, " name='" as *u8, 7); sys_write(2, ((jp_src(ctx) as i64) + ev_tok_start(ctx, callee)) as *u8, ev_tok_len(ctx, callee)); sys_write(2, "'" as *u8, 1) } 2588 sys_write(2, "\n" as *u8, 1) 2589 } 2590 js_rt_mark(ctx, callee) 2591 ev_set(out, VAL_UNDEF, 0); return 1 // calling a non-function -> error 2592} 2593 2594// CALL a USER function CLOSURE (clos = [fnode, defenv] record ptr). Opens a call frame 2595// parented at the closure's CAPTURED defining env (NOT the global env -- the rung-6 fix), 2596// binds params from the CALL node's args, runs the body, catches RETURN. Because the frame 2597// parents `defenv`, the body's free variables resolve up the LEXICAL chain to the enclosing 2598// function's locals (proper closures), and a top-level function's defenv IS genv (so globals 2599// + recursion still resolve). Extracted so the plain-call + property-method-call paths share 2600// one implementation. (`this` binding for user fns = OPEN; arrows would inherit it lexically.) 2601func js_call_userfn(ctx: *i64, idx: i64, env: *i64, genv: *i64, clos: *i64, thisval: *i64, out: *i64) -> i64 { 2602 // Evaluate the CALL node's AST argument expressions into a contiguous VALUE buffer (2 i64 2603 // per arg = [t,p]) -- then delegate to the shared closure-invoke core. This is the normal 2604 // call path; the array-iteration methods build their own argbuf and call js_call_core 2605 // directly with PRE-EVALUATED element/index values (the inverse direction). 2606 let argbuf: *i64 = sys_mmap(8 * 2 * 256) as *i64 2607 let n: i64 = js_eval_args(ctx, idx, env, genv, argbuf) // n = post-spread-expansion arg count 2608 if n < 0 { ev_set(out, VAL_UNDEF, 0); return 1 } // an argument expression errored 2609 let rr: i64 = js_call_core(ctx, clos, argbuf, n, thisval, genv, out) 2610 if rr == 1 { if js_rt_dbg == 1 { 2611 // gated CALL-TRACE: fires on each frame as an error unwinds -> innermost-first JS stack trace. 2612 // Anchor = the CALLEE node (exact for ident callees); excerpt shows the argument expressions. 2613 let cb2: i64 = ev_tok_start(ctx, jp_na(ctx, idx)) 2614 sys_write(2, "CALL-TRACE @" as *u8, 12) 2615 nx_dbg_num(cb2) 2616 sys_write(2, " '" as *u8, 2) 2617 let sp4: *u8 = jp_src(ctx) 2618 var ti2: i64 = cb2 2619 let te2: i64 = cb2 + 56 2620 while ti2 < te2 { if sp4[ti2] == (0 as u8) { ti2 = te2 } else { sys_write(2, ((sp4 as i64) + ti2) as *u8, 1); ti2 = ti2 + 1 } } 2621 sys_write(2, "'\n" as *u8, 2) 2622 } } 2623 return rr 2624} 2625 2626// SHARED CLOSURE-INVOKE CORE (rung 7). Given a closure value (clos = [fnode, defenv] record) 2627// + an array of PRE-EVALUATED argument value-cells (argbuf, 2 i64 per arg) + argc, open a 2628// call frame parented at the closure's CAPTURED defenv (lexical scope -- rung 6), bind the 2629// params positionally from argbuf (missing args = undefined; extra args ignored), pre-hoist 2630// the body's nested function declarations, run the body, and catch RETURN. Returns 0 ok 2631// (value in out) or 1 ERROR. Used by BOTH the normal AST call (js_call_userfn) and the array 2632// iteration methods (forEach/map/filter/reduce/some/every) -- one call core, no duplication. 2633// Bind a destructuring pattern (ND_OBJ_PAT / ND_ARRAY_PAT) against a value into env. Shared by var-decls and 2634// FUNCTION PARAMS (vk: `function e({needRedirect:e})`). ND_OBJ_PAT child = ND_IDENT whose token is the source 2635// property KEY; its slot-a (jp_na) is the ALIAS binding node when renamed (`{key: alias}`), else -1 (`{x}`). 2636func js_is_pattern_kind(k: i64) -> i64 { if k == ND_ARRAY_PAT { return 1 } if k == ND_OBJ_PAT { return 1 } return 0 } 2637func js_bind_pattern(ctx: *i64, patnode: i64, vt: i64, vp: i64, env: *i64) -> i64 { 2638 let nk: i64 = jp_nkind(ctx, patnode) 2639 if nk == ND_ARRAY_PAT { 2640 let pc: i64 = jp_nb(ctx, patnode) 2641 var pi: i64 = 0 2642 while pi < pc { 2643 let nm: i64 = jp_child_at(ctx, patnode, pi) 2644 let eb: *i64 = ev_cell() 2645 ev_set(eb, VAL_UNDEF, 0) 2646 if vt == VAL_ARRAY { arr_get((vp) as *i64, pi, eb) } 2647 if js_is_pattern_kind(jp_nkind(ctx, nm)) == 1 { js_bind_pattern(ctx, nm, eb[0], eb[1], env) } // NESTED [a,[b,c]] 2648 else { env_define(env, jp_src(ctx), ev_tok_start(ctx, nm), ev_tok_len(ctx, nm), eb[0], eb[1]) } 2649 pi = pi + 1 2650 } 2651 return 0 2652 } 2653 if nk == ND_OBJ_PAT { 2654 let pc: i64 = jp_nb(ctx, patnode) 2655 var pi: i64 = 0 2656 while pi < pc { 2657 let nm: i64 = jp_child_at(ctx, patnode, pi) 2658 let na: i64 = jp_na(ctx, nm) // alias node (-1 if bare) -- IDENT alias OR a nested pattern 2659 let eb: *i64 = ev_cell() 2660 ev_set(eb, VAL_UNDEF, 0) 2661 if vt == VAL_OBJECT { 2662 let key: *i64 = je_str_range(jp_src(ctx), ev_tok_start(ctx, nm), ev_tok_len(ctx, nm)) 2663 obj_get((vp) as *i64, key, eb) 2664 } 2665 if na >= 0 { 2666 if js_is_pattern_kind(jp_nkind(ctx, na)) == 1 { js_bind_pattern(ctx, na, eb[0], eb[1], env) } // {k: [a,b]} 2667 else { env_define(env, jp_src(ctx), ev_tok_start(ctx, na), ev_tok_len(ctx, na), eb[0], eb[1]) } 2668 } else { env_define(env, jp_src(ctx), ev_tok_start(ctx, nm), ev_tok_len(ctx, nm), eb[0], eb[1]) } 2669 pi = pi + 1 2670 } 2671 return 0 2672 } 2673 return 0 2674} 2675func js_call_core(ctx: *i64, clos: *i64, argbuf: *i64, argc: i64, thisval: *i64, genv: *i64, out: *i64) -> i64 { 2676 // VM RE-ENTRY (rung 5): a bytecode-VM closure arriving via a callback native / the event 2677 // loop routes back into the bound VM. Unbound hook -> honest error (never misread the record). 2678 if clos[0] == VMF_MAGIC { 2679 if js_vm_hook_addr == 0 { ev_set(out, VAL_UNDEF, 0); return 1 } 2680 let hf: func(i64, i64, i64, i64, i64, i64) -> i64 = (js_vm_hook_addr) as func(i64, i64, i64, i64, i64, i64) -> i64 2681 return hf(js_vm_vs_addr, clos as i64, argbuf as i64, argc, thisval as i64, out as i64) 2682 } 2683 let fnode: i64 = clos_fnode(clos) 2684 let defenv: *i64 = clos_defenv(clos) 2685 let params: i64 = jp_nb(ctx, fnode) 2686 let body: i64 = jp_nc(ctx, fnode) 2687 let pcount: i64 = jp_nb(ctx, params) 2688 let src: *u8 = jp_src(ctx) 2689 2690 let callenv: *i64 = env_child(defenv) 2691 if (thisval as i64) != 0 { env_define(callenv, jp_src(ctx), THIS_NS, 4, thisval[0], thisval[1]) } // bind `this` (method/new) 2692 var i: i64 = 0 2693 while i < pcount { 2694 let pnode: i64 = jp_child_at(ctx, params, i) 2695 if jp_nextra(ctx, pnode) == 1 { // rest param ...r -> array of the remaining args 2696 let ra: *i64 = arr_new() 2697 var ri: i64 = i 2698 var rk: i64 = 0 2699 while ri < argc { arr_set(ra, rk, argbuf[ri * 2 + 0], argbuf[ri * 2 + 1]); rk = rk + 1; ri = ri + 1 } 2700 env_define(callenv, src, ev_tok_start(ctx, pnode), ev_tok_len(ctx, pnode), VAL_ARRAY, ra as i64) 2701 i = pcount 2702 } else { 2703 let pk: i64 = jp_nkind(ctx, pnode) 2704 var vt: i64 = VAL_UNDEF 2705 var vp: i64 = 0 2706 if i < argc { vt = argbuf[i * 2 + 0]; vp = argbuf[i * 2 + 1] } 2707 if pk == ND_OBJ_PAT { js_bind_pattern(ctx, pnode, vt, vp, callenv) } // destructuring param {a,b:c} 2708 else { if pk == ND_ARRAY_PAT { js_bind_pattern(ctx, pnode, vt, vp, callenv) } // [a,b] 2709 else { 2710 if vt == VAL_UNDEF { 2711 let defx: i64 = jp_na(ctx, pnode) 2712 if defx != (0 - 1) { 2713 let db: *i64 = ev_cell() 2714 if js_eval(ctx, defx, callenv, genv, db) == 0 { vt = db[0]; vp = db[1] } 2715 } 2716 } 2717 env_define(callenv, src, ev_tok_start(ctx, pnode), ev_tok_len(ctx, pnode), vt, vp) 2718 } } 2719 i = i + 1 2720 } 2721 } 2722 2723 // `arguments`: array of ALL actual args (array-like: .length + [i]), bound per call frame. The name is 2724 // keyed by BYTE CONTENT (env_name_eq), so a static literal source works as the binding key. 2725 let argsarr: *i64 = arr_new() 2726 var aai: i64 = 0 2727 while aai < argc { arr_set(argsarr, aai, argbuf[aai * 2], argbuf[aai * 2 + 1]); aai = aai + 1 } 2728 env_define(callenv, "arguments\x00" as *u8, 0, 9, VAL_ARRAY, argsarr as i64) 2729 2730 // INTRA-BODY FUNCTION HOISTING (rung 7): pre-pass over the body block's DIRECT children, 2731 // binding each ND_FUNC_DECL as a CLOSURE capturing this call frame -- mirroring the 2732 // top-level hoist in js_run_source -- so a nested `function inner(){}` is callable BEFORE 2733 // its textual position. Re-binding when the decl is reached during execution is idempotent. 2734 js_hoist_body(ctx, body, callenv) 2735 2736 let st: i64 = js_exec_block(ctx, body, callenv, genv, out) 2737 if st == CS_ERROR { ev_set(out, VAL_UNDEF, 0); return 1 } 2738 if st == CS_RETURN { return 0 } // out holds the returned value 2739 // a break/continue that escaped to the function boundary was OUTSIDE any loop -> 2740 // NAMED error (real JS: Illegal break/continue statement), never silent. 2741 if st == CS_BREAK { ev_set(out, VAL_UNDEF, 0); return 1 } 2742 if st == CS_CONTINUE { ev_set(out, VAL_UNDEF, 0); return 1 } 2743 ev_set(out, VAL_UNDEF, 0); return 0 // fell off the end -> undefined 2744} 2745 2746// INTRA-BODY HOIST pre-pass: bind each ND_FUNC_DECL among `body` block's DIRECT children as a 2747// closure capturing `callenv`, so the decl is callable before its textual position. Mirrors 2748// the top-level hoist loop in js_run_source. Idempotent: the ND_FUNC_DECL exec case re-binds 2749// the same name to the same closure when reached. 2750func js_hoist_body(ctx: *i64, body: i64, callenv: *i64) -> i64 { 2751 if jp_nkind(ctx, body) != ND_BLOCK { return 0 } 2752 let cnt: i64 = jp_nb(ctx, body) 2753 var h: i64 = 0 2754 while h < cnt { 2755 let ch: i64 = jp_child_at(ctx, body, h) 2756 if jp_nkind(ctx, ch) == ND_FUNC_DECL { 2757 let nm: i64 = jp_na(ctx, ch) 2758 if nm >= 0 { 2759 let c: *i64 = clos_new(ch, callenv) 2760 env_define(callenv, jp_src(ctx), ev_tok_start(ctx, nm), ev_tok_len(ctx, nm), VAL_FUNC, c as i64) 2761 } 2762 } 2763 h = h + 1 2764 } 2765 return 0 2766} 2767 2768// ===================== native builtin invocation (R-JS-RUNTIME) ===================== 2769// Evaluate the CALL node's argument expressions into a contiguous VALUE buffer (2 i64 per 2770// arg: [t,p]) and return the count. Returns -1 on an argument eval ERROR (propagated). 2771// Evaluate a CALL/NEW node's argument expressions into a flat [t,p] buffer. A `...expr` argument 2772// (ND_SPREAD) is SPLICED: its operand is evaluated as an array and each element becomes its own 2773// positional arg -- so `f(...xs)`, `f(a, ...xs, b)`, and the synthesized derived-ctor `super(...args)` 2774// all forward correctly. RETURNS the ACTUAL (post-expansion) arg count -- callers MUST use this, not 2775// the static child count (they diverge whenever a spread is present). Capped at 256 (buffer size). 2776func js_eval_args(ctx: *i64, idx: i64, env: *i64, genv: *i64, argbuf: *i64) -> i64 { 2777 let acount: i64 = jp_nc(ctx, idx) 2778 let argstart: i64 = jp_nb(ctx, idx) 2779 let children: *i64 = jp_children(ctx) 2780 var oi: i64 = 0 2781 var i: i64 = 0 2782 while i < acount { 2783 let anode: i64 = children[argstart + i] 2784 if jp_nkind(ctx, anode) == ND_SPREAD { 2785 let sb: *i64 = ev_cell() 2786 if js_eval(ctx, jp_na(ctx, anode), env, genv, sb) == 1 { return 0 - 1 } 2787 if sb[0] == VAL_ARRAY { 2788 let src2: *i64 = (sb[1]) as *i64 2789 let slen: i64 = arr_len(src2) 2790 var si: i64 = 0 2791 while si < slen { 2792 if oi < 256 { 2793 let eb: *i64 = ev_cell() 2794 arr_get(src2, si, eb) 2795 argbuf[oi * 2 + 0] = eb[0] 2796 argbuf[oi * 2 + 1] = eb[1] 2797 oi = oi + 1 2798 } 2799 si = si + 1 2800 } 2801 } 2802 } else { 2803 let av: *i64 = ev_cell() 2804 if js_eval(ctx, anode, env, genv, av) == 1 { return 0 - 1 } 2805 if oi < 256 { argbuf[oi * 2 + 0] = av[0]; argbuf[oi * 2 + 1] = av[1]; oi = oi + 1 } 2806 } 2807 i = i + 1 2808 } 2809 return oi 2810} 2811// accessor for arg k's [t,p] cell inside an evaluated arg buffer. 2812func ja_t(argbuf: *i64, k: i64) -> i64 { return argbuf[k * 2 + 0] } 2813func ja_p(argbuf: *i64, k: i64) -> i64 { return argbuf[k * 2 + 1] } 2814 2815// classify a builtin id: does it REQUIRE a string receiver (`this` = a VAL_STR)? 2816// Named against the BI_STR_* constants (not a magic range) so adding a string method 2817// here keeps the receiver guard correct. 2818func ev_bid_needs_str(bid: i64) -> i64 { 2819 if bid == BI_STR_CHARAT { return 1 } 2820 if bid == BI_STR_INDEXOF { return 1 } 2821 if bid == BI_STR_SLICE { return 1 } 2822 if bid == BI_STR_UPPER { return 1 } 2823 if bid == BI_STR_LOWER { return 1 } 2824 if bid == BI_STR_INCLUDES { return 1 } 2825 if bid == BI_STR_CHARCODEAT { return 1 } 2826 if bid == BI_STR_SUBSTRING { return 1 } 2827 if bid == BI_STR_SUBSTR { return 1 } 2828 if bid == BI_STR_CONCAT { return 1 } 2829 return 0 2830} 2831// does this builtin REQUIRE an array receiver (`this` = a VAL_ARRAY)? 2832func ev_bid_needs_arr(bid: i64) -> i64 { 2833 // BI_ARR_PUSH removed: bi_arr_push now writes a generic array-LIKE receiver (jQuery pushStack collections), 2834 // so push.apply(collection, nodes) -- Sizzle's result accumulation in .find() -- is no longer VAL_ARRAY-gated. 2835 if bid == BI_ARR_POP { return 1 } 2836 // BI_ARR_INDEXOF removed: bi_arr_indexof now reads a generic array-LIKE receiver (jQuery collections, 2837 // NodeLists, arguments), so indexOf.call(arrayLikeObject, x) must NOT be gated to VAL_ARRAY -- jQuery's 2838 // inArray = indexOf.call(collection, elem) is the hot path this unblocks. 2839 if bid == BI_ARR_JOIN { return 1 } 2840 // BI_ARR_SLICE removed: bi_arr_slice now reads an array-LIKE receiver -- jQuery's .get()=slice.call(this) 2841 // and .slice()=slice.apply(this,args) borrow Array.prototype.slice on the array-like jQuery collection. 2842 if bid == BI_ARR_REVERSE { return 1 } 2843 if bid == BI_ARR_SPLICE { return 1 } 2844 if bid == BI_ARR_SORT { return 1 } 2845 if bid == BI_ARR_SHIFT { return 1 } 2846 if bid == BI_ARR_UNSHIFT { return 1 } 2847 if bid == BI_ARR_CONCAT { return 1 } 2848 if bid == BI_ARR_FOREACH { return 1 } 2849 if bid == BI_ARR_MAP { return 1 } 2850 if bid == BI_ARR_FILTER { return 1 } 2851 if bid == BI_ARR_REDUCE { return 1 } 2852 if bid == BI_ARR_SOME { return 1 } 2853 if bid == BI_ARR_EVERY { return 1 } 2854 return 0 2855} 2856// INVOKE a native builtin `bid` with receiver `thisv` and the CALL node's evaluated args. 2857// Returns 0 ok (result in out) or 1 ERROR. This is the single dispatch hub: every starter 2858// builtin routes through here so the method-call path and the value-call path agree. 2859// ================= R-JS-EVENTLOOP + R-JS-PROMISE: event loop + promises ================= 2860// genv[5] (lazy). MACRO queue = raw closure ptrs (setTimeout/rAF). MICRO queue = TYPED JOBS (kind0 = a 2861// plain queueMicrotask closure; kind1 = a promise reaction). The DRAIN (js_run_source_doc, after top-level): 2862// all microtasks, then EACH macrotask followed by a full microtask drain -- micro-before-next-macro BY 2863// CONSTRUCTION. Promises schedule .then reactions as microtasks, so promise order rides the same law. 2864func el_state(genv: *i64) -> *i64 { 2865 if genv[5] == 0 { 2866 let s: *i64 = sys_mmap(8 * (PEND_BASE + 1 + PEND_MAX * PEND_ENT)) as *i64 2867 s[0] = 0; s[1] = 0; s[2] = 0; s[3] = 0; s[PEND_BASE] = 0 // +pending count 2868 genv[5] = s as i64 2869 } 2870 return (genv[5]) as *i64 2871} 2872func el_push_job(genv: *i64, job: *i64) -> i64 { let s: *i64 = el_state(genv); if s[1] >= EL_MAXQ { return 1 } s[EL_HDR + s[1]] = job as i64; s[1] = s[1] + 1; return 0 } 2873// queueMicrotask(fn): wrap the closure in a kind-0 job. 2874func el_push_micro(genv: *i64, clos: i64) -> i64 { let j: *i64 = sys_mmap(8 * JOB_SZ) as *i64; j[0] = 0; j[1] = clos; return el_push_job(genv, j) } 2875func el_push_macro(genv: *i64, clos: i64) -> i64 { let s: *i64 = el_state(genv); if s[3] >= EL_MAXQ { return 1 } s[EL_HDR + EL_MAXQ + s[3]] = clos; s[3] = s[3] + 1; return 0 } 2876// schedule a promise REACTION as a microtask (kind-1 job). ptstate = the settled state (1 fulfil/2 reject), 2877// used for the no-handler pass-through case. 2878func el_push_reaction(genv: *i64, htag: i64, hpay: i64, vtag: i64, vpay: i64, resultprom: *i64, ptstate: i64) -> i64 { 2879 let j: *i64 = sys_mmap(8 * JOB_SZ) as *i64 2880 j[0] = 1; j[1] = htag; j[2] = hpay; j[3] = vtag; j[4] = vpay; j[5] = resultprom as i64; j[6] = ptstate 2881 return el_push_job(genv, j) 2882} 2883func prom_new() -> *i64 { let p: *i64 = sys_mmap(8 * (PROM_HDR + PROM_MAXR * PROM_REACT)) as *i64; p[0] = 0; p[1] = 0; p[2] = 0; p[3] = 0; return p } 2884// SETTLE a promise (idempotent): set state+value, then schedule every stored reaction as a microtask. 2885func prom_settle(ctx: *i64, genv: *i64, p: *i64, state: i64, vtag: i64, vpay: i64) -> i64 { 2886 if p[0] != 0 { return 0 } // already settled -> ignore (a promise resolves once) 2887 p[0] = state; p[1] = vtag; p[2] = vpay 2888 let n: i64 = p[3] 2889 var i: i64 = 0 2890 while i < n { 2891 let b: i64 = PROM_HDR + i * PROM_REACT 2892 var htag: i64 = 0; var hpay: i64 = 0 2893 if state == 1 { htag = p[b + 0]; hpay = p[b + 1] } else { htag = p[b + 2]; hpay = p[b + 3] } 2894 el_push_reaction(genv, htag, hpay, vtag, vpay, (p[b + 4]) as *i64, state) 2895 i = i + 1 2896 } 2897 p[3] = 0 2898 return 0 2899} 2900// attach a (onF,onR)->resultprom reaction. Pending -> store; already settled -> schedule now (still async). 2901func prom_add_reaction(ctx: *i64, genv: *i64, p: *i64, onFtag: i64, onFpay: i64, onRtag: i64, onRpay: i64, resultprom: *i64) -> i64 { 2902 if p[0] == 0 { 2903 let c: i64 = p[3] 2904 if c < PROM_MAXR { 2905 let b: i64 = PROM_HDR + c * PROM_REACT 2906 p[b + 0] = onFtag; p[b + 1] = onFpay; p[b + 2] = onRtag; p[b + 3] = onRpay; p[b + 4] = resultprom as i64 2907 p[3] = c + 1 2908 } 2909 return 0 2910 } 2911 var htag: i64 = 0; var hpay: i64 = 0 2912 if p[0] == 1 { htag = onFtag; hpay = onFpay } else { htag = onRtag; hpay = onRpay } 2913 el_push_reaction(genv, htag, hpay, p[1], p[2], resultprom, p[0]) 2914 return 0 2915} 2916// ADOPT: make R settle when `inner` settles (used when a .then handler RETURNS a promise). The handlers are 2917// the internal VAL_RESOLVE/VAL_REJECT markers bound to R. 2918func prom_adopt(ctx: *i64, genv: *i64, R: *i64, inner: *i64) -> i64 { 2919 return prom_add_reaction(ctx, genv, inner, VAL_RESOLVE, R as i64, VAL_REJECT, R as i64, (0 as *i64)) 2920} 2921// resolve R with a value: a promise value -> ADOPT it; anything else -> fulfil R with it. 2922func prom_resolve_value(ctx: *i64, genv: *i64, R: *i64, tag: i64, pay: i64) -> i64 { 2923 if tag == VAL_PROMISE { return prom_adopt(ctx, genv, R, (pay) as *i64) } 2924 return prom_settle(ctx, genv, R, 1, tag, pay) 2925} 2926// RUN one reaction microtask: apply the handler to the value, then settle the result promise. 2927func prom_run_reaction(ctx: *i64, genv: *i64, htag: i64, hpay: i64, vtag: i64, vpay: i64, resultprom: *i64, ptstate: i64) -> i64 { 2928 if htag == VAL_FUNC { 2929 let ab: *i64 = sys_mmap(8 * 2 * 2) as *i64 2930 ab[0] = vtag; ab[1] = vpay 2931 let res: *i64 = ev_cell() 2932 let undv: *i64 = ev_cell(); ev_set(undv, VAL_UNDEF, 0) 2933 let rc: i64 = js_call_core(ctx, (hpay) as *i64, ab, 1, undv, genv, res) 2934 if rc == 0 { prom_resolve_value(ctx, genv, resultprom, res[0], res[1]) } 2935 else { prom_settle(ctx, genv, resultprom, 2, VAL_UNDEF, 0) } // a throw in the handler -> reject (reason=undefined; named) 2936 return 0 2937 } 2938 if htag == VAL_RESOLVE { return prom_resolve_value(ctx, genv, (hpay) as *i64, vtag, vpay) } 2939 if htag == VAL_REJECT { return prom_settle(ctx, genv, (hpay) as *i64, 2, vtag, vpay) } 2940 // no handler (undefined) -> PASS THROUGH the settled value/state to the result promise. 2941 if ptstate == 1 { return prom_resolve_value(ctx, genv, resultprom, vtag, vpay) } 2942 return prom_settle(ctx, genv, resultprom, 2, vtag, vpay) 2943} 2944// run every queued MICROtask to exhaustion (both plain closures and promise reactions; a microtask may 2945// enqueue more -- real JS drains them all before the next macrotask). 2946func el_drain_micro(ctx: *i64, genv: *i64) -> i64 { 2947 let s: *i64 = el_state(genv) 2948 let ab: *i64 = sys_mmap(8 * 2 * 2) as *i64 2949 let tmp: *i64 = ev_cell() 2950 let undv: *i64 = ev_cell(); ev_set(undv, VAL_UNDEF, 0) 2951 var guard: i64 = 0 2952 while s[0] < s[1] { 2953 if guard > 2000000 { return 1 } // runaway-microtask backstop 2954 let job: *i64 = (s[EL_HDR + s[0]]) as *i64; s[0] = s[0] + 1 2955 if js_rt_dbg == 2 { if job[0] == 0 { sys_write(2, "MICRO fnode@" as *u8, 12); nx_dbg_num(ev_tok_start(ctx, clos_fnode((job[1]) as *i64))); sys_write(2, "\n" as *u8, 1) } } 2956 if job[0] == 0 { js_call_core(ctx, (job[1]) as *i64, ab, 0, undv, genv, tmp) } 2957 else { prom_run_reaction(ctx, genv, job[1], job[2], job[3], job[4], (job[5]) as *i64, job[6]) } 2958 guard = guard + 1 2959 } 2960 return 0 2961} 2962// the full event-loop drain: microtasks, then each macrotask followed by a microtask drain. 2963func el_drain(ctx: *i64, genv: *i64) -> i64 { 2964 if genv[5] == 0 { return 0 } // event loop never touched -> nothing to do (sync scripts unaffected) 2965 let s: *i64 = el_state(genv) 2966 let ab: *i64 = sys_mmap(8 * 2 * 2) as *i64 2967 let tmp: *i64 = ev_cell() 2968 let undv: *i64 = ev_cell(); ev_set(undv, VAL_UNDEF, 0) 2969 el_drain_micro(ctx, genv) 2970 var guard: i64 = 0 2971 while s[2] < s[3] { 2972 if guard > 2000000 { return 1 } // runaway-macrotask backstop 2973 let clos: i64 = s[EL_HDR + EL_MAXQ + s[2]]; s[2] = s[2] + 1 2974 if js_rt_dbg == 2 { sys_write(2, "MACRO fnode@" as *u8, 12); nx_dbg_num(ev_tok_start(ctx, clos_fnode((clos as *i64)))); sys_write(2, "\n" as *u8, 1) } 2975 js_call_core(ctx, (clos as *i64), ab, 0, undv, genv, tmp) 2976 el_drain_micro(ctx, genv) 2977 guard = guard + 1 2978 } 2979 return 0 2980} 2981// Promise.resolve(v): a promise value is returned as-is (idempotent); anything else -> a fulfilled promise. 2982func bi_prom_resolve(argbuf: *i64, argc: i64, out: *i64) -> i64 { 2983 if argc >= 1 { if argbuf[0] == VAL_PROMISE { ev_set(out, VAL_PROMISE, argbuf[1]); return 0 } } 2984 let p: *i64 = prom_new(); p[0] = 1 2985 if argc >= 1 { p[1] = argbuf[0]; p[2] = argbuf[1] } else { p[1] = VAL_UNDEF; p[2] = 0 } 2986 ev_set(out, VAL_PROMISE, p as i64); return 0 2987} 2988func bi_prom_reject(argbuf: *i64, argc: i64, out: *i64) -> i64 { 2989 let p: *i64 = prom_new(); p[0] = 2 2990 if argc >= 1 { p[1] = argbuf[0]; p[2] = argbuf[1] } else { p[1] = VAL_UNDEF; p[2] = 0 } 2991 ev_set(out, VAL_PROMISE, p as i64); return 0 2992} 2993// p.then(onF, onR) / p.catch(onR): attach handlers, return a NEW promise resolved by the handler's return. 2994func bi_prom_then(ctx: *i64, genv: *i64, thisv: *i64, argbuf: *i64, argc: i64, iscatch: i64, out: *i64) -> i64 { 2995 if thisv[0] != VAL_PROMISE { ev_set(out, VAL_UNDEF, 0); return 1 } 2996 let p: *i64 = (thisv[1]) as *i64 2997 var onFt: i64 = VAL_UNDEF; var onFp: i64 = 0 2998 var onRt: i64 = VAL_UNDEF; var onRp: i64 = 0 2999 if iscatch == 1 { if argc >= 1 { onRt = argbuf[0]; onRp = argbuf[1] } } 3000 else { 3001 if argc >= 1 { onFt = argbuf[0]; onFp = argbuf[1] } 3002 if argc >= 2 { onRt = argbuf[2]; onRp = argbuf[3] } 3003 } 3004 let R: *i64 = prom_new() 3005 prom_add_reaction(ctx, genv, p, onFt, onFp, onRt, onRp, R) 3006 ev_set(out, VAL_PROMISE, R as i64); return 0 3007} 3008// method resolver for a promise receiver (mirrors ev_native_str/arr). 3009func ev_native_promise(key: *i64) -> i64 { 3010 if ev_key_is(key, "then\x00" as *u8) == 1 { return BI_PROM_THEN } 3011 if ev_key_is(key, "catch\x00" as *u8) == 1 { return BI_PROM_CATCH } 3012 return 0 3013} 3014 3015// ================= R-JS-FETCH: fetch() + Response ================= 3016// SETTLE a FRESH promise (no reactions yet -> just set state+value; no scheduling). Used by fetch/text 3017// which create a promise and immediately settle it. 3018func prom_settle_fresh(p: *i64, state: i64, vtag: i64, vpay: i64) -> i64 { p[0] = state; p[1] = vtag; p[2] = vpay; return 0 } 3019func resp_new(status: i64, btag: i64, bpay: i64) -> *i64 { let r: *i64 = sys_mmap(8 * RESP_HDR) as *i64; r[0] = status; r[1] = btag; r[2] = bpay; return r } 3020func js_str_from_bytes(b: *u8, n: i64) -> *i64 { let rec: *i64 = ev_str_new(n); let dst: *u8 = ev_str_bytes(rec); var i: i64 = 0; while i < n { dst[i] = b[i]; i = i + 1 } return rec } 3021// fetch(url) -> Promise<Response>. A data: URL ("data:...,<body>") resolves inline to a 200 Response; any 3022// other URL -> a REJECTED promise (the engine stays TLS-free; rung 5's consumer drives real network). 3023func bi_fetch(genv: *i64, argbuf: *i64, argc: i64, out: *i64) -> i64 { 3024 let p: *i64 = prom_new() 3025 if argc < 1 { prom_settle_fresh(p, 2, VAL_UNDEF, 0); ev_set(out, VAL_PROMISE, p as i64); return 0 } 3026 if argbuf[0] != VAL_STR { prom_settle_fresh(p, 2, VAL_UNDEF, 0); ev_set(out, VAL_PROMISE, p as i64); return 0 } 3027 let urlrec: *i64 = (argbuf[1]) as *i64 3028 let ub: *u8 = ev_str_bytes(urlrec) 3029 let ul: i64 = ev_str_len(urlrec) 3030 if ul >= 6 { 3031 if (ub[0] & 0xff) == 100 { if (ub[1] & 0xff) == 97 { if (ub[2] & 0xff) == 116 { if (ub[3] & 0xff) == 97 { if (ub[4] & 0xff) == 58 { 3032 var ci: i64 = 5 3033 var found: i64 = 0 3034 while found == 0 { if ci >= ul { found = 2 } else { if (ub[ci] & 0xff) == 44 { found = 1 } else { ci = ci + 1 } } } 3035 if found == 1 { 3036 let bstart: i64 = ci + 1 3037 let blen: i64 = ul - bstart 3038 let brec: *i64 = js_str_from_bytes(((ub as i64) + bstart) as *u8, blen) 3039 let r: *i64 = resp_new(200, VAL_STR, brec as i64) 3040 prom_settle_fresh(p, 1, VAL_RESPONSE, r as i64) 3041 ev_set(out, VAL_PROMISE, p as i64); return 0 3042 } 3043 } } } } } 3044 } 3045 // non-data URL: RECORD as PENDING -> the consumer (headless render) services it over sovereign TLS and 3046 // re-drains. Returns a PENDING promise (state 0); .then reactions attach and fire once serviced. 3047 pend_record(genv, urlrec, p) 3048 ev_set(out, VAL_PROMISE, p as i64); return 0 3049} 3050// response.text() -> Promise<string> (the body). (.json() = a named follow-on: JSON.parse(await r.text()).) 3051func bi_resp_text(thisv: *i64, out: *i64) -> i64 { 3052 if thisv[0] != VAL_RESPONSE { ev_set(out, VAL_UNDEF, 0); return 1 } 3053 let r: *i64 = (thisv[1]) as *i64 3054 let p: *i64 = prom_new() 3055 prom_settle_fresh(p, 1, r[1], r[2]) 3056 ev_set(out, VAL_PROMISE, p as i64); return 0 3057} 3058// response.json() -> Promise<parsed>. Reuses the sovereign JSON parser (bi_json_parse; forward-ref OK). 3059func bi_resp_json(thisv: *i64, out: *i64) -> i64 { 3060 if thisv[0] != VAL_RESPONSE { ev_set(out, VAL_UNDEF, 0); return 1 } 3061 let r: *i64 = (thisv[1]) as *i64 3062 let p: *i64 = prom_new() 3063 let ab: *i64 = sys_mmap(8 * 2 * 2) as *i64 3064 ab[0] = r[1]; ab[1] = r[2] // the body string as JSON.parse's arg0 3065 let parsed: *i64 = ev_cell() 3066 if bi_json_parse(ab, 1, parsed) == 0 { prom_settle_fresh(p, 1, parsed[0], parsed[1]) } else { prom_settle_fresh(p, 2, VAL_UNDEF, 0) } 3067 ev_set(out, VAL_PROMISE, p as i64); return 0 3068} 3069func ev_native_response(key: *i64) -> i64 { 3070 if ev_key_is(key, "text\x00" as *u8) == 1 { return BI_RESP_TEXT } 3071 if ev_key_is(key, "json\x00" as *u8) == 1 { return BI_RESP_JSON } 3072 return 0 3073} 3074 3075// ================= R-JS-XHR: XMLHttpRequest ================= 3076// An XHR is a VAL_OBJECT: open/send/etc are VAL_NATIVE props (the object-method path dispatches them); 3077// status/readyState/responseText/onload are plain props. send() resolves a data: URL inline + fires 3078// onreadystatechange/onload as MICROtasks (async, like a real XHR). 3079func bi_xhr_new(out: *i64) -> i64 { 3080 let o: *i64 = obj_new() 3081 obj_set(o, ev_cstr("open\x00" as *u8), VAL_NATIVE, BI_XHR_OPEN) 3082 obj_set(o, ev_cstr("send\x00" as *u8), VAL_NATIVE, BI_XHR_SEND) 3083 obj_set(o, ev_cstr("setRequestHeader\x00" as *u8), VAL_NATIVE, BI_XHR_NOOP) 3084 obj_set(o, ev_cstr("getResponseHeader\x00" as *u8), VAL_NATIVE, BI_XHR_NOOP) 3085 obj_set(o, ev_cstr("abort\x00" as *u8), VAL_NATIVE, BI_XHR_NOOP) 3086 obj_set(o, ev_cstr("status\x00" as *u8), VAL_NUM, 0) 3087 obj_set(o, ev_cstr("readyState\x00" as *u8), VAL_NUM, 0) 3088 obj_set(o, ev_cstr("responseText\x00" as *u8), VAL_STR, (ev_cstr("\x00" as *u8)) as i64) 3089 ev_set(out, VAL_OBJECT, o as i64); return 0 3090} 3091// `new <native>()` -> construct (currently XHR). These constructors take no args. 3092func js_construct_native(bid: i64, out: *i64) -> i64 { 3093 if bid == BI_XHR_NEW { return bi_xhr_new(out) } 3094 ev_set(out, VAL_UNDEF, 0); return 1 3095} 3096// native constructor WITH evaluated args (argbuf: [tag,pay] pairs). new Array(n)=length-n array of 3097// undefined; new Array(a,b,..)=elements; Array()/new Array()=empty. Falls back to the arg-less form. 3098// Error-object core shared by Error/TypeError/RangeError (the caller stamps .name): a plain object with 3099// .message = ToString(arg) when present+non-undefined, else "" (V8-exact). 2 data args by design -- the 3100// 3-data-arg helper-call miscompile (see gotchas) forbids threading the name through here. 3101func bi_err_build(argbuf: *i64, argc: i64) -> *i64 { 3102 let o: *i64 = obj_new() 3103 var mrec: i64 = 0 3104 var have: i64 = 0 3105 if argc >= 1 { if argbuf[0] != VAL_UNDEF { 3106 if argbuf[0] == VAL_STR { mrec = argbuf[1] } else { 3107 let tb: *i64 = ev_cell() 3108 tb[0] = argbuf[0] 3109 tb[1] = argbuf[1] 3110 mrec = (ev_coerce_str(tb)) as i64 3111 } 3112 have = 1 3113 } } 3114 if have == 0 { mrec = (ev_cstr("\x00" as *u8)) as i64 } // explicit-NUL literal = reliable empty string (gotcha: bare "" aliases) 3115 obj_set(o, ev_cstr("message\x00" as *u8), VAL_STR, mrec) 3116 return o 3117} 3118func js_construct_native_args(bid: i64, argbuf: *i64, argc: i64, out: *i64) -> i64 { 3119 if bid == BI_ARRAY_CTOR { 3120 let a: *i64 = arr_new() 3121 if argc == 1 { if argbuf[0] == VAL_NUM { 3122 let n: i64 = argbuf[1] 3123 if n < 0 { ev_set(out, VAL_UNDEF, 0); return 1 } // RangeError in real JS 3124 var i: i64 = 0 3125 while i < n { arr_set(a, i, VAL_UNDEF, 0); i = i + 1 } 3126 ev_set(out, VAL_ARRAY, a as i64); return 0 3127 } } 3128 var j: i64 = 0 3129 while j < argc { arr_set(a, j, argbuf[j * 2], argbuf[j * 2 + 1]); j = j + 1 } 3130 ev_set(out, VAL_ARRAY, a as i64); return 0 3131 } 3132 // Error family: { message, name }. Distinct bids per subtype so .name matches V8. message follows the 3133 // spec: present+non-undefined -> ToString(arg); absent/undefined -> "" (V8: new Error().message === ""). 3134 if bid == BI_ERROR_CTOR { 3135 let o: *i64 = bi_err_build(argbuf, argc) 3136 obj_set(o, ev_cstr("name\x00" as *u8), VAL_STR, (ev_cstr("Error\x00" as *u8)) as i64) 3137 ev_set(out, VAL_OBJECT, o as i64); return 0 3138 } 3139 if bid == BI_TYPEERR_CTOR { 3140 let o: *i64 = bi_err_build(argbuf, argc) 3141 obj_set(o, ev_cstr("name\x00" as *u8), VAL_STR, (ev_cstr("TypeError\x00" as *u8)) as i64) 3142 ev_set(out, VAL_OBJECT, o as i64); return 0 3143 } 3144 if bid == BI_RANGEERR_CTOR { 3145 let o: *i64 = bi_err_build(argbuf, argc) 3146 obj_set(o, ev_cstr("name\x00" as *u8), VAL_STR, (ev_cstr("RangeError\x00" as *u8)) as i64) 3147 ev_set(out, VAL_OBJECT, o as i64); return 0 3148 } 3149 if bid == BI_REGEX_CTOR { return bi_regex_ctor(argbuf, argc, out) } // new RegExp(pat,flags) 3150 return js_construct_native(bid, out) 3151} 3152func bi_xhr_open(thisv: *i64, argbuf: *i64, argc: i64, out: *i64) -> i64 { 3153 if thisv[0] == VAL_OBJECT { 3154 let o: *i64 = (thisv[1]) as *i64 3155 if argc >= 2 { obj_set(o, ev_cstr("@xurl\x00" as *u8), argbuf[2], argbuf[3]) } // arg1 = url 3156 obj_set(o, ev_cstr("readyState\x00" as *u8), VAL_NUM, 1) 3157 } 3158 ev_set(out, VAL_UNDEF, 0); return 0 3159} 3160func bi_xhr_send(ctx: *i64, genv: *i64, thisv: *i64, out: *i64) -> i64 { 3161 if thisv[0] != VAL_OBJECT { ev_set(out, VAL_UNDEF, 0); return 0 } 3162 let o: *i64 = (thisv[1]) as *i64 3163 var status: i64 = 0 3164 var bodyp: i64 = (ev_cstr("\x00" as *u8)) as i64 3165 let ub: *i64 = ev_cell() 3166 if obj_get(o, ev_cstr("@xurl\x00" as *u8), ub) == 1 { 3167 if ub[0] == VAL_STR { 3168 let urlrec: *i64 = (ub[1]) as *i64 3169 let uu: *u8 = ev_str_bytes(urlrec); let ul: i64 = ev_str_len(urlrec) 3170 if ul >= 6 { if (uu[0] & 0xff) == 100 { if (uu[1] & 0xff) == 97 { if (uu[2] & 0xff) == 116 { if (uu[3] & 0xff) == 97 { if (uu[4] & 0xff) == 58 { 3171 var ci: i64 = 5; var found: i64 = 0 3172 while found == 0 { if ci >= ul { found = 2 } else { if (uu[ci] & 0xff) == 44 { found = 1 } else { ci = ci + 1 } } } 3173 if found == 1 { let bs: i64 = ci + 1; let bl: i64 = ul - bs; bodyp = (js_str_from_bytes(((uu as i64) + bs) as *u8, bl)) as i64; status = 200 } 3174 } } } } } } 3175 } 3176 } 3177 obj_set(o, ev_cstr("status\x00" as *u8), VAL_NUM, status) 3178 obj_set(o, ev_cstr("responseText\x00" as *u8), VAL_STR, bodyp) 3179 obj_set(o, ev_cstr("readyState\x00" as *u8), VAL_NUM, 4) 3180 let cb: *i64 = ev_cell() 3181 if obj_get(o, ev_cstr("onreadystatechange\x00" as *u8), cb) == 1 { if cb[0] == VAL_FUNC { el_push_micro(genv, cb[1]) } } 3182 if obj_get(o, ev_cstr("onload\x00" as *u8), cb) == 1 { if cb[0] == VAL_FUNC { el_push_micro(genv, cb[1]) } } 3183 ev_set(out, VAL_UNDEF, 0); return 0 3184} 3185 3186// ================= R-JS-PENDING-FETCH: record + service (the real-web loop) ================= 3187// record a pending request: (url string, the promise to settle when serviced). 0 ok / 1 if full. 3188func pend_record(genv: *i64, urlrec: *i64, prom: *i64) -> i64 { 3189 let s: *i64 = el_state(genv) 3190 let c: i64 = s[PEND_BASE] 3191 if c >= PEND_MAX { return 1 } 3192 let b: i64 = PEND_BASE + 1 + c * PEND_ENT 3193 s[b + 0] = urlrec as i64; s[b + 1] = prom as i64; s[b + 2] = 0 3194 s[PEND_BASE] = c + 1 3195 return 0 3196} 3197// count of UNSERVICED pending requests (the consumer loop's termination test). 3198// ⚠NULL-GENV GUARD (2026-07-27): js_render_page_pending_begin returns 1 and leaves 3199// outbox[1]=0 on a PARSE ERROR, so a consumer that ignores the rc arrives here with 3200// genv=NULL. `genv[5]` then reads address 0x28 -- the exact SIGSEGV measured against 3201// real wikipedia. A browser must NEVER crash on unparseable page JS: guard genv 3202// itself before dereferencing it, in every pending accessor. 3203func js_pending_count(genv: *i64) -> i64 { 3204 if (genv as i64) == 0 { return 0 } 3205 if genv[5] == 0 { return 0 } 3206 let s: *i64 = (genv[5]) as *i64 3207 let c: i64 = s[PEND_BASE] 3208 var i: i64 = 0; var live: i64 = 0 3209 while i < c { if s[PEND_BASE + 1 + i * PEND_ENT + 2] == 0 { live = live + 1 } i = i + 1 } 3210 return live 3211} 3212// RAW count of recorded requests (serviced + unserviced) -- the consumer iterates 0..total each round. 3213func js_pending_total(genv: *i64) -> i64 { if (genv as i64) == 0 { return 0 } if genv[5] == 0 { return 0 } let s: *i64 = (genv[5]) as *i64; return s[PEND_BASE] } // two-step cast: inline `((genv[5]) as *i64)[k]` MISCOMPILES under nx_cc (returns garbage) -- see nx_cc-gotchas 3214// is pending entry i already serviced? (skip it in the consumer loop) 3215func js_pending_serviced(genv: *i64, i: i64) -> i64 { 3216 if (genv as i64) == 0 { return 1 } 3217 if genv[5] == 0 { return 1 } 3218 let s: *i64 = (genv[5]) as *i64 3219 if i < 0 { return 1 } 3220 if i >= s[PEND_BASE] { return 1 } 3221 return s[PEND_BASE + 1 + i * PEND_ENT + 2] 3222} 3223// write the URL C-string of pending entry `i` into buf; returns its length (0 if out of range). 3224func js_pending_url(genv: *i64, i: i64, buf: *u8, cap: i64) -> i64 { 3225 if (genv as i64) == 0 { return 0 } 3226 if genv[5] == 0 { return 0 } 3227 let s: *i64 = (genv[5]) as *i64 3228 if i < 0 { return 0 } 3229 if i >= s[PEND_BASE] { return 0 } 3230 let urlrec: *i64 = (s[PEND_BASE + 1 + i * PEND_ENT + 0]) as *i64 3231 let ub: *u8 = ev_str_bytes(urlrec); let ul: i64 = ev_str_len(urlrec) 3232 var n: i64 = ul; if n > (cap - 1) { n = cap - 1 } 3233 var k: i64 = 0; while k < n { buf[k] = ub[k]; k = k + 1 } buf[n] = 0 as u8 3234 return n 3235} 3236// SERVICE pending `i`: settle its promise with Response(status, body), mark serviced. Consumer then el_drains. 3237func js_pending_service(ctx: *i64, genv: *i64, i: i64, status: i64, body: *u8, bodylen: i64) -> i64 { 3238 if genv[5] == 0 { return 1 } 3239 let s: *i64 = (genv[5]) as *i64 3240 if i < 0 { return 1 } 3241 if i >= s[PEND_BASE] { return 1 } 3242 let b: i64 = PEND_BASE + 1 + i * PEND_ENT 3243 if s[b + 2] != 0 { return 0 } 3244 let brec: *i64 = js_str_from_bytes(body, bodylen) 3245 let r: *i64 = resp_new(status, VAL_STR, brec as i64) 3246 prom_settle(ctx, genv, (s[b + 1]) as *i64, 1, VAL_RESPONSE, r as i64) 3247 s[b + 2] = 1 3248 return 0 3249} 3250 3251func js_invoke_native(ctx: *i64, idx: i64, env: *i64, genv: *i64, bid: i64, thisv: *i64, out: *i64) -> i64 { 3252 // RECEIVER-TYPE GUARD (council BLOCKER fix): the string/array bi_* dereference 3253 // thisv[1] as a record pointer. A DETACHED method value called as a plain function 3254 // (`var f='hi'.charAt; f(0)` or `var p=[1].push; p(2)`) arrives here with thisv = 3255 // [VAL_UNDEF,0], so the deref would be a NULL crash (process exit 139). Require the 3256 // receiver to MATCH the method family BEFORE any deref; a mismatch is an honest 3257 // rc=1 error (exactly like the missing-method path, KAT39), never a crash. Math/ 3258 // Object/console builtins ignore `this`, so they are not guarded. (Guard kept HERE, 3259 // before arg-eval, to preserve the original error ORDER; js_native_apply re-guards 3260 // for its direct callers -- idempotent, zero behavior change on this path.) 3261 if ev_bid_needs_str(bid) == 1 { if thisv[0] != VAL_STR { ev_set(out, VAL_UNDEF, 0); return 1 } } 3262 if ev_bid_needs_arr(bid) == 1 { if thisv[0] != VAL_ARRAY { ev_set(out, VAL_UNDEF, 0); return 1 } } 3263 let argbuf: *i64 = sys_mmap(8 * 2 * 256) as *i64 3264 let argc: i64 = js_eval_args(ctx, idx, env, genv, argbuf) 3265 if argc < 0 { ev_set(out, VAL_UNDEF, 0); return 1 } // an argument errored 3266 let nr: i64 = js_native_apply(ctx, genv, bid, thisv, argbuf, argc, out) 3267 if nr == 1 { js_rt_mark(ctx, idx) } // a builtin returned an honest error (unsupported feature) -> record where 3268 return nr 3269} 3270// VALUE-BASED NATIVE CORE (bytecode-VM rung 4b seam): dispatch builtin `bid` over 3271// PRE-EVALUATED argument cells (argbuf, 2 i64 per arg) + receiver `thisv`. No AST/env 3272// access -- callable by BOTH the tree-walker (js_invoke_native evals the CALL node's args 3273// then delegates here) and the bytecode VM (whose args are already on its operand stack). 3274// ctx/genv feed the callback/DOM/event-loop builtins (bi_arr_foreach's js_call_core, 3275// bi_doc_* page reads, setTimeout's queues). WARNING: the callback-taking builtins 3276// (forEach/map/filter/reduce/some/every, setTimeout/queueMicrotask/rAF, promise then) 3277// invoke a VAL_FUNC argument via js_call_core, which expects a TREE-WALKER closure record 3278// [fnode,defenv]; passing a VM closure into them is the rung-5 wire (VM re-entry), not yet valid. 3279// NUMBER-receiver method resolver (mirrors ev_native_str/ev_native_arr; used by all three tiers' 3280// method-call dispatch for VAL_NUM/VAL_FLOAT/VAL_BOOL receivers). 3281func ev_native_num(key: *i64) -> i64 { 3282 if ev_key_is(key, "toString\x00" as *u8) == 1 { return BI_NUM_TOSTRING } 3283 return 0 3284} 3285// (n).toString([radix]): radix 10 (or float/bool receiver) -> the same string '+'-coercion produces; 3286// integer receiver with radix 2..36 -> base-N digits, lowercase, V8-exact for integers. 3287func bi_num_tostring(thisv: *i64, argbuf: *i64, argc: i64, out: *i64) -> i64 { 3288 var radix: i64 = 10 3289 if argc >= 1 { if argbuf[0] == VAL_NUM { radix = argbuf[1] } } 3290 if radix < 2 { radix = 10 } 3291 if radix > 36 { radix = 10 } 3292 if radix == 10 { ev_set(out, VAL_STR, (ev_coerce_str(thisv)) as i64); return 0 } 3293 if thisv[0] != VAL_NUM { ev_set(out, VAL_STR, (ev_coerce_str(thisv)) as i64); return 0 } // float+radix: decimal (named divergence) 3294 var m: i64 = thisv[1] 3295 var neg: i64 = 0 3296 if m < 0 { neg = 1; m = 0 - m } 3297 let tmp: *u8 = sys_mmap(80) 3298 var k: i64 = 0 3299 if m == 0 { tmp[0] = 48 as u8; k = 1 } 3300 while m > 0 { 3301 let d: i64 = m % radix 3302 var c: i64 = 48 + d 3303 if d >= 10 { c = 87 + d } 3304 tmp[k] = c as u8 3305 m = m / radix 3306 k = k + 1 3307 } 3308 var n: i64 = k 3309 if neg == 1 { n = n + 1 } 3310 let rec: *i64 = ev_str_new(n) 3311 let dst: *u8 = ev_str_bytes(rec) 3312 var w2: i64 = 0 3313 if neg == 1 { dst[0] = 45 as u8; w2 = 1 } 3314 var q: i64 = k - 1 3315 while q >= 0 { dst[w2] = tmp[q]; w2 = w2 + 1; q = q - 1 } 3316 ev_set(out, VAL_STR, rec as i64) 3317 return 0 3318} 3319func js_native_apply(ctx: *i64, genv: *i64, bid: i64, thisv: *i64, argbuf: *i64, argc: i64, out: *i64) -> i64 { 3320 if ev_bid_needs_str(bid) == 1 { if thisv[0] != VAL_STR { ev_set(out, VAL_UNDEF, 0); return 1 } } 3321 if ev_bid_needs_arr(bid) == 1 { if thisv[0] != VAL_ARRAY { ev_set(out, VAL_UNDEF, 0); return 1 } } 3322 // R-JS-EVENTLOOP free functions: enqueue the callback closure (arg0 = a VAL_FUNC; argbuf[1]=clos ptr). 3323 // A non-function callback is ignored (real JS throws; foundation is lenient -> named open). setTimeout 3324 // returns a timer id (a number); queueMicrotask returns undefined; clearTimeout is a no-op. 3325 if bid == BI_SETTIMEOUT { if argc >= 1 { if argbuf[0] == VAL_FUNC { el_push_macro(genv, argbuf[1]) } } ev_set(out, VAL_NUM, 1); return 0 } 3326 if bid == BI_RAF { if argc >= 1 { if argbuf[0] == VAL_FUNC { el_push_macro(genv, argbuf[1]) } } ev_set(out, VAL_NUM, 1); return 0 } 3327 if bid == BI_QUEUEMICROTASK { if argc >= 1 { if argbuf[0] == VAL_FUNC { el_push_micro(genv, argbuf[1]) } } ev_set(out, VAL_UNDEF, 0); return 0 } 3328 if bid == BI_CLEARTIMEOUT { ev_set(out, VAL_UNDEF, 0); return 0 } 3329 // R-JS-PROMISE: Promise.resolve/reject (free) + p.then/p.catch (this = a promise). 3330 if bid == BI_PROM_RESOLVE { return bi_prom_resolve(argbuf, argc, out) } 3331 if bid == BI_PROM_REJECT { return bi_prom_reject(argbuf, argc, out) } 3332 if bid == BI_PROM_THEN { return bi_prom_then(ctx, genv, thisv, argbuf, argc, 0, out) } 3333 if bid == BI_PROM_CATCH { return bi_prom_then(ctx, genv, thisv, argbuf, argc, 1, out) } 3334 // R-JS-FETCH: fetch() (free) + response.text() (this = a Response). 3335 if bid == BI_FETCH { return bi_fetch(genv, argbuf, argc, out) } 3336 if bid == BI_RESP_TEXT { return bi_resp_text(thisv, out) } 3337 if bid == BI_RESP_JSON { return bi_resp_json(thisv, out) } 3338 if bid == BI_RE_TEST { return bi_re_test(thisv, argbuf, argc, out) } // R-JS-REGEX 3339 if bid == BI_RE_EXEC { return bi_re_exec(thisv, argbuf, argc, out) } 3340 if bid == BI_STR_SEARCH { return bi_str_search(thisv, argbuf, argc, out) } 3341 if bid == BI_STR_MATCH { return bi_str_match(thisv, argbuf, argc, out) } 3342 if bid == BI_STR_REPLACE { return bi_str_replace(thisv, argbuf, argc, out) } 3343 if bid == BI_STR_SPLIT { return bi_str_split(thisv, argbuf, argc, out) } 3344 if bid == BI_REGEX_CTOR { return bi_regex_ctor(argbuf, argc, out) } // RegExp(...) plain call 3345 if bid == BI_NUM_TOSTRING { return bi_num_tostring(thisv, argbuf, argc, out) } // (n).toString([radix]) 3346 // Array/Error-family called WITHOUT `new` construct all the same (JS spec: Array(5) === new Array(5), 3347 // Error(m) === new Error(m)). Their bids are UNIQUE (111-114) so this dispatch can exist at all -- 3348 // the old 101/102 bids collided with BI_XHR_OPEN/BI_XHR_SEND above and never reached construct. 3349 if bid == BI_ARRAY_CTOR { return js_construct_native_args(bid, argbuf, argc, out) } 3350 if bid == BI_ERROR_CTOR { return js_construct_native_args(bid, argbuf, argc, out) } 3351 if bid == BI_TYPEERR_CTOR { return js_construct_native_args(bid, argbuf, argc, out) } 3352 if bid == BI_RANGEERR_CTOR { return js_construct_native_args(bid, argbuf, argc, out) } 3353 if bid == BI_BOM_NOOP { ev_set(out, VAL_UNDEF, 0); return 0 } // R-JS-BOM: location.reload/history.pushState/... 3354 if bid == BI_WIN_ADD_LISTENER { return bi_win_add_listener(genv, argbuf, argc, out) } 3355 if bid == BI_STUB_NULL { ev_set(out, VAL_NULL, 0); return 0 } // headless element-stub method -> null 3356 if bid == BI_STUB_RETARG { if argc >= 1 { ev_set(out, ja_t(argbuf, 0), ja_p(argbuf, 0)) } else { ev_set(out, VAL_UNDEF, 0) } return 0 } // -> arg0 (chainable) 3357 if bid == BI_EL_CLONE { ev_set(out, VAL_OBJECT, (je_headless_el_stub()) as i64); return 0 } // cloneNode -> fresh stub 3358 if bid == BI_STUB_EMPTYARR { ev_set(out, VAL_ARRAY, (arr_new()) as i64); return 0 } 3359 if bid == BI_STUB_FALSE { ev_set(out, VAL_BOOL, 0); return 0 } 3360 if bid == BI_DATE_NOW { ev_set(out, VAL_NUM, sys_now_ms()); return 0 } // Date.now() -> ms timestamp 3361 if bid == BI_XHR_NEW { return bi_xhr_new(out) } // R-JS-XHR 3362 if bid == BI_XHR_OPEN { return bi_xhr_open(thisv, argbuf, argc, out) } 3363 if bid == BI_XHR_SEND { return bi_xhr_send(ctx, genv, thisv, out) } 3364 if bid == BI_XHR_NOOP { ev_set(out, VAL_UNDEF, 0); return 0 } 3365 // String methods (this = a string value). 3366 if bid == BI_STR_CHARAT { return bi_str_charat(thisv, argbuf, argc, out) } 3367 if bid == BI_STR_INDEXOF { return bi_str_indexof(thisv, argbuf, argc, out) } 3368 if bid == BI_STR_SLICE { return bi_str_slice(thisv, argbuf, argc, out) } 3369 if bid == BI_STR_UPPER { return bi_str_case(thisv, 1, out) } 3370 if bid == BI_STR_LOWER { return bi_str_case(thisv, 0, out) } 3371 if bid == BI_STR_INCLUDES { return bi_str_includes(thisv, argbuf, argc, out) } 3372 // Array methods (this = an array value). 3373 if bid == BI_ARR_PUSH { return bi_arr_push(thisv, argbuf, argc, out) } 3374 if bid == BI_ARR_POP { return bi_arr_pop(thisv, out) } 3375 if bid == BI_ARR_SHIFT { return bi_arr_shift(thisv, out) } 3376 if bid == BI_ARR_UNSHIFT { return bi_arr_unshift(thisv, argbuf, argc, out) } 3377 if bid == BI_ARR_CONCAT { return bi_arr_concat(thisv, argbuf, argc, out) } 3378 if bid == BI_STR_CONCAT { return bi_str_concat_m(thisv, argbuf, argc, out) } 3379 if bid == BI_ARR_ISARRAY { var iar: i64 = 0; if argc >= 1 { if ja_t(argbuf, 0) == VAL_ARRAY { iar = 1 } } ev_set(out, VAL_BOOL, iar); return 0 } 3380 if bid == BI_ARR_INDEXOF { return bi_arr_indexof(thisv, argbuf, argc, out) } 3381 if bid == BI_ARR_JOIN { return bi_arr_join(thisv, argbuf, argc, out) } 3382 if bid == BI_ARR_SLICE { return bi_arr_slice(thisv, argbuf, argc, out) } 3383 if bid == BI_ARR_REVERSE { return bi_arr_reverse(thisv, out) } 3384 if bid == BI_ARR_SPLICE { return bi_arr_splice(thisv, argbuf, argc, out) } 3385 // Array ITERATION methods (rung 7) -- they CALL BACK into the user closure (arg0), so they 3386 // need ctx + genv to invoke js_call_core. argbuf[0] = the callback, argbuf[1] = reduce init. 3387 if bid == BI_ARR_FOREACH { return bi_arr_foreach(ctx, genv, thisv, argbuf, argc, out) } 3388 if bid == BI_ARR_MAP { return bi_arr_map(ctx, genv, thisv, argbuf, argc, out) } 3389 if bid == BI_ARR_FILTER { return bi_arr_filter(ctx, genv, thisv, argbuf, argc, out) } 3390 if bid == BI_ARR_REDUCE { return bi_arr_reduce(ctx, genv, thisv, argbuf, argc, out) } 3391 if bid == BI_ARR_SOME { return bi_arr_some(ctx, genv, thisv, argbuf, argc, 0, out) } 3392 if bid == BI_ARR_EVERY { return bi_arr_some(ctx, genv, thisv, argbuf, argc, 1, out) } 3393 if bid == BI_ARR_SORT { return bi_arr_sort(ctx, genv, thisv, argbuf, argc, out) } 3394 // Object.* (operand = arg0). 3395 if bid == BI_OBJ_KEYS { return bi_obj_keys(argbuf, argc, out) } 3396 if bid == BI_OBJ_VALUES { return bi_obj_values(argbuf, argc, out) } 3397 if bid == BI_OBJ_DEFINEPROP { return bi_obj_defineprop(argbuf, argc, out) } 3398 // Math.* (free functions over args). 3399 if bid == BI_MATH_MAX { return bi_math_maxmin(argbuf, argc, 1, out) } 3400 if bid == BI_MATH_MIN { return bi_math_maxmin(argbuf, argc, 0, out) } 3401 if bid == BI_MATH_ABS { return bi_math_abs(argbuf, argc, out) } 3402 if bid == BI_MATH_FLOOR { return bi_math_floorceil(argbuf, argc, 0, out) } 3403 if bid == BI_MATH_CEIL { return bi_math_floorceil(argbuf, argc, 1, out) } 3404 if bid == BI_MATH_POW { return bi_math_pow(argbuf, argc, out) } 3405 if bid == BI_MATH_RANDOM { return bi_math_random(out) } 3406 if bid == BI_MATH_SQRT { return bi_math_sqrt(argbuf, argc, out) } 3407 if bid == BI_STR_CHARCODEAT { return bi_str_charcodeat(thisv, argbuf, argc, out) } 3408 if bid == BI_STR_FROMCHARCODE { return bi_str_fromcharcode(argbuf, argc, out) } 3409 if bid == BI_STR_SUBSTRING { return bi_str_substring(thisv, argbuf, argc, out) } 3410 if bid == BI_STR_SUBSTR { return bi_str_substr(thisv, argbuf, argc, out) } 3411 if bid == BI_PARSEINT { return bi_parseint(argbuf, argc, out) } 3412 if bid == BI_STRING_CTOR { 3413 if argc < 1 { ev_set(out, VAL_STR, (ev_cstr("\x00" as *u8)) as i64); return 0 } 3414 ev_set(out, VAL_STR, (ev_coerce_str_arg(argbuf, 0)) as i64) 3415 return 0 3416 } 3417 // console.* (page-observability primitive). 3418 if bid == BI_CONSOLE_LOG { return bi_console_log(argbuf, argc, out) } 3419 // document.* (DOM binding) -- reads the page HTML from the global env (genv[2]/genv[3]). 3420 if bid == BI_DOC_GET_BY_ID { return bi_doc_get_by_id(genv, argbuf, argc, out) } 3421 if bid == BI_DOC_QUERY_SELECTOR { return bi_doc_query_selector(genv, argbuf, argc, out) } 3422 if bid == BI_EL_GET_ATTR { return bi_el_get_attr(genv, thisv, argbuf, argc, out) } 3423 if bid == BI_DOC_QUERY_SELECTOR_ALL { return bi_doc_query_selector_all(genv, argbuf, argc, out) } 3424 if bid == BI_DOC_CREATE_ELEMENT { return bi_doc_create_element(genv, argbuf, argc, out) } 3425 if bid == BI_DOC_CREATE_FRAGMENT { return bi_doc_create_fragment(genv, out) } 3426 if bid == BI_DOC_CREATE_TEXT { return bi_doc_create_text(genv, argbuf, argc, out) } 3427 if bid == BI_DOC_GET_BY_TAG { return bi_doc_get_by_tag_class(genv, argbuf, argc, 3, out) } 3428 if bid == BI_DOC_GET_BY_CLASS { return bi_doc_get_by_tag_class(genv, argbuf, argc, 2, out) } 3429 if bid == BI_EL_GET_BY_TAG { return bi_el_get_by_tag(thisv, argbuf, argc, out) } 3430 if bid == BI_EL_GET_BY_CLASS { return bi_el_get_by_class(thisv, argbuf, argc, out) } 3431 if bid == BI_EL_QSA { return bi_el_qsa(thisv, argbuf, argc, out) } 3432 if bid == BI_EL_QS { return bi_el_qs(thisv, argbuf, argc, out) } 3433 if bid == BI_EL_MATCHES { return bi_el_matches(thisv, argbuf, argc, out) } 3434 if bid == BI_EL_GET_ATTR_NODE { return bi_el_get_attr_node(thisv, argbuf, argc, out) } 3435 if bid == BI_EL_REMOVE_CHILD { return bi_el_remove_child(thisv, argbuf, argc, out) } 3436 if bid == BI_EL_APPEND_CHILD { return bi_el_append_child(thisv, argbuf, argc, out) } 3437 if bid == BI_EL_CLONE_TREE { return bi_el_clone_tree(thisv, out) } 3438 if bid == BI_EL_CMPDOCPOS { return bi_el_cmpdocpos(thisv, argbuf, argc, out) } 3439 if bid == BI_EL_SET_ATTR { return bi_el_set_attr(thisv, argbuf, argc, out) } 3440 if bid == BI_EL_ADD_LISTENER { return bi_el_add_listener(genv, thisv, argbuf, argc, out) } // R-JS-EVENT 3441 if bid == BI_EL_CLICK { return bi_el_click(ctx, genv, thisv, out) } 3442 if bid == BI_CLS_TOGGLE { return bi_cls_do(thisv, argbuf, argc, out, 0) } // R-JS-CLASSLIST 3443 if bid == BI_CLS_ADD { return bi_cls_do(thisv, argbuf, argc, out, 1) } 3444 if bid == BI_CLS_REMOVE { return bi_cls_do(thisv, argbuf, argc, out, 2) } 3445 if bid == BI_CLS_CONTAINS { return bi_cls_do(thisv, argbuf, argc, out, 3) } 3446 if bid == BI_JSON_PARSE { return bi_json_parse(argbuf, argc, out) } 3447 // Object.prototype methods (this = the receiver) + Object.getPrototypeOf (operand = arg0). 3448 if bid == BI_OBJ_TOSTRING { return bi_obj_tostring(thisv, out) } 3449 if bid == BI_OBJ_HASOWN { return bi_obj_hasown(thisv, argbuf, argc, out) } 3450 if bid == BI_OBJ_VALUEOF { ev_set(out, thisv[0], thisv[1]); return 0 } // valueOf on an object = identity 3451 if bid == BI_OBJ_ISPROTOTYPEOF { return bi_obj_isprototypeof(thisv, argbuf, argc, out) } 3452 if bid == BI_FN_TOSTRING { return bi_fn_tostring(thisv, out) } 3453 if bid == BI_OBJ_GETPROTO { return bi_obj_getproto(argbuf, argc, out) } 3454 if bid == BI_OBJ_CREATE { return bi_obj_create(argbuf, argc, out) } 3455 if bid == BI_OBJ_ASSIGN { return bi_obj_assign(argbuf, argc, out) } 3456 ev_set(out, VAL_UNDEF, 0); return 1 // unknown builtin id -> honest error 3457} 3458 3459// ===================== starter builtin implementations (R-JS-RUNTIME) ===================== 3460// Each `bi_*` receives the receiver `thisv` (where meaningful) + an evaluated arg buffer 3461// (2 i64 per arg) + argc. It writes a VALUE to out and returns 0 ok / 1 ERROR. Integer-number 3462// world: indices/lengths are i64; non-integer coercions stay HONEST OPEN (see header). 3463 3464// clamp a (possibly negative, possibly out-of-bounds) slice endpoint into [0,len]. 3465// Negative endpoints count from the end (real JS slice): idx<0 -> len+idx, then clamp. 3466func ev_clamp_idx(i: i64, len: i64) -> i64 { 3467 var n: i64 = i 3468 if n < 0 { n = len + n } 3469 if n < 0 { n = 0 } 3470 if n > len { n = len } 3471 return n 3472} 3473// byte-substring search: index in `hay` where `needle` first occurs, or -1. Empty needle = 0. 3474func ev_str_find(hay: *i64, needle: *i64) -> i64 { 3475 let hl: i64 = ev_str_len(hay) 3476 let nl: i64 = ev_str_len(needle) 3477 if nl == 0 { return 0 } 3478 if nl > hl { return 0 - 1 } 3479 let hb: *u8 = ev_str_bytes(hay) 3480 let nb: *u8 = ev_str_bytes(needle) 3481 var i: i64 = 0 3482 let last: i64 = hl - nl 3483 while i <= last { 3484 var j: i64 = 0 3485 var ok: i64 = 1 3486 while j < nl { 3487 if (hb[i + j] & 0xff) != (nb[j] & 0xff) { ok = 0; j = nl } else { j = j + 1 } 3488 } 3489 if ok == 1 { return i } 3490 i = i + 1 3491 } 3492 return 0 - 1 3493} 3494// build a string value = bytes [a,b) of record `rec` (a,b already clamped, a<=b). 3495func ev_str_substr(rec: *i64, a: i64, b: i64, out: *i64) -> i64 { 3496 var len: i64 = b - a 3497 if len < 0 { len = 0 } 3498 let nr: *i64 = ev_str_new(len) 3499 let nd: *u8 = ev_str_bytes(nr) 3500 let sb: *u8 = ev_str_bytes(rec) 3501 var i: i64 = 0 3502 while i < len { nd[i] = sb[a + i]; i = i + 1 } 3503 ev_set(out, VAL_STR, nr as i64) 3504 return 0 3505} 3506 3507// ---- String methods ---- 3508func bi_str_charat(thisv: *i64, argbuf: *i64, argc: i64, out: *i64) -> i64 { 3509 let rec: *i64 = (thisv[1]) as *i64 3510 var i: i64 = 0 3511 if argc >= 1 { i = ja_p(argbuf, 0) } 3512 return ev_str_char_at(rec, i, out) 3513} 3514func bi_str_indexof(thisv: *i64, argbuf: *i64, argc: i64, out: *i64) -> i64 { 3515 let rec: *i64 = (thisv[1]) as *i64 3516 if argc < 1 { ev_set(out, VAL_NUM, 0 - 1); return 0 } 3517 let sub: *i64 = ev_coerce_str_arg(argbuf, 0) 3518 ev_set(out, VAL_NUM, ev_str_find(rec, sub)) 3519 return 0 3520} 3521func bi_str_includes(thisv: *i64, argbuf: *i64, argc: i64, out: *i64) -> i64 { 3522 let rec: *i64 = (thisv[1]) as *i64 3523 if argc < 1 { ev_set(out, VAL_BOOL, 0); return 0 } 3524 let sub: *i64 = ev_coerce_str_arg(argbuf, 0) 3525 if ev_str_find(rec, sub) >= 0 { ev_set(out, VAL_BOOL, 1); return 0 } 3526 ev_set(out, VAL_BOOL, 0); return 0 3527} 3528func bi_str_slice(thisv: *i64, argbuf: *i64, argc: i64, out: *i64) -> i64 { 3529 let rec: *i64 = (thisv[1]) as *i64 3530 let len: i64 = ev_str_len(rec) 3531 var a: i64 = 0 3532 var b: i64 = len 3533 if argc >= 1 { a = ja_p(argbuf, 0) } 3534 if argc >= 2 { b = ja_p(argbuf, 1) } 3535 let ca: i64 = ev_clamp_idx(a, len) 3536 let cb: i64 = ev_clamp_idx(b, len) 3537 return ev_str_substr(rec, ca, cb, out) 3538} 3539func bi_str_case(thisv: *i64, upper: i64, out: *i64) -> i64 { 3540 let rec: *i64 = (thisv[1]) as *i64 3541 let l: i64 = ev_str_len(rec) 3542 let nr: *i64 = ev_str_new(l) 3543 let nd: *u8 = ev_str_bytes(nr) 3544 let sb: *u8 = ev_str_bytes(rec) 3545 var i: i64 = 0 3546 while i < l { 3547 var c: i64 = sb[i] & 0xff 3548 if upper == 1 { if c >= 97 { if c <= 122 { c = c - 32 } } } 3549 if upper == 0 { if c >= 65 { if c <= 90 { c = c + 32 } } } 3550 nd[i] = c as u8 3551 i = i + 1 3552 } 3553 ev_set(out, VAL_STR, nr as i64) 3554 return 0 3555} 3556// coerce arg k (any value cell in argbuf) to a string record, for string-needle args. 3557func ev_coerce_str_arg(argbuf: *i64, k: i64) -> *i64 { 3558 let tmp: *i64 = ev_cell() 3559 tmp[0] = ja_t(argbuf, k) 3560 tmp[1] = ja_p(argbuf, k) 3561 return ev_coerce_str(tmp) 3562} 3563 3564// ---- Array methods ---- 3565func bi_arr_push(thisv: *i64, argbuf: *i64, argc: i64, out: *i64) -> i64 { 3566 var len: i64 = ev_arraylike_len(thisv) // array OR array-like object (jQuery pushStack collection) 3567 var k: i64 = 0 3568 while k < argc { 3569 if ev_arraylike_set(thisv, len, ja_t(argbuf, k), ja_p(argbuf, k)) == 1 { ev_set(out, VAL_UNDEF, 0); return 1 } 3570 len = len + 1 3571 k = k + 1 3572 } 3573 ev_set(out, VAL_NUM, len) // push returns the NEW length 3574 return 0 3575} 3576func bi_arr_pop(thisv: *i64, out: *i64) -> i64 { 3577 let a: *i64 = (thisv[1]) as *i64 3578 let len: i64 = arr_len(a) 3579 if len == 0 { ev_set(out, VAL_UNDEF, 0); return 0 } // pop of empty -> undefined 3580 let last: i64 = len - 1 3581 arr_get(a, last, out) 3582 a[0] = last // shrink length (additive history not needed for the live array model) 3583 return 0 3584} 3585// arr.shift() -- remove+return arr[0], shift the rest down one slot, length-=1 (empty -> undefined). 3586func bi_arr_shift(thisv: *i64, out: *i64) -> i64 { 3587 let a: *i64 = (thisv[1]) as *i64 3588 let len: i64 = arr_len(a) 3589 if len == 0 { ev_set(out, VAL_UNDEF, 0); return 0 } 3590 arr_get(a, 0, out) // save element 0 3591 let d: *i64 = (a[2]) as *i64 3592 var i: i64 = 0 3593 while i < len - 1 { 3594 let b: i64 = i * ARR_ENT 3595 let nb: i64 = (i + 1) * ARR_ENT 3596 d[b + 0] = d[nb + 0]; d[b + 1] = d[nb + 1] 3597 i = i + 1 3598 } 3599 a[0] = len - 1 3600 return 0 3601} 3602// arr.unshift(...items) -- prepend items (existing shift up), return the new length. Grow first via arr_set 3603// (reallocs the backing block), THEN re-read d and move existing elements BACKWARDS to avoid clobbering. 3604func bi_arr_unshift(thisv: *i64, argbuf: *i64, argc: i64, out: *i64) -> i64 { 3605 let a: *i64 = (thisv[1]) as *i64 3606 let len: i64 = arr_len(a) 3607 if argc == 0 { ev_set(out, VAL_NUM, len); return 0 } 3608 let newlen: i64 = len + argc 3609 if arr_set(a, newlen - 1, VAL_UNDEF, 0) == 1 { ev_set(out, VAL_UNDEF, 0); return 1 } // grow + length=newlen 3610 let d: *i64 = (a[2]) as *i64 3611 var i: i64 = len - 1 3612 while i >= 0 { 3613 let src: i64 = i * ARR_ENT 3614 let dst: i64 = (i + argc) * ARR_ENT 3615 d[dst + 0] = d[src + 0]; d[dst + 1] = d[src + 1] 3616 i = i - 1 3617 } 3618 var k: i64 = 0 3619 while k < argc { 3620 let b: i64 = k * ARR_ENT 3621 d[b + 0] = ja_t(argbuf, k); d[b + 1] = ja_p(argbuf, k) 3622 k = k + 1 3623 } 3624 ev_set(out, VAL_NUM, newlen) 3625 return 0 3626} 3627// arr.concat(...items) -> NEW array = this's elements then each item (ARRAY items spread one level, else pushed). 3628func bi_arr_concat(thisv: *i64, argbuf: *i64, argc: i64, out: *i64) -> i64 { 3629 let r: *i64 = arr_new() 3630 var ri: i64 = 0 3631 let a: *i64 = (thisv[1]) as *i64 3632 let al: i64 = arr_len(a) 3633 let eb: *i64 = ev_cell() 3634 var i: i64 = 0 3635 while i < al { if arr_get(a, i, eb) == 0 { ev_set(eb, VAL_UNDEF, 0) } arr_set(r, ri, eb[0], eb[1]); ri = ri + 1; i = i + 1 } 3636 var ai: i64 = 0 3637 while ai < argc { 3638 if ja_t(argbuf, ai) == VAL_ARRAY { 3639 let sa: *i64 = (ja_p(argbuf, ai)) as *i64 3640 let sl: i64 = arr_len(sa) 3641 var j: i64 = 0 3642 while j < sl { if arr_get(sa, j, eb) == 0 { ev_set(eb, VAL_UNDEF, 0) } arr_set(r, ri, eb[0], eb[1]); ri = ri + 1; j = j + 1 } 3643 } else { 3644 arr_set(r, ri, ja_t(argbuf, ai), ja_p(argbuf, ai)); ri = ri + 1 3645 } 3646 ai = ai + 1 3647 } 3648 ev_set(out, VAL_ARRAY, r as i64) 3649 return 0 3650} 3651// str.concat(...args) -> this followed by each arg coerced to a string. 3652func bi_str_concat_m(thisv: *i64, argbuf: *i64, argc: i64, out: *i64) -> i64 { 3653 let accb: *i64 = ev_cell() 3654 ev_set(accb, VAL_STR, (ev_coerce_str(thisv)) as i64) 3655 var i: i64 = 0 3656 while i < argc { 3657 let ab: *i64 = ev_cell() 3658 ab[0] = ja_t(argbuf, i); ab[1] = ja_p(argbuf, i) 3659 let nb: *i64 = ev_cell() 3660 ev_str_concat((accb[1]) as *i64, ev_coerce_str(ab), nb) 3661 ev_copy(accb, nb) 3662 i = i + 1 3663 } 3664 ev_copy(out, accb) 3665 return 0 3666} 3667// LENGTH of an array OR array-like object. jQuery collections / NodeLists / `arguments` borrow 3668// Array.prototype methods via .call/.apply on a non-array `this` (e.g. inArray = indexOf.call(coll,x)), 3669// so these natives must read a generic array-like (numeric-indexed object with a `.length`), not 3670// assume VAL_ARRAY -- indexOf.call(jqObject,...) previously threw. 3671func ev_arraylike_len(v: *i64) -> i64 { 3672 if v[0] == VAL_ARRAY { return arr_len((v[1]) as *i64) } 3673 if v[0] == VAL_OBJECT { 3674 let lb: *i64 = ev_cell() 3675 if obj_get((v[1]) as *i64, ev_cstr("length\x00" as *u8), lb) == 1 { if lb[0] == VAL_NUM { return lb[1] } } 3676 } 3677 return 0 3678} 3679// element i of an array OR array-like object -> out (undefined if absent). 3680func ev_arraylike_get(v: *i64, i: i64, out: *i64) -> i64 { 3681 if v[0] == VAL_ARRAY { arr_get((v[1]) as *i64, i, out); return 0 } 3682 if v[0] == VAL_OBJECT { if obj_get((v[1]) as *i64, ev_num_to_str(i), out) == 1 { return 0 } } 3683 ev_set(out, VAL_UNDEF, 0) 3684 return 0 3685} 3686// SET element i of an array OR array-like object; for an object also grow its `.length` to i+1. 3687// This is what lets Array.prototype.push.apply(jqCollection, nodes) (Sizzle's result accumulation) 3688// mutate a jQuery collection (array-like object) rather than assuming a VAL_ARRAY receiver. 3689func ev_arraylike_set(v: *i64, i: i64, vt: i64, vp: i64) -> i64 { 3690 if v[0] == VAL_ARRAY { return arr_set((v[1]) as *i64, i, vt, vp) } 3691 if v[0] == VAL_OBJECT { 3692 let o: *i64 = (v[1]) as *i64 3693 obj_set(o, ev_num_to_str(i), vt, vp) 3694 let lb: *i64 = ev_cell() 3695 var cur: i64 = 0 3696 if obj_get(o, ev_cstr("length\x00" as *u8), lb) == 1 { if lb[0] == VAL_NUM { cur = lb[1] } } 3697 if (i + 1) > cur { obj_set(o, ev_cstr("length\x00" as *u8), VAL_NUM, i + 1) } 3698 return 0 3699 } 3700 return 0 // non-array-like receiver: no-op (don't abort the whole call) 3701} 3702func bi_arr_indexof(thisv: *i64, argbuf: *i64, argc: i64, out: *i64) -> i64 { 3703 if argc < 1 { ev_set(out, VAL_NUM, 0 - 1); return 0 } 3704 let needle: *i64 = ev_cell() 3705 needle[0] = ja_t(argbuf, 0) 3706 needle[1] = ja_p(argbuf, 0) 3707 let len: i64 = ev_arraylike_len(thisv) // array OR jQuery-collection/NodeList (borrowed via .call) 3708 var i: i64 = 0 3709 while i < len { 3710 let el: *i64 = ev_cell() 3711 ev_arraylike_get(thisv, i, el) 3712 if ev_strict_eq(el, needle) == 1 { ev_set(out, VAL_NUM, i); return 0 } 3713 i = i + 1 3714 } 3715 ev_set(out, VAL_NUM, 0 - 1) 3716 return 0 3717} 3718func bi_arr_join(thisv: *i64, argbuf: *i64, argc: i64, out: *i64) -> i64 { 3719 let a: *i64 = (thisv[1]) as *i64 3720 let len: i64 = arr_len(a) 3721 var sep: *i64 = ev_cstr(",\x00" as *u8) // default separator is ',' (real JS) 3722 if argc >= 1 { sep = ev_coerce_str_arg(argbuf, 0) } 3723 var acc: *i64 = ev_str_new(0) 3724 var i: i64 = 0 3725 while i < len { 3726 if i > 0 { let m: *i64 = ev_cell(); ev_str_concat(acc, sep, m); acc = (m[1]) as *i64 } 3727 let el: *i64 = ev_cell() 3728 arr_get(a, i, el) 3729 // undefined/null join as empty string (real JS); other values stringify. 3730 var es: *i64 = ev_str_new(0) 3731 if el[0] != VAL_UNDEF { if el[0] != VAL_NULL { es = ev_coerce_str(el) } } 3732 let m2: *i64 = ev_cell() 3733 ev_str_concat(acc, es, m2); acc = (m2[1]) as *i64 3734 i = i + 1 3735 } 3736 ev_set(out, VAL_STR, acc as i64) 3737 return 0 3738} 3739func bi_arr_slice(thisv: *i64, argbuf: *i64, argc: i64, out: *i64) -> i64 { 3740 let len: i64 = ev_arraylike_len(thisv) // array OR array-like: jQuery's .get()=slice.call(this), .slice()=slice.apply(this,..) 3741 var sa: i64 = 0 3742 var sb: i64 = len 3743 if argc >= 1 { sa = ja_p(argbuf, 0) } 3744 if argc >= 2 { sb = ja_p(argbuf, 1) } 3745 let ca: i64 = ev_clamp_idx(sa, len) 3746 let cb: i64 = ev_clamp_idx(sb, len) 3747 let r: *i64 = arr_new() 3748 var i: i64 = ca 3749 var w: i64 = 0 3750 while i < cb { 3751 let el: *i64 = ev_cell() 3752 ev_arraylike_get(thisv, i, el) 3753 arr_set(r, w, el[0], el[1]) 3754 w = w + 1 3755 i = i + 1 3756 } 3757 ev_set(out, VAL_ARRAY, r as i64) 3758 return 0 3759} 3760// arr.reverse() -- reverse IN PLACE, return the (same) array. A YouTube signature-decipher transform. 3761func bi_arr_reverse(thisv: *i64, out: *i64) -> i64 { 3762 let a: *i64 = (thisv[1]) as *i64 3763 let len: i64 = arr_len(a) 3764 var i: i64 = 0 3765 var j: i64 = len - 1 3766 while i < j { 3767 let ei: *i64 = ev_cell(); arr_get(a, i, ei) 3768 let ej: *i64 = ev_cell(); arr_get(a, j, ej) 3769 arr_set(a, i, ej[0], ej[1]) 3770 arr_set(a, j, ei[0], ei[1]) 3771 i = i + 1 3772 j = j - 1 3773 } 3774 ev_set(out, VAL_ARRAY, a as i64) 3775 return 0 3776} 3777// arr.splice(start, deleteCount, ...items) -- remove deleteCount at start (clamped; negative start = from end), 3778// insert items, MUTATE the array in place, return the removed elements as a NEW array. YouTube uses 3779// splice(0,b) to drop the first b chars of the signature. Rebuild-then-copy-back for correctness. 3780func bi_arr_splice(thisv: *i64, argbuf: *i64, argc: i64, out: *i64) -> i64 { 3781 let a: *i64 = (thisv[1]) as *i64 3782 let len: i64 = arr_len(a) 3783 var start: i64 = 0 3784 if argc >= 1 { start = ja_p(argbuf, 0) } 3785 if start < 0 { start = len + start; if start < 0 { start = 0 } } 3786 if start > len { start = len } 3787 var delc: i64 = len - start // splice(start) with no count removes the rest 3788 if argc >= 2 { delc = ja_p(argbuf, 1); if delc < 0 { delc = 0 } if delc > (len - start) { delc = len - start } } 3789 let removed: *i64 = arr_new() 3790 var ri: i64 = 0 3791 while ri < delc { let el: *i64 = ev_cell(); arr_get(a, start + ri, el); arr_set(removed, ri, el[0], el[1]); ri = ri + 1 } 3792 var nitems: i64 = 0 3793 if argc > 2 { nitems = argc - 2 } 3794 let tmp: *i64 = arr_new() // [0..start) + items + [start+delc..len) 3795 var w: i64 = 0 3796 var k: i64 = 0 3797 while k < start { let el: *i64 = ev_cell(); arr_get(a, k, el); arr_set(tmp, w, el[0], el[1]); w = w + 1; k = k + 1 } 3798 var mi: i64 = 0 3799 while mi < nitems { arr_set(tmp, w, ja_t(argbuf, 2 + mi), ja_p(argbuf, 2 + mi)); w = w + 1; mi = mi + 1 } 3800 k = start + delc 3801 while k < len { let el: *i64 = ev_cell(); arr_get(a, k, el); arr_set(tmp, w, el[0], el[1]); w = w + 1; k = k + 1 } 3802 a[0] = 0 // reset + rebuild `a` from tmp (mutate in place) 3803 var p: i64 = 0 3804 while p < w { let el: *i64 = ev_cell(); arr_get(tmp, p, el); arr_set(a, p, el[0], el[1]); p = p + 1 } 3805 ev_set(out, VAL_ARRAY, removed as i64) 3806 return 0 3807} 3808 3809// ---- Array ITERATION methods (R-JS-CALLBACK, rung 7) ---- 3810// Each `this` = an array (the receiver guard already verified VAL_ARRAY). arg0 = the callback, 3811// which MUST be a user CLOSURE/arrow (VAL_FUNC) -- a non-function callback is an HONEST ERROR 3812// (rc=1), never a silent no-op. They call back per element via js_call_core (the shared 3813// closure-invoke core), passing (element, index) as PRE-EVALUATED args. The callback's 3rd 3814// arg (the array itself) and thisArg are NAMED OPENS (not passed) -- see the header. 3815 3816// invoke the callback closure `cb` (a VAL_FUNC value cell) with (element, index) for array 3817// element i, writing the result to `res`. Returns 0 ok, 1 ERROR (propagated from the body). 3818func bi_cb_call_ei(ctx: *i64, genv: *i64, cb: *i64, a: *i64, i: i64, res: *i64) -> i64 { 3819 let clos: *i64 = (cb[1]) as *i64 3820 let cbargs: *i64 = sys_mmap(8 * 2 * 2) as *i64 3821 let el: *i64 = ev_cell() 3822 arr_get(a, i, el) // out-of-range -> el stays undefined (cells are dense here) 3823 cbargs[0] = el[0] // arg0 = element 3824 cbargs[1] = el[1] 3825 cbargs[2] = VAL_NUM // arg1 = index 3826 cbargs[3] = i 3827 return js_call_core(ctx, clos, cbargs, 2, 0 as *i64, genv, res) 3828} 3829// validate arg0 is a callback FUNCTION; returns its value-cell ptr, or 0 (null) if not a 3830// function -- the caller then errors. Centralizes the non-function-callback tamper contract. 3831func bi_cb_arg(argbuf: *i64, argc: i64) -> *i64 { 3832 if argc < 1 { return 0 as *i64 } 3833 if ja_t(argbuf, 0) != VAL_FUNC { return 0 as *i64 } 3834 let cb: *i64 = ev_cell() 3835 cb[0] = ja_t(argbuf, 0) 3836 cb[1] = ja_p(argbuf, 0) 3837 return cb 3838} 3839 3840func bi_arr_foreach(ctx: *i64, genv: *i64, thisv: *i64, argbuf: *i64, argc: i64, out: *i64) -> i64 { 3841 let cb: *i64 = bi_cb_arg(argbuf, argc) 3842 if ev_isnull(cb) == 1 { ev_set(out, VAL_UNDEF, 0); return 1 } // non-function callback -> ERROR 3843 let a: *i64 = (thisv[1]) as *i64 3844 let len: i64 = arr_len(a) 3845 var i: i64 = 0 3846 while i < len { 3847 let res: *i64 = ev_cell() 3848 if bi_cb_call_ei(ctx, genv, cb, a, i, res) == 1 { ev_set(out, VAL_UNDEF, 0); return 1 } 3849 i = i + 1 3850 } 3851 ev_set(out, VAL_UNDEF, 0) // forEach returns undefined 3852 return 0 3853} 3854 3855func bi_arr_map(ctx: *i64, genv: *i64, thisv: *i64, argbuf: *i64, argc: i64, out: *i64) -> i64 { 3856 let cb: *i64 = bi_cb_arg(argbuf, argc) 3857 if ev_isnull(cb) == 1 { ev_set(out, VAL_UNDEF, 0); return 1 } 3858 let a: *i64 = (thisv[1]) as *i64 3859 let len: i64 = arr_len(a) 3860 let r: *i64 = arr_new() 3861 var i: i64 = 0 3862 while i < len { 3863 let res: *i64 = ev_cell() 3864 if bi_cb_call_ei(ctx, genv, cb, a, i, res) == 1 { ev_set(out, VAL_UNDEF, 0); return 1 } 3865 arr_set(r, i, res[0], res[1]) // NEW array, same length 3866 i = i + 1 3867 } 3868 ev_set(out, VAL_ARRAY, r as i64) 3869 return 0 3870} 3871 3872func bi_arr_filter(ctx: *i64, genv: *i64, thisv: *i64, argbuf: *i64, argc: i64, out: *i64) -> i64 { 3873 let cb: *i64 = bi_cb_arg(argbuf, argc) 3874 if ev_isnull(cb) == 1 { ev_set(out, VAL_UNDEF, 0); return 1 } 3875 let a: *i64 = (thisv[1]) as *i64 3876 let len: i64 = arr_len(a) 3877 let r: *i64 = arr_new() 3878 var i: i64 = 0 3879 var w: i64 = 0 3880 while i < len { 3881 let res: *i64 = ev_cell() 3882 if bi_cb_call_ei(ctx, genv, cb, a, i, res) == 1 { ev_set(out, VAL_UNDEF, 0); return 1 } 3883 if ev_truthy(res) == 1 { 3884 let el: *i64 = ev_cell() 3885 arr_get(a, i, el) 3886 arr_set(r, w, el[0], el[1]) // keep the ORIGINAL element when predicate truthy 3887 w = w + 1 3888 } 3889 i = i + 1 3890 } 3891 ev_set(out, VAL_ARRAY, r as i64) 3892 return 0 3893} 3894 3895func bi_arr_reduce(ctx: *i64, genv: *i64, thisv: *i64, argbuf: *i64, argc: i64, out: *i64) -> i64 { 3896 let cb: *i64 = bi_cb_arg(argbuf, argc) 3897 if ev_isnull(cb) == 1 { ev_set(out, VAL_UNDEF, 0); return 1 } 3898 let a: *i64 = (thisv[1]) as *i64 3899 let len: i64 = arr_len(a) 3900 let acc: *i64 = ev_cell() 3901 var start: i64 = 0 3902 // init form: arg1 present -> acc = init, fold from element 0. no-init form: acc = element 0, 3903 // fold from element 1. Empty array WITHOUT init = honest ERROR (real JS TypeError). 3904 if argc >= 2 { 3905 acc[0] = ja_t(argbuf, 1) 3906 acc[1] = ja_p(argbuf, 1) 3907 start = 0 3908 } else { 3909 if len == 0 { ev_set(out, VAL_UNDEF, 0); return 1 } // reduce of empty + no init -> ERROR 3910 arr_get(a, 0, acc) 3911 start = 1 3912 } 3913 var i: i64 = start 3914 while i < len { 3915 // fold: acc = cb(acc, element, index). Build the 3-arg buffer [acc, el, i]. 3916 let clos: *i64 = (cb[1]) as *i64 3917 let cbargs: *i64 = sys_mmap(8 * 2 * 3) as *i64 3918 cbargs[0] = acc[0] 3919 cbargs[1] = acc[1] 3920 let el: *i64 = ev_cell() 3921 arr_get(a, i, el) 3922 cbargs[2] = el[0] 3923 cbargs[3] = el[1] 3924 cbargs[4] = VAL_NUM 3925 cbargs[5] = i 3926 let res: *i64 = ev_cell() 3927 if js_call_core(ctx, clos, cbargs, 3, 0 as *i64, genv, res) == 1 { ev_set(out, VAL_UNDEF, 0); return 1 } 3928 acc[0] = res[0] 3929 acc[1] = res[1] 3930 i = i + 1 3931 } 3932 ev_copy(out, acc) 3933 return 0 3934} 3935 3936// some/every share one body (`want_all` = 0 for some, 1 for every) with SHORT-CIRCUIT: 3937// some -> true on the FIRST truthy callback (else false); stops early on the truthy hit. 3938// every -> false on the FIRST falsy callback (else true); stops early on the falsy hit. 3939func bi_arr_some(ctx: *i64, genv: *i64, thisv: *i64, argbuf: *i64, argc: i64, want_all: i64, out: *i64) -> i64 { 3940 let cb: *i64 = bi_cb_arg(argbuf, argc) 3941 if ev_isnull(cb) == 1 { ev_set(out, VAL_UNDEF, 0); return 1 } 3942 let a: *i64 = (thisv[1]) as *i64 3943 let len: i64 = arr_len(a) 3944 var i: i64 = 0 3945 while i < len { 3946 let res: *i64 = ev_cell() 3947 if bi_cb_call_ei(ctx, genv, cb, a, i, res) == 1 { ev_set(out, VAL_UNDEF, 0); return 1 } 3948 let t: i64 = ev_truthy(res) 3949 if want_all == 0 { if t == 1 { ev_set(out, VAL_BOOL, 1); return 0 } } // some: first truthy -> true 3950 if want_all == 1 { if t == 0 { ev_set(out, VAL_BOOL, 0); return 0 } } // every: first falsy -> false 3951 i = i + 1 3952 } 3953 if want_all == 0 { ev_set(out, VAL_BOOL, 0); return 0 } // some over empty / no-truthy -> false 3954 ev_set(out, VAL_BOOL, 1) // every over empty / all-truthy -> true 3955 return 0 3956} 3957// sort comparison of two VALUES: sign of cmp(x,y)'s numeric return if a comparator is given, else ToString 3958// (byte-lexicographic) order. cmp is a user closure -> js_call_core (thisval undefined). A comparator ERROR 3959// or non-numeric return counts as 0 (keep order) rather than aborting the sort. 3960func bi_sort_cmp(ctx: *i64, genv: *i64, cmp: *i64, x: *i64, y: *i64) -> i64 { 3961 if (cmp as i64) != 0 { 3962 let cbargs: *i64 = sys_mmap(8 * 2 * 2) as *i64 3963 cbargs[0] = x[0]; cbargs[1] = x[1]; cbargs[2] = y[0]; cbargs[3] = y[1] 3964 let res: *i64 = ev_cell() 3965 if js_call_core(ctx, (cmp[1]) as *i64, cbargs, 2, 0 as *i64, genv, res) == 1 { return 0 } 3966 if res[0] == VAL_FLOAT { if nx_f64_lt(res[1], 0) == 1 { return 0 - 1 } if nx_f64_gt(res[1], 0) == 1 { return 1 } return 0 } 3967 let n: i64 = ev_tonum(res) 3968 if n < 0 { return 0 - 1 } 3969 if n > 0 { return 1 } 3970 return 0 3971 } 3972 return ev_str_cmp(ev_coerce_str(x), ev_coerce_str(y)) 3973} 3974// Array.prototype.sort([cmp]) -- STABLE in-place insertion sort (Sizzle's sortStable feature-detect requires a 3975// stable sort; a non-stable sort would make jQuery mis-classify + fall to a slower path or misorder). cmp is a 3976// user closure invoked via bi_sort_cmp; no cmp -> ToString order. Mutates thisv's cells, returns the array. 3977func bi_arr_sort(ctx: *i64, genv: *i64, thisv: *i64, argbuf: *i64, argc: i64, out: *i64) -> i64 { 3978 if thisv[0] != VAL_ARRAY { ev_set(out, VAL_UNDEF, 0); return 1 } 3979 let a: *i64 = (thisv[1]) as *i64 3980 let len: i64 = arr_len(a) 3981 var cmp: *i64 = 0 as *i64 3982 if argc >= 1 { if ja_t(argbuf, 0) == VAL_FUNC { cmp = ev_cell(); cmp[0] = ja_t(argbuf, 0); cmp[1] = ja_p(argbuf, 0) } } 3983 let cur: *i64 = ev_cell() 3984 let prev: *i64 = ev_cell() 3985 var i: i64 = 1 3986 while i < len { 3987 if arr_get(a, i, cur) == 0 { ev_set(cur, VAL_UNDEF, 0) } 3988 var j: i64 = i - 1 3989 var placed: i64 = 0 3990 while placed == 0 { 3991 if j < 0 { placed = 1 } else { 3992 if arr_get(a, j, prev) == 0 { ev_set(prev, VAL_UNDEF, 0) } 3993 if bi_sort_cmp(ctx, genv, cmp, prev, cur) > 0 { arr_set(a, j + 1, prev[0], prev[1]); j = j - 1 } else { placed = 1 } 3994 } 3995 } 3996 arr_set(a, j + 1, cur[0], cur[1]) 3997 i = i + 1 3998 } 3999 ev_set(out, VAL_ARRAY, a as i64) 4000 return 0 4001} 4002 4003// ---- Object.* free functions (operand object = arg0) ---- 4004func bi_obj_keys(argbuf: *i64, argc: i64, out: *i64) -> i64 { 4005 if argc < 1 { ev_set(out, VAL_UNDEF, 0); return 1 } 4006 if ja_t(argbuf, 0) != VAL_OBJECT { ev_set(out, VAL_UNDEF, 0); return 1 } // Object.keys(non-object) = open error 4007 let o: *i64 = (ja_p(argbuf, 0)) as *i64 4008 let c: i64 = obj_count(o) 4009 let r: *i64 = arr_new() 4010 var i: i64 = 0 4011 var j: i64 = 0 4012 while i < c { 4013 let kr: *i64 = obj_key(o, i) 4014 if (kr as i64) != 0 { arr_set(r, j, VAL_STR, kr as i64); j = j + 1 } // skip tombstoned (deleted) slots 4015 i = i + 1 4016 } 4017 ev_set(out, VAL_ARRAY, r as i64) 4018 return 0 4019} 4020// Object.defineProperty(target, key, descriptor): set target[key] = descriptor.value (the `value` form, 4021// which is what DeltaBlue's inheritsFrom install + typical polyfills use; get/set/enumerable/writable are a 4022// follow-on). target = VAL_OBJECT (e.g. Object.prototype). Returns target (real JS). Non-object target errors. 4023func bi_obj_defineprop(argbuf: *i64, argc: i64, out: *i64) -> i64 { 4024 if argc < 3 { ev_set(out, VAL_UNDEF, 0); return 1 } 4025 if ja_t(argbuf, 0) != VAL_OBJECT { ev_set(out, VAL_UNDEF, 0); return 1 } 4026 let target: *i64 = (ja_p(argbuf, 0)) as *i64 4027 let keyrec: *i64 = ev_coerce_str_arg(argbuf, 1) 4028 if ja_t(argbuf, 2) != VAL_OBJECT { ev_set(out, VAL_UNDEF, 0); return 1 } 4029 let desc: *i64 = (ja_p(argbuf, 2)) as *i64 4030 let vb: *i64 = ev_cell() 4031 if obj_get(desc, ev_cstr("value\x00" as *u8), vb) == 0 { ev_set(vb, VAL_UNDEF, 0) } 4032 if obj_set(target, keyrec, vb[0], vb[1]) == 1 { ev_set(out, VAL_UNDEF, 0); return 1 } 4033 ev_set(out, VAL_OBJECT, target as i64) 4034 return 0 4035} 4036func bi_obj_values(argbuf: *i64, argc: i64, out: *i64) -> i64 { 4037 if argc < 1 { ev_set(out, VAL_UNDEF, 0); return 1 } 4038 if ja_t(argbuf, 0) != VAL_OBJECT { ev_set(out, VAL_UNDEF, 0); return 1 } 4039 let o: *i64 = (ja_p(argbuf, 0)) as *i64 4040 let c: i64 = obj_count(o) 4041 let r: *i64 = arr_new() 4042 let d: *i64 = (o[OBJ_BACK]) as *i64 4043 var i: i64 = 0 4044 while i < c { 4045 let b: i64 = i * OBJ_ENT 4046 arr_set(r, i, d[b + 1], d[b + 2]) // entry value-tag / value-payload 4047 i = i + 1 4048 } 4049 ev_set(out, VAL_ARRAY, r as i64) 4050 return 0 4051} 4052 4053// Object.prototype.toString -> the "[object Type]" brand tag. This is jQuery's `toType` primitive 4054// (class2type[ toString.call(obj) ]) and the single most-called stdlib method in real page JS: every 4055// isArray/isFunction/type-switch routes through it. Brand is by the receiver's VALUE TAG (integer world). 4056func bi_obj_tostring(thisv: *i64, out: *i64) -> i64 { 4057 let t: i64 = thisv[0] 4058 if t == VAL_ARRAY { ev_set(out, VAL_STR, (ev_cstr("[object Array]\x00" as *u8)) as i64); return 0 } 4059 if t == VAL_FUNC { ev_set(out, VAL_STR, (ev_cstr("[object Function]\x00" as *u8)) as i64); return 0 } 4060 if t == VAL_NATIVE { ev_set(out, VAL_STR, (ev_cstr("[object Function]\x00" as *u8)) as i64); return 0 } 4061 if t == VAL_STR { ev_set(out, VAL_STR, (ev_cstr("[object String]\x00" as *u8)) as i64); return 0 } 4062 if t == VAL_NUM { ev_set(out, VAL_STR, (ev_cstr("[object Number]\x00" as *u8)) as i64); return 0 } 4063 if t == VAL_FLOAT { ev_set(out, VAL_STR, (ev_cstr("[object Number]\x00" as *u8)) as i64); return 0 } 4064 if t == VAL_BOOL { ev_set(out, VAL_STR, (ev_cstr("[object Boolean]\x00" as *u8)) as i64); return 0 } 4065 if t == VAL_UNDEF { ev_set(out, VAL_STR, (ev_cstr("[object Undefined]\x00" as *u8)) as i64); return 0 } 4066 if t == VAL_NULL { ev_set(out, VAL_STR, (ev_cstr("[object Null]\x00" as *u8)) as i64); return 0 } 4067 if t == VAL_REGEX { ev_set(out, VAL_STR, (ev_cstr("[object RegExp]\x00" as *u8)) as i64); return 0 } 4068 ev_set(out, VAL_STR, (ev_cstr("[object Object]\x00" as *u8)) as i64); return 0 4069} 4070// Object.prototype.hasOwnProperty(key) -> is `key` an OWN (not inherited) property of the receiver. 4071// jQuery calls it as hasOwn.call(obj,key); receiver arrives as thisv. Own-only = obj_get (proto walk is separate). 4072func bi_obj_hasown(thisv: *i64, argbuf: *i64, argc: i64, out: *i64) -> i64 { 4073 if argc < 1 { ev_set(out, VAL_BOOL, 0); return 0 } 4074 if thisv[0] != VAL_OBJECT { ev_set(out, VAL_BOOL, 0); return 0 } 4075 let o: *i64 = (thisv[1]) as *i64 4076 let key: *i64 = ev_coerce_str_arg(argbuf, 0) 4077 let probe: *i64 = ev_cell() 4078 if obj_get(o, key, probe) == 1 { ev_set(out, VAL_BOOL, 1); return 0 } 4079 ev_set(out, VAL_BOOL, 0); return 0 4080} 4081// proto.isPrototypeOf(obj) -> is the receiver anywhere on obj's [[Prototype]] chain (depth-bounded). 4082func bi_obj_isprototypeof(thisv: *i64, argbuf: *i64, argc: i64, out: *i64) -> i64 { 4083 if argc < 1 { ev_set(out, VAL_BOOL, 0); return 0 } 4084 if thisv[0] != VAL_OBJECT { ev_set(out, VAL_BOOL, 0); return 0 } 4085 if ja_t(argbuf, 0) != VAL_OBJECT { ev_set(out, VAL_BOOL, 0); return 0 } 4086 let target: i64 = thisv[1] 4087 var p: i64 = obj_proto((ja_p(argbuf, 0)) as *i64) 4088 var depth: i64 = 0 4089 while depth < 200 { 4090 if p == 0 { ev_set(out, VAL_BOOL, 0); return 0 } 4091 if p == target { ev_set(out, VAL_BOOL, 1); return 0 } 4092 p = obj_proto(p as *i64) 4093 depth = depth + 1 4094 } 4095 ev_set(out, VAL_BOOL, 0); return 0 4096} 4097// Function.prototype.toString -> a source-ish string. jQuery stores fnToString.call(Object) and only 4098// compares it in isPlainObject's DEEP path (which we short-circuit via a null [[Prototype]] on plain {}), 4099// so a stable native-code brand suffices to unblock the module-level read. 4100func bi_fn_tostring(thisv: *i64, out: *i64) -> i64 { 4101 ev_set(out, VAL_STR, (ev_cstr("function () { [native code] }\x00" as *u8)) as i64); return 0 4102} 4103// Object.getPrototypeOf(o) -> o's [[Prototype]] OBJECT, or null. A plain `{}` (obj_new) carries no 4104// explicit link (OBJ_PROTO==0) -- the implicit Object.prototype is applied at lookup time, not stored -- 4105// so we honestly report null there. jQuery's isPlainObject reads `if(!proto)return true`, so plain 4106// objects classify correctly; `new Ctor()` instances (OBJ_PROTO set by `new`) return their real proto. 4107func bi_obj_getproto(argbuf: *i64, argc: i64, out: *i64) -> i64 { 4108 if argc < 1 { ev_set(out, VAL_NULL, 0); return 0 } 4109 if ja_t(argbuf, 0) == VAL_OBJECT { 4110 let p: i64 = obj_proto((ja_p(argbuf, 0)) as *i64) 4111 if p == 0 { ev_set(out, VAL_NULL, 0); return 0 } 4112 ev_set(out, VAL_OBJECT, p); return 0 4113 } 4114 ev_set(out, VAL_NULL, 0); return 0 4115} 4116// Object.create(proto): fresh object whose [[Prototype]] is proto (a plain empty obj for Object.create(null)). 4117// jQuery's event system does `elemData.events = Object.create(null)` for its handler map -> without this .on() threw. 4118func bi_obj_create(argbuf: *i64, argc: i64, out: *i64) -> i64 { 4119 let o: *i64 = obj_new() 4120 if argc >= 1 { if ja_t(argbuf, 0) == VAL_OBJECT { obj_set_proto(o, ja_p(argbuf, 0)) } } 4121 ev_set(out, VAL_OBJECT, o as i64) 4122 return 0 4123} 4124// Object.assign(target, ...sources): shallow-copy each source's OWN enumerable props into target; return target. 4125// Ubiquitous ES6 (vk + most modern frameworks use it for merging config/state/props). 4126func bi_obj_assign(argbuf: *i64, argc: i64, out: *i64) -> i64 { 4127 if argc < 1 { ev_set(out, VAL_UNDEF, 0); return 0 } 4128 if ja_t(argbuf, 0) != VAL_OBJECT { ev_set(out, ja_t(argbuf, 0), ja_p(argbuf, 0)); return 0 } 4129 let tgt: *i64 = (ja_p(argbuf, 0)) as *i64 4130 var s: i64 = 1 4131 while s < argc { 4132 if ja_t(argbuf, s) == VAL_OBJECT { 4133 let src: *i64 = (ja_p(argbuf, s)) as *i64 4134 let n: i64 = src[0] 4135 var i: i64 = 0 4136 while i < n { let kp: *i64 = obj_key(src, i); if (kp as i64) != 0 { let vb: *i64 = ev_cell(); obj_get(src, kp, vb); obj_set(tgt, kp, vb[0], vb[1]) } i = i + 1 } 4137 } 4138 s = s + 1 4139 } 4140 ev_set(out, VAL_OBJECT, tgt as i64) 4141 return 0 4142} 4143 4144// ---- Math.* free functions (integer-number world) ---- 4145func bi_math_maxmin(argbuf: *i64, argc: i64, want_max: i64, out: *i64) -> i64 { 4146 // Math.max() = -Infinity / Math.min() = +Infinity in real JS; with no float this rung, 4147 // an empty call is an HONEST error rather than a fabricated sentinel. 4148 if argc < 1 { ev_set(out, VAL_UNDEF, 0); return 1 } 4149 var best: i64 = ev_arg_num(argbuf, 0) 4150 var i: i64 = 1 4151 while i < argc { 4152 let v: i64 = ev_arg_num(argbuf, i) 4153 if want_max == 1 { if v > best { best = v } } 4154 if want_max == 0 { if v < best { best = v } } 4155 i = i + 1 4156 } 4157 ev_set(out, VAL_NUM, best) 4158 return 0 4159} 4160func bi_math_abs(argbuf: *i64, argc: i64, out: *i64) -> i64 { 4161 if argc < 1 { ev_set(out, VAL_UNDEF, 0); return 1 } 4162 var n: i64 = ev_arg_num(argbuf, 0) 4163 if n < 0 { n = 0 - n } 4164 ev_set(out, VAL_NUM, n) 4165 return 0 4166} 4167func bi_math_identity(argbuf: *i64, argc: i64, out: *i64) -> i64 { 4168 if argc < 1 { ev_set(out, VAL_UNDEF, 0); return 1 } 4169 ev_set(out, VAL_NUM, ev_arg_num(argbuf, 0)) // floor/ceil of an integer = the integer 4170 return 0 4171} 4172// Math.random() -> VAL_FLOAT in [0,1). xorshift64 (left-shifts ok; right-shift masked to be LOGICAL via the 4173// hex constant to dodge NishiLang's arithmetic >>). Mantissa = low 53 bits / 2^53 (exactly representable). 4174// Seeded deterministically (nonzero) -- non-standard vs V8's nondeterminism but valid + testable. 4175static rng_state: i64 4176func bi_math_random(out: *i64) -> i64 { 4177 if rng_state == 0 { rng_state = 88172645463325252 } 4178 var x: i64 = rng_state 4179 x = x ^ (x << 13) 4180 x = x ^ ((x >> 7) & 0x01FFFFFFFFFFFFFF) 4181 x = x ^ (x << 17) 4182 rng_state = x 4183 let m: i64 = x & 0x1FFFFFFFFFFFFF 4184 ev_set(out, VAL_FLOAT, nx_f64_div(ji2f(m), ji2f(9007199254740992))) 4185 return 0 4186} 4187// Math.floor / Math.ceil. Integer arg -> identity. Float arg -> round to -inf (floor) / +inf (ceil) via 4188// trunc-toward-zero + a one-step correction, using nx_f64_lt (handles ±0 / NaN correctly). Returns VAL_NUM. 4189func bi_math_floorceil(argbuf: *i64, argc: i64, is_ceil: i64, out: *i64) -> i64 { 4190 if argc < 1 { ev_set(out, VAL_UNDEF, 0); return 1 } 4191 if ja_t(argbuf, 0) != VAL_FLOAT { ev_set(out, VAL_NUM, ev_arg_num(argbuf, 0)); return 0 } 4192 let raw: i64 = ja_p(argbuf, 0) 4193 var k: i64 = jf2i(raw) // trunc toward zero 4194 let back: i64 = ji2f(k) // f64 bits of trunc(f) 4195 if is_ceil == 1 { if nx_f64_lt(back, raw) == 1 { k = k + 1 } } // trunc<f -> ceil = trunc+1 4196 if is_ceil == 0 { if nx_f64_lt(raw, back) == 1 { k = k - 1 } } // f<trunc -> floor = trunc-1 4197 ev_set(out, VAL_NUM, k) 4198 return 0 4199} 4200// Math.sqrt(x) -> VAL_FLOAT. Int or float arg -> f64 bits -> soft-float sqrt (NaN for negatives). 4201func bi_math_sqrt(argbuf: *i64, argc: i64, out: *i64) -> i64 { 4202 if argc < 1 { ev_set(out, VAL_UNDEF, 0); return 1 } 4203 var raw: i64 = 0 4204 if ja_t(argbuf, 0) == VAL_FLOAT { raw = ja_p(argbuf, 0) } else { raw = ji2f(ev_arg_num(argbuf, 0)) } 4205 ev_set(out, VAL_FLOAT, nx_f64_sqrt(raw)) 4206 return 0 4207} 4208func bi_math_pow(argbuf: *i64, argc: i64, out: *i64) -> i64 { 4209 if argc < 2 { ev_set(out, VAL_UNDEF, 0); return 1 } 4210 // EXACT integer fast path: both operands are non-negative-exponent integers -> VAL_NUM (2^5=32 stays int). 4211 if ja_t(argbuf, 0) == VAL_NUM { if ja_t(argbuf, 1) == VAL_NUM { 4212 let base: i64 = ja_p(argbuf, 0) 4213 let exp: i64 = ja_p(argbuf, 1) 4214 if exp >= 0 { if exp <= 62 { 4215 var r: i64 = 1 4216 var e: i64 = 0 4217 while e < exp { r = r * base; e = e + 1 } 4218 ev_set(out, VAL_NUM, r) 4219 return 0 4220 } } 4221 } } 4222 // GENERAL Math.pow over reals (float base and/or float/negative exponent): x^y = fdlibm soft-float pow. 4223 let xc: *i64 = ev_cell() 4224 xc[0] = ja_t(argbuf, 0); xc[1] = ja_p(argbuf, 0) 4225 let yc: *i64 = ev_cell() 4226 yc[0] = ja_t(argbuf, 1); yc[1] = ja_p(argbuf, 1) 4227 ev_set(out, VAL_FLOAT, nx_f64_pow(ev_tof64(xc), ev_tof64(yc))) 4228 return 0 4229} 4230// coerce arg k to an i64 number (number/bool/null -> via ev_tonum; others -> 0 this rung). 4231func ev_arg_num(argbuf: *i64, k: i64) -> i64 { 4232 let tmp: *i64 = ev_cell() 4233 tmp[0] = ja_t(argbuf, k) 4234 tmp[1] = ja_p(argbuf, k) 4235 return ev_tonum(tmp) 4236} 4237 4238// ---- console.log: page-observability primitive ---- 4239// Write each arg (space-separated) to stdout + a trailing newline; return undefined. 4240func bi_console_log(argbuf: *i64, argc: i64, out: *i64) -> i64 { 4241 var i: i64 = 0 4242 while i < argc { 4243 if i > 0 { sys_write(1, " " as *u8, 1) } 4244 let tmp: *i64 = ev_cell() 4245 tmp[0] = ja_t(argbuf, i) 4246 tmp[1] = ja_p(argbuf, i) 4247 let s: *i64 = ev_coerce_str(tmp) 4248 sys_write(1, ev_str_bytes(s), ev_str_len(s)) 4249 i = i + 1 4250 } 4251 sys_write(1, "\n" as *u8, 1) 4252 ev_set(out, VAL_UNDEF, 0) 4253 return 0 4254} 4255 4256// ===================== member / index access (R-JS-OBJ) ===================== 4257// "length" string record, lazily-cached, for arr.length / arr['length']. 4258func ev_is_length_key(key: *i64) -> i64 { 4259 let lit: *i64 = ev_cstr("length\x00" as *u8) 4260 return ev_str_eq(key, lit) 4261} 4262// look a property `key` (string record) up on a VALUE; write result to out. 4263// Returns 0 ok (out = value or undefined-when-absent), 1 ERROR (member on a primitive). 4264// Objects: property table lookup, absent -> undefined. Arrays: `.length` -> number, 4265// else a numeric-string key indexes the array (out-of-range -> undefined). Member/index 4266// on a number/bool/null/undefined is a NAMED OPEN -> ERROR (no prototype this rung). 4267func ev_get_prop(base: *i64, key: *i64, out: *i64) -> i64 { 4268 let t: i64 = base[0] 4269 if t == VAL_OBJECT { 4270 let o: *i64 = (base[1]) as *i64 4271 if obj_get(o, key, out) == 1 { return 0 } 4272 if obj_proto_lookup(o, key, out) == 1 { return 0 } // inherited from the [[Prototype]] chain 4273 if obj_get((js_object_prototype()) as *i64, key, out) == 1 { return 0 } // implicit Object.prototype (user-installed) 4274 let bmo: i64 = ev_native_obj(key) // implicit Object.prototype METHOD (toString/hasOwnProperty/valueOf/...) 4275 if bmo != 0 { ev_set(out, VAL_NATIVE, bmo); return 0 } 4276 if je_dom_node_nav(o, key, out) == 1 { return 0 } // tree-DOM node lazy navigation (firstChild/lastChild/nextSibling/parentNode/childNodes/checked) 4277 ev_set(out, VAL_UNDEF, 0); return 0 // absent property -> undefined (real JS) 4278 } 4279 // A FUNCTION is an object: `.prototype` is the shared instance prototype; any other key reads from the 4280 // function's own-property store (Foo.Node, Foo.count, ...), then the implicit Object.prototype chain. 4281 if t == VAL_FUNC { 4282 if ev_key_is(key, "prototype\x00" as *u8) == 1 { ev_set(out, VAL_OBJECT, func_prototype(base[1])); return 0 } 4283 if obj_get(func_own(base[1]), key, out) == 1 { return 0 } 4284 if obj_get((js_object_prototype()) as *i64, key, out) == 1 { return 0 } // functions inherit Object.prototype (user-installed) 4285 if ev_key_is(key, "toString\x00" as *u8) == 1 { ev_set(out, VAL_NATIVE, BI_FN_TOSTRING); return 0 } // Function.prototype.toString 4286 let bmf: i64 = ev_native_obj(key) // hasOwnProperty/valueOf/isPrototypeOf inherited from Object.prototype 4287 if bmf != 0 { ev_set(out, VAL_NATIVE, bmf); return 0 } 4288 ev_set(out, VAL_UNDEF, 0); return 0 4289 } 4290 if t == VAL_ARRAY { 4291 let a: *i64 = (base[1]) as *i64 4292 if ev_is_length_key(key) == 1 { ev_set(out, VAL_NUM, arr_len(a)); return 0 } 4293 // an array METHOD name (push/pop/...) reads as a native function value (real JS). 4294 let bm: i64 = ev_native_arr(key) 4295 if bm != 0 { ev_set(out, VAL_NATIVE, bm); return 0 } 4296 if js_arrproto != 0 { if obj_get((js_arrproto) as *i64, key, out) == 1 { return 0 } } // user Array.prototype method 4297 // numeric string key -> indexed element; non-numeric -> the array's NAMED-property expando (arrays are 4298 // objects: jQuery reads handlers.delegateCount etc.), else undefined. 4299 let ki: i64 = ev_str_to_index(key) 4300 if ki >= 0 { if arr_get(a, ki, out) == 1 { return 0 } ev_set(out, VAL_UNDEF, 0); return 0 } 4301 if a[3] != 0 { if obj_get((a[3]) as *i64, key, out) == 1 { return 0 } } 4302 ev_set(out, VAL_UNDEF, 0); return 0 4303 } 4304 // STRING member READ (R-JS-RUNTIME): `.length` -> number; a string METHOD name -> a native 4305 // function value; numeric index -> the 1-char substring; anything else -> undefined. 4306 if t == VAL_STR { 4307 let rec: *i64 = (base[1]) as *i64 4308 if ev_is_length_key(key) == 1 { ev_set(out, VAL_NUM, ev_str_len(rec)); return 0 } 4309 let bm: i64 = ev_native_str(key) 4310 if bm != 0 { ev_set(out, VAL_NATIVE, bm); return 0 } 4311 if js_strproto != 0 { if obj_get((js_strproto) as *i64, key, out) == 1 { return 0 } } // user String.prototype method 4312 let ki: i64 = ev_str_to_index(key) 4313 if ki >= 0 { ev_str_char_at(rec, ki, out); return 0 } 4314 ev_set(out, VAL_UNDEF, 0); return 0 4315 } 4316 // GLOBAL NAMESPACE member READ (Math/Object/console/JSON): a known method name -> a native 4317 // function value; an unknown name -> undefined (real JS, e.g. Math.zzz === undefined). 4318 if t == VAL_GLOBALNS { 4319 if base[1] == NS_OBJECT { if ev_key_is(key, "prototype\x00" as *u8) == 1 { ev_set(out, VAL_OBJECT, js_object_prototype()); return 0 } } 4320 if obj_get(js_ns_statics(base[1]), key, out) == 1 { return 0 } // user statics (Object.extend) win 4321 let bm: i64 = ev_native_global(base[1], key) 4322 if bm != 0 { ev_set(out, VAL_NATIVE, bm); return 0 } 4323 ev_set(out, VAL_UNDEF, 0); return 0 4324 } 4325 // member on a built-in CONSTRUCTOR native (String/Array): `.prototype` -> the writable proto object so user 4326 // code can extend it (`String.prototype.m = fn`). Other keys -> undefined (statics like fromCharcode are 4327 // resolved at the CALL site). This was the EarleyBoyer runtime blocker (String.prototype was an error). 4328 if t == VAL_NATIVE { 4329 if base[1] == BI_STRING_CTOR { if ev_key_is(key, "prototype\x00" as *u8) == 1 { ev_set(out, VAL_OBJECT, js_string_prototype()); return 0 } } 4330 if base[1] == BI_ARRAY_CTOR { if ev_key_is(key, "prototype\x00" as *u8) == 1 { ev_set(out, VAL_OBJECT, js_array_prototype()); return 0 } } 4331 if ev_key_is(key, "toString\x00" as *u8) == 1 { ev_set(out, VAL_NATIVE, BI_FN_TOSTRING); return 0 } // a native IS a function (jQuery: fnToString = hasOwn.toString) 4332 let bmn: i64 = ev_native_obj(key) // hasOwnProperty/valueOf/isPrototypeOf on the function object 4333 if bmn != 0 { ev_set(out, VAL_NATIVE, bmn); return 0 } 4334 ev_set(out, VAL_UNDEF, 0); return 0 4335 } 4336 if t == VAL_REGEX { // re.lastIndex / re.source / re.global; other props -> undefined (V8 object semantics) 4337 let rr: *i64 = (base[1]) as *i64 4338 if ev_key_is(key, "lastIndex\x00" as *u8) == 1 { ev_set(out, VAL_NUM, rr[4]); return 0 } 4339 if ev_key_is(key, "source\x00" as *u8) == 1 { ev_set(out, VAL_STR, (je_str_range((rr[1]) as *u8, 0, rr[2])) as i64); return 0 } 4340 if ev_key_is(key, "global\x00" as *u8) == 1 { var gb: i64 = 0; if (rr[3] & RXF_G) != 0 { gb = 1 } ev_set(out, VAL_BOOL, gb); return 0 } 4341 ev_set(out, VAL_UNDEF, 0); return 0 4342 } 4343 // property READ on a NUMBER/BOOL/FLOAT primitive -> undefined (V8: primitives wrap; a missing 4344 // prop reads undefined -- `(132).foo` is NOT an error; scheme2js feature-detects methods this way). 4345 // Methods that DO exist on numbers dispatch via ev_native_num in the call paths, not here. 4346 if t == VAL_NUM { ev_set(out, VAL_UNDEF, 0); return 0 } 4347 if t == VAL_FLOAT { ev_set(out, VAL_UNDEF, 0); return 0 } 4348 if t == VAL_BOOL { ev_set(out, VAL_UNDEF, 0); return 0 } 4349 // member on undefined/null (and anything else unhandled) IS an error (V8: TypeError). 4350 if js_rt_dbg == 1 { sys_write(2, "PROP-ERR base-tag=" as *u8, 18); nx_dbg_num(t); sys_write(2, " key='" as *u8, 6); sys_write(2, ev_str_bytes(key), ev_str_len(key)); sys_write(2, "'\n" as *u8, 2) } 4351 ev_set(out, VAL_UNDEF, 0); return 1 4352} 4353// compare a STRING-RECORD key against a NUL-terminated literal name; 1 iff equal bytes. 4354func ev_key_is(key: *i64, lit: *u8) -> i64 { 4355 let l: i64 = ev_str_len(key) 4356 var n: i64 = 0 4357 while lit[n] != (0 as u8) { n = n + 1 } 4358 if l != n { return 0 } 4359 let by: *u8 = ev_str_bytes(key) 4360 var i: i64 = 0 4361 while i < l { if (by[i] & 0xff) != (lit[i] & 0xff) { return 0 } i = i + 1 } 4362 return 1 4363} 4364// resolve a STRING method name -> its builtin id, or 0 if not a string method. 4365func ev_native_str(key: *i64) -> i64 { 4366 if ev_key_is(key, "toString\x00" as *u8) == 1 { return BI_NUM_TOSTRING } // "s".toString() = identity (radix-10 path coerces = self) 4367 if ev_key_is(key, "charAt\x00" as *u8) == 1 { return BI_STR_CHARAT } 4368 if ev_key_is(key, "indexOf\x00" as *u8) == 1 { return BI_STR_INDEXOF } 4369 if ev_key_is(key, "slice\x00" as *u8) == 1 { return BI_STR_SLICE } 4370 if ev_key_is(key, "concat\x00" as *u8) == 1 { return BI_STR_CONCAT } 4371 if ev_key_is(key, "toUpperCase\x00" as *u8) == 1 { return BI_STR_UPPER } 4372 if ev_key_is(key, "toLowerCase\x00" as *u8) == 1 { return BI_STR_LOWER } 4373 if ev_key_is(key, "includes\x00" as *u8) == 1 { return BI_STR_INCLUDES } 4374 if ev_key_is(key, "search\x00" as *u8) == 1 { return BI_STR_SEARCH } // R-JS-REGEX: str.search(re) 4375 if ev_key_is(key, "match\x00" as *u8) == 1 { return BI_STR_MATCH } 4376 if ev_key_is(key, "replace\x00" as *u8) == 1 { return BI_STR_REPLACE } 4377 if ev_key_is(key, "split\x00" as *u8) == 1 { return BI_STR_SPLIT } 4378 if ev_key_is(key, "charCodeAt\x00" as *u8) == 1 { return BI_STR_CHARCODEAT } 4379 if ev_key_is(key, "substring\x00" as *u8) == 1 { return BI_STR_SUBSTRING } 4380 if ev_key_is(key, "substr\x00" as *u8) == 1 { return BI_STR_SUBSTR } 4381 return 0 4382} 4383// s.charCodeAt(i): byte code unit (ASCII-exact for our byte strings); out-of-range -> NaN (real JS). 4384func bi_str_charcodeat(thisv: *i64, argbuf: *i64, argc: i64, out: *i64) -> i64 { 4385 let srec: *i64 = (thisv[1]) as *i64 4386 let sl: i64 = ev_str_len(srec) 4387 var ix: i64 = 0 4388 if argc >= 1 { ix = ev_arg_num(argbuf, 0) } 4389 if ix < 0 { ev_set(out, VAL_FLOAT, nx_f64_div(ji2f(0), ji2f(0))); return 0 } 4390 if ix >= sl { ev_set(out, VAL_FLOAT, nx_f64_div(ji2f(0), ji2f(0))); return 0 } 4391 let b: *u8 = ev_str_bytes(srec) 4392 ev_set(out, VAL_NUM, b[ix] & 0xff) 4393 return 0 4394} 4395// String.fromCharCode(a,b,..): build a byte string from the low bytes (ASCII range; crypto/base64 use). 4396// LENGTH-based build (NOT ev_cstr, which NUL-scans): JS strings may contain \0 -- fromCharCode(0) is a 4397// 1-char string, and Octane RegExp's computeInputVariants inserts fromCharCode(0) mid-string. 4398func bi_str_fromcharcode(argbuf: *i64, argc: i64, out: *i64) -> i64 { 4399 let b: *u8 = sys_mmap(argc + 8) 4400 var i: i64 = 0 4401 while i < argc { b[i] = (ev_arg_num(argbuf, i) & 0xff) as u8; i = i + 1 } 4402 ev_set(out, VAL_STR, (je_buf_to_str(b, argc)) as i64) 4403 return 0 4404} 4405// s.substring(a,b?) with JS clamp-and-swap; s.substr(a,len?). LENGTH-based copy (NOT ev_cstr, which 4406// NUL-scans -> would truncate a substring that spans an interior \0; see bi_str_fromcharcode). 4407func bi_str_subcopy(srec: *i64, lo: i64, hi: i64, out: *i64) -> i64 { 4408 let sb: *u8 = ev_str_bytes(srec) 4409 let n: i64 = hi - lo 4410 ev_set(out, VAL_STR, (je_str_range(sb, lo, n)) as i64) 4411 return 0 4412} 4413func bi_str_substring(thisv: *i64, argbuf: *i64, argc: i64, out: *i64) -> i64 { 4414 let srec: *i64 = (thisv[1]) as *i64 4415 let sl: i64 = ev_str_len(srec) 4416 var a: i64 = 0 4417 var b2: i64 = sl 4418 if argc >= 1 { a = ev_arg_num(argbuf, 0) } 4419 if argc >= 2 { b2 = ev_arg_num(argbuf, 1) } 4420 if a < 0 { a = 0 } 4421 if a > sl { a = sl } 4422 if b2 < 0 { b2 = 0 } 4423 if b2 > sl { b2 = sl } 4424 if a > b2 { let t: i64 = a; a = b2; b2 = t } 4425 return bi_str_subcopy(srec, a, b2, out) 4426} 4427func bi_str_substr(thisv: *i64, argbuf: *i64, argc: i64, out: *i64) -> i64 { 4428 let srec: *i64 = (thisv[1]) as *i64 4429 let sl: i64 = ev_str_len(srec) 4430 var a: i64 = 0 4431 var ln: i64 = sl 4432 if argc >= 1 { a = ev_arg_num(argbuf, 0) } 4433 if argc >= 2 { ln = ev_arg_num(argbuf, 1) } 4434 if a < 0 { a = sl + a; if a < 0 { a = 0 } } 4435 if a > sl { a = sl } 4436 if ln < 0 { ln = 0 } 4437 var hi: i64 = a + ln 4438 if hi > sl { hi = sl } 4439 return bi_str_subcopy(srec, a, hi, out) 4440} 4441// parseInt(str, radix?): leading ws + optional sign + radix digits (0-9 a-z A-Z < radix); none -> NaN. 4442func bi_parseint(argbuf: *i64, argc: i64, out: *i64) -> i64 { 4443 if argc < 1 { ev_set(out, VAL_UNDEF, 0); return 1 } 4444 let srec: *i64 = ev_coerce_str_arg(argbuf, 0) 4445 let s: *u8 = ev_str_bytes(srec) 4446 let sl: i64 = ev_str_len(srec) 4447 var radix: i64 = 10 4448 if argc >= 2 { let r2: i64 = ev_arg_num(argbuf, 1); if r2 != 0 { radix = r2 } } 4449 var i: i64 = 0 4450 var ws: i64 = 1 4451 while ws == 1 { if i < sl { if s[i] == 32 { i = i + 1 } else { if s[i] == 9 { i = i + 1 } else { ws = 0 } } } else { ws = 0 } } 4452 var sign: i64 = 1 4453 if i < sl { if s[i] == 45 { sign = 0 - 1; i = i + 1 } else { if s[i] == 43 { i = i + 1 } } } 4454 var v: i64 = 0 4455 var got: i64 = 0 4456 var go: i64 = 1 4457 while go == 1 { 4458 if i >= sl { go = 0 } else { 4459 let c: i64 = s[i] & 0xff 4460 var d: i64 = 0 - 1 4461 if c >= 48 { if c <= 57 { d = c - 48 } } 4462 if c >= 97 { if c <= 122 { d = c - 87 } } 4463 if c >= 65 { if c <= 90 { d = c - 55 } } 4464 if d < 0 { go = 0 } else { if d >= radix { go = 0 } else { v = v * radix + d; got = 1; i = i + 1 } } 4465 } 4466 } 4467 if got == 0 { ev_set(out, VAL_FLOAT, nx_f64_div(ji2f(0), ji2f(0))); return 0 } 4468 ev_set(out, VAL_NUM, sign * v) 4469 return 0 4470} 4471// shared VAL_REGEX builder: compile pattern bytes + flag bytes -> a regex record. (patb persists as a heap 4472// string's bytes; the compiled *Regex is self-contained so match doesn't need patb.) 4473func js_make_regex_val(patb: *u8, patlen: i64, flb: *u8, fllen: i64, out: *i64) -> i64 { 4474 let fbits: i64 = nx_regex_flags(flb, fllen) 4475 let re: *Regex = nx_regex_compile(patb, patlen, fbits) 4476 let rec: *i64 = sys_mmap(5 * 8) as *i64 4477 rec[0] = re as i64; rec[1] = patb as i64; rec[2] = patlen; rec[3] = fbits; rec[4] = 0 // rec[4] = lastIndex 4478 ev_set(out, VAL_REGEX, rec as i64) 4479 return 0 4480} 4481// new RegExp(pat[,flags]) / RegExp(...): pat=string(compiled) or a regex(cloned); flags=optional string. 4482func bi_regex_ctor(argbuf: *i64, argc: i64, out: *i64) -> i64 { 4483 if argc == 0 { let e: *i64 = ev_str_new(0); return js_make_regex_val(ev_str_bytes(e), 0, ev_str_bytes(e), 0, out) } 4484 if argbuf[0] == VAL_REGEX { ev_set(out, VAL_REGEX, argbuf[1]); return 0 } // clone 4485 let patrec: *i64 = ev_coerce_str_arg(argbuf, 0) 4486 let e0: *i64 = ev_str_new(0) 4487 var flb: *u8 = ev_str_bytes(e0) 4488 var fllen: i64 = 0 4489 if argc >= 2 { let flrec: *i64 = ev_coerce_str_arg(argbuf, 1); flb = ev_str_bytes(flrec); fllen = ev_str_len(flrec) } 4490 return js_make_regex_val(ev_str_bytes(patrec), ev_str_len(patrec), flb, fllen, out) 4491} 4492// str.split(re): split on each (non-empty) regex match. zero-width matches are skipped (empty-sep = named open). 4493func bi_str_split(thisv: *i64, argbuf: *i64, argc: i64, out: *i64) -> i64 { 4494 if thisv[0] != VAL_STR { ev_set(out, VAL_UNDEF, 0); return 1 } 4495 let srec: *i64 = (thisv[1]) as *i64 4496 let sb: *u8 = ev_str_bytes(srec); let sl: i64 = ev_str_len(srec) 4497 let a: *i64 = arr_new() 4498 if argc < 1 { arr_set(a, 0, VAL_STR, srec as i64); ev_set(out, VAL_ARRAY, a as i64); return 0 } 4499 // STRING separator: "a,b,c".split(",") -> ["a","b","c"] ("5,5".split(",") drives RayTrace's pixelSize). 4500 // Empty "" separator -> one element per character (real JS). 4501 if argbuf[0] == VAL_STR { 4502 let seprec: *i64 = (argbuf[1]) as *i64 4503 let pb: *u8 = ev_str_bytes(seprec) 4504 let pl: i64 = ev_str_len(seprec) 4505 if pl == 0 { 4506 var ci: i64 = 0 4507 while ci < sl { arr_set(a, ci, VAL_STR, (js_substr(sb, ci, ci + 1)) as i64); ci = ci + 1 } 4508 ev_set(out, VAL_ARRAY, a as i64); return 0 4509 } 4510 var start: i64 = 0 4511 var pos: i64 = 0 4512 var cnt: i64 = 0 4513 while (pos + pl) <= sl { 4514 var m: i64 = 1 4515 var k: i64 = 0 4516 while k < pl { if (sb[pos + k] & 0xff) != (pb[k] & 0xff) { m = 0; k = pl } else { k = k + 1 } } 4517 if m == 1 { 4518 arr_set(a, cnt, VAL_STR, (js_substr(sb, start, pos)) as i64); cnt = cnt + 1 4519 pos = pos + pl; start = pos 4520 } else { pos = pos + 1 } 4521 } 4522 arr_set(a, cnt, VAL_STR, (js_substr(sb, start, sl)) as i64) 4523 ev_set(out, VAL_ARRAY, a as i64); return 0 4524 } 4525 if argbuf[0] != VAL_REGEX { arr_set(a, 0, VAL_STR, srec as i64); ev_set(out, VAL_ARRAY, a as i64); return 0 } 4526 let rrec: *i64 = (argbuf[1]) as *i64 4527 let re: *Regex = (rrec[0]) as *Regex 4528 let saves: *i64 = sys_mmap(re.nsave * 8) as *i64 4529 var pos: i64 = 0; var start: i64 = 0; var cnt: i64 = 0; var go: i64 = 1 4530 while go == 1 { 4531 let ms: i64 = nx_regex_exec(re, sb, sl, pos, saves) 4532 if ms < 0 { go = 0 } else { 4533 let me: i64 = saves[1] 4534 if me == ms { pos = ms + 1; if pos > sl { go = 0 } } 4535 else { arr_set(a, cnt, VAL_STR, (js_substr(sb, start, ms)) as i64); cnt = cnt + 1; start = me; pos = me; if pos > sl { go = 0 } } 4536 } 4537 } 4538 arr_set(a, cnt, VAL_STR, (js_substr(sb, start, sl)) as i64) 4539 ev_set(out, VAL_ARRAY, a as i64) 4540 return 0 4541} 4542// substring sb[lo..hi) as a fresh string rec. 4543func js_substr(sb: *u8, lo: i64, hi: i64) -> *i64 { 4544 var n: i64 = hi - lo 4545 if n < 0 { n = 0 } 4546 let rec: *i64 = ev_str_new(n) 4547 let db: *u8 = ev_str_bytes(rec) 4548 var i: i64 = 0 4549 while i < n { db[i] = sb[lo + i]; i = i + 1 } 4550 return rec 4551} 4552// build a match-result array [fullmatch, g1, g2, ...] from `saves`; undefined for unmatched groups. 4553func js_regex_result(re: *Regex, sb: *u8, saves: *i64) -> *i64 { 4554 let a: *i64 = arr_new() 4555 let ng: i64 = re.ngroup 4556 var gi: i64 = 0 4557 while gi <= ng { 4558 let lo: i64 = saves[2 * gi]; let hi: i64 = saves[2 * gi + 1] 4559 if lo < 0 { arr_set(a, gi, VAL_UNDEF, 0) } else { arr_set(a, gi, VAL_STR, (js_substr(sb, lo, hi)) as i64) } 4560 gi = gi + 1 4561 } 4562 return a 4563} 4564// re.exec(str): [full,g1,..] or null. With the g flag, resumes from + advances the regex record's lastIndex 4565// (rec[4]) so `while((m=re.exec(s)))` walks every match; a null result resets lastIndex to 0. 4566func bi_re_exec(thisv: *i64, argbuf: *i64, argc: i64, out: *i64) -> i64 { 4567 if thisv[0] != VAL_REGEX { ev_set(out, VAL_UNDEF, 0); return 1 } 4568 let rec: *i64 = (thisv[1]) as *i64 4569 let re: *Regex = (rec[0]) as *Regex 4570 if argc < 1 { ev_set(out, VAL_NULL, 0); return 0 } 4571 let srec: *i64 = ev_coerce_str_arg(argbuf, 0) 4572 let sb: *u8 = ev_str_bytes(srec); let sl: i64 = ev_str_len(srec) 4573 let g: i64 = rec[3] & RXF_G 4574 var start: i64 = 0 4575 if g != 0 { start = rec[4]; if start < 0 { start = 0 } } 4576 let saves: *i64 = sys_mmap(re.nsave * 8) as *i64 4577 let ms: i64 = nx_regex_exec(re, sb, sl, start, saves) 4578 if ms < 0 { if g != 0 { rec[4] = 0 } ev_set(out, VAL_NULL, 0); return 0 } 4579 let me: i64 = saves[1] 4580 if g != 0 { if me == ms { rec[4] = me + 1 } else { rec[4] = me } } 4581 ev_set(out, VAL_ARRAY, (js_regex_result(re, sb, saves)) as i64) 4582 return 0 4583} 4584// str.match(re): g-flag -> array of ALL full-match strings; else -> [full, g1, g2, ...]; no match -> null. 4585func bi_str_match(thisv: *i64, argbuf: *i64, argc: i64, out: *i64) -> i64 { 4586 if thisv[0] != VAL_STR { ev_set(out, VAL_UNDEF, 0); return 1 } 4587 if argc < 1 { ev_set(out, VAL_NULL, 0); return 0 } 4588 if argbuf[0] != VAL_REGEX { ev_set(out, VAL_NULL, 0); return 0 } 4589 let srec: *i64 = (thisv[1]) as *i64 4590 let sb: *u8 = ev_str_bytes(srec); let sl: i64 = ev_str_len(srec) 4591 let rrec: *i64 = (argbuf[1]) as *i64 4592 let re: *Regex = (rrec[0]) as *Regex 4593 let saves: *i64 = sys_mmap(re.nsave * 8) as *i64 4594 if (rrec[3] & RXF_G) != 0 { 4595 let a: *i64 = arr_new() 4596 var pos: i64 = 0; var cnt: i64 = 0; var go: i64 = 1 4597 while go == 1 { 4598 let ms: i64 = nx_regex_exec(re, sb, sl, pos, saves) 4599 if ms < 0 { go = 0 } else { 4600 let me: i64 = saves[1] 4601 arr_set(a, cnt, VAL_STR, (js_substr(sb, ms, me)) as i64); cnt = cnt + 1 4602 if me == ms { pos = ms + 1 } else { pos = me } 4603 if pos > sl { go = 0 } 4604 } 4605 } 4606 if cnt == 0 { ev_set(out, VAL_NULL, 0); return 0 } 4607 ev_set(out, VAL_ARRAY, a as i64); return 0 4608 } 4609 let ms: i64 = nx_regex_exec(re, sb, sl, 0, saves) 4610 if ms < 0 { ev_set(out, VAL_NULL, 0); return 0 } 4611 let a: *i64 = arr_new() 4612 let ng: i64 = re.ngroup 4613 var gi: i64 = 0 4614 while gi <= ng { 4615 let lo: i64 = saves[2 * gi]; let hi: i64 = saves[2 * gi + 1] 4616 if lo < 0 { arr_set(a, gi, VAL_UNDEF, 0) } else { arr_set(a, gi, VAL_STR, (js_substr(sb, lo, hi)) as i64) } 4617 gi = gi + 1 4618 } 4619 ev_set(out, VAL_ARRAY, a as i64); return 0 4620} 4621// append a replacement template (literal + $& whole-match + $1..$9 groups + $$) into buf at bpp[0]. 4622func js_repl_expand(rb: *u8, rl: i64, sb: *u8, saves: *i64, ng: i64, buf: *u8, bpp: *i64) -> i64 { 4623 var i: i64 = 0 4624 while i < rl { 4625 let c: i64 = (rb[i]) as i64 4626 if c == 36 { if i + 1 < rl { 4627 let n: i64 = (rb[i + 1]) as i64 4628 if n == 36 { buf[bpp[0]] = 36 as u8; bpp[0] = bpp[0] + 1; i = i + 2 } 4629 else { if n == 38 { let lo: i64 = saves[0]; let hi: i64 = saves[1]; var k: i64 = lo; while k < hi { buf[bpp[0]] = sb[k]; bpp[0] = bpp[0] + 1; k = k + 1 } i = i + 2 } 4630 else { var isg: i64 = 0; if n >= 49 { if n <= 57 { isg = 1 } } 4631 if isg == 1 { let g: i64 = n - 48; if g <= ng { let lo: i64 = saves[2 * g]; let hi: i64 = saves[2 * g + 1]; if lo >= 0 { var k: i64 = lo; while k < hi { buf[bpp[0]] = sb[k]; bpp[0] = bpp[0] + 1; k = k + 1 } } } i = i + 2 } 4632 else { buf[bpp[0]] = 36 as u8; bpp[0] = bpp[0] + 1; i = i + 1 } } } 4633 } else { buf[bpp[0]] = 36 as u8; bpp[0] = bpp[0] + 1; i = i + 1 } } 4634 else { buf[bpp[0]] = c as u8; bpp[0] = bpp[0] + 1; i = i + 1 } 4635 } 4636 return 0 4637} 4638// str.replace(re, repl): regex replace. g-flag -> all matches, else first. repl = string with $ substitution. 4639func bi_str_replace(thisv: *i64, argbuf: *i64, argc: i64, out: *i64) -> i64 { 4640 if thisv[0] != VAL_STR { ev_set(out, VAL_UNDEF, 0); return 1 } 4641 let srec: *i64 = (thisv[1]) as *i64 4642 let sb: *u8 = ev_str_bytes(srec);