novac 2.2.0 → 2.2.2

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 (122) hide show
  1. package/LICENSE +0 -0
  2. package/README.md +0 -0
  3. package/bin/novac +6 -3
  4. package/bin/nvc +0 -0
  5. package/bin/nvml +0 -0
  6. package/demo.nv +0 -0
  7. package/demo_builtins.nv +0 -0
  8. package/demo_http.nv +0 -0
  9. package/examples/bf.nv +5 -13
  10. package/examples/math.nv +2 -2
  11. package/kits/kitffmpeg/kitdef.js +1174 -0
  12. package/kits/libos/kitdef.js +3135 -0
  13. package/kits/libtasker/kitdef.js +125 -0
  14. package/package.json +1 -1
  15. package/scripts/update-bin.js +0 -0
  16. package/src/core/executor.js +7 -4
  17. package/src/core/lexer.js +2 -2
  18. package/src/index.js +0 -0
  19. package/novac/LICENSE +0 -21
  20. package/novac/README.md +0 -1823
  21. package/novac/bin/novac +0 -950
  22. package/novac/bin/nvc +0 -522
  23. package/novac/bin/nvml +0 -542
  24. package/novac/demo.nv +0 -245
  25. package/novac/demo_builtins.nv +0 -209
  26. package/novac/demo_http.nv +0 -62
  27. package/novac/examples/bf.nv +0 -69
  28. package/novac/examples/math.nv +0 -21
  29. package/novac/kits/kitai/kitdef.js +0 -2185
  30. package/novac/kits/kitansi/kitdef.js +0 -1402
  31. package/novac/kits/kitformat/kitdef.js +0 -1485
  32. package/novac/kits/kitgps/kitdef.js +0 -1862
  33. package/novac/kits/kitlibfs/kitdef.js +0 -231
  34. package/novac/kits/kitlibproc/kitdef.js +0 -78
  35. package/novac/kits/kitmatrix/ex.js +0 -19
  36. package/novac/kits/kitmatrix/kitdef.js +0 -960
  37. package/novac/kits/kitmpatch/kitdef.js +0 -906
  38. package/novac/kits/kitnovacweb/README.md +0 -1572
  39. package/novac/kits/kitnovacweb/demo.nv +0 -12
  40. package/novac/kits/kitnovacweb/demo.nvml +0 -71
  41. package/novac/kits/kitnovacweb/index.nova +0 -12
  42. package/novac/kits/kitnovacweb/kitdef.js +0 -692
  43. package/novac/kits/kitnovacweb/nova.kit.json +0 -8
  44. package/novac/kits/kitnovacweb/nvml/executor.js +0 -739
  45. package/novac/kits/kitnovacweb/nvml/index.js +0 -67
  46. package/novac/kits/kitnovacweb/nvml/lexer.js +0 -263
  47. package/novac/kits/kitnovacweb/nvml/parser.js +0 -508
  48. package/novac/kits/kitnovacweb/nvml/renderer.js +0 -924
  49. package/novac/kits/kitparse/kitdef.js +0 -1688
  50. package/novac/kits/kitregex++/kitdef.js +0 -1353
  51. package/novac/kits/kitrequire/kitdef.js +0 -1599
  52. package/novac/kits/kitx11/kitdef.js +0 -1
  53. package/novac/kits/kitx11/kitx11.js +0 -2472
  54. package/novac/kits/kitx11/kitx11_conn.js +0 -948
  55. package/novac/kits/kitx11/kitx11_worker.js +0 -121
  56. package/novac/kits/libtea/tf.js +0 -2691
  57. package/novac/kits/libterm/ex.js +0 -285
  58. package/novac/kits/libterm/kitdef.js +0 -1927
  59. package/novac/node_modules/chalk/license +0 -9
  60. package/novac/node_modules/chalk/package.json +0 -83
  61. package/novac/node_modules/chalk/readme.md +0 -297
  62. package/novac/node_modules/chalk/source/index.d.ts +0 -325
  63. package/novac/node_modules/chalk/source/index.js +0 -225
  64. package/novac/node_modules/chalk/source/utilities.js +0 -33
  65. package/novac/node_modules/chalk/source/vendor/ansi-styles/index.d.ts +0 -236
  66. package/novac/node_modules/chalk/source/vendor/ansi-styles/index.js +0 -223
  67. package/novac/node_modules/chalk/source/vendor/supports-color/browser.d.ts +0 -1
  68. package/novac/node_modules/chalk/source/vendor/supports-color/browser.js +0 -34
  69. package/novac/node_modules/chalk/source/vendor/supports-color/index.d.ts +0 -55
  70. package/novac/node_modules/chalk/source/vendor/supports-color/index.js +0 -190
  71. package/novac/node_modules/commander/LICENSE +0 -22
  72. package/novac/node_modules/commander/Readme.md +0 -1176
  73. package/novac/node_modules/commander/esm.mjs +0 -16
  74. package/novac/node_modules/commander/index.js +0 -24
  75. package/novac/node_modules/commander/lib/argument.js +0 -150
  76. package/novac/node_modules/commander/lib/command.js +0 -2777
  77. package/novac/node_modules/commander/lib/error.js +0 -39
  78. package/novac/node_modules/commander/lib/help.js +0 -747
  79. package/novac/node_modules/commander/lib/option.js +0 -380
  80. package/novac/node_modules/commander/lib/suggestSimilar.js +0 -101
  81. package/novac/node_modules/commander/package-support.json +0 -19
  82. package/novac/node_modules/commander/package.json +0 -82
  83. package/novac/node_modules/commander/typings/esm.d.mts +0 -3
  84. package/novac/node_modules/commander/typings/index.d.ts +0 -1113
  85. package/novac/node_modules/node-addon-api/LICENSE.md +0 -9
  86. package/novac/node_modules/node-addon-api/README.md +0 -95
  87. package/novac/node_modules/node-addon-api/common.gypi +0 -21
  88. package/novac/node_modules/node-addon-api/except.gypi +0 -25
  89. package/novac/node_modules/node-addon-api/index.js +0 -14
  90. package/novac/node_modules/node-addon-api/napi-inl.deprecated.h +0 -186
  91. package/novac/node_modules/node-addon-api/napi-inl.h +0 -7165
  92. package/novac/node_modules/node-addon-api/napi.h +0 -3364
  93. package/novac/node_modules/node-addon-api/node_addon_api.gyp +0 -42
  94. package/novac/node_modules/node-addon-api/node_api.gyp +0 -9
  95. package/novac/node_modules/node-addon-api/noexcept.gypi +0 -26
  96. package/novac/node_modules/node-addon-api/nothing.c +0 -0
  97. package/novac/node_modules/node-addon-api/package-support.json +0 -21
  98. package/novac/node_modules/node-addon-api/package.json +0 -480
  99. package/novac/node_modules/node-addon-api/tools/README.md +0 -73
  100. package/novac/node_modules/node-addon-api/tools/check-napi.js +0 -99
  101. package/novac/node_modules/node-addon-api/tools/clang-format.js +0 -71
  102. package/novac/node_modules/node-addon-api/tools/conversion.js +0 -301
  103. package/novac/node_modules/serialize-javascript/LICENSE +0 -27
  104. package/novac/node_modules/serialize-javascript/README.md +0 -149
  105. package/novac/node_modules/serialize-javascript/index.js +0 -297
  106. package/novac/node_modules/serialize-javascript/package.json +0 -33
  107. package/novac/package.json +0 -27
  108. package/novac/scripts/update-bin.js +0 -24
  109. package/novac/src/core/bstd.js +0 -1035
  110. package/novac/src/core/config.js +0 -155
  111. package/novac/src/core/describe.js +0 -187
  112. package/novac/src/core/emitter.js +0 -499
  113. package/novac/src/core/error.js +0 -86
  114. package/novac/src/core/executor.js +0 -5606
  115. package/novac/src/core/formatter.js +0 -686
  116. package/novac/src/core/lexer.js +0 -1026
  117. package/novac/src/core/nova_builtins.js +0 -717
  118. package/novac/src/core/nova_thread_worker.js +0 -166
  119. package/novac/src/core/parser.js +0 -2181
  120. package/novac/src/core/types.js +0 -112
  121. package/novac/src/index.js +0 -28
  122. package/novac/src/runtime/stdlib.js +0 -244
@@ -1,739 +0,0 @@
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 };