rapydscript-ng 0.7.24 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (114) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/README.md +72 -26
  3. package/bin/package.json +1 -0
  4. package/bin/rapydscript +65 -62
  5. package/editor-plugins/nvim/rapydscript/README.md +184 -0
  6. package/editor-plugins/nvim/rapydscript/bin/rapydscript.so +0 -0
  7. package/editor-plugins/nvim/rapydscript/ftdetect/rapydscript.lua +10 -0
  8. package/editor-plugins/nvim/rapydscript/lua/rapydscript/health.lua +88 -0
  9. package/editor-plugins/nvim/rapydscript/lua/rapydscript/init.lua +279 -0
  10. package/local-agent.md +28 -0
  11. package/package.json +4 -5
  12. package/release/baselib-plain-pretty.js +448 -326
  13. package/release/baselib-plain-ugly.js +3 -3
  14. package/release/compiler.js +2528 -1672
  15. package/release/signatures.json +17 -17
  16. package/src/ast.pyj +73 -25
  17. package/src/baselib-builtins.pyj +2 -2
  18. package/src/baselib-containers.pyj +153 -151
  19. package/src/baselib-internal.pyj +3 -3
  20. package/src/baselib-str.pyj +5 -5
  21. package/src/lib/aes.pyj +1 -1
  22. package/src/lib/encodings.pyj +1 -1
  23. package/src/lib/pythonize.pyj +1 -1
  24. package/src/lib/re.pyj +2 -2
  25. package/src/lib/traceback.pyj +165 -28
  26. package/src/lib/uuid.pyj +1 -1
  27. package/src/output/codegen.pyj +10 -3
  28. package/src/output/functions.pyj +31 -40
  29. package/src/output/literals.pyj +11 -14
  30. package/src/output/loops.pyj +25 -87
  31. package/src/output/modules.pyj +5 -47
  32. package/src/output/statements.pyj +1 -1
  33. package/src/output/stream.pyj +58 -31
  34. package/src/parse.pyj +777 -427
  35. package/src/tokenizer.pyj +16 -4
  36. package/src/utils.pyj +1 -1
  37. package/test/_treeshake_mod.pyj +30 -0
  38. package/test/_treeshake_side_effect.pyj +9 -0
  39. package/test/ast_serialization.pyj +392 -0
  40. package/test/async.pyj +95 -0
  41. package/test/baselib.pyj +1 -2
  42. package/test/embedded_compiler.pyj +57 -0
  43. package/test/fmt.pyj +201 -0
  44. package/test/generic.pyj +7 -3
  45. package/test/imports.pyj +8 -6
  46. package/test/internationalization.pyj +38 -37
  47. package/test/lint.pyj +120 -117
  48. package/test/lsp.pyj +222 -0
  49. package/test/repl.pyj +70 -65
  50. package/test/sourcemaps.pyj +315 -0
  51. package/test/starargs.pyj +46 -39
  52. package/test/traceback.pyj +263 -0
  53. package/test/treeshaking.pyj +181 -0
  54. package/test/web_repl_export.pyj +88 -0
  55. package/tools/ast_serialize.mjs +557 -0
  56. package/tools/cli.mjs +510 -0
  57. package/tools/compile.mjs +201 -0
  58. package/tools/compiler.mjs +127 -0
  59. package/tools/{completer.js → completer.mjs} +6 -6
  60. package/tools/{embedded_compiler.js → embedded_compiler.mjs} +17 -13
  61. package/tools/export.js +109 -56
  62. package/tools/fmt.mjs +735 -0
  63. package/tools/{gettext.js → gettext.mjs} +46 -53
  64. package/tools/{ini.js → ini.mjs} +9 -10
  65. package/tools/{lint.js → lint.mjs} +78 -55
  66. package/tools/lsp.mjs +914 -0
  67. package/tools/lsp_protocol.mjs +259 -0
  68. package/tools/lsp_symbols.mjs +418 -0
  69. package/tools/{msgfmt.js → msgfmt.mjs} +18 -18
  70. package/tools/{repl.js → repl.mjs} +52 -41
  71. package/tools/{self.js → self.mjs} +56 -46
  72. package/tools/sourcemap.mjs +123 -0
  73. package/tools/test.mjs +152 -0
  74. package/tools/treeshake.mjs +400 -0
  75. package/tools/{utils.js → utils.mjs} +26 -23
  76. package/tools/{web_repl.js → web_repl.mjs} +15 -13
  77. package/tools/web_repl_export.mjs +93 -0
  78. package/tree-sitter/README.md +101 -0
  79. package/tree-sitter/grammar.js +992 -0
  80. package/tree-sitter/package.json +43 -0
  81. package/tree-sitter/queries/rapydscript/highlights.scm +232 -0
  82. package/tree-sitter/queries/rapydscript/indents.scm +64 -0
  83. package/tree-sitter/queries/rapydscript/injections.scm +14 -0
  84. package/tree-sitter/queries/rapydscript/locals.scm +108 -0
  85. package/tree-sitter/src/grammar.json +4976 -0
  86. package/tree-sitter/src/node-types.json +2901 -0
  87. package/tree-sitter/src/parser.c +196465 -0
  88. package/tree-sitter/src/scanner.c +294 -0
  89. package/tree-sitter/src/tree_sitter/alloc.h +54 -0
  90. package/tree-sitter/src/tree_sitter/array.h +330 -0
  91. package/tree-sitter/src/tree_sitter/parser.h +286 -0
  92. package/tree-sitter/test/corpus/chaining.txt +99 -0
  93. package/tree-sitter/test/corpus/classes.txt +147 -0
  94. package/tree-sitter/test/corpus/comprehensions.txt +155 -0
  95. package/tree-sitter/test/corpus/containers.txt +242 -0
  96. package/tree-sitter/test/corpus/control_flow.txt +361 -0
  97. package/tree-sitter/test/corpus/decorators.txt +64 -0
  98. package/tree-sitter/test/corpus/existential.txt +102 -0
  99. package/tree-sitter/test/corpus/functions.txt +293 -0
  100. package/tree-sitter/test/corpus/imports.txt +117 -0
  101. package/tree-sitter/test/corpus/literals.txt +135 -0
  102. package/tree-sitter/test/corpus/operators.txt +296 -0
  103. package/tree-sitter/test/corpus/statements.txt +189 -0
  104. package/tree-sitter/test/corpus/strings.txt +90 -0
  105. package/tree-sitter/test/corpus/subscripts.txt +227 -0
  106. package/tree-sitter/tree-sitter.json +36 -0
  107. package/web-repl/env.js +35 -23
  108. package/web-repl/main.js +8 -8
  109. package/bin/export +0 -75
  110. package/bin/web-repl-export +0 -102
  111. package/tools/cli.js +0 -523
  112. package/tools/compile.js +0 -184
  113. package/tools/compiler.js +0 -84
  114. package/tools/test.js +0 -110
@@ -0,0 +1,557 @@
1
+ // AST serialization/deserialization for RapydScript.
2
+ //
3
+ // Strategy: flat node pool. Every unique AST object is assigned a numeric index.
4
+ // Child references are stored as {_n: idx}. Pre-registering each node before
5
+ // serializing its properties handles shared references (e.g. AST_Class.init
6
+ // pointing to a node also in body) and self-referential structures (e.g.
7
+ // AST_Toplevel.imports["__main__"] pointing back to itself) without infinite
8
+ // recursion.
9
+ //
10
+ // Properties skipped (SymbolDef back-references or redundant):
11
+ // scope - AST_Symbol/AST_Directive back-ref to ancestor scope
12
+ // globals - AST_Toplevel SymbolDef map (not needed for code generation)
13
+ // exports - AST_Toplevel SymbolDef array (used only for non-main modules)
14
+ //
15
+ // thedef on AST_Symbol is handled specially: if it is an AST node (class
16
+ // variables get thedef=AST_SymbolDefun whose .name is "Cls.prototype.var"),
17
+ // it is serialized as a normal node reference; otherwise it is a SymbolDef
18
+ // plain object and only its .name/.mangled_name are preserved (as a compact
19
+ // stub), because those are the only fields read by code generation.
20
+ //
21
+ // AST_Import.body is a lazy getter returning another module's full AST_Toplevel;
22
+ // already captured in AST_Toplevel.imports — skipped to avoid duplication.
23
+ //
24
+ // AST_RegExp.value is a live RegExp object; serialized as {_re: source, _rf: flags}.
25
+
26
+ 'use strict';
27
+
28
+ const SKIP_PROPS = new Set(['scope', 'globals', 'exports']);
29
+ // AST_Toplevel.imports is the global shared imported_modules dict (all modules
30
+ // across the entire compilation, including back-references to itself). It is
31
+ // rebuilt at load time and must not be serialized.
32
+ const SKIP_PER_TYPE = {
33
+ AST_Import: new Set(['body']),
34
+ AST_Toplevel: new Set(['imports']),
35
+ // file is redundant on every token — filled in from AST_Toplevel.filename at load time
36
+ AST_Token: new Set(['file']),
37
+ };
38
+ const RE_TAG = '[object RegExp]';
39
+
40
+ function is_ast_node(val) {
41
+ return (
42
+ val !== null &&
43
+ typeof val === 'object' &&
44
+ typeof val.constructor === 'function' &&
45
+ val.constructor.name.startsWith('AST_')
46
+ );
47
+ }
48
+
49
+ function is_regexp(val) {
50
+ return Object.prototype.toString.call(val) === RE_TAG;
51
+ }
52
+
53
+ const _declared_keys_cache = new Map();
54
+
55
+ // Walk the prototype chain collecting all keys from each class's `properties` dict.
56
+ function collect_declared_keys(node) {
57
+ const cls = node.constructor;
58
+ if (_declared_keys_cache.has(cls)) return _declared_keys_cache.get(cls);
59
+ const keys = new Set();
60
+ let proto = node;
61
+ while ((proto = Object.getPrototypeOf(proto)) !== null) {
62
+ if (Object.prototype.hasOwnProperty.call(proto, 'properties')) {
63
+ for (const k of Object.keys(proto.properties)) keys.add(k);
64
+ }
65
+ }
66
+ _declared_keys_cache.set(cls, keys);
67
+ return keys;
68
+ }
69
+
70
+ // ─── Serialization ───────────────────────────────────────────────────────────
71
+
72
+ function ser_val(val, pool, seen) {
73
+ if (val === null || val === undefined) return null;
74
+ const t = typeof val;
75
+ if (t === 'string' || t === 'boolean' || t === 'number') return val;
76
+ if (is_regexp(val)) {
77
+ return { _re: val.source, _rf: (val.flags !== undefined ? val.flags : '') };
78
+ }
79
+ if (Array.isArray(val)) {
80
+ return val.map(el => ser_val(el, pool, seen));
81
+ }
82
+ if (is_ast_node(val)) {
83
+ return { _n: ser_node(val, pool, seen) };
84
+ }
85
+ // Plain object: defaults dict, classvars, static dict, baselib, etc.
86
+ const out = {};
87
+ for (const k of Object.keys(val)) {
88
+ out[k] = ser_val(val[k], pool, seen);
89
+ }
90
+ return out;
91
+ }
92
+
93
+ function ser_node(node, pool, seen) {
94
+ if (seen.has(node)) return seen.get(node);
95
+ const idx = pool.length;
96
+ pool.push(null); // placeholder — must be pushed before recursive calls
97
+ seen.set(node, idx);
98
+
99
+ const type_name = node.constructor.name;
100
+ const per_type_skip = SKIP_PER_TYPE[type_name];
101
+ const p = {};
102
+
103
+ for (const key of collect_declared_keys(node)) {
104
+ if (SKIP_PROPS.has(key)) continue;
105
+ if (per_type_skip && per_type_skip.has(key)) continue;
106
+ const val = node[key];
107
+ if (val === undefined) continue;
108
+ // thedef: AST node (class-variable case) or SymbolDef plain object.
109
+ // For SymbolDef only preserve name+mangled_name; avoid serializing
110
+ // its scope/orig/refs back-references to keep JSON compact.
111
+ if (key === 'thedef') {
112
+ if (is_ast_node(val)) {
113
+ p.thedef = { _n: ser_node(val, pool, seen) };
114
+ } else if (val !== null && typeof val.name === 'string') {
115
+ p.thedef = { _td: 1, _n: val.name, _mn: val.mangled_name || null };
116
+ }
117
+ } else {
118
+ p[key] = ser_val(val, pool, seen);
119
+ }
120
+ }
121
+
122
+ pool[idx] = { t: type_name, p };
123
+ return idx;
124
+ }
125
+
126
+ // ─── Deserialization ──────────────────────────────────────────────────────────
127
+
128
+ // Returns a pair of mutually-recursive functions bound to a specific
129
+ // compiler_exports + pool + built triple.
130
+ function make_deserializer(compiler_exports, pool, built) {
131
+ function dv(val) {
132
+ if (val === null || val === undefined) return null;
133
+ const t = typeof val;
134
+ if (t !== 'object') return val;
135
+ if (Array.isArray(val)) return val.map(dv);
136
+ // Fast sentinel detection without Object.keys allocation.
137
+ // Node ref: {_n: integer} — most common case, check first
138
+ const vn = val._n;
139
+ if (vn !== undefined) {
140
+ if (typeof vn === 'number') return bn(vn);
141
+ // SymbolDef stub: {_td: 1, _n: string, _mn: string|null}
142
+ return { name: vn, mangled_name: val._mn || null };
143
+ }
144
+ // RegExp sentinel: {_re: string, _rf: string}
145
+ if (val._re !== undefined) return new RegExp(val._re, val._rf || '');
146
+ // Plain object (defaults, classvars, baselib, etc.)
147
+ const out = {};
148
+ for (const k of Object.keys(val)) out[k] = dv(val[k]);
149
+ return out;
150
+ }
151
+
152
+ function bn(idx) {
153
+ if (built[idx] !== null) return built[idx];
154
+ const { t, p } = pool[idx];
155
+ const cls = compiler_exports[t];
156
+ if (!cls) throw new Error('ast_from_json: unknown AST type "' + t + '"');
157
+ // Create instance without the constructor to avoid side-effects.
158
+ const node = Object.create(cls.prototype);
159
+ built[idx] = node; // register BEFORE filling props (handles shared/self refs)
160
+ for (const k of Object.keys(p)) node[k] = dv(p[k]);
161
+ // imports is the global shared dict, skipped during serialization.
162
+ // Set a minimal self-referential default so print() works in standalone
163
+ // contexts (e.g. tests). The module-cache loader overwrites this with
164
+ // the full imported_modules dict after calling ast_from_json.
165
+ if (t === 'AST_Toplevel' && !node.imports) {
166
+ node.imports = {};
167
+ if (node.module_id) node.imports[node.module_id] = node;
168
+ }
169
+ return node;
170
+ }
171
+
172
+ return bn;
173
+ }
174
+
175
+ // ─── Binary format (RSPB v1) ─────────────────────────────────────────────────
176
+ //
177
+ // Header (16 bytes):
178
+ // [0-3] magic: 'R','S','P','B'
179
+ // [4-5] format version: uint16 LE = 1
180
+ // [6-9] string_count: uint32 LE
181
+ // [10-13] node_count: uint32 LE
182
+ // [14-15] reserved: 0
183
+ //
184
+ // String table (string_count entries):
185
+ // uint16 LE byte-length + utf-8 bytes
186
+ //
187
+ // Node table (node_count entries, in serialization order):
188
+ // uint16 LE type_idx, uint16 LE prop_count
189
+ // [uint16 key_idx + value] * prop_count
190
+ //
191
+ // Value encoding (uint8 tag + payload):
192
+ // 0 NULL 1 FALSE 2 TRUE
193
+ // 3 INT_U8 +u8 4 INT_U16 +u16LE 5 INT_I32 +i32LE 6 FLOAT +f64LE
194
+ // 7 STR +u32LE string_index
195
+ // 8 ARR +u32LE count + count*value
196
+ // 9 NODE +u32LE node_index
197
+ // 10 OBJ +u16LE count + count*(u16 key_idx + value)
198
+ // 11 REGEXP +u32LE source_idx +u32LE flags_idx
199
+ // 12 SYMDEF +u32LE name_idx +u8 has_mangled [+u32LE mangled_idx]
200
+ //
201
+ // Deserialization is two-pass: pass 1 allocates all node objects (so forward
202
+ // references resolve to the right instance), pass 2 fills their properties.
203
+
204
+ const RSPB_MAGIC = [0x52, 0x53, 0x50, 0x42]; // "RSPB"
205
+ const RSPB_VERSION = 1;
206
+
207
+ function ast_to_bin(root) {
208
+ // Phase 1: build flat pool using existing serializer logic.
209
+ const pool = [];
210
+ const seen = new WeakMap();
211
+ ser_node(root, pool, seen);
212
+
213
+ // Phase 2: intern all strings into a string table.
214
+ const strings = [];
215
+ const str_map = new Map();
216
+
217
+ function intern(s) {
218
+ if (typeof s !== 'string') return 0;
219
+ let i = str_map.get(s);
220
+ if (i === undefined) { i = strings.length; strings.push(s); str_map.set(s, i); }
221
+ return i;
222
+ }
223
+
224
+ function scan_val(val) {
225
+ if (val === null || val === undefined) return;
226
+ const t = typeof val;
227
+ if (t === 'string') { intern(val); return; }
228
+ if (t !== 'object') return;
229
+ if (Array.isArray(val)) { for (const el of val) scan_val(el); return; }
230
+ if (val._n !== undefined) {
231
+ if (typeof val._n === 'string') { intern(val._n); if (val._mn) intern(val._mn); }
232
+ return;
233
+ }
234
+ if (val._re !== undefined) { intern(val._re); intern(val._rf || ''); return; }
235
+ for (const k of Object.keys(val)) { intern(k); scan_val(val[k]); }
236
+ }
237
+
238
+ for (const node of pool) {
239
+ if (!node) continue;
240
+ intern(node.t);
241
+ for (const k of Object.keys(node.p)) { intern(k); scan_val(node.p[k]); }
242
+ }
243
+
244
+ // Phase 3: write binary into a growable Uint8Array.
245
+ const te = new TextEncoder();
246
+ let cap = 4 * 1024 * 1024;
247
+ let buf = new Uint8Array(cap);
248
+ let dv = new DataView(buf.buffer);
249
+ let pos = 0;
250
+
251
+ function ensure(n) {
252
+ if (pos + n <= cap) return;
253
+ cap = Math.max(cap * 2, pos + n);
254
+ const nb = new Uint8Array(cap);
255
+ nb.set(buf.subarray(0, pos));
256
+ buf = nb;
257
+ dv = new DataView(buf.buffer);
258
+ }
259
+
260
+ function w8(v) { ensure(1); buf[pos++] = v & 0xFF; }
261
+ function w16(v) { ensure(2); dv.setUint16(pos, v, true); pos += 2; }
262
+ function w32(v) { ensure(4); dv.setUint32(pos, v >>> 0, true); pos += 4; }
263
+ function wi32(v) { ensure(4); dv.setInt32(pos, v, true); pos += 4; }
264
+ function wf64(v) { ensure(8); dv.setFloat64(pos, v, true); pos += 8; }
265
+
266
+ // Header
267
+ for (const b of RSPB_MAGIC) w8(b);
268
+ w16(RSPB_VERSION);
269
+ w32(strings.length);
270
+ w32(pool.length);
271
+ w16(0); // reserved
272
+
273
+ // String table
274
+ for (const s of strings) {
275
+ const bytes = te.encode(s);
276
+ w16(bytes.length);
277
+ ensure(bytes.length);
278
+ buf.set(bytes, pos);
279
+ pos += bytes.length;
280
+ }
281
+
282
+ // Value writer
283
+ function write_val(val) {
284
+ if (val === null || val === undefined) { w8(0); return; }
285
+ const t = typeof val;
286
+ if (t === 'boolean') { w8(val ? 2 : 1); return; }
287
+ if (t === 'number') {
288
+ if (Number.isInteger(val)) {
289
+ if (val >= 0 && val <= 255) { w8(3); w8(val); }
290
+ else if (val >= 0 && val <= 65535) { w8(4); w16(val); }
291
+ else if (val >= -2147483648 && val <= 2147483647) { w8(5); wi32(val); }
292
+ else { w8(6); wf64(val); }
293
+ } else { w8(6); wf64(val); }
294
+ return;
295
+ }
296
+ if (t === 'string') { w8(7); w32(str_map.get(val)); return; }
297
+ if (Array.isArray(val)) {
298
+ w8(8); w32(val.length);
299
+ for (const el of val) write_val(el);
300
+ return;
301
+ }
302
+ // Sentinel objects from ser_val/ser_node
303
+ if (val._n !== undefined) {
304
+ if (typeof val._n === 'number') {
305
+ w8(9); w32(val._n);
306
+ } else {
307
+ // SymbolDef stub: {_td:1, _n: name_str, _mn: mangled_str|null}
308
+ w8(12);
309
+ w32(str_map.get(val._n) || 0);
310
+ if (val._mn) { w8(1); w32(str_map.get(val._mn) || 0); }
311
+ else w8(0);
312
+ }
313
+ return;
314
+ }
315
+ if (val._re !== undefined) {
316
+ w8(11); w32(str_map.get(val._re) || 0); w32(str_map.get(val._rf || '') || 0);
317
+ return;
318
+ }
319
+ // Plain object (defaults dict, classvars, baselib, etc.)
320
+ const keys = Object.keys(val);
321
+ w8(10); w16(keys.length);
322
+ for (const k of keys) { w16(str_map.get(k)); write_val(val[k]); }
323
+ }
324
+
325
+ // Node table
326
+ for (const node of pool) {
327
+ if (!node) { w16(0); w16(0); continue; }
328
+ w16(str_map.get(node.t));
329
+ const keys = Object.keys(node.p);
330
+ w16(keys.length);
331
+ for (const k of keys) { w16(str_map.get(k)); write_val(node.p[k]); }
332
+ }
333
+
334
+ return buf.subarray(0, pos);
335
+ }
336
+
337
+ // Combine metadata JSON string and binary AST into one Uint8Array:
338
+ // <meta_json_str>\n<binary_ast>
339
+ function encode_cache(meta_json_str, root) {
340
+ const te = new TextEncoder();
341
+ const meta_bytes = te.encode(meta_json_str + '\n');
342
+ const ast_bytes = ast_to_bin(root);
343
+ const out = new Uint8Array(meta_bytes.length + ast_bytes.length);
344
+ out.set(meta_bytes, 0);
345
+ out.set(ast_bytes, meta_bytes.length);
346
+ return out;
347
+ }
348
+
349
+ // Split a cache entry (Uint8Array or legacy string) into {meta: string, ast}.
350
+ // For binary data: ast is a Uint8Array (subarray — zero-copy).
351
+ // For legacy string data: ast is a string (old JSON format) or null (no newline).
352
+ // Returns null if data is falsy.
353
+ function decode_cache(data) {
354
+ if (!data) return null;
355
+ if (typeof data === 'string') {
356
+ const nl = data.indexOf('\n');
357
+ if (nl < 0) return { meta: data, ast: null };
358
+ return { meta: data.slice(0, nl), ast: data.slice(nl + 1) };
359
+ }
360
+ // Uint8Array (or Buffer, which is a subclass of Uint8Array)
361
+ let nl = -1;
362
+ for (let i = 0; i < data.length; i++) { if (data[i] === 10) { nl = i; break; } }
363
+ if (nl < 0) return null;
364
+ const td = new TextDecoder();
365
+ return { meta: td.decode(data.subarray(0, nl)), ast: data.subarray(nl + 1) };
366
+ }
367
+
368
+ // ─── Public API ───────────────────────────────────────────────────────────────
369
+
370
+ export function make_ast_serializer(compiler_exports) {
371
+ function ast_to_json(root) {
372
+ const pool = [];
373
+ const seen = new WeakMap();
374
+ ser_node(root, pool, seen);
375
+ return { v: 1, nodes: pool, root: 0 };
376
+ }
377
+
378
+ function ast_from_json(data) {
379
+ if (data.v !== 1) {
380
+ throw new Error('ast_from_json: unsupported version ' + data.v);
381
+ }
382
+ const built = new Array(data.nodes.length).fill(null);
383
+ const bn = make_deserializer(compiler_exports, data.nodes, built);
384
+ const root = bn(data.root);
385
+ const filename = (root && root.filename) || null;
386
+ for (const node of built) {
387
+ if (node !== null && node.constructor && node.constructor.name === 'AST_Token') {
388
+ node.file = filename;
389
+ }
390
+ }
391
+ return root;
392
+ }
393
+
394
+ // Binary deserializer — needs compiler_exports to resolve AST class names.
395
+ // Two-pass: pass 1 allocates node shells, pass 2 fills properties.
396
+ // This lets forward references (node A references node B at a later index)
397
+ // resolve to the correct already-allocated object without recursion.
398
+ function ast_from_bin(buf) {
399
+ const dv = new DataView(buf.buffer, buf.byteOffset, buf.byteLength);
400
+ let pos = 0;
401
+ function r8() { return dv.getUint8(pos++); }
402
+ function r16() { const v = dv.getUint16(pos, true); pos += 2; return v; }
403
+ function r32() { const v = dv.getUint32(pos, true); pos += 4; return v; }
404
+ function ri32() { const v = dv.getInt32(pos, true); pos += 4; return v; }
405
+ function rf64() { const v = dv.getFloat64(pos, true); pos += 8; return v; }
406
+
407
+ if (r8()!==0x52||r8()!==0x53||r8()!==0x50||r8()!==0x42)
408
+ throw new Error('ast_from_bin: invalid magic');
409
+ const version = r16();
410
+ if (version !== RSPB_VERSION)
411
+ throw new Error('ast_from_bin: unsupported version ' + version);
412
+ const str_count = r32();
413
+ const node_count = r32();
414
+ r16(); // reserved
415
+
416
+ // String table
417
+ const td = new TextDecoder();
418
+ const str_table = new Array(str_count);
419
+ for (let i = 0; i < str_count; i++) {
420
+ const len = r16();
421
+ str_table[i] = td.decode(new Uint8Array(buf.buffer, buf.byteOffset + pos, len));
422
+ pos += len;
423
+ }
424
+
425
+ const nodes_start = pos;
426
+
427
+ // Pass 1: allocate all node objects without filling properties.
428
+ const built = new Array(node_count);
429
+
430
+ function skip_val() {
431
+ switch (r8()) {
432
+ case 0: case 1: case 2: break;
433
+ case 3: pos += 1; break;
434
+ case 4: pos += 2; break;
435
+ case 5: pos += 4; break;
436
+ case 6: pos += 8; break;
437
+ case 7: pos += 4; break;
438
+ case 8: { const n = r32(); for (let i = 0; i < n; i++) skip_val(); break; }
439
+ case 9: pos += 4; break;
440
+ case 10: { const n = r16(); for (let i = 0; i < n; i++) { pos += 2; skip_val(); } break; }
441
+ case 11: pos += 8; break;
442
+ case 12: pos += 4; if (r8()) pos += 4; break;
443
+ }
444
+ }
445
+
446
+ for (let i = 0; i < node_count; i++) {
447
+ const type_name = str_table[r16()];
448
+ const prop_count = r16();
449
+ const cls = compiler_exports[type_name];
450
+ if (!cls) throw new Error('ast_from_bin: unknown AST type "' + type_name + '"');
451
+ built[i] = Object.create(cls.prototype);
452
+ for (let j = 0; j < prop_count; j++) { pos += 2; skip_val(); }
453
+ }
454
+
455
+ // Pass 2: fill properties. All nodes exist now so forward refs are safe.
456
+ pos = nodes_start;
457
+
458
+ function decode_val() {
459
+ switch (r8()) {
460
+ case 0: return null;
461
+ case 1: return false;
462
+ case 2: return true;
463
+ case 3: return r8();
464
+ case 4: return r16();
465
+ case 5: return ri32();
466
+ case 6: return rf64();
467
+ case 7: return str_table[r32()];
468
+ case 8: { const n = r32(); const a = new Array(n); for (let i = 0; i < n; i++) a[i] = decode_val(); return a; }
469
+ case 9: return built[r32()];
470
+ case 10: { const n = r16(); const o = {}; for (let i = 0; i < n; i++) { o[str_table[r16()]] = decode_val(); } return o; }
471
+ case 11: return new RegExp(str_table[r32()], str_table[r32()]);
472
+ case 12: { const name = str_table[r32()]; const mn = r8() ? str_table[r32()] : null; return { name, mangled_name: mn }; }
473
+ }
474
+ }
475
+
476
+ for (let i = 0; i < node_count; i++) {
477
+ pos += 2; // skip type_idx (already consumed in pass 1)
478
+ const prop_count = r16();
479
+ const node = built[i];
480
+ for (let j = 0; j < prop_count; j++) {
481
+ node[str_table[r16()]] = decode_val();
482
+ }
483
+ if (node.constructor.name === 'AST_Toplevel' && !node.imports) {
484
+ node.imports = {};
485
+ if (node.module_id) node.imports[node.module_id] = node;
486
+ }
487
+ }
488
+
489
+ const filename = built[0] ? (built[0].filename || null) : null;
490
+ if (filename) {
491
+ for (const node of built) {
492
+ if (node.constructor.name === 'AST_Token') node.file = filename;
493
+ }
494
+ }
495
+
496
+ return built[0];
497
+ }
498
+
499
+ // Properties deferred until first access of any of them — these are needed
500
+ // by the code generator before or alongside body.
501
+ const LAZY_PROPS = ['body', 'localvars', 'nonlocalvars'];
502
+
503
+ // Create a lazy AST_Toplevel shell: metadata is available immediately,
504
+ // body (and the rest of the full AST) is deserialized on first access of
505
+ // any lazy prop. Dead modules (never accessed by the tree-shaker) are
506
+ // never deserialized.
507
+ // ast_data: Uint8Array (binary, v9+), JSON string (v8 legacy), or
508
+ // already-parsed object (legacy bootstrap path).
509
+ function make_lazy_ast_module(ast_data, meta) {
510
+ const AST_Toplevel = compiler_exports['AST_Toplevel'];
511
+ const shell = Object.create(AST_Toplevel.prototype);
512
+ const preserve = new Set(Object.keys(meta));
513
+ for (const k of Object.keys(meta)) shell[k] = meta[k];
514
+
515
+ let _loaded = false;
516
+
517
+ function ensure_loaded() {
518
+ if (_loaded) return;
519
+ _loaded = true;
520
+ let full;
521
+ if (ast_data instanceof Uint8Array) {
522
+ full = ast_from_bin(ast_data);
523
+ } else {
524
+ // String (legacy JSON format) or already-parsed object (bootstrap).
525
+ const parsed = typeof ast_data === 'string' ? JSON.parse(ast_data) : ast_data;
526
+ full = ast_from_json(parsed);
527
+ }
528
+ for (const k of Object.keys(full)) {
529
+ if (!preserve.has(k) && LAZY_PROPS.indexOf(k) === -1) shell[k] = full[k];
530
+ }
531
+ // Use defineProperty to atomically replace getter descriptors with values,
532
+ // bypassing the setters below (which would also replace them, but
533
+ // defineProperty is unconditional and handles the undefined case cleanly).
534
+ for (const p of LAZY_PROPS) {
535
+ Object.defineProperty(shell, p, {
536
+ value: (full[p] !== undefined) ? full[p] : [],
537
+ writable: true, configurable: true, enumerable: true,
538
+ });
539
+ }
540
+ }
541
+
542
+ for (const prop of LAZY_PROPS) {
543
+ Object.defineProperty(shell, prop, {
544
+ get() { ensure_loaded(); return shell[prop]; },
545
+ set(v) {
546
+ Object.defineProperty(shell, prop, {value: v, writable: true, configurable: true, enumerable: true});
547
+ },
548
+ configurable: true,
549
+ enumerable: true,
550
+ });
551
+ }
552
+
553
+ return shell;
554
+ }
555
+
556
+ return { ast_to_json, ast_from_json, make_lazy_ast_module, encode_cache, decode_cache };
557
+ }