novac 2.1.1 → 2.2.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 (138) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +0 -0
  3. package/demo.nv +0 -0
  4. package/demo_builtins.nv +0 -0
  5. package/demo_http.nv +0 -0
  6. package/examples/bf.nv +69 -0
  7. package/examples/math.nv +21 -0
  8. package/kits/birdAPI/kitdef.js +954 -0
  9. package/kits/kitRNG/kitdef.js +740 -0
  10. package/kits/kitSSH/kitdef.js +1272 -0
  11. package/kits/kitadb/kitdef.js +606 -0
  12. package/kits/kitai/kitdef.js +2185 -0
  13. package/kits/kitcanvas/kitdef.js +914 -0
  14. package/kits/kitclippy/kitdef.js +925 -0
  15. package/kits/kitgps/kitdef.js +1862 -0
  16. package/kits/kitlibproc/kitdef.js +3 -2
  17. package/kits/kitmorse/kitdef.js +229 -0
  18. package/kits/kitmpatch/kitdef.js +906 -0
  19. package/kits/kitnet/kitdef.js +1401 -0
  20. package/kits/kitproto/kitdef.js +613 -0
  21. package/kits/kitqr/kitdef.js +637 -0
  22. package/kits/kitrequire/kitdef.js +1599 -0
  23. package/kits/libtea/kitdef.js +2691 -0
  24. package/kits/libterm/kitdef.js +2 -0
  25. package/novac/LICENSE +21 -0
  26. package/novac/README.md +1823 -0
  27. package/novac/bin/novac +950 -0
  28. package/novac/bin/nvc +522 -0
  29. package/novac/bin/nvml +542 -0
  30. package/novac/demo.nv +245 -0
  31. package/novac/demo_builtins.nv +209 -0
  32. package/novac/demo_http.nv +62 -0
  33. package/novac/examples/bf.nv +69 -0
  34. package/novac/examples/math.nv +21 -0
  35. package/novac/kits/kitai/kitdef.js +2185 -0
  36. package/novac/kits/kitansi/kitdef.js +1402 -0
  37. package/novac/kits/kitformat/kitdef.js +1485 -0
  38. package/novac/kits/kitgps/kitdef.js +1862 -0
  39. package/novac/kits/kitlibfs/kitdef.js +231 -0
  40. package/{examples/example-project/nova_modules → novac/kits}/kitlibproc/kitdef.js +3 -2
  41. package/novac/kits/kitmatrix/ex.js +19 -0
  42. package/novac/kits/kitmatrix/kitdef.js +960 -0
  43. package/novac/kits/kitmpatch/kitdef.js +906 -0
  44. package/novac/kits/kitnovacweb/README.md +1572 -0
  45. package/novac/kits/kitnovacweb/demo.nv +12 -0
  46. package/novac/kits/kitnovacweb/demo.nvml +71 -0
  47. package/novac/kits/kitnovacweb/index.nova +12 -0
  48. package/novac/kits/kitnovacweb/kitdef.js +692 -0
  49. package/novac/kits/kitnovacweb/nova.kit.json +8 -0
  50. package/novac/kits/kitnovacweb/nvml/executor.js +739 -0
  51. package/novac/kits/kitnovacweb/nvml/index.js +67 -0
  52. package/novac/kits/kitnovacweb/nvml/lexer.js +263 -0
  53. package/novac/kits/kitnovacweb/nvml/parser.js +508 -0
  54. package/novac/kits/kitnovacweb/nvml/renderer.js +924 -0
  55. package/novac/kits/kitparse/kitdef.js +1688 -0
  56. package/novac/kits/kitregex++/kitdef.js +1353 -0
  57. package/novac/kits/kitrequire/kitdef.js +1599 -0
  58. package/novac/kits/kitx11/kitdef.js +1 -0
  59. package/novac/kits/kitx11/kitx11.js +2472 -0
  60. package/novac/kits/kitx11/kitx11_conn.js +948 -0
  61. package/novac/kits/kitx11/kitx11_worker.js +121 -0
  62. package/novac/kits/libterm/ex.js +285 -0
  63. package/novac/kits/libterm/kitdef.js +1927 -0
  64. package/novac/node_modules/chalk/license +9 -0
  65. package/novac/node_modules/chalk/package.json +83 -0
  66. package/novac/node_modules/chalk/readme.md +297 -0
  67. package/novac/node_modules/chalk/source/index.d.ts +325 -0
  68. package/novac/node_modules/chalk/source/index.js +225 -0
  69. package/novac/node_modules/chalk/source/utilities.js +33 -0
  70. package/novac/node_modules/chalk/source/vendor/ansi-styles/index.d.ts +236 -0
  71. package/novac/node_modules/chalk/source/vendor/ansi-styles/index.js +223 -0
  72. package/novac/node_modules/chalk/source/vendor/supports-color/browser.d.ts +1 -0
  73. package/novac/node_modules/chalk/source/vendor/supports-color/browser.js +34 -0
  74. package/novac/node_modules/chalk/source/vendor/supports-color/index.d.ts +55 -0
  75. package/novac/node_modules/chalk/source/vendor/supports-color/index.js +190 -0
  76. package/novac/node_modules/commander/LICENSE +22 -0
  77. package/novac/node_modules/commander/Readme.md +1176 -0
  78. package/novac/node_modules/commander/esm.mjs +16 -0
  79. package/novac/node_modules/commander/index.js +24 -0
  80. package/novac/node_modules/commander/lib/argument.js +150 -0
  81. package/novac/node_modules/commander/lib/command.js +2777 -0
  82. package/novac/node_modules/commander/lib/error.js +39 -0
  83. package/novac/node_modules/commander/lib/help.js +747 -0
  84. package/novac/node_modules/commander/lib/option.js +380 -0
  85. package/novac/node_modules/commander/lib/suggestSimilar.js +101 -0
  86. package/novac/node_modules/commander/package-support.json +19 -0
  87. package/novac/node_modules/commander/package.json +82 -0
  88. package/novac/node_modules/commander/typings/esm.d.mts +3 -0
  89. package/novac/node_modules/commander/typings/index.d.ts +1113 -0
  90. package/novac/node_modules/node-addon-api/LICENSE.md +9 -0
  91. package/novac/node_modules/node-addon-api/README.md +95 -0
  92. package/novac/node_modules/node-addon-api/common.gypi +21 -0
  93. package/novac/node_modules/node-addon-api/except.gypi +25 -0
  94. package/novac/node_modules/node-addon-api/index.js +14 -0
  95. package/novac/node_modules/node-addon-api/napi-inl.deprecated.h +186 -0
  96. package/novac/node_modules/node-addon-api/napi-inl.h +7165 -0
  97. package/novac/node_modules/node-addon-api/napi.h +3364 -0
  98. package/novac/node_modules/node-addon-api/node_addon_api.gyp +42 -0
  99. package/novac/node_modules/node-addon-api/node_api.gyp +9 -0
  100. package/novac/node_modules/node-addon-api/noexcept.gypi +26 -0
  101. package/novac/node_modules/node-addon-api/package-support.json +21 -0
  102. package/novac/node_modules/node-addon-api/package.json +480 -0
  103. package/novac/node_modules/node-addon-api/tools/README.md +73 -0
  104. package/novac/node_modules/node-addon-api/tools/check-napi.js +99 -0
  105. package/novac/node_modules/node-addon-api/tools/clang-format.js +71 -0
  106. package/novac/node_modules/node-addon-api/tools/conversion.js +301 -0
  107. package/novac/node_modules/serialize-javascript/LICENSE +27 -0
  108. package/novac/node_modules/serialize-javascript/README.md +149 -0
  109. package/novac/node_modules/serialize-javascript/index.js +297 -0
  110. package/novac/node_modules/serialize-javascript/package.json +33 -0
  111. package/novac/package.json +27 -0
  112. package/novac/scripts/update-bin.js +24 -0
  113. package/novac/src/core/bstd.js +1035 -0
  114. package/novac/src/core/config.js +155 -0
  115. package/novac/src/core/describe.js +187 -0
  116. package/novac/src/core/emitter.js +499 -0
  117. package/novac/src/core/error.js +86 -0
  118. package/novac/src/core/executor.js +5606 -0
  119. package/novac/src/core/formatter.js +686 -0
  120. package/novac/src/core/lexer.js +1026 -0
  121. package/novac/src/core/nova_builtins.js +717 -0
  122. package/novac/src/core/nova_thread_worker.js +166 -0
  123. package/novac/src/core/parser.js +2181 -0
  124. package/novac/src/core/types.js +112 -0
  125. package/novac/src/index.js +28 -0
  126. package/novac/src/runtime/stdlib.js +244 -0
  127. package/package.json +3 -2
  128. package/scripts/update-bin.js +0 -0
  129. package/src/core/bstd.js +835 -361
  130. package/src/core/executor.js +427 -246
  131. package/src/core/lexer.js +19 -2
  132. package/src/core/parser.js +13 -0
  133. package/src/index.js +0 -0
  134. package/examples/example-project/README.md +0 -3
  135. package/examples/example-project/src/main.nova +0 -3
  136. package/src/core/environment.js +0 -0
  137. /package/{kits → novac/kits}/libtea/tf.js +0 -0
  138. /package/{examples/example-project/bin/example-project.nv → novac/node_modules/node-addon-api/nothing.c} +0 -0
@@ -4,6 +4,7 @@ const {
4
4
  NovaStruct, NovaEnum, NOVA_META,
5
5
  } = require('./types.js');
6
6
  const { CustomError, formatError, NovaException } = require('./error');
7
+ const { atob } = require('buffer');
7
8
 
8
9
  // ── Default sentinel ─────────────────────────────────────────────────────────
9
10
  // When `default` is passed as a function argument, the parameter's declared
@@ -193,8 +194,34 @@ class Executor {
193
194
  this.novaServers = new Map(); // port -> routes[], for in-process dispatch
194
195
  this.dot_commands = {
195
196
  print: (...args) => {
196
- console.log(...args.map(a=>_self.evaluate(a)));
197
- }
197
+ console.log(...args.map(a => _self.evaluate(a)));
198
+ },
199
+ clear: () => process.stdout.write('\x1Bc'),
200
+ code: () => 0, // no-op, used for doc comments that should be ignored by the executor but visible to external tools
201
+ zap: (target) => {
202
+ const t = this.evaluate(target);
203
+ if (t instanceof NovaObject) {
204
+ for (const k of Object.keys(t.inner)) delete t.inner[k];
205
+ } else if (t instanceof NovaStruct) {
206
+ for (const k of Object.keys(t.inner)) delete t.inner[k];
207
+ } else if (t && typeof t === 'object') {
208
+ for (const k of Object.keys(t)) delete t[k];
209
+ } else {
210
+ throw new Error('.zap target must be an object or struct instance');
211
+ }
212
+ },
213
+ cookie() {
214
+ const asciiCookie = `(\\_/)
215
+ ( •_•)
216
+ / >🍪 Here's a cookie for you!`;
217
+ console.log(asciiCookie);
218
+ },
219
+ js: (code) => {
220
+ const codeStr = this.evaluate(code);
221
+ if (typeof codeStr === 'string') eval(codeStr);
222
+ else if (codeStr instanceof NovaString) eval(codeStr.value);
223
+ else throw new Error('.js command requires a string argument');
224
+ },
198
225
  }
199
226
  this.moduleExportsStack = [];
200
227
  this.options = {};
@@ -203,21 +230,44 @@ class Executor {
203
230
  this.globalScope.variables.std = stdlib;
204
231
  const self = this;
205
232
 
206
- this.globalScope.set('setTimeout', setTimeout);
207
- this.globalScope.set('clearTimeout', clearTimeout);
208
- this.globalScope.set('setInterval', setInterval);
209
- this.globalScope.set('clearInterval', clearInterval);
210
- this.globalScope.set('setImmediate', setImmediate);
211
- this.globalScope.set('clearImmediate', clearImmediate);
212
- this.globalScope.set('queueMicrotask', queueMicrotask);
213
- this.globalScope.set('parseInt', parseInt);
214
- this.globalScope.set('parseFloat', parseFloat);
215
- this.globalScope.set('isNaN', isNaN);
216
- this.globalScope.set('isFinite', isFinite);
217
- this.globalScope.set('sleepAsync', (ms)=>new Promise(r=>setTimeout(r,ms)));
218
- this.globalScope.set('Scope', Scope);
219
- // Expose the default sentinel so runtime code can detect it
220
- this.globalScope.set('__DEFAULT_SENTINEL__', DEFAULT_SENTINEL);
233
+ this.globalScope.set('avaibleKits', (() => {
234
+ const kits = [];
235
+ const kitsDir = require('path').join(__dirname, '../..', 'kits');
236
+ try {
237
+ const fs = require('fs');
238
+ for (const kitName of fs.readdirSync(kitsDir)) {
239
+ kits.push(kitName)
240
+ }
241
+ } catch (e) {
242
+ console.warn('Could not load kits:', e.message);
243
+ }
244
+ return kits;
245
+ })());
246
+ this.globalScope.set('setTimeout', setTimeout);
247
+ this.globalScope.set('clearTimeout', clearTimeout);
248
+ this.globalScope.set('setInterval', setInterval);
249
+ this.globalScope.set('clearInterval', clearInterval);
250
+ this.globalScope.set('setImmediate', setImmediate);
251
+ this.globalScope.set('clearImmediate', clearImmediate);
252
+ this.globalScope.set('queueMicrotask', queueMicrotask);
253
+ this.globalScope.set('parseInt', parseInt);
254
+ this.globalScope.set('parseFloat', parseFloat);
255
+ this.globalScope.set('isNaN', isNaN);
256
+ this.globalScope.set('isFinite', isFinite);
257
+ this.globalScope.set('sleepAsync', (ms) => new Promise(r => setTimeout(r, ms)));
258
+ this.globalScope.set('Scope', Scope);
259
+ this.globalScope.set('Atomics', Atomics);
260
+ this.globalScope.set('SharedArrayBuffer', SharedArrayBuffer);
261
+ this.globalScope.set('Buffer', Buffer);
262
+ this.globalScope.set('CError', CustomError);
263
+ this.globalScope.set('Error', Error);
264
+ //remaining built in nodejs variables and objects excluding global, process, console (which we want to control access to), and a few others for safety
265
+ this.globalScope.set('textEncoder', TextEncoder);
266
+ this.globalScope.set('textDecoder', TextDecoder);
267
+ this.globalScope.set('Util', require('util'));
268
+ this.globalScope.set('Date', Date);
269
+ // Expose the default sentinel so runtime code can detect it
270
+ this.globalScope.set('__DEFAULT_SENTINEL__', DEFAULT_SENTINEL);
221
271
 
222
272
  this.globalScope.set('core', {
223
273
  getAst: () => this.ast,
@@ -314,7 +364,7 @@ this.globalScope.set('__DEFAULT_SENTINEL__', DEFAULT_SENTINEL);
314
364
  check: (a, pred) => typeof pred === 'function' ? pred(a) : (_qae[pred] ? _qae[pred](a) : false),
315
365
  };
316
366
  this.globalScope.set('qae', _qae);
317
- this.globalScope.set('novaRegex', (pattern, flags) => this._novaRegex(pattern, flags || ''));
367
+ this.globalScope.set('cregex', (pattern, flags) => this._novaRegex(pattern, flags || ''));
318
368
 
319
369
  // ── Sync HTTP fetch ──
320
370
  const _self = this;
@@ -423,7 +473,7 @@ this.globalScope.set('__DEFAULT_SENTINEL__', DEFAULT_SENTINEL);
423
473
  info: (msg) => process.stdout.write('INFO: ' + String(msg) + '\n'),
424
474
  };
425
475
  this.globalScope.set('nvk', nvk);
426
-
476
+ this.globalScope.set('charAt', String.fromCharCode);
427
477
  // ── waitsync(fn, ...args) ─────────────────────────────────────────────
428
478
  // Calls fn(...args) and blocks synchronously until the result settles,
429
479
  // whether the function is:
@@ -457,14 +507,14 @@ this.globalScope.set('__DEFAULT_SENTINEL__', DEFAULT_SENTINEL);
457
507
  // Shared memory layout (Int32Array, 3 slots):
458
508
  // [0] flag: 0=pending, 1=resolved, 2=rejected
459
509
  // [1-2] unused (value is passed via a separate Buffer)
460
- const sab = new SharedArrayBuffer(4);
461
- const flag = new Int32Array(sab);
510
+ const sab = new SharedArrayBuffer(4);
511
+ const flag = new Int32Array(sab);
462
512
 
463
513
  // We pass the resolved/rejected value as a JSON string through a
464
514
  // larger shared buffer (up to 4 MB). For values that can't be
465
515
  // JSON-serialised we fall back to null.
466
516
  const valueBufSize = 4 * 1024 * 1024; // 4 MB
467
- const valueSab = new SharedArrayBuffer(valueBufSize + 4); // +4 for length prefix
517
+ const valueSab = new SharedArrayBuffer(valueBufSize + 4); // +4 for length prefix
468
518
  const valueMeta = new Int32Array(valueSab, 0, 1); // [0] = byte length
469
519
  const valueData = new Uint8Array(valueSab, 4); // rest = UTF-8 bytes
470
520
 
@@ -520,9 +570,9 @@ p.then((v) => {
520
570
  // Atomics.wait call. The main thread does the actual resolution.
521
571
  result.then((v) => {
522
572
  try {
523
- const json = JSON.stringify(v === undefined ? null : v);
573
+ const json = JSON.stringify(v === undefined ? null : v);
524
574
  const bytes = Buffer.from(json, 'utf8');
525
- const len = Math.min(bytes.length, valueData.length);
575
+ const len = Math.min(bytes.length, valueData.length);
526
576
  bytes.copy(Buffer.from(valueData.buffer, valueData.byteOffset), 0, 0, len);
527
577
  Atomics.store(valueMeta, 0, len);
528
578
  Atomics.store(flag, 0, 1);
@@ -534,9 +584,9 @@ p.then((v) => {
534
584
  }
535
585
  }).catch((e) => {
536
586
  try {
537
- const msg = e && e.message ? e.message : String(e);
587
+ const msg = e && e.message ? e.message : String(e);
538
588
  const bytes = Buffer.from(JSON.stringify(msg), 'utf8');
539
- const len = Math.min(bytes.length, valueData.length);
589
+ const len = Math.min(bytes.length, valueData.length);
540
590
  bytes.copy(Buffer.from(valueData.buffer, valueData.byteOffset), 0, 0, len);
541
591
  Atomics.store(valueMeta, 0, len);
542
592
  } catch (_) { Atomics.store(valueMeta, 0, 0); }
@@ -598,19 +648,19 @@ Atomics.wait(flag, 0, 0);
598
648
 
599
649
  const { Worker } = require('worker_threads');
600
650
 
601
- const sab = new SharedArrayBuffer(4);
602
- const flag = new Int32Array(sab);
651
+ const sab = new SharedArrayBuffer(4);
652
+ const flag = new Int32Array(sab);
603
653
  const valueBufSize = 4 * 1024 * 1024;
604
- const valueSab = new SharedArrayBuffer(valueBufSize + 4);
605
- const valueMeta = new Int32Array(valueSab, 0, 1);
606
- const valueData = new Uint8Array(valueSab, 4);
654
+ const valueSab = new SharedArrayBuffer(valueBufSize + 4);
655
+ const valueMeta = new Int32Array(valueSab, 0, 1);
656
+ const valueData = new Uint8Array(valueSab, 4);
607
657
 
608
658
  // Attach resolution handlers in the main thread — microtasks fire here
609
659
  promise.then((v) => {
610
660
  try {
611
- const json = JSON.stringify(v === undefined ? null : v);
661
+ const json = JSON.stringify(v === undefined ? null : v);
612
662
  const bytes = Buffer.from(json, 'utf8');
613
- const len = Math.min(bytes.length, valueData.length);
663
+ const len = Math.min(bytes.length, valueData.length);
614
664
  bytes.copy(Buffer.from(valueData.buffer, valueData.byteOffset), 0, 0, len);
615
665
  Atomics.store(valueMeta, 0, len);
616
666
  Atomics.store(flag, 0, 1);
@@ -622,9 +672,9 @@ Atomics.wait(flag, 0, 0);
622
672
  }
623
673
  }).catch((e) => {
624
674
  try {
625
- const msg = e && e.message ? e.message : String(e);
675
+ const msg = e && e.message ? e.message : String(e);
626
676
  const bytes = Buffer.from(JSON.stringify(msg), 'utf8');
627
- const len = Math.min(bytes.length, valueData.length);
677
+ const len = Math.min(bytes.length, valueData.length);
628
678
  bytes.copy(Buffer.from(valueData.buffer, valueData.byteOffset), 0, 0, len);
629
679
  Atomics.store(valueMeta, 0, len);
630
680
  } catch (_) { Atomics.store(valueMeta, 0, 0); }
@@ -643,7 +693,7 @@ Atomics.wait(flag, 0, 0);
643
693
  Atomics.wait(flag, 0, 0);
644
694
  waiter.terminate();
645
695
 
646
- const status = Atomics.load(flag, 0);
696
+ const status = Atomics.load(flag, 0);
647
697
  const byteLen = Atomics.load(valueMeta, 0);
648
698
  let parsed = null;
649
699
  if (byteLen > 0) {
@@ -777,8 +827,8 @@ Atomics.wait(flag, 0, 0);
777
827
  // guards, interceptors, tasks/jobs, handlers, services, resources,
778
828
  // schemas — all injected as plain named functions/objects in scope
779
829
  const injectGroups = [
780
- 'filters','transforms','validators','decorators','reducers','effects',
781
- 'guards','interceptors','handlers','services','resources','schemas',
830
+ 'filters', 'transforms', 'validators', 'decorators', 'reducers', 'effects',
831
+ 'guards', 'interceptors', 'handlers', 'services', 'resources', 'schemas',
782
832
  ];
783
833
  for (const group of injectGroups) {
784
834
  const g = mod[group] || {};
@@ -813,13 +863,13 @@ Atomics.wait(flag, 0, 0);
813
863
  // init / setup — called once at load time
814
864
  const initFn = mod.init || mod.setup;
815
865
  if (typeof initFn === 'function') {
816
- try { initFn(_self); } catch (_) {}
866
+ try { initFn(_self); } catch (_) { }
817
867
  }
818
868
 
819
869
  // teardown — called at process exit
820
870
  const teardownFn = mod.teardown;
821
871
  if (typeof teardownFn === 'function') {
822
- process.on('exit', () => { try { teardownFn(); } catch (_) {} });
872
+ process.on('exit', () => { try { teardownFn(); } catch (_) { } });
823
873
  }
824
874
 
825
875
  // meta / docs / provides / permissions / dependencies — informational only
@@ -842,10 +892,10 @@ Atomics.wait(flag, 0, 0);
842
892
  // merges any exported names directly into it (same as a bare import).
843
893
  // Accepts .nova / .nv (parsed + executed) or .js (require()'d as a JS kit).
844
894
  this.globalScope.set('include', (filePath) => {
845
- const _fs = require('fs');
895
+ const _fs = require('fs');
846
896
  const _pth = require('path');
847
- const src = String(filePath);
848
- const abs = _pth.isAbsolute(src)
897
+ const src = String(filePath);
898
+ const abs = _pth.isAbsolute(src)
849
899
  ? src
850
900
  : _pth.resolve(process.cwd(), src);
851
901
 
@@ -890,25 +940,24 @@ Atomics.wait(flag, 0, 0);
890
940
  // falling back to index.nova if no kitdef.js exists.
891
941
  // The kit's exported namespace is merged into global scope AND returned.
892
942
  this.globalScope.set('includeKit', (kitName) => {
893
- const _fs = require('fs');
943
+ const _fs = require('fs');
894
944
  const _pth = require('path');
895
945
  const name = String(kitName);
896
946
  const kitsRoot = _pth.join(__dirname, '..', '..', 'kits');
897
- const kitDir = _pth.join(kitsRoot, name);
947
+ const kitDir = _pth.join(kitsRoot, name);
898
948
 
899
949
  if (!_fs.existsSync(kitDir)) {
900
950
  throw new Error(`includeKit: kit "${name}" not found at ${kitDir}`);
901
951
  }
902
952
 
903
- const kitdefPath = _pth.join(kitDir, 'kitdef.js');
904
- const indexNova = _pth.join(kitDir, 'index.nova');
905
- const indexNv = _pth.join(kitDir, 'index.nv');
953
+ const kitdefPath = _pth.join(kitDir, 'kitdef.js');
954
+ const indexNova = _pth.join(kitDir, 'index.nova');
955
+ const indexNv = _pth.join(kitDir, 'index.nv');
906
956
 
907
957
  if (_fs.existsSync(kitdefPath)) {
908
958
  // JS kit — load via require and process all metadata fields
909
959
  const mod = require(kitdefPath);
910
960
  if (!mod || !mod.kitdef) throw new Error(`includeKit: "${name}/kitdef.js" must export { kitdef }`);
911
- _self._processKitMod(mod, name);
912
961
  return new NovaObject(mod.kitdef);
913
962
  }
914
963
 
@@ -941,6 +990,16 @@ Atomics.wait(flag, 0, 0);
941
990
  return '';
942
991
  });
943
992
 
993
+ this.globalScope.set('printr', (...args) => {
994
+ process.stdout.write(args.join(' ') + '\n');
995
+ return '';
996
+ });
997
+
998
+ this.globalScope.set('eprintr', (...args) => {
999
+ process.stderr.write(args.join(' ') + '\n');
1000
+ return '';
1001
+ });
1002
+
944
1003
  // ── println(...args) / eprint(...args) ──────────────────────────────
945
1004
  this.globalScope.set('println', (...args) => {
946
1005
  process.stdout.write(args.map(a => _self.stringify(a)).join(' ') + '\n');
@@ -955,7 +1014,7 @@ Atomics.wait(flag, 0, 0);
955
1014
  // Pretty-prints a value with type info — useful for debugging.
956
1015
  this.globalScope.set('dump', (v) => {
957
1016
  const type = _self._typeOf(v);
958
- const str = _self.stringify(v);
1017
+ const str = _self.stringify(v);
959
1018
  process.stdout.write(`[${type}] ${str}\n`);
960
1019
  return v;
961
1020
  });
@@ -978,7 +1037,7 @@ Atomics.wait(flag, 0, 0);
978
1037
  if (st === 0) throw new Error('range: step cannot be 0');
979
1038
  const out = [];
980
1039
  if (st > 0) { for (let i = s; i < e; i += st) out.push(i); }
981
- else { for (let i = s; i > e; i += st) out.push(i); }
1040
+ else { for (let i = s; i > e; i += st) out.push(i); }
982
1041
  return new NovaArray(out);
983
1042
  });
984
1043
 
@@ -1011,17 +1070,17 @@ Atomics.wait(flag, 0, 0);
1011
1070
 
1012
1071
  // ── parseInt / parseFloat / isNaN / isFinite ─────────────────────────
1013
1072
  // Expose JS globals directly so Nova scripts can use them without prefix.
1014
- this.globalScope.set('parseInt', (v, radix) => parseInt(String(v), radix));
1015
- this.globalScope.set('parseFloat', (v) => parseFloat(String(v)));
1016
- this.globalScope.set('isNaN', (v) => isNaN(v));
1017
- this.globalScope.set('isFinite', (v) => isFinite(v));
1073
+ this.globalScope.set('parseInt', (v, radix) => parseInt(String(v), radix));
1074
+ this.globalScope.set('parseFloat', (v) => parseFloat(String(v)));
1075
+ this.globalScope.set('isNaN', (v) => isNaN(v));
1076
+ this.globalScope.set('isFinite', (v) => isFinite(v));
1018
1077
 
1019
1078
  // ── keys(obj) / values(obj) / entries(obj) ──────────────────────────
1020
- this.globalScope.set('keys', (o) => {
1079
+ this.globalScope.set('keys', (o) => {
1021
1080
  const raw = o instanceof NovaObject ? o.inner : o;
1022
1081
  return new NovaArray(Object.keys(raw ?? {}));
1023
1082
  });
1024
- this.globalScope.set('values', (o) => {
1083
+ this.globalScope.set('values', (o) => {
1025
1084
  const raw = o instanceof NovaObject ? o.inner : o;
1026
1085
  return new NovaArray(Object.values(raw ?? {}));
1027
1086
  });
@@ -1030,15 +1089,63 @@ Atomics.wait(flag, 0, 0);
1030
1089
  return new NovaArray(Object.entries(raw ?? {}).map(([k, v]) => new NovaArray([k, v])));
1031
1090
  });
1032
1091
 
1092
+ // ── Wasm ──────────────────────────────────────────────────────────────
1093
+ // Wasm object with many methods
1094
+ this.globalScope.set('Wasm', {
1095
+ run: async (bytes, imports) => {
1096
+ const module = await WebAssembly.compile(bytes instanceof NovaArray ? new Uint8Array(bytes.inner) : bytes);
1097
+ const instance = await WebAssembly.instantiate(module, imports instanceof NovaObject ? imports.inner : imports);
1098
+ if (instance.exports && typeof instance.exports._start === 'function') {
1099
+ return instance.exports._start();
1100
+ }
1101
+ return instance;
1102
+ },
1103
+ ...WebAssembly, // re-export all WebAssembly static methods/properties
1104
+ })
1033
1105
  // ── len(v) ───────────────────────────────────────────────────────────
1034
1106
  this.globalScope.set('len', (v) => {
1035
- if (v instanceof NovaArray) return v.length;
1107
+ if (v instanceof NovaArray) return v.length;
1036
1108
  if (v instanceof NovaObject) return Object.keys(v.inner).length;
1037
- if (typeof v === 'string') return v.length;
1109
+ if (typeof v === 'string') return v.length;
1038
1110
  if (v && v.length !== undefined) return v.length;
1039
1111
  return 0;
1040
1112
  });
1041
-
1113
+ // ── new WaiterObject() ───────────────────────────────────────────────────
1114
+ // Expose the WaiterObject class so Nova scripts can create waiters directly.
1115
+ class WaiterObject { // a waiter is an object like this: { state: Int, value: any }
1116
+ constructor() {
1117
+ this._state = 0; // 0 = pending, 1 = resolved, 2 = rejected
1118
+ this.value = null;
1119
+ this.onStateChange = (newState) => newState; // override this to react to state changes
1120
+ }
1121
+ get state() { return this._state; }
1122
+ set state(v) { this._state = this.onStateChange(Number(v)); }
1123
+ }
1124
+ this.globalScope.set('WaiterObject', WaiterObject);
1125
+ // ── new Mutex() ───────────────────────────────────────────────────────────
1126
+ // Expose a simple Mutex class for mutual exclusion in async code.
1127
+ class Mutex {
1128
+ constructor(...waiters) {
1129
+ this._locked = false;
1130
+ this._waiters = waiters;
1131
+ }
1132
+ lock() {
1133
+ if (!this._locked) {
1134
+ this._locked = true;
1135
+ return Promise.resolve();
1136
+ }
1137
+ return new Promise(resolve => this._waiters.push(resolve));
1138
+ }
1139
+ unlock() {
1140
+ if (this._waiters.length > 0) {
1141
+ const next = this._waiters.shift();
1142
+ next();
1143
+ } else {
1144
+ this._locked = false;
1145
+ }
1146
+ }
1147
+ }
1148
+ this.globalScope.set('Mutex', Mutex);
1042
1149
  // ── typeOf_(v) ───────────────────────────────────────────────────────
1043
1150
  // Returns a string type name for any nova value.
1044
1151
  // Named typeOf_ to avoid collision with the `type` keyword (type declarations).
@@ -1046,8 +1153,8 @@ Atomics.wait(flag, 0, 0);
1046
1153
  this.globalScope.set('typeOf_', (v) => _self._typeOf(v));
1047
1154
 
1048
1155
  // ── str(v) / num(v) / bool(v) ────────────────────────────────────────
1049
- this.globalScope.set('str', (v) => _self.stringify(v));
1050
- this.globalScope.set('num', (v) => Number(v instanceof NovaValue ? v.valueOf() : v));
1156
+ this.globalScope.set('str', (v) => _self.stringify(v));
1157
+ this.globalScope.set('num', (v) => Number(v instanceof NovaValue ? v.valueOf() : v));
1051
1158
  this.globalScope.set('bool', (v) => {
1052
1159
  const raw = v instanceof NovaBool ? v.valueOf() : v;
1053
1160
  return !!raw;
@@ -1061,8 +1168,8 @@ Atomics.wait(flag, 0, 0);
1061
1168
  // Named zipArrays to avoid collision with nova's infix `zip` operator.
1062
1169
  this.globalScope.set('zipArrays', (...arrays) => {
1063
1170
  const arrs = arrays.map(a => a instanceof NovaArray ? a.inner : a);
1064
- const len = Math.min(...arrs.map(a => a.length));
1065
- const out = [];
1171
+ const len = Math.min(...arrs.map(a => a.length));
1172
+ const out = [];
1066
1173
  for (let i = 0; i < len; i++) out.push(new NovaArray(arrs.map(a => a[i])));
1067
1174
  return new NovaArray(out);
1068
1175
  });
@@ -1121,13 +1228,13 @@ Atomics.wait(flag, 0, 0);
1121
1228
  if (t === 'symbol') return v;
1122
1229
  if (t !== 'object') return v;
1123
1230
  if (seen.has(v)) return seen.get(v);
1124
- if (v instanceof NovaBool) return new NovaBool(v.valueOf());
1125
- if (v instanceof NovaNull) return new NovaNull();
1231
+ if (v instanceof NovaBool) return new NovaBool(v.valueOf());
1232
+ if (v instanceof NovaNull) return new NovaNull();
1126
1233
  if (v instanceof NovaNumber) return new NovaNumber(v.valueOf());
1127
1234
  if (v instanceof NovaString) return new NovaString(v.valueOf());
1128
- if (v instanceof NovaRange) return new NovaRange(v.start, v.end, v.step);
1235
+ if (v instanceof NovaRange) return new NovaRange(v.start, v.end, v.step);
1129
1236
  if (v instanceof NovaPointer) return new NovaPointer(_deepClone(v.inner, seen), v.readFn, v.writeFn, v.address);
1130
- if (v instanceof NovaEnum) { const e = new NovaEnum(v.typeName, v.variant, _deepClone(v.inner, seen)); e.value = _deepClone(v.value, seen); return e; }
1237
+ if (v instanceof NovaEnum) { const e = new NovaEnum(v.typeName, v.variant, _deepClone(v.inner, seen)); e.value = _deepClone(v.value, seen); return e; }
1131
1238
  if (v instanceof NovaStruct) {
1132
1239
  const inner = {};
1133
1240
  seen.set(v, inner); // register early to handle circular refs
@@ -1177,10 +1284,10 @@ Atomics.wait(flag, 0, 0);
1177
1284
  // ── shallowClone(value) ───────────────────────────────────────────────
1178
1285
  // One-level clone — top container is new but inner values are shared.
1179
1286
  this.globalScope.set('shallowClone', (v) => {
1180
- if (v instanceof NovaArray) return new NovaArray([...v.inner]);
1287
+ if (v instanceof NovaArray) return new NovaArray([...v.inner]);
1181
1288
  if (v instanceof NovaObject) return new NovaObject({ ...v.inner });
1182
1289
  if (v instanceof NovaStruct) return new NovaStruct(v.typeName, { ...v.inner });
1183
- if (Array.isArray(v)) return [...v];
1290
+ if (Array.isArray(v)) return [...v];
1184
1291
  if (v && typeof v === 'object') return { ...v };
1185
1292
  return v;
1186
1293
  });
@@ -1193,7 +1300,7 @@ Atomics.wait(flag, 0, 0);
1193
1300
  // executor's globalScope since Nova scripts can't pass their own scope ref.
1194
1301
  this.globalScope.set('saveCurrentScope', () => {
1195
1302
  const snap = new Scope('saved', null, _self.globalScope);
1196
- const SKIP = new Set(['scope','array','std','globalScope','parent']);
1303
+ const SKIP = new Set(['scope', 'array', 'std', 'globalScope', 'parent']);
1197
1304
  for (const [k, v] of Object.entries(_self.globalScope.variables)) {
1198
1305
  if (SKIP.has(k)) continue;
1199
1306
  try { snap.variables[k] = _deepClone(v); } catch { snap.variables[k] = v; }
@@ -1214,7 +1321,7 @@ Atomics.wait(flag, 0, 0);
1214
1321
  if (!(scope instanceof Scope))
1215
1322
  throw new Error('saveScope: argument must be a Scope (got ' + _self._typeOf(scope) + ')');
1216
1323
  const snap = new Scope('saved', null, _self.globalScope);
1217
- const SKIP = new Set(['scope','array','std','globalScope','parent']);
1324
+ const SKIP = new Set(['scope', 'array', 'std', 'globalScope', 'parent']);
1218
1325
  for (const [k, v] of Object.entries(scope.variables)) {
1219
1326
  if (SKIP.has(k)) continue;
1220
1327
  try { snap.variables[k] = _deepClone(v); } catch { snap.variables[k] = v; }
@@ -1230,7 +1337,7 @@ Atomics.wait(flag, 0, 0);
1230
1337
  // the current global scope. Good for inspection / serialization.
1231
1338
  this.globalScope.set('scopeSnapshot', (scope) => {
1232
1339
  const src = (scope instanceof Scope) ? scope : _self.globalScope;
1233
- const SKIP = new Set(['scope','array','std','globalScope','parent']);
1340
+ const SKIP = new Set(['scope', 'array', 'std', 'globalScope', 'parent']);
1234
1341
  const out = {};
1235
1342
  for (const [k, v] of Object.entries(src.variables)) {
1236
1343
  if (SKIP.has(k)) continue;
@@ -1285,7 +1392,7 @@ Atomics.wait(flag, 0, 0);
1285
1392
  this.globalScope.set('scopeMerge', (target, source) => {
1286
1393
  if (!(target instanceof Scope) || !(source instanceof Scope))
1287
1394
  throw new Error('scopeMerge: both args must be Scopes');
1288
- const SKIP = new Set(['scope','array','std','globalScope','parent']);
1395
+ const SKIP = new Set(['scope', 'array', 'std', 'globalScope', 'parent']);
1289
1396
  for (const [k, v] of Object.entries(source.variables)) {
1290
1397
  if (SKIP.has(k)) continue;
1291
1398
  target.variables[k] = v;
@@ -1326,7 +1433,7 @@ Atomics.wait(flag, 0, 0);
1326
1433
  // List all user-defined variable names in a scope (own only).
1327
1434
  this.globalScope.set('scopeKeys', (scope) => {
1328
1435
  const src = (scope instanceof Scope) ? scope : _self.globalScope;
1329
- const SKIP = new Set(['scope','array','std','globalScope','parent']);
1436
+ const SKIP = new Set(['scope', 'array', 'std', 'globalScope', 'parent']);
1330
1437
  return new NovaArray(Object.keys(src.variables).filter(k => !SKIP.has(k)));
1331
1438
  });
1332
1439
 
@@ -1364,7 +1471,7 @@ Atomics.wait(flag, 0, 0);
1364
1471
  // Freeze all current variables in scope as constants (no further writes).
1365
1472
  this.globalScope.set('freezeScope', (scope) => {
1366
1473
  const src = (scope instanceof Scope) ? scope : _self.globalScope;
1367
- const SKIP = new Set(['scope','array','std','globalScope','parent']);
1474
+ const SKIP = new Set(['scope', 'array', 'std', 'globalScope', 'parent']);
1368
1475
  for (const k of Object.keys(src.variables)) {
1369
1476
  if (!SKIP.has(k)) src.consts[k] = src.variables[k];
1370
1477
  }
@@ -1377,7 +1484,7 @@ Atomics.wait(flag, 0, 0);
1377
1484
  this.globalScope.set('scopeDiff', (a, b) => {
1378
1485
  if (!(a instanceof Scope) || !(b instanceof Scope))
1379
1486
  throw new Error('scopeDiff: both args must be Scopes');
1380
- const SKIP = new Set(['scope','array','std','globalScope','parent']);
1487
+ const SKIP = new Set(['scope', 'array', 'std', 'globalScope', 'parent']);
1381
1488
  const keysA = new Set(Object.keys(a.variables).filter(k => !SKIP.has(k)));
1382
1489
  const keysB = new Set(Object.keys(b.variables).filter(k => !SKIP.has(k)));
1383
1490
  const onlyInA = [...keysA].filter(k => !keysB.has(k));
@@ -1401,13 +1508,13 @@ Atomics.wait(flag, 0, 0);
1401
1508
  const n = String(name);
1402
1509
  let _val = scope.variables[n];
1403
1510
  const descriptor = {
1404
- read: () => _val,
1511
+ read: () => _val,
1405
1512
  write: (v) => {
1406
1513
  const old = _val; _val = v;
1407
1514
  try {
1408
1515
  if (typeof fn === 'function') fn(v, old);
1409
1516
  else if (fn && fn.body !== undefined) _self.runFunctionNode(fn, _self.globalScope, [v, old]);
1410
- } catch (_) {}
1517
+ } catch (_) { }
1411
1518
  },
1412
1519
  };
1413
1520
  scope.variables[n] = descriptor;
@@ -1463,7 +1570,7 @@ Atomics.wait(flag, 0, 0);
1463
1570
  const _deepFreeze = (v, seen = new WeakSet()) => {
1464
1571
  if (!v || typeof v !== 'object' || seen.has(v)) return v;
1465
1572
  seen.add(v);
1466
- if (v instanceof NovaArray) { Object.freeze(v.inner); v.inner.forEach(x => _deepFreeze(x, seen)); }
1573
+ if (v instanceof NovaArray) { Object.freeze(v.inner); v.inner.forEach(x => _deepFreeze(x, seen)); }
1467
1574
  if (v instanceof NovaObject) { Object.freeze(v.inner); Object.values(v.inner).forEach(x => _deepFreeze(x, seen)); }
1468
1575
  if (v instanceof NovaStruct) { Object.freeze(v.inner); Object.values(v.inner).forEach(x => _deepFreeze(x, seen)); }
1469
1576
  return v;
@@ -1666,7 +1773,7 @@ Atomics.wait(flag, 0, 0);
1666
1773
  if (st === 0) throw new Error('range2: step cannot be 0');
1667
1774
  const out = [];
1668
1775
  if (st > 0) { for (let i = s; i <= e; i += st) out.push(i); }
1669
- else { for (let i = s; i >= e; i += st) out.push(i); }
1776
+ else { for (let i = s; i >= e; i += st) out.push(i); }
1670
1777
  return new NovaArray(out);
1671
1778
  });
1672
1779
 
@@ -1919,7 +2026,7 @@ Atomics.wait(flag, 0, 0);
1919
2026
  const d = new Date(ms !== undefined ? Number(ms) : Date.now());
1920
2027
  const pad = (n) => String(n).padStart(2, '0');
1921
2028
  const tokens = {
1922
- YYYY: d.getFullYear(), MM: pad(d.getMonth()+1), DD: pad(d.getDate()),
2029
+ YYYY: d.getFullYear(), MM: pad(d.getMonth() + 1), DD: pad(d.getDate()),
1923
2030
  HH: pad(d.getHours()), mm: pad(d.getMinutes()), ss: pad(d.getSeconds()),
1924
2031
  };
1925
2032
  const f = String(fmt || 'YYYY-MM-DD HH:mm:ss');
@@ -1950,7 +2057,7 @@ Atomics.wait(flag, 0, 0);
1950
2057
  // Return a human-readable debug string with type info.
1951
2058
  this.globalScope.set('inspect', (v) => {
1952
2059
  const type = _self._typeOf(v);
1953
- const str = _self.stringify(v);
2060
+ const str = _self.stringify(v);
1954
2061
  return `[${type}] ${str}`;
1955
2062
  });
1956
2063
 
@@ -1998,7 +2105,7 @@ Atomics.wait(flag, 0, 0);
1998
2105
  }
1999
2106
  // NovaBool / NovaNumber / NovaString / NovaNull — all have _v
2000
2107
  if (v instanceof NovaBool || v instanceof NovaNumber ||
2001
- v instanceof NovaString || v instanceof NovaNull) {
2108
+ v instanceof NovaString || v instanceof NovaNull) {
2002
2109
  return { _v: v.valueOf !== undefined ? v.valueOf() : null };
2003
2110
  }
2004
2111
  if (v instanceof NovaRange) return { start: v.start, end: v.end };
@@ -2026,10 +2133,10 @@ Atomics.wait(flag, 0, 0);
2026
2133
  if (Array.isArray(v)) return v.map(x => this._rehydrate(x));
2027
2134
  if ('__jsfn__' in v) { try { return eval('(' + v.__jsfn__ + ')'); } catch { return null; } }
2028
2135
  if ('_v' in v) {
2029
- if (v._v === null) return new NovaNull();
2136
+ if (v._v === null) return new NovaNull();
2030
2137
  if (typeof v._v === 'boolean') return new NovaBool(v._v);
2031
- if (typeof v._v === 'number') return new NovaNumber(v._v);
2032
- if (typeof v._v === 'string') return new NovaString(v._v);
2138
+ if (typeof v._v === 'number') return new NovaNumber(v._v);
2139
+ if (typeof v._v === 'string') return new NovaString(v._v);
2033
2140
  return v._v;
2034
2141
  }
2035
2142
  if ('start' in v && 'end' in v && Object.keys(v).length === 2)
@@ -2070,12 +2177,12 @@ Atomics.wait(flag, 0, 0);
2070
2177
  // Also snapshot the global user-defined variables (functions, declared vars)
2071
2178
  // but skip built-in keys that are circular, huge, or executor-internal.
2072
2179
  const SKIP = new Set([
2073
- 'scope','array','std','core','nova','nvk','qae','novaRegex','fetch',
2074
- 'setTimeout','typecheck','satisfies','typeOf','Thread',
2075
- 'ForLoop','WhileLoop','IfBlock','TryCatch','Pipeline','FuncDef',
2076
- 'MatchBlock','Timer','Counter','Stack','Queue','LinkedList',
2077
- 'State','Observable','Validator','DataStream','Transformer',
2078
- 'TransformerJSON','TransformerBase64','Router','EventBus','Memo','Lazy','Signal',
2180
+ 'scope', 'array', 'std', 'core', 'nova', 'nvk', 'qae', 'cregex', 'fetch',
2181
+ 'setTimeout', 'typecheck', 'satisfies', 'typeOf', 'Thread',
2182
+ 'ForLoop', 'WhileLoop', 'IfBlock', 'TryCatch', 'Pipeline', 'FuncDef',
2183
+ 'MatchBlock', 'Timer', 'Counter', 'Stack', 'Queue', 'LinkedList',
2184
+ 'State', 'Observable', 'Validator', 'DataStream', 'Transformer',
2185
+ 'TransformerJSON', 'TransformerBase64', 'Router', 'EventBus', 'Memo', 'Lazy', 'Signal',
2079
2186
  ]);
2080
2187
  for (const [k, v] of Object.entries(this.globalScope.variables || {})) {
2081
2188
  if (SKIP.has(k) || snap.hasOwnProperty(k)) continue;
@@ -2091,20 +2198,20 @@ Atomics.wait(flag, 0, 0);
2091
2198
  _initThread() {
2092
2199
  const _path = require('path');
2093
2200
  const _workerScriptPath = _path.join(__dirname, 'nova_thread_worker.js');
2094
- const _executorPath = _path.join(__dirname, 'executor.js');
2201
+ const _executorPath = _path.join(__dirname, 'executor.js');
2095
2202
  const _exe = this;
2096
2203
 
2097
2204
  class NovaThread {
2098
2205
  constructor(fnNode, callerScope) {
2099
- this._fnNode = fnNode;
2206
+ this._fnNode = fnNode;
2100
2207
  this._callerScope = callerScope;
2101
- this._worker = null;
2102
- this._mutations = []; // { name, value } buffered until join()
2208
+ this._worker = null;
2209
+ this._mutations = []; // { name, value } buffered until join()
2103
2210
  this._msgHandlers = [];
2104
- this._done = false;
2105
- this._result = undefined;
2106
- this._error = null;
2107
- this._sab = new SharedArrayBuffer(8);
2211
+ this._done = false;
2212
+ this._result = undefined;
2213
+ this._error = null;
2214
+ this._sab = new SharedArrayBuffer(8);
2108
2215
  this._flag = new Int32Array(this._sab);
2109
2216
  this._proxy = this._makeProxy();
2110
2217
  }
@@ -2112,13 +2219,13 @@ Atomics.wait(flag, 0, 0);
2112
2219
  _makeProxy() {
2113
2220
  const t = this;
2114
2221
  return new NovaObject({
2115
- start: () => { t.start(); return t._proxy; },
2116
- join: () => t.join(),
2117
- send: (v) => { t.send(v); return t._proxy; },
2222
+ start: () => { t.start(); return t._proxy; },
2223
+ join: () => t.join(),
2224
+ send: (v) => { t.send(v); return t._proxy; },
2118
2225
  on_message: (fn) => { t.on_message(fn); return t._proxy; },
2119
- get result() { return t._result; },
2120
- get error() { return t._error; },
2121
- get done() { return t._done; },
2226
+ get result() { return t._result; },
2227
+ get error() { return t._error; },
2228
+ get done() { return t._done; },
2122
2229
  });
2123
2230
  }
2124
2231
 
@@ -2503,143 +2610,189 @@ Atomics.wait(flag, 0, 0);
2503
2610
  return content;
2504
2611
  }
2505
2612
 
2506
- stringify(v, indent = 0, seen = new WeakSet()) {
2507
- const pad = " ".repeat(indent);
2508
-
2509
- const color = {
2510
- reset: "\x1b[0m",
2511
- key: "\x1b[36m",
2512
- string: "\x1b[32m",
2513
- number: "\x1b[33m",
2514
- bool: "\x1b[35m",
2515
- null: "\x1b[90m",
2516
- func: "\x1b[31m",
2517
- circular: "\x1b[91m",
2518
- };
2519
-
2520
- const nl = "\n";
2521
-
2522
- const next = (x, i = indent + 1) =>
2523
- this.stringify(x, i, seen);
2524
-
2525
- if (v === null || v === undefined)
2526
- return color.null + "null" + color.reset;
2527
-
2528
- if (v instanceof NovaNull)
2529
- return color.null + "null" + color.reset;
2613
+ stringify(v, indent = 0, seen = new WeakSet()) {
2614
+ const pad = " ".repeat(indent);
2615
+
2616
+ const color = {
2617
+ reset: "\x1b[0m",
2618
+ key: "\x1b[36m",
2619
+ string: "\x1b[32m",
2620
+ number: "\x1b[33m",
2621
+ bool: "\x1b[35m",
2622
+ null: "\x1b[90m",
2623
+ func: "\x1b[31m",
2624
+ circular: "\x1b[91m",
2625
+ };
2530
2626
 
2531
- if (v instanceof NovaBool)
2532
- return color.bool + String(v.valueOf()) + color.reset;
2627
+ const nl = "\n";
2533
2628
 
2534
- if (v instanceof NovaString)
2535
- return color.string + `"${v.valueOf()}"` + color.reset;
2629
+ const next = (x, i = indent + 1) =>
2630
+ this.stringify(x, i, seen);
2536
2631
 
2537
- if (v instanceof NovaNumber)
2538
- return color.number + String(v.valueOf()) + color.reset;
2632
+ if (v === null || v === undefined)
2633
+ return color.null + "null" + color.reset;
2539
2634
 
2540
- if (v instanceof Promise)
2541
- return color.func + `Promise [${v.name || "anon"}]` + color.reset;
2635
+ if (v instanceof NovaNull)
2636
+ return color.null + "null" + color.reset;
2542
2637
 
2543
- if (v instanceof NovaRange)
2544
- return v.toString();
2638
+ if (v instanceof NovaBool)
2639
+ return color.bool + String(v.valueOf()) + color.reset;
2545
2640
 
2546
- if (v instanceof NovaStruct)
2547
- return v.toString();
2641
+ if (v instanceof NovaString)
2642
+ return color.string + `"${v.valueOf()}"` + color.reset;
2548
2643
 
2549
- if (typeof v === "object" && v !== null) {
2550
- if (seen.has(v))
2551
- return color.circular + "[Circular]" + color.reset;
2644
+ if (v instanceof NovaNumber)
2645
+ return color.number + String(v.valueOf()) + color.reset;
2552
2646
 
2553
- seen.add(v);
2554
- }
2647
+ if (v instanceof Promise)
2648
+ return color.func + `Promise [${v.name || "anon"}]` + color.reset;
2555
2649
 
2556
- if (v instanceof NovaArray) {
2557
- if (!v.inner.length)
2558
- return "[]";
2559
-
2560
- return "[\n" +
2561
- v.inner.map(x =>
2562
- pad + " " + next(x, indent + 1)
2563
- ).join(",\n") +
2564
- nl + pad + "]";
2565
- }
2650
+ if (v instanceof NovaRange)
2651
+ return v.toString();
2566
2652
 
2567
- if (v instanceof NovaObject) {
2568
- const entries = Object.entries(v.inner);
2653
+ if (v instanceof NovaStruct)
2654
+ return v.toString();
2569
2655
 
2570
- if (!entries.length)
2571
- return "{}";
2656
+ // ── Plain JS getter/setter descriptor { read, write } ────────────────────
2657
+ // Scope variables with modifiers (tracked, lazy, clamp, etc.) are stored as
2658
+ // plain objects with read() / write() functions. Show the live value instead.
2659
+ if (
2660
+ v !== null && typeof v === 'object' &&
2661
+ !(v instanceof NovaValue) && !(v instanceof NovaArray) &&
2662
+ !(v instanceof NovaObject) && !(v instanceof NovaStruct) &&
2663
+ !(v instanceof NovaEnum) && !(v instanceof Scope) &&
2664
+ !(v instanceof Promise) && !Array.isArray(v) &&
2665
+ typeof v.read === 'function' && typeof v.write === 'function'
2666
+ ) {
2667
+ return color.key + '[get/set ×]' + color.reset;
2668
+ }
2572
2669
 
2573
- return "{\n" +
2574
- entries.map(([k, x]) =>
2575
- pad + " " +
2576
- color.key + k + color.reset +
2577
- ": " +
2578
- next(x, indent + 1)
2579
- ).join(",\n") +
2580
- nl + pad + "}";
2581
- }
2670
+ if (typeof v === "object" && v !== null) {
2671
+ if (seen.has(v))
2672
+ return color.circular + "[Circular]" + color.reset;
2582
2673
 
2583
- if (v instanceof NovaValue)
2584
- return v.toString();
2674
+ seen.add(v);
2675
+ }
2585
2676
 
2586
- if (v instanceof Scope)
2587
- return v.toString();
2677
+ if (v instanceof NovaArray) {
2678
+ if (!v.inner.length)
2679
+ return "[]";
2588
2680
 
2589
- if (v && v.kind === "function")
2590
- return color.func +
2591
- `Function [${v.name || "anon"}]` +
2592
- color.reset;
2681
+ return "[\n" +
2682
+ v.inner.map(x =>
2683
+ pad + " " + next(x, indent + 1)
2684
+ ).join(",\n") +
2685
+ nl + pad + "]";
2686
+ }
2593
2687
 
2594
- if (v && v.kind === "class")
2595
- return color.func +
2596
- `Class [${v.node?.name || "Unknown"}]` +
2597
- color.reset;
2688
+ if (v instanceof NovaObject) {
2689
+ // ── Build annotation badges ─────────────────────────────────────────────
2690
+ const _annotations = [];
2691
+
2692
+ // Operator overloads stored under __operators
2693
+ const _opsRaw = v.inner.__operators;
2694
+ if (_opsRaw) {
2695
+ const _opsObj = _opsRaw instanceof NovaObject
2696
+ ? _opsRaw.inner
2697
+ : (typeof _opsRaw === 'object' && _opsRaw !== null ? _opsRaw : {});
2698
+ const _binOps = Object.keys(
2699
+ _opsObj.binary instanceof NovaObject ? _opsObj.binary.inner : (_opsObj.binary || {})
2700
+ );
2701
+ const _unOps = Object.keys(
2702
+ _opsObj.unary instanceof NovaObject ? _opsObj.unary.inner : (_opsObj.unary || {})
2703
+ ).map(k => 'u' + k);
2704
+ const _allOps = [..._binOps, ..._unOps];
2705
+ _annotations.push('\x1b[33m[ops:' + (_allOps.length ? ' ' + _allOps.join(' ') : '') + ']\x1b[0m');
2706
+ }
2707
+
2708
+ // Meta traps attached via attachMeta()
2709
+ if (v._meta) {
2710
+ const _traps = [];
2711
+ if (typeof v._meta.get === 'function') _traps.push('get');
2712
+ if (typeof v._meta.set === 'function') _traps.push('set');
2713
+ if (typeof v._meta.missing === 'function') _traps.push('missing');
2714
+ if (_traps.length) _annotations.push('\x1b[36m[meta:' + _traps.join(',') + ']\x1b[0m');
2715
+ }
2716
+
2717
+ const _prefix = _annotations.length ? _annotations.join(' ') + ' ' : '';
2718
+
2719
+ // Hide internal plumbing keys from display
2720
+ const _HIDE = new Set(['__operators']);
2721
+ const _entries = Object.entries(v.inner).filter(([k]) => !_HIDE.has(k));
2722
+
2723
+ if (!_entries.length)
2724
+ return _prefix + "{}";
2725
+
2726
+ return _prefix + "{\n" +
2727
+ _entries.map(([k, x]) =>
2728
+ pad + " " +
2729
+ color.key + k + color.reset +
2730
+ ": " +
2731
+ next(x, indent + 1)
2732
+ ).join(",\n") +
2733
+ nl + pad + "}";
2734
+ }
2598
2735
 
2599
- if (v instanceof NovaEnum)
2600
- return v.toString();
2736
+ if (v instanceof NovaValue)
2737
+ return v.toString();
2601
2738
 
2602
- switch (typeof v) {
2603
- case "number":
2604
- return color.number +
2605
- (isNaN(v) ? "NaN" : String(v)) +
2606
- color.reset;
2739
+ if (v instanceof Scope)
2740
+ return v.toString();
2607
2741
 
2608
- case "string":
2609
- return color.string +
2610
- `"${v}"` +
2742
+ if (v && v.kind === "function")
2743
+ return color.func +
2744
+ `Function [${v.name || "anon"}]` +
2611
2745
  color.reset;
2612
2746
 
2613
- case "boolean":
2614
- return color.bool +
2615
- String(v) +
2747
+ if (v && v.kind === "class")
2748
+ return color.func +
2749
+ `Class [${v.node?.name || "Unknown"}]` +
2616
2750
  color.reset;
2617
2751
 
2618
- case "object": {
2619
- const entries = Object.entries(v);
2620
-
2621
- if (!entries.length)
2622
- return "{}";
2752
+ if (v instanceof NovaEnum)
2753
+ return v.toString();
2623
2754
 
2624
- return "{\n" +
2625
- entries.map(([k, val]) =>
2626
- pad + " " +
2627
- color.key + k + color.reset +
2628
- ": " +
2629
- next(val, indent + 1)
2630
- ).join(",\n") +
2631
- nl + pad + "}";
2755
+ switch (typeof v) {
2756
+ case "number":
2757
+ return color.number +
2758
+ (isNaN(v) ? "NaN" : String(v)) +
2759
+ color.reset;
2760
+
2761
+ case "string":
2762
+ return color.string +
2763
+ `"${v}"` +
2764
+ color.reset;
2765
+
2766
+ case "boolean":
2767
+ return color.bool +
2768
+ String(v) +
2769
+ color.reset;
2770
+
2771
+ case "object": {
2772
+ const entries = Object.entries(v);
2773
+
2774
+ if (!entries.length)
2775
+ return "Native {}";
2776
+
2777
+ return "Native {\n" +
2778
+ entries.map(([k, val]) =>
2779
+ pad + " " +
2780
+ color.key + k + color.reset +
2781
+ ": " +
2782
+ next(val, indent + 1)
2783
+ ).join(",\n") +
2784
+ nl + pad + "}";
2785
+ }
2786
+
2787
+ case "function":
2788
+ return color.func +
2789
+ `Function [${v.registered ? "Registered" : "NativeJs"}]` +
2790
+ color.reset;
2632
2791
  }
2633
2792
 
2634
- case "function":
2635
- return color.func +
2636
- `Function [${v.registered ? "Registered" : "NativeJs"}]` +
2637
- color.reset;
2793
+ return String(v);
2638
2794
  }
2639
2795
 
2640
- return String(v);
2641
- }
2642
-
2643
2796
  runFunctionNode(fn, scope, args = []) {
2644
2797
  if (!fn.execCount) fn.execCount = 0;
2645
2798
  fn.execCount++;
@@ -2788,12 +2941,20 @@ stringify(v, indent = 0, seen = new WeakSet()) {
2788
2941
  switch (current.type) {
2789
2942
  case 'if': {
2790
2943
  if (this.evaluate(current.args, scope)) {
2791
- this._runBodyWithYields(current.body, scope, collector);
2944
+ const _sig = this._runBodyWithYields(current.body, scope, collector);
2792
2945
  executed = true;
2946
+ if (_sig === 'break') throw { __break: true };
2947
+ if (_sig === 'continue') throw { __continue: true };
2793
2948
  } else current = current.next;
2794
2949
  break;
2795
2950
  }
2796
- case 'else': { this._runBodyWithYields(current.body, scope, collector); executed = true; break; }
2951
+ case 'else': {
2952
+ const _sig = this._runBodyWithYields(current.body, scope, collector);
2953
+ executed = true;
2954
+ if (_sig === 'break') throw { __break: true };
2955
+ if (_sig === 'continue') throw { __continue: true };
2956
+ break;
2957
+ }
2797
2958
  case 'while': {
2798
2959
  const c = Array.isArray(current.args) ? current.args[0] : current.args;
2799
2960
  while (this.evaluate(c, scope)) {
@@ -2811,10 +2972,10 @@ stringify(v, indent = 0, seen = new WeakSet()) {
2811
2972
  executed = true; break;
2812
2973
  }
2813
2974
  default: {
2814
- try { this.execute(node, scope); }
2975
+ try { this.execute(node, scope); }
2815
2976
  catch (e) { if (e && '__yield' in e) { collector.push(e.__yield); } else throw e; }
2816
- executed = true; break;
2817
- }
2977
+ executed = true; break;
2978
+ }
2818
2979
  }
2819
2980
  }
2820
2981
  return;
@@ -2985,7 +3146,8 @@ stringify(v, indent = 0, seen = new WeakSet()) {
2985
3146
  // ── declare ──
2986
3147
  case 'declare': {
2987
3148
  if (node.destructure) {
2988
- const val = this.evaluate(node.value, scope);
3149
+ const evaled = this.evaluate(node.value, scope);
3150
+ const val = evaled instanceof NovaObject ? evaled.inner : evaled;
2989
3151
  if (node.destructure.kind === 'objpattern') {
2990
3152
  for (const { key, alias, defaultValue } of node.destructure.props) {
2991
3153
  let v = val && Object.prototype.hasOwnProperty.call(val, key) ? val[key] : undefined;
@@ -3062,7 +3224,7 @@ stringify(v, indent = 0, seen = new WeakSet()) {
3062
3224
  }
3063
3225
  return false;
3064
3226
  });
3065
- if (!ok) throw new Error("Variable '" + varName + "': value must be one of the declared union variants, got " + self._typeOf(v) + "("+v+")");
3227
+ if (!ok) throw new Error("Variable '" + varName + "': value must be one of the declared union variants, got " + self._typeOf(v) + "(" + v + ")");
3066
3228
  }
3067
3229
  };
3068
3230
  }
@@ -3152,18 +3314,27 @@ stringify(v, indent = 0, seen = new WeakSet()) {
3152
3314
  case 'if': {
3153
3315
  if (this.evaluate(current.args, scope)) {
3154
3316
  const bs = new Scope('block', scope, this.globalScope);
3155
- this.runLoop(current.body, bs); executed = true;
3317
+ const _sig = this.runLoop(current.body, bs);
3318
+ executed = true;
3319
+ if (_sig === 'break') throw { __break: true };
3320
+ if (_sig === 'continue') throw { __continue: true };
3156
3321
  } else current = current.next;
3157
3322
  break;
3158
3323
  }
3159
3324
  case 'else': {
3160
3325
  const bs = new Scope('block', scope, this.globalScope);
3161
- this.runLoop(current.body, bs); executed = true; break;
3326
+ const _sig = this.runLoop(current.body, bs);
3327
+ executed = true;
3328
+ if (_sig === 'break') throw { __break: true };
3329
+ if (_sig === 'continue') throw { __continue: true };
3330
+ break;
3162
3331
  }
3163
3332
  case 'unless': {
3164
3333
  if (!this.evaluate(current.args, scope)) {
3165
3334
  const bs = new Scope('block', scope, this.globalScope);
3166
- this.runLoop(current.body, bs);
3335
+ const _sig = this.runLoop(current.body, bs);
3336
+ if (_sig === 'break') throw { __break: true };
3337
+ if (_sig === 'continue') throw { __continue: true };
3167
3338
  }
3168
3339
  executed = true; break;
3169
3340
  }
@@ -3290,7 +3461,7 @@ stringify(v, indent = 0, seen = new WeakSet()) {
3290
3461
  const val = node.value ? this.evaluate(node.value, scope) : undefined;
3291
3462
  for (const h of (this.eventBus.get(name) || [])) {
3292
3463
  if (h._native && typeof h._native === 'function') {
3293
- try { h._native(val); } catch (_) {}
3464
+ try { h._native(val); } catch (_) { }
3294
3465
  } else {
3295
3466
  const hs = new Scope('function', scope, this.globalScope);
3296
3467
  if (h.param) hs.set(h.param, val);
@@ -3417,7 +3588,7 @@ stringify(v, indent = 0, seen = new WeakSet()) {
3417
3588
  return undefined;
3418
3589
  }
3419
3590
 
3420
- case 'import': {
3591
+ case 'import': {
3421
3592
  const exports = this._loadModuleExports(node.source, scope);
3422
3593
  if (Array.isArray(node.names) && node.names.length > 0) {
3423
3594
  for (const name of node.names) scope.set(name, exports[name]);
@@ -3488,6 +3659,10 @@ stringify(v, indent = 0, seen = new WeakSet()) {
3488
3659
  switch (expr.kind) {
3489
3660
  case 'EOF': return undefined;
3490
3661
 
3662
+ case 'regex_literal':
3663
+ let pattern = this.evaluate(expr.pattern);
3664
+ return new RegExp(typeof pattern === 'string' ? pattern : pattern.inner, expr.flags);
3665
+
3491
3666
  case 'url_literal':
3492
3667
  return expr.value;
3493
3668
 
@@ -3500,7 +3675,7 @@ stringify(v, indent = 0, seen = new WeakSet()) {
3500
3675
  case 'fetch_expr': {
3501
3676
  const url = this.evaluate(expr.url, scope);
3502
3677
  const options = expr.options ? this.evaluate(expr.options, scope) : null;
3503
- return this._syncFetch(url, options);
3678
+ return fetch(url, options);
3504
3679
  }
3505
3680
 
3506
3681
  case 'value': {
@@ -3517,7 +3692,7 @@ stringify(v, indent = 0, seen = new WeakSet()) {
3517
3692
 
3518
3693
  // ── Dynamic / meta variables ──────────────────────────────────────────
3519
3694
  case 'dvar': {
3520
- const _os = require('os');
3695
+ const _os = require('os');
3521
3696
  const _pth = require('path');
3522
3697
  const _cry = require('crypto');
3523
3698
  const dname = expr.name;
@@ -3728,11 +3903,11 @@ stringify(v, indent = 0, seen = new WeakSet()) {
3728
3903
  if (key === '__meta__' || (entry.key.kind === 'ref' && entry.key.name === 'method')) {
3729
3904
  metaDescriptor = this.evaluate(entry.value, scope);
3730
3905
  } else if (key === '__expr__' || (entry.key.kind === 'ref' && entry.key.name === 'expr')) {
3731
- //detect [expr] key
3732
- obj['.expr'] = this.evaluate(entry.value, scope);
3906
+ //detect [expr] key
3907
+ obj['.expr'] = this.evaluate(entry.value, scope);
3733
3908
  } else if (key === '__op__' || (entry.key.kind === 'ref' && entry.key.name === 'op')) {
3734
- //detect [operator] key
3735
- obj['__operators'] = this.evaluate(entry.value, scope);
3909
+ //detect [operator] key
3910
+ obj['__operators'] = this.evaluate(entry.value, scope);
3736
3911
  } else {
3737
3912
  obj[key] = this.evaluate(entry.value, scope);
3738
3913
  }
@@ -3804,7 +3979,7 @@ stringify(v, indent = 0, seen = new WeakSet()) {
3804
3979
  case 'assign': {
3805
3980
  const val = this.evaluate(expr.value, scope);
3806
3981
  let n = expr.name;
3807
- if (n.kind === 'dexpr') n = { name: this.evaluate(n.code, scope) }
3982
+ if (n.kind === 'dexpr') n = { name: this.evaluate(n.code, scope) }
3808
3983
  if (n.kind === 'deref') {
3809
3984
  const ptr = this.evaluate(n.operand, scope);
3810
3985
  if (!(ptr instanceof NovaPointer)) this.error('Deref non-pointer', expr);
@@ -3849,22 +4024,28 @@ stringify(v, indent = 0, seen = new WeakSet()) {
3849
4024
  ? this.evaluate(expr.consequent, scope) : this.evaluate(expr.alternate, scope);
3850
4025
 
3851
4026
  case 'if_ternary': {
3852
- let cond = this.evaluate(expr.cond, scope);
4027
+ let cond = this.evaluate(expr.cond, scope);
3853
4028
  if (cond) { return this.evaluate(expr.operand, scope) }
3854
4029
  else if (expr.elseExpr) { return this.evaluate(expr.elseExpr, scope) }
3855
4030
  }
3856
4031
 
3857
4032
  case 'run_expr': return this.execute(expr.code, scope)
3858
4033
 
4034
+ case 'native':
4035
+ let val = this.evaluate(expr.value);
4036
+ return val instanceof NovaValue ? val.inner : val;
4037
+
4038
+ case 'ast': return { kind:"ast", ast: expr.ast };
4039
+
3859
4040
  case 'link': return {
3860
4041
  read: () => this.evaluate(expr.linked),
3861
4042
  write: (val) =>
3862
- this.evaluate({
3863
- kind: 'assign',
3864
- name: expr.linked,
3865
- value: { kind: 'value', value: val }
3866
- }, scope)
3867
- };
4043
+ this.evaluate({
4044
+ kind: 'assign',
4045
+ name: expr.linked,
4046
+ value: { kind: 'value', value: val }
4047
+ }, scope)
4048
+ };
3868
4049
 
3869
4050
  case 'dexpr': return this.evaluate(expr.code, scope);
3870
4051
 
@@ -3993,7 +4174,7 @@ stringify(v, indent = 0, seen = new WeakSet()) {
3993
4174
  const m = this._objectMethod(obj, pn);
3994
4175
  return m !== undefined ? m : null;
3995
4176
  }
3996
- if (!(Object.hasOwnProperty(obj, 'name'))) return (typeof expr.object.name === 'string') ? expr.object.name : 'anon';
4177
+ if (!(Object.hasOwnProperty(obj, 'name')) && pn === 'name') return (typeof expr.object.name === 'string') ? expr.object.name : 'anon';
3997
4178
  return (obj != null ? obj[pn] : null);
3998
4179
  }
3999
4180
 
@@ -4188,7 +4369,7 @@ stringify(v, indent = 0, seen = new WeakSet()) {
4188
4369
  else if (obj instanceof Scope) obj.delete(o.name);
4189
4370
  else delete obj[o.name];
4190
4371
  return true;
4191
- } if (o.kind === 'subscript') {
4372
+ } if (o.kind === 'subscript') {
4192
4373
  const obj = this.evaluate(o.object, scope); const idx = this.evaluate(o.index, scope);
4193
4374
  if (obj instanceof NovaObject || obj instanceof NovaArray) obj.delete(idx);
4194
4375
  else if (obj instanceof Scope) obj.delete(idx);
@@ -4199,8 +4380,8 @@ stringify(v, indent = 0, seen = new WeakSet()) {
4199
4380
  }
4200
4381
  }
4201
4382
  let operand = this.evaluate(expr.operand, scope);
4202
- if (operand instanceof NovaObject) {
4203
- let overload = operand?.get?.('__operators')?.get?.('unary')?.get?.(expr.operator);
4383
+ if (operand instanceof NovaObject) {
4384
+ let overload = operand?.get?.('__operators')?.get?.('unary')?.get?.(expr.operator);
4204
4385
  if (overload) return overload();
4205
4386
  }
4206
4387
  switch (expr.operator) {