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.
- package/LICENSE +1 -1
- package/README.md +0 -0
- package/demo.nv +0 -0
- package/demo_builtins.nv +0 -0
- package/demo_http.nv +0 -0
- package/examples/bf.nv +69 -0
- package/examples/math.nv +21 -0
- package/kits/birdAPI/kitdef.js +954 -0
- package/kits/kitRNG/kitdef.js +740 -0
- package/kits/kitSSH/kitdef.js +1272 -0
- package/kits/kitadb/kitdef.js +606 -0
- package/kits/kitai/kitdef.js +2185 -0
- package/kits/kitcanvas/kitdef.js +914 -0
- package/kits/kitclippy/kitdef.js +925 -0
- package/kits/kitgps/kitdef.js +1862 -0
- package/kits/kitlibproc/kitdef.js +3 -2
- package/kits/kitmorse/kitdef.js +229 -0
- package/kits/kitmpatch/kitdef.js +906 -0
- package/kits/kitnet/kitdef.js +1401 -0
- package/kits/kitproto/kitdef.js +613 -0
- package/kits/kitqr/kitdef.js +637 -0
- package/kits/kitrequire/kitdef.js +1599 -0
- package/kits/libtea/kitdef.js +2691 -0
- package/kits/libterm/kitdef.js +2 -0
- package/novac/LICENSE +21 -0
- package/novac/README.md +1823 -0
- package/novac/bin/novac +950 -0
- package/novac/bin/nvc +522 -0
- package/novac/bin/nvml +542 -0
- package/novac/demo.nv +245 -0
- package/novac/demo_builtins.nv +209 -0
- package/novac/demo_http.nv +62 -0
- package/novac/examples/bf.nv +69 -0
- package/novac/examples/math.nv +21 -0
- package/novac/kits/kitai/kitdef.js +2185 -0
- package/novac/kits/kitansi/kitdef.js +1402 -0
- package/novac/kits/kitformat/kitdef.js +1485 -0
- package/novac/kits/kitgps/kitdef.js +1862 -0
- package/novac/kits/kitlibfs/kitdef.js +231 -0
- package/{examples/example-project/nova_modules → novac/kits}/kitlibproc/kitdef.js +3 -2
- package/novac/kits/kitmatrix/ex.js +19 -0
- package/novac/kits/kitmatrix/kitdef.js +960 -0
- package/novac/kits/kitmpatch/kitdef.js +906 -0
- package/novac/kits/kitnovacweb/README.md +1572 -0
- package/novac/kits/kitnovacweb/demo.nv +12 -0
- package/novac/kits/kitnovacweb/demo.nvml +71 -0
- package/novac/kits/kitnovacweb/index.nova +12 -0
- package/novac/kits/kitnovacweb/kitdef.js +692 -0
- package/novac/kits/kitnovacweb/nova.kit.json +8 -0
- package/novac/kits/kitnovacweb/nvml/executor.js +739 -0
- package/novac/kits/kitnovacweb/nvml/index.js +67 -0
- package/novac/kits/kitnovacweb/nvml/lexer.js +263 -0
- package/novac/kits/kitnovacweb/nvml/parser.js +508 -0
- package/novac/kits/kitnovacweb/nvml/renderer.js +924 -0
- package/novac/kits/kitparse/kitdef.js +1688 -0
- package/novac/kits/kitregex++/kitdef.js +1353 -0
- package/novac/kits/kitrequire/kitdef.js +1599 -0
- package/novac/kits/kitx11/kitdef.js +1 -0
- package/novac/kits/kitx11/kitx11.js +2472 -0
- package/novac/kits/kitx11/kitx11_conn.js +948 -0
- package/novac/kits/kitx11/kitx11_worker.js +121 -0
- package/novac/kits/libterm/ex.js +285 -0
- package/novac/kits/libterm/kitdef.js +1927 -0
- package/novac/node_modules/chalk/license +9 -0
- package/novac/node_modules/chalk/package.json +83 -0
- package/novac/node_modules/chalk/readme.md +297 -0
- package/novac/node_modules/chalk/source/index.d.ts +325 -0
- package/novac/node_modules/chalk/source/index.js +225 -0
- package/novac/node_modules/chalk/source/utilities.js +33 -0
- package/novac/node_modules/chalk/source/vendor/ansi-styles/index.d.ts +236 -0
- package/novac/node_modules/chalk/source/vendor/ansi-styles/index.js +223 -0
- package/novac/node_modules/chalk/source/vendor/supports-color/browser.d.ts +1 -0
- package/novac/node_modules/chalk/source/vendor/supports-color/browser.js +34 -0
- package/novac/node_modules/chalk/source/vendor/supports-color/index.d.ts +55 -0
- package/novac/node_modules/chalk/source/vendor/supports-color/index.js +190 -0
- package/novac/node_modules/commander/LICENSE +22 -0
- package/novac/node_modules/commander/Readme.md +1176 -0
- package/novac/node_modules/commander/esm.mjs +16 -0
- package/novac/node_modules/commander/index.js +24 -0
- package/novac/node_modules/commander/lib/argument.js +150 -0
- package/novac/node_modules/commander/lib/command.js +2777 -0
- package/novac/node_modules/commander/lib/error.js +39 -0
- package/novac/node_modules/commander/lib/help.js +747 -0
- package/novac/node_modules/commander/lib/option.js +380 -0
- package/novac/node_modules/commander/lib/suggestSimilar.js +101 -0
- package/novac/node_modules/commander/package-support.json +19 -0
- package/novac/node_modules/commander/package.json +82 -0
- package/novac/node_modules/commander/typings/esm.d.mts +3 -0
- package/novac/node_modules/commander/typings/index.d.ts +1113 -0
- package/novac/node_modules/node-addon-api/LICENSE.md +9 -0
- package/novac/node_modules/node-addon-api/README.md +95 -0
- package/novac/node_modules/node-addon-api/common.gypi +21 -0
- package/novac/node_modules/node-addon-api/except.gypi +25 -0
- package/novac/node_modules/node-addon-api/index.js +14 -0
- package/novac/node_modules/node-addon-api/napi-inl.deprecated.h +186 -0
- package/novac/node_modules/node-addon-api/napi-inl.h +7165 -0
- package/novac/node_modules/node-addon-api/napi.h +3364 -0
- package/novac/node_modules/node-addon-api/node_addon_api.gyp +42 -0
- package/novac/node_modules/node-addon-api/node_api.gyp +9 -0
- package/novac/node_modules/node-addon-api/noexcept.gypi +26 -0
- package/novac/node_modules/node-addon-api/package-support.json +21 -0
- package/novac/node_modules/node-addon-api/package.json +480 -0
- package/novac/node_modules/node-addon-api/tools/README.md +73 -0
- package/novac/node_modules/node-addon-api/tools/check-napi.js +99 -0
- package/novac/node_modules/node-addon-api/tools/clang-format.js +71 -0
- package/novac/node_modules/node-addon-api/tools/conversion.js +301 -0
- package/novac/node_modules/serialize-javascript/LICENSE +27 -0
- package/novac/node_modules/serialize-javascript/README.md +149 -0
- package/novac/node_modules/serialize-javascript/index.js +297 -0
- package/novac/node_modules/serialize-javascript/package.json +33 -0
- package/novac/package.json +27 -0
- package/novac/scripts/update-bin.js +24 -0
- package/novac/src/core/bstd.js +1035 -0
- package/novac/src/core/config.js +155 -0
- package/novac/src/core/describe.js +187 -0
- package/novac/src/core/emitter.js +499 -0
- package/novac/src/core/error.js +86 -0
- package/novac/src/core/executor.js +5606 -0
- package/novac/src/core/formatter.js +686 -0
- package/novac/src/core/lexer.js +1026 -0
- package/novac/src/core/nova_builtins.js +717 -0
- package/novac/src/core/nova_thread_worker.js +166 -0
- package/novac/src/core/parser.js +2181 -0
- package/novac/src/core/types.js +112 -0
- package/novac/src/index.js +28 -0
- package/novac/src/runtime/stdlib.js +244 -0
- package/package.json +3 -2
- package/scripts/update-bin.js +0 -0
- package/src/core/bstd.js +835 -361
- package/src/core/executor.js +427 -246
- package/src/core/lexer.js +19 -2
- package/src/core/parser.js +13 -0
- package/src/index.js +0 -0
- package/examples/example-project/README.md +0 -3
- package/examples/example-project/src/main.nova +0 -3
- package/src/core/environment.js +0 -0
- /package/{kits → novac/kits}/libtea/tf.js +0 -0
- /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 };
|