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
@@ -0,0 +1,739 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * NVML Executor v2
5
+ *
6
+ * Walks the AST and builds a live NvmlDocument.
7
+ * New in v2:
8
+ * - @state → reactive signal store
9
+ * - @computed → derived signals (Nova code evaluated server-side)
10
+ * - @effect → side-effect declarations (run client-side on signal change)
11
+ * - @component → named reusable component definitions
12
+ * - @route → client-side route definitions
13
+ * - @slot → slot content definitions
14
+ * - @use → declare component imports for a page
15
+ * - bindings → one-way (->) and two-way (<->) signal bindings on elements
16
+ * - transitions → CSS transition hints (~) on elements
17
+ * - each_block → @each signal as item [ ... ] — reactive list rendering
18
+ * - conditional → ? signal {element} — conditional rendering
19
+ * - slot_outlet → @slot name inside a component — slot insertion point
20
+ */
21
+
22
+ class ExecError extends Error {
23
+ constructor(msg) { super(`[NVML Executor] ${msg}`); this.name = 'ExecError'; }
24
+ }
25
+
26
+ // ── Document model ────────────────────────────────────────────────────
27
+
28
+ class NvmlElement {
29
+ constructor(tag, id) {
30
+ this.tag = tag;
31
+ this.id = id;
32
+ this.textValue = null;
33
+ this.props = {};
34
+ this.ss = null; // scoped CSS string
35
+ this.code = null; // script code
36
+ this.children = [];
37
+ this.extraProps = {};
38
+ this.bindings = []; // [{ prop, signal, twoWay }]
39
+ this.transitions = []; // [{ prop, name }]
40
+ this.cond = null; // signal name — only render if truthy
41
+ this.eachSignal = null; // for each_block wrapper
42
+ this.eachItemVar = null;
43
+ this.isSlotOutlet = false;
44
+ this.slotName = null;
45
+ }
46
+ }
47
+
48
+ class NvmlComponent {
49
+ constructor(name, body) {
50
+ this.name = name;
51
+ this.body = body; // raw AST body — instantiated on use
52
+ }
53
+ }
54
+
55
+ /**
56
+ * NvmlLangDef — a user-defined language block registered via @lang
57
+ *
58
+ * @lang {name} [
59
+ * runtime_language="nv" // "nv" | "js" | "nodejs"
60
+ * config="" // JSON string or path to config.json
61
+ * src="PATH" // path to implementation file
62
+ * code=(...) // inline implementation code
63
+ * ]
64
+ *
65
+ * Scope rules:
66
+ * runtime_language="nv" → server-side Nova (same as scope='server' lang='nv')
67
+ * runtime_language="nodejs" → server-side Node.js (new built-in)
68
+ * runtime_language="js" → client-side JavaScript (embedded in page)
69
+ */
70
+ class NvmlLangDef {
71
+ constructor(name, opts = {}) {
72
+ this.name = name;
73
+ this.runtimeLanguage = opts.runtime_language || opts.runtimeLanguage || 'js';
74
+ this.config = opts.config || '';
75
+ this.src = opts.src || null;
76
+ this.code = opts.code || '';
77
+ this.scope = this._deriveScope();
78
+ }
79
+
80
+ _deriveScope() {
81
+ switch (this.runtimeLanguage) {
82
+ case 'nv':
83
+ case 'nova':
84
+ case 'novac': return 'server-nova';
85
+ case 'nodejs':
86
+ case 'node': return 'server-node';
87
+ case 'js':
88
+ default: return 'client';
89
+ }
90
+ }
91
+ }
92
+
93
+ class NvmlDocument {
94
+ constructor() {
95
+ this.config = {}; // @config key→value
96
+ this.visual = []; // top-level NvmlElement[]
97
+ this.globalStyles = ''; // @ss content
98
+ this.state = {}; // @state signal name→initial value
99
+ this.computed = []; // [{ name, code }]
100
+ this.effects = []; // [{ deps, code }]
101
+ this.components = {}; // name→NvmlComponent
102
+ this.routes = []; // [{ path, body }]
103
+ this.slots = {}; // name→NvmlElement[]
104
+ this.usedComponents= []; // component names declared via @use
105
+ this.langs = {}; // name→NvmlLangDef (user-defined language extensions)
106
+ this._idCounter = 0;
107
+ }
108
+
109
+ _nextId() { return '_nvml_' + (this._idCounter++); }
110
+
111
+ setConfig(key, value) { this.config[key] = value; }
112
+ set(id, content) { this._mutateElement(id, el => { el.textValue = content; }); }
113
+ get(id) { return this._findElement(id)?.textValue ?? null; }
114
+ setProp(id, key, value) { this._mutateElement(id, el => { el.props[key] = value; }); }
115
+ getProp(id, key) { return this._findElement(id)?.props[key] ?? null; }
116
+ addClass(id, cls) { this._mutateElement(id, el => { el.props.class = ((el.props.class || '') + ' ' + cls).trim(); }); }
117
+ setClass(id, cls) { this._mutateElement(id, el => { el.props.class = cls; }); }
118
+ addStyle(css) { this.globalStyles += '\n' + css; }
119
+ addElementStyle(id, css) { this._mutateElement(id, el => { el.ss = (el.ss || '') + '\n' + css; }); }
120
+ hide(id) { this._mutateElement(id, el => { el.props.style = ((el.props.style || '') + '; display:none').replace(/^;/, ''); }); }
121
+ show(id, display = 'block') { this._mutateElement(id, el => { el.props.style = ((el.props.style || '') + `; display:${display}`).replace(/^;/, ''); }); }
122
+ setTitle(t) { this.config.title = t; }
123
+ setMeta(k, v) { this.config[k] = v; }
124
+ alert(msg) { /* no-op at render time */ }
125
+ setHTML(id, html) { this._mutateElement(id, el => { el._rawHTML = html; }); }
126
+
127
+ _findElement(id) {
128
+ const search = (els) => {
129
+ for (const el of els) {
130
+ if (el.props?.id === id || el.id === id) return el;
131
+ if (el.children) { const r = search(el.children); if (r) return r; }
132
+ }
133
+ return null;
134
+ };
135
+ return search(this.visual);
136
+ }
137
+
138
+ _mutateElement(id, fn) {
139
+ const el = this._findElement(id);
140
+ if (el) fn(el);
141
+ }
142
+ }
143
+
144
+ // ── Executor ──────────────────────────────────────────────────────────
145
+
146
+ class Executor {
147
+ constructor(novaRunner) {
148
+ this.novaRunner = novaRunner;
149
+ this.doc = null;
150
+ }
151
+
152
+ execute(ast, existingDoc) {
153
+ this.doc = existingDoc || new NvmlDocument();
154
+ for (const node of ast.body) this._execNode(node, null);
155
+ return this.doc;
156
+ }
157
+
158
+ _execNode(node, parentElement) {
159
+ switch (node.kind) {
160
+ case 'at_config': return this._execConfig(node);
161
+ case 'at_state': return this._execState(node);
162
+ case 'at_computed': return this._execComputed(node);
163
+ case 'at_effect': return this._execEffect(node);
164
+ case 'at_component': return this._execComponent(node);
165
+ case 'at_route': return this._execRoute(node);
166
+ case 'at_slot': return this._execSlot(node);
167
+ case 'at_use': return this._execUse(node);
168
+ case 'at_lang': return this._execLang(node);
169
+ case 'at_block': return this._execAtBlock(node, parentElement);
170
+ case 'element': return this._execElement(node, parentElement);
171
+ case 'each_block': return this._execEachBlock(node, parentElement);
172
+ case 'text_node': return this._execTextNode(node, parentElement);
173
+ case 'parent_prop': return this._execParentProp(node, parentElement);
174
+ case 'slot_outlet': return this._execSlotOutlet(node, parentElement);
175
+ case 'component_use': return this._execComponentUse(node, parentElement);
176
+ case 'signal_ref': return; // resolved at render time
177
+ case 'css_decl': return;
178
+ default:
179
+ throw new ExecError(`Unknown AST node kind: ${node.kind}`);
180
+ }
181
+ }
182
+
183
+ // ── @config ──────────────────────────────────────────────────────
184
+
185
+ _execConfig(node) {
186
+ for (const [key, valNode] of Object.entries(node.pairs)) {
187
+ this.doc.config[key] = this._resolveValue(valNode);
188
+ }
189
+ }
190
+
191
+ // ── @state ───────────────────────────────────────────────────────
192
+
193
+ _execState(node) {
194
+ for (const [key, valNode] of Object.entries(node.pairs)) {
195
+ this.doc.state[key] = this._resolveValue(valNode);
196
+ }
197
+ }
198
+
199
+ // ── @computed ────────────────────────────────────────────────────
200
+
201
+ _execComputed(node) {
202
+ for (const entry of node.entries) {
203
+ const code = this._resolveValue(entry.code);
204
+ let value = null;
205
+ // Run the Nova code server-side if possible to get an initial value
206
+ if (this.novaRunner && code) {
207
+ try { value = this.novaRunner(code, this.doc); } catch (e) { value = null; }
208
+ }
209
+ this.doc.computed.push({ name: entry.name, code, initialValue: value });
210
+ }
211
+ }
212
+
213
+ // ── @effect ──────────────────────────────────────────────────────
214
+
215
+ _execEffect(node) {
216
+ for (const entry of node.entries) {
217
+ this.doc.effects.push({
218
+ deps: entry.deps,
219
+ code: this._resolveValue(entry.code),
220
+ });
221
+ }
222
+ }
223
+
224
+ // ── @component ───────────────────────────────────────────────────
225
+
226
+ _execComponent(node) {
227
+ this.doc.components[node.name] = new NvmlComponent(node.name, node.body);
228
+ }
229
+
230
+ // ── @route ───────────────────────────────────────────────────────
231
+
232
+ _execRoute(node) {
233
+ this.doc.routes.push({ path: this._resolveValue(node.arg || node.path), body: node.body });
234
+ }
235
+
236
+ // ── @slot ────────────────────────────────────────────────────────
237
+
238
+ _execSlot(node) {
239
+ const els = [];
240
+ for (const child of node.body) {
241
+ const el = this._buildElement(child, null);
242
+ if (el) els.push(el);
243
+ }
244
+ this.doc.slots[node.name] = els;
245
+ }
246
+
247
+ // ── @lang ─────────────────────────────────────────────────────────
248
+
249
+ _execLang(node) {
250
+ const opts = {};
251
+ for (const [key, valNode] of Object.entries(node.pairs || {})) {
252
+ opts[key] = this._resolveValue(valNode);
253
+ }
254
+ // code may come from inline code= or src= file
255
+ if (!opts.code && opts.src) {
256
+ try {
257
+ const fs = require('fs');
258
+ const path = require('path');
259
+ opts.code = fs.readFileSync(path.resolve(process.cwd(), opts.src), 'utf8');
260
+ } catch (e) {
261
+ process.stderr.write(`[NVML @lang ${node.name}] Cannot read src: ${e.message}\n`);
262
+ }
263
+ }
264
+ // load JSON config if it's a path string
265
+ if (opts.config && !opts.config.trim().startsWith('{')) {
266
+ try {
267
+ const fs = require('fs');
268
+ const path = require('path');
269
+ opts.config = fs.readFileSync(path.resolve(process.cwd(), opts.config), 'utf8');
270
+ } catch (e) {
271
+ process.stderr.write(`[NVML @lang ${node.name}] Cannot read config file: ${e.message}\n`);
272
+ }
273
+ }
274
+ this.doc.langs[node.name] = new NvmlLangDef(node.name, opts);
275
+ }
276
+
277
+ _execUse(node) {
278
+ for (const name of node.names) {
279
+ if (!this.doc.usedComponents.includes(name)) this.doc.usedComponents.push(name);
280
+ }
281
+ }
282
+
283
+ // ── @visual / @ss / custom ───────────────────────────────────────
284
+
285
+ _execAtBlock(node, parentElement) {
286
+ if (node.name === 'ss') {
287
+ const css = node.body.map(c => this._nodeToCSS(c)).join('\n');
288
+ if (parentElement) parentElement.ss = (parentElement.ss || '') + css;
289
+ else this.doc.globalStyles += css;
290
+ return;
291
+ }
292
+ if (node.name === 'visual') {
293
+ for (const child of node.body) this._execNode(child, null);
294
+ return;
295
+ }
296
+ // each inside element body
297
+ if (node.name === 'each') {
298
+ // handled by parseEachBlock
299
+ return;
300
+ }
301
+ // unknown @block → store in doc.config
302
+ const section = [];
303
+ for (const child of node.body) {
304
+ const el = this._buildElement(child, null);
305
+ if (el) section.push(el);
306
+ }
307
+ this.doc.config[node.name] = section;
308
+ }
309
+
310
+ // ── element ───────────────────────────────────────────────────────
311
+
312
+ _execElement(node, parentElement) {
313
+ const el = this._buildElement(node, parentElement);
314
+ if (!el) return;
315
+ if (parentElement) parentElement.children.push(el);
316
+ else this.doc.visual.push(el);
317
+ }
318
+
319
+ _buildElement(node, parentElement) {
320
+ if (!node) return null;
321
+
322
+ if (node.kind === 'text_node') {
323
+ if (parentElement) parentElement.textValue = (parentElement.textValue || '') + node.value;
324
+ return null;
325
+ }
326
+ if (node.kind === 'signal_ref') {
327
+ // Inline signal reference: bind text content to signal
328
+ if (parentElement) parentElement._signalText = node.name || node.value;
329
+ return null;
330
+ }
331
+ if (node.kind === 'each_block') {
332
+ const wrapper = new NvmlElement('each-block', this.doc._nextId());
333
+ wrapper.eachSignal = node.signal;
334
+ wrapper.eachItemVar = node.itemVar;
335
+ wrapper.eachIndexVar = node.indexVar;
336
+ wrapper._eachBody = node.body; // raw AST — rendered per-item at client
337
+ if (parentElement) parentElement.children.push(wrapper);
338
+ else this.doc.visual.push(wrapper);
339
+ return null;
340
+ }
341
+ if (node.kind === 'slot_outlet') {
342
+ const outlet = new NvmlElement('slot-outlet', this.doc._nextId());
343
+ outlet.isSlotOutlet = true;
344
+ outlet.slotName = node.name || 'default';
345
+ if (parentElement) parentElement.children.push(outlet);
346
+ return null;
347
+ }
348
+ if (node.kind !== 'element') return null;
349
+
350
+ // resolve tag
351
+ let tag = node.tag;
352
+ if (tag.startsWith('element.')) tag = tag.slice('element.'.length);
353
+ else if (tag.includes('.')) tag = tag.split('.').pop();
354
+ if (tag === '..' || tag === '') return null;
355
+
356
+ // Check if this tag is a component name
357
+ if (this.doc.components[tag] || this.doc.usedComponents.includes(tag)) {
358
+ return this._instantiateComponent(tag, node, parentElement);
359
+ }
360
+
361
+ const el = new NvmlElement(tag, this.doc._nextId());
362
+
363
+ if (node.textValue !== null && node.textValue !== undefined) {
364
+ const tv = node.textValue;
365
+ if (tv && tv.kind === 'signal_ref') el._signalText = tv.name || tv.value;
366
+ else el.textValue = this._resolveValue(tv);
367
+ }
368
+
369
+ // conditional render
370
+ if (node.cond) el.cond = node.cond;
371
+
372
+ // bindings
373
+ el.bindings = (node.bindings || []).map(b => ({ prop: b.prop, signal: b.signal, twoWay: b.twoWay }));
374
+ el.transitions = (node.transitions|| []).map(t => ({ prop: t.prop, name: t.name }));
375
+
376
+ // props
377
+ for (const [key, valNode] of Object.entries(node.props || {})) {
378
+ if (valNode && valNode.__subBody !== undefined) {
379
+ if (key === 'ss') {
380
+ el.ss = valNode.__subBody.map(c => this._nodeToCSS(c)).filter(Boolean).join('\n');
381
+ } else if (key === 'code') {
382
+ el.code = valNode.__subBody.map(c => c.kind === 'text_node' ? c.value : '').join('\n');
383
+ } else {
384
+ el.extraProps[key] = valNode.__subBody;
385
+ }
386
+ } else if (valNode && valNode.kind === 'signal_ref') {
387
+ // prop -> signal binding via value shorthand
388
+ el.bindings.push({ prop: key, signal: valNode.name || valNode.value, twoWay: false });
389
+ } else {
390
+ const resolved = this._resolveValue(valNode);
391
+ if (key === 'ss') el.ss = resolved;
392
+ else if (key === 'code') el.code = resolved;
393
+ else el.props[key] = resolved;
394
+ }
395
+ }
396
+
397
+ // body children
398
+ for (const child of (node.body || [])) {
399
+ if (child.kind === 'text_node') {
400
+ el.textValue = (el.textValue || '') + child.value;
401
+ } else if (child.kind === 'parent_prop') {
402
+ this._applyParentProp(child, el);
403
+ } else if (child.kind === 'signal_ref') {
404
+ el._signalText = child.name || child.value;
405
+ } else {
406
+ this._execNode(child, el);
407
+ }
408
+ }
409
+
410
+ // script handling
411
+ if (tag === 'script') this._handleScript(el);
412
+
413
+ return el;
414
+ }
415
+
416
+ // ── component instantiation ───────────────────────────────────────
417
+
418
+ _instantiateComponent(name, node, parentElement) {
419
+ const comp = this.doc.components[name];
420
+ if (!comp) {
421
+ // External component (registered via @use but defined elsewhere) — emit a placeholder
422
+ const placeholder = new NvmlElement('div', this.doc._nextId());
423
+ placeholder.props['data-component'] = name;
424
+ placeholder.props['data-props'] = JSON.stringify(
425
+ Object.fromEntries(Object.entries(node.props || {}).map(([k,v]) => [k, this._resolveValue(v)]))
426
+ );
427
+ // pass bindings as data attrs
428
+ for (const b of (node.bindings || [])) {
429
+ placeholder.props[`data-bind-${b.prop}`] = b.signal + (b.twoWay ? ':2way' : '');
430
+ }
431
+ if (parentElement) parentElement.children.push(placeholder);
432
+ return null;
433
+ }
434
+ // Inline component: instantiate its body with prop overrides
435
+ const compRoot = new NvmlElement('div', this.doc._nextId());
436
+ compRoot.props['data-component'] = name;
437
+ for (const child of comp.body) this._execNode(child, compRoot);
438
+ if (parentElement) parentElement.children.push(compRoot);
439
+ return null;
440
+ }
441
+
442
+ // ── @each block ───────────────────────────────────────────────────
443
+
444
+ _execEachBlock(node, parentElement) {
445
+ const wrapper = new NvmlElement('each-block', this.doc._nextId());
446
+ wrapper.eachSignal = node.signal;
447
+ wrapper.eachItemVar = node.itemVar;
448
+ wrapper.eachIndexVar = node.indexVar;
449
+ wrapper._eachBody = node.body;
450
+ if (parentElement) parentElement.children.push(wrapper);
451
+ else this.doc.visual.push(wrapper);
452
+ }
453
+
454
+ // ── slot outlet ───────────────────────────────────────────────────
455
+
456
+ _execSlotOutlet(node, parentElement) {
457
+ const outlet = new NvmlElement('slot-outlet', this.doc._nextId());
458
+ outlet.isSlotOutlet = true;
459
+ outlet.slotName = node.name || 'default';
460
+ if (parentElement) parentElement.children.push(outlet);
461
+ else this.doc.visual.push(outlet);
462
+ }
463
+
464
+ // ── [..] prop ────────────────────────────────────────────────────
465
+
466
+ _applyParentProp(node, el) {
467
+ const prop = node.prop;
468
+ if (node.subBody !== null && node.subBody !== undefined) {
469
+ if (prop === 'ss') {
470
+ el.ss = (el.ss || '') + node.subBody.map(c => this._nodeToCSS(c)).join('\n');
471
+ } else if (prop === 'code') {
472
+ el.code = node.subBody.map(c => c.kind === 'text_node' ? c.value : '').join('\n');
473
+ } else {
474
+ el.extraProps[prop] = node.subBody;
475
+ }
476
+ } else {
477
+ const resolved = node.value !== null ? this._resolveValue(node.value) : true;
478
+ if (prop === 'ss') el.ss = resolved;
479
+ else if (prop === 'code') el.code = resolved;
480
+ else el.extraProps[prop] = resolved;
481
+ }
482
+ }
483
+
484
+ _execParentProp(node, parentElement) {
485
+ if (!parentElement) return;
486
+ this._applyParentProp(node, parentElement);
487
+ }
488
+
489
+ _execTextNode(node, parentElement) {
490
+ if (parentElement) parentElement.textValue = (parentElement.textValue || '') + node.value;
491
+ }
492
+
493
+ // ── script handling ───────────────────────────────────────────────
494
+
495
+ _handleScript(el) {
496
+ const lang = el.props.language || el.props.lang || 'js';
497
+ const scope = el.props.scope || 'client';
498
+ const code = el.code || el.textValue || '';
499
+ const trigger = el.props.trigger || null;
500
+
501
+ el._scriptLang = lang;
502
+ el._scriptScope = scope;
503
+ el._scriptCode = code;
504
+ el._ranOnServer = false;
505
+
506
+ // ── Nova server-side (scope=server, no trigger) ──────────────
507
+ if ((lang === 'novac' || lang === 'nv') && scope === 'server' && !trigger && code.trim()) {
508
+ if (this.novaRunner) {
509
+ el._ranOnServer = true;
510
+ try { this.novaRunner(code, this.doc); }
511
+ catch (e) { process.stderr.write(`[NVML Executor] Server script error: ${e.message}\n`); }
512
+ } else {
513
+ process.stderr.write('[NVML Executor] Warning: server-side Nova script found but no novaRunner provided.\n');
514
+ }
515
+ return;
516
+ }
517
+
518
+ // ── Node.js server-side (lang=nodejs, no trigger) ─────────────
519
+ if ((lang === 'nodejs' || lang === 'node') && !trigger && code.trim()) {
520
+ el._ranOnServer = true;
521
+ try { this._runNodejsScript(code, el); }
522
+ catch (e) { process.stderr.write(`[NVML Executor] nodejs script error: ${e.message}\n`); }
523
+ return;
524
+ }
525
+
526
+ // ── User-defined language (registered via @lang) ──────────────
527
+ const langDef = this.doc.langs[lang];
528
+ if (langDef && !trigger && code.trim()) {
529
+ if (langDef.scope === 'server-nova' && this.novaRunner) {
530
+ el._ranOnServer = true;
531
+ // Wrap: call the lang's implementation code first, then user code
532
+ const wrapped = langDef.code
533
+ ? langDef.code + '\n' + code
534
+ : code;
535
+ try { this.novaRunner(wrapped, this.doc); }
536
+ catch (e) { process.stderr.write(`[NVML Executor] @lang ${lang} (nv) error: ${e.message}\n`); }
537
+ } else if (langDef.scope === 'server-node') {
538
+ el._ranOnServer = true;
539
+ try { this._runNodejsScript((langDef.code ? langDef.code + '\n' : '') + code, el); }
540
+ catch (e) { process.stderr.write(`[NVML Executor] @lang ${lang} (nodejs) error: ${e.message}\n`); }
541
+ }
542
+ // client-scoped custom langs: renderer handles embedding
543
+ return;
544
+ }
545
+ }
546
+
547
+ // ── Node.js script runner ────────────────────────────────────────
548
+ // Runs code in Node's VM with document proxy + bf object in scope.
549
+ _runNodejsScript(code, el) {
550
+ const vm = require('vm');
551
+ const doc = this.doc;
552
+
553
+ // Build the document proxy (same surface as Nova runner)
554
+ const documentProxy = this._makeNodejsDocumentProxy(doc);
555
+
556
+ const ctx = vm.createContext({
557
+ document: documentProxy,
558
+ bf: makeBfObject(),
559
+ console,
560
+ require,
561
+ process,
562
+ __filename: '',
563
+ __dirname: process.cwd(),
564
+ });
565
+
566
+ vm.runInContext(code, ctx, { timeout: 5000 });
567
+ }
568
+
569
+ _makeNodejsDocumentProxy(doc) {
570
+ const findEl = (id) => {
571
+ const search = (els) => {
572
+ for (const el of els) {
573
+ if (el.props?.id === id || el.id === id) return el;
574
+ if (el.children) { const r = search(el.children); if (r) return r; }
575
+ }
576
+ return null;
577
+ };
578
+ return search(doc.visual);
579
+ };
580
+ return {
581
+ setConfig: (k, v) => { doc.config[k] = v; },
582
+ setTitle: (t) => { doc.config.title = t; },
583
+ setMeta: (k, v) => { doc.config[k] = v; },
584
+ getConfig: (k) => doc.config[k],
585
+ set: (id, val) => { const el = findEl(id); if (el) el.textValue = String(val); },
586
+ get: (id) => { const el = findEl(id); return el ? (el.textValue || null) : null; },
587
+ setHTML: (id, html)=> { const el = findEl(id); if (el) el._rawHTML = html; },
588
+ setProp: (id, k, v)=> { const el = findEl(id); if (el) el.props[k] = String(v); },
589
+ getProp: (id, k) => { const el = findEl(id); return el ? (el.props[k] || null) : null; },
590
+ addClass: (id, cls) => { const el = findEl(id); if (!el) return; el.props.class = ((el.props.class||'')+' '+cls).trim(); },
591
+ setClass: (id, cls) => { const el = findEl(id); if (el) el.props.class = cls; },
592
+ addStyle: (css) => { doc.globalStyles += '\n' + css; },
593
+ addElementStyle:(id, css) => { const el = findEl(id); if (el) el.ss = (el.ss||'')+'\n'+css; },
594
+ hide: (id) => { const el = findEl(id); if (el) el.props.style = ((el.props.style||'')+';display:none').replace(/^;/,''); },
595
+ show: (id, d) => { const el = findEl(id); if (el) el.props.style = ((el.props.style||'')+`;display:${d||'block'}`).replace(/^;/,''); },
596
+ setSignal: (n, v) => { doc.state[n] = v; },
597
+ getSignal: (n) => doc.state[n],
598
+ alert: (_) => {},
599
+ config: doc.config,
600
+ _doc: doc,
601
+ };
602
+ }
603
+
604
+ // ── CSS serializer ────────────────────────────────────────────────
605
+
606
+ _nodeToCSS(node) {
607
+ if (!node) return '';
608
+ if (node.kind === 'text_node') return node.value;
609
+ if (node.kind === 'css_decl') return `${node.key}: ${this._resolveValue(node.value)};`;
610
+ if (node.kind === 'element') {
611
+ const tv = node.textValue;
612
+ if ((tv !== null && tv !== undefined) &&
613
+ Object.keys(node.props || {}).length === 0 &&
614
+ (node.body || []).length === 0) {
615
+ const propVal = typeof tv === 'object' ? this._resolveValue(tv) : tv;
616
+ return ` ${node.tag}: ${propVal};`;
617
+ }
618
+ const sel = node.tag;
619
+ const decls = Object.entries(node.props || {}).map(([k,v]) => ` ${k}: ${this._resolveValue(v)};`).join('\n');
620
+ const nested= (node.body || []).map(c => this._nodeToCSS(c)).filter(Boolean).join('\n');
621
+ return `${sel} {\n${decls}${nested ? '\n' + nested : ''}\n}`;
622
+ }
623
+ return '';
624
+ }
625
+
626
+ // ── value resolver ────────────────────────────────────────────────
627
+
628
+ _resolveValue(valNode) {
629
+ if (!valNode) return undefined;
630
+ if (valNode.kind === 'value') return valNode.value;
631
+ if (valNode.kind === 'pipe_list') return valNode.items;
632
+ if (valNode.kind === 'signal_ref') return `__sig:${valNode.name || valNode.value}`;
633
+ return undefined;
634
+ }
635
+ }
636
+
637
+ // ── Brainfuck object ──────────────────────────────────────────────────
638
+ //
639
+ // Available as `bf` in every language scope (server and client).
640
+ //
641
+ // Server-side (Nova / nodejs scripts): a live JS object.
642
+ // Client-side: serialised to the page as window.__nvml_bf and also
643
+ // injected into the custom-language JS wrapper.
644
+ //
645
+ // Properties / methods:
646
+ // bf.tape — Uint8Array(30000) — the cell tape
647
+ // bf.pointer — current data pointer (integer)
648
+ // bf.output — string accumulated by '.' instructions
649
+ // bf.input — string used as the input buffer for ',' instructions
650
+ // bf.run(code) — run a BF program; returns the output string
651
+ // bf.reset() — clear tape, pointer, I/O buffers
652
+ // bf.cell(n?) — get (or with value: set) the value of cell n (default: current cell)
653
+
654
+ function makeBfObject(opts = {}) {
655
+ const TAPE_SIZE = opts.tapeSize || 30000;
656
+ let tape = new Uint8Array(TAPE_SIZE);
657
+ let ptr = 0;
658
+ let output = '';
659
+ let input = opts.input || '';
660
+ let inPtr = 0;
661
+
662
+ const obj = {
663
+ get tape() { return tape; },
664
+ get pointer() { return ptr; },
665
+ get output() { return output; },
666
+ get input() { return input; },
667
+ set input(v) { input = String(v); inPtr = 0; },
668
+
669
+ cell(n, v) {
670
+ const idx = (n === undefined) ? ptr : Number(n);
671
+ if (v !== undefined) tape[idx] = Number(v) & 0xFF;
672
+ return tape[idx];
673
+ },
674
+
675
+ reset() {
676
+ tape = new Uint8Array(TAPE_SIZE);
677
+ ptr = 0;
678
+ output = '';
679
+ input = '';
680
+ inPtr = 0;
681
+ return obj;
682
+ },
683
+
684
+ /**
685
+ * run(code, inputStr?) → output string
686
+ * Executes BF source. Stops on EOF (',') if input buffer is exhausted.
687
+ * Cell values wrap at 255/0 (unsigned byte).
688
+ */
689
+ run(code, inputStr) {
690
+ if (inputStr !== undefined) { input = String(inputStr); inPtr = 0; }
691
+ const src = String(code).split('').filter(c => '><+-.,[]'.includes(c));
692
+ let ip = 0;
693
+ const MAX_OPS = opts.maxOps || 10_000_000;
694
+ let ops = 0;
695
+
696
+ // pre-build bracket map for O(1) jump
697
+ const brackets = {};
698
+ const stack = [];
699
+ for (let i = 0; i < src.length; i++) {
700
+ if (src[i] === '[') { stack.push(i); }
701
+ else if (src[i] === ']') {
702
+ const open = stack.pop();
703
+ if (open === undefined) throw new Error(`[bf] Unmatched ']' at ${i}`);
704
+ brackets[open] = i;
705
+ brackets[i] = open;
706
+ }
707
+ }
708
+ if (stack.length) throw new Error(`[bf] Unmatched '[' at ${stack[0]}`);
709
+
710
+ while (ip < src.length) {
711
+ if (++ops > MAX_OPS) throw new Error('[bf] Maximum operations exceeded');
712
+ const cmd = src[ip];
713
+ switch (cmd) {
714
+ case '>': ptr = (ptr + 1) % TAPE_SIZE; break;
715
+ case '<': ptr = (ptr - 1 + TAPE_SIZE) % TAPE_SIZE; break;
716
+ case '+': tape[ptr] = (tape[ptr] + 1) & 0xFF; break;
717
+ case '-': tape[ptr] = (tape[ptr] - 1 + 256) & 0xFF; break;
718
+ case '.': output += String.fromCharCode(tape[ptr]); break;
719
+ case ',': {
720
+ if (inPtr < input.length) {
721
+ tape[ptr] = input.charCodeAt(inPtr++) & 0xFF;
722
+ } else {
723
+ tape[ptr] = 0; // EOF → 0
724
+ }
725
+ break;
726
+ }
727
+ case '[': if (tape[ptr] === 0) ip = brackets[ip]; break;
728
+ case ']': if (tape[ptr] !== 0) ip = brackets[ip]; break;
729
+ }
730
+ ip++;
731
+ }
732
+ return output;
733
+ },
734
+ };
735
+
736
+ return obj;
737
+ }
738
+
739
+ module.exports = { Executor, NvmlDocument, NvmlElement, NvmlComponent, NvmlLangDef, ExecError, makeBfObject };