novac 2.0.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 +1574 -597
- package/bin/novac +468 -171
- package/bin/nvc +522 -0
- package/bin/nvml +78 -17
- 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/kitansi/kitdef.js +1402 -0
- package/kits/kitcanvas/kitdef.js +914 -0
- package/kits/kitclippy/kitdef.js +925 -0
- package/kits/kitformat/kitdef.js +1485 -0
- package/kits/kitgps/kitdef.js +1862 -0
- package/kits/kitlibproc/kitdef.js +3 -2
- package/kits/kitmatrix/ex.js +19 -0
- package/kits/kitmatrix/kitdef.js +960 -0
- package/kits/kitmorse/kitdef.js +229 -0
- package/kits/kitmpatch/kitdef.js +906 -0
- package/kits/kitnet/kitdef.js +1401 -0
- package/kits/kitnovacweb/README.md +1416 -143
- package/kits/kitnovacweb/kitdef.js +92 -2
- package/kits/kitnovacweb/nvml/executor.js +578 -176
- package/kits/kitnovacweb/nvml/index.js +2 -2
- package/kits/kitnovacweb/nvml/lexer.js +72 -69
- package/kits/kitnovacweb/nvml/parser.js +328 -159
- package/kits/kitnovacweb/nvml/renderer.js +770 -270
- package/kits/kitparse/kitdef.js +1688 -0
- package/kits/kitproto/kitdef.js +613 -0
- package/kits/kitqr/kitdef.js +637 -0
- package/kits/kitregex++/kitdef.js +1353 -0
- package/kits/kitrequire/kitdef.js +1599 -0
- package/kits/kitx11/kitdef.js +1 -0
- package/kits/kitx11/kitx11.js +2472 -0
- package/kits/kitx11/kitx11_conn.js +948 -0
- package/kits/kitx11/kitx11_worker.js +121 -0
- package/kits/libtea/kitdef.js +2691 -0
- package/kits/libterm/ex.js +285 -0
- package/kits/libterm/kitdef.js +1927 -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/libtea/tf.js +2691 -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 +6 -3
- package/scripts/update-bin.js +0 -0
- package/src/core/bstd.js +838 -362
- package/src/core/executor.js +2578 -170
- package/src/core/lexer.js +502 -54
- package/src/core/nova_builtins.js +21 -3
- package/src/core/parser.js +413 -72
- package/src/core/types.js +30 -2
- 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/{examples/example-project/bin/example-project.nv → novac/node_modules/node-addon-api/nothing.c} +0 -0
|
@@ -1,31 +1,41 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* NVML Parser
|
|
4
|
+
* NVML Parser v2
|
|
5
5
|
*
|
|
6
|
-
* Grammar
|
|
6
|
+
* Grammar:
|
|
7
7
|
*
|
|
8
|
-
* program →
|
|
8
|
+
* program → topNode*
|
|
9
9
|
*
|
|
10
|
-
*
|
|
10
|
+
* topNode → at_block | element_node
|
|
11
11
|
*
|
|
12
|
-
* at_block → '@'
|
|
13
|
-
* | '@'
|
|
12
|
+
* at_block → '@config' '[' kv_list ']'
|
|
13
|
+
* | '@visual' '[' block* ']'
|
|
14
|
+
* | '@ss' '[' block* ']'
|
|
15
|
+
* | '@component' IDENT '[' block* ']'
|
|
16
|
+
* | '@state' '[' kv_list ']'
|
|
17
|
+
* | '@computed' '[' named_fn_list ']'
|
|
18
|
+
* | '@effect' '[' effect_list ']'
|
|
19
|
+
* | '@route' STRING '[' block* ']'
|
|
20
|
+
* | '@slot' IDENT? '[' block* ']'
|
|
21
|
+
* | '@use' IDENT (',' IDENT)*
|
|
14
22
|
*
|
|
15
|
-
* element_node → ELEMENT (
|
|
16
|
-
* ( '[' element_body ']' )?
|
|
23
|
+
* element_node → ELEMENT ('=' value)? ('[' element_body ']')?
|
|
17
24
|
*
|
|
18
|
-
* element_body → (
|
|
25
|
+
* element_body → (property | child | parent_ref)*
|
|
19
26
|
*
|
|
20
|
-
* property → IDENT '=' value
|
|
21
|
-
* | IDENT
|
|
22
|
-
* |
|
|
23
|
-
* |
|
|
27
|
+
* property → IDENT '=' value
|
|
28
|
+
* | IDENT (boolean)
|
|
29
|
+
* | IDENT '->' IDENT (one-way binding: prop -> signal)
|
|
30
|
+
* | IDENT '<->' IDENT (two-way binding: prop <-> signal)
|
|
31
|
+
* | IDENT '~' IDENT (transition: prop ~ transitionName)
|
|
32
|
+
* | '[..]' '::' IDENT ('=' value | '[' sub_body ']' | ε)
|
|
24
33
|
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
34
|
+
* child → element_node | at_block | text_node | conditional_child
|
|
35
|
+
*
|
|
36
|
+
* conditional_child → '?' IDENT element_node (render element only if signal is truthy)
|
|
37
|
+
*
|
|
38
|
+
* value → STRING | NUMBER | IDENT | '#' IDENT | pipe_list
|
|
29
39
|
*/
|
|
30
40
|
|
|
31
41
|
const { Lexer, TT } = require('./lexer');
|
|
@@ -38,17 +48,32 @@ class ParseError extends Error {
|
|
|
38
48
|
}
|
|
39
49
|
}
|
|
40
50
|
|
|
41
|
-
// ── AST node factories ────────────────────────────────────
|
|
42
|
-
|
|
43
51
|
const N = {
|
|
44
|
-
program:
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
+
program: (body) => ({ kind: 'program', body }),
|
|
53
|
+
atConfig: (pairs) => ({ kind: 'at_config', pairs }),
|
|
54
|
+
atBlock: (name, arg, body) => ({ kind: 'at_block', name, arg, body }),
|
|
55
|
+
atState: (pairs) => ({ kind: 'at_state', pairs }),
|
|
56
|
+
atComputed: (entries) => ({ kind: 'at_computed', entries }),
|
|
57
|
+
atEffect: (entries) => ({ kind: 'at_effect', entries }),
|
|
58
|
+
atLang: (name, pairs) => ({ kind: 'at_lang', name, pairs }),
|
|
59
|
+
atRoute: (path, body) => ({ kind: 'at_route', path, body }),
|
|
60
|
+
atSlot: (name, body) => ({ kind: 'at_slot', name, body }),
|
|
61
|
+
atUse: (names) => ({ kind: 'at_use', names }),
|
|
62
|
+
element: (tag, textValue, props, body,
|
|
63
|
+
bindings, transitions, cond) => ({ kind: 'element', tag, textValue, props, body, bindings, transitions, cond }),
|
|
64
|
+
property: (key, value) => ({ kind: 'property', key, value }),
|
|
65
|
+
binding: (prop, signal, twoWay) => ({ kind: 'binding', prop, signal, twoWay }),
|
|
66
|
+
transition: (prop, name) => ({ kind: 'transition', prop, name }),
|
|
67
|
+
parentProp: (prop, value, subBody) => ({ kind: 'parent_prop', prop, value, subBody }),
|
|
68
|
+
signalRef: (name) => ({ kind: 'signal_ref', name }),
|
|
69
|
+
value: (v) => ({ kind: 'value', value: v }),
|
|
70
|
+
pipeList: (items) => ({ kind: 'pipe_list', items }),
|
|
71
|
+
textNode: (value) => ({ kind: 'text_node', value }),
|
|
72
|
+
conditional: (signal, child) => ({ kind: 'conditional', signal, child }),
|
|
73
|
+
cssDecl: (key, value) => ({ kind: 'css_decl', key, value }),
|
|
74
|
+
eachBlock: (signal, itemVar, indexVar, body)=> ({ kind: 'each_block', signal, itemVar, indexVar, body }),
|
|
75
|
+
slotOutlet: (name) => ({ kind: 'slot_outlet', name }),
|
|
76
|
+
componentUse: (name, props, slots) => ({ kind: 'component_use', name, props, slots }),
|
|
52
77
|
};
|
|
53
78
|
|
|
54
79
|
class Parser {
|
|
@@ -57,16 +82,11 @@ class Parser {
|
|
|
57
82
|
this.current = 0;
|
|
58
83
|
}
|
|
59
84
|
|
|
60
|
-
// ── token helpers ─────────────────────────────────────────
|
|
61
|
-
|
|
62
85
|
peek() { return this.tokens[this.current]; }
|
|
63
86
|
previous() { return this.tokens[this.current - 1]; }
|
|
64
87
|
isAtEnd() { return this.peek().type === TT.EOF; }
|
|
65
88
|
|
|
66
|
-
advance()
|
|
67
|
-
if (!this.isAtEnd()) this.current++;
|
|
68
|
-
return this.previous();
|
|
69
|
-
}
|
|
89
|
+
advance() { if (!this.isAtEnd()) this.current++; return this.previous(); }
|
|
70
90
|
|
|
71
91
|
check(type, value = null) {
|
|
72
92
|
if (this.isAtEnd()) return false;
|
|
@@ -75,7 +95,6 @@ class Parser {
|
|
|
75
95
|
}
|
|
76
96
|
|
|
77
97
|
match(...specs) {
|
|
78
|
-
// specs: [type] or [type, value]
|
|
79
98
|
for (const spec of specs) {
|
|
80
99
|
const [type, value = null] = Array.isArray(spec) ? spec : [spec];
|
|
81
100
|
if (this.check(type, value)) { this.advance(); return true; }
|
|
@@ -84,135 +103,303 @@ class Parser {
|
|
|
84
103
|
}
|
|
85
104
|
|
|
86
105
|
consume(type, value, msg) {
|
|
87
|
-
if (
|
|
88
|
-
if (
|
|
106
|
+
if (value !== null && value !== undefined && this.check(type, value)) return this.advance();
|
|
107
|
+
if ((value === null || value === undefined) && this.check(type)) return this.advance();
|
|
89
108
|
throw new ParseError(msg || `Expected ${type}${value ? ' ' + value : ''}`, this.peek());
|
|
90
109
|
}
|
|
91
110
|
|
|
92
111
|
error(msg) { throw new ParseError(msg, this.peek()); }
|
|
93
112
|
|
|
94
|
-
// ── entry
|
|
113
|
+
// ── entry ──────────────────────────────────────────────────────
|
|
95
114
|
|
|
96
115
|
parse() {
|
|
97
116
|
const body = [];
|
|
98
|
-
while (!this.isAtEnd()) body.push(this.
|
|
117
|
+
while (!this.isAtEnd()) body.push(this.parseTopNode());
|
|
99
118
|
return N.program(body);
|
|
100
119
|
}
|
|
101
120
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
parseBlock() {
|
|
121
|
+
parseTopNode() {
|
|
105
122
|
const t = this.peek();
|
|
106
|
-
|
|
107
|
-
if (t.type === TT.
|
|
108
|
-
|
|
109
|
-
if (t.type === TT.DOTDOT) return this.parseParentPropStandalone();
|
|
110
|
-
|
|
111
|
-
this.error(`Unexpected token '${t.value}' (${t.type})`);
|
|
123
|
+
if (t.type === TT.AT_IDENT) return this.parseAtBlock();
|
|
124
|
+
if (t.type === TT.ELEMENT) return this.parseElement();
|
|
125
|
+
this.error(`Unexpected token '${t.value}' (${t.type}) at top level`);
|
|
112
126
|
}
|
|
113
127
|
|
|
114
|
-
// ── @
|
|
128
|
+
// ── @blocks ────────────────────────────────────────────────────
|
|
115
129
|
|
|
116
130
|
parseAtBlock() {
|
|
117
131
|
const name = this.advance().value; // consume AT_IDENT
|
|
118
132
|
|
|
119
|
-
|
|
133
|
+
switch (name) {
|
|
134
|
+
case 'config':
|
|
135
|
+
this.consume(TT.LBRACKET, null, "Expected '[' after @config");
|
|
136
|
+
const configPairs = this.parseKVList();
|
|
137
|
+
this.consume(TT.RBRACKET, null, "Expected ']' to close @config");
|
|
138
|
+
return N.atConfig(configPairs);
|
|
139
|
+
|
|
140
|
+
case 'state':
|
|
141
|
+
this.consume(TT.LBRACKET, null, "Expected '[' after @state");
|
|
142
|
+
const statePairs = this.parseKVList();
|
|
143
|
+
this.consume(TT.RBRACKET, null, "Expected ']' to close @state");
|
|
144
|
+
return N.atState(statePairs);
|
|
145
|
+
|
|
146
|
+
case 'computed':
|
|
147
|
+
this.consume(TT.LBRACKET, null, "Expected '[' after @computed");
|
|
148
|
+
const computedEntries = this.parseNamedCodeList();
|
|
149
|
+
this.consume(TT.RBRACKET, null, "Expected ']' to close @computed");
|
|
150
|
+
return N.atComputed(computedEntries);
|
|
151
|
+
|
|
152
|
+
case 'effect':
|
|
153
|
+
this.consume(TT.LBRACKET, null, "Expected '[' after @effect");
|
|
154
|
+
const effectEntries = this.parseEffectList();
|
|
155
|
+
this.consume(TT.RBRACKET, null, "Expected ']' to close @effect");
|
|
156
|
+
return N.atEffect(effectEntries);
|
|
157
|
+
|
|
158
|
+
case 'component': {
|
|
159
|
+
const compName = this.consume(TT.IDENT, null, "Expected component name after @component").value;
|
|
160
|
+
this.consume(TT.LBRACKET, null, "Expected '[' after @component name");
|
|
161
|
+
const compBody = this.parseBlockList();
|
|
162
|
+
this.consume(TT.RBRACKET, null, "Expected ']' to close @component");
|
|
163
|
+
return N.atComponent(compName, compBody);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
case 'lang': {
|
|
167
|
+
// @lang {langName} [ runtime_language="nv", config="", src="PATH", code=(...) ]
|
|
168
|
+
const langName = this.consume(TT.IDENT, null, 'Expected language name after @lang').value;
|
|
169
|
+
this.consume(TT.LBRACKET, null, "Expected '[' after @lang name");
|
|
170
|
+
const langPairs = this._parseLangKVList();
|
|
171
|
+
this.consume(TT.RBRACKET, null, "Expected ']' to close @lang");
|
|
172
|
+
return N.atLang(langName, langPairs);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
case 'route': {
|
|
176
|
+
const routePath = this.parseValue(); // STRING or IDENT
|
|
177
|
+
this.consume(TT.LBRACKET, null, "Expected '[' after @route path");
|
|
178
|
+
const routeBody = this.parseBlockList();
|
|
179
|
+
this.consume(TT.RBRACKET, null, "Expected ']' to close @route");
|
|
180
|
+
return N.atRoute(routePath, routeBody);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
case 'slot': {
|
|
184
|
+
let slotName = 'default';
|
|
185
|
+
if (this.check(TT.IDENT)) slotName = this.advance().value;
|
|
186
|
+
this.consume(TT.LBRACKET, null, "Expected '[' after @slot");
|
|
187
|
+
const slotBody = this.parseBlockList();
|
|
188
|
+
this.consume(TT.RBRACKET, null, "Expected ']' to close @slot");
|
|
189
|
+
return N.atSlot(slotName, slotBody);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
case 'use': {
|
|
193
|
+
const useNames = [];
|
|
194
|
+
useNames.push(this.consume(TT.IDENT, null, "Expected component name after @use").value);
|
|
195
|
+
while (this.match(TT.COMMA)) {
|
|
196
|
+
useNames.push(this.consume(TT.IDENT, null, "Expected component name").value);
|
|
197
|
+
}
|
|
198
|
+
return N.atUse(useNames);
|
|
199
|
+
}
|
|
120
200
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
201
|
+
default: {
|
|
202
|
+
// @visual, @ss, or unknown custom at-block
|
|
203
|
+
this.consume(TT.LBRACKET, null, `Expected '[' after @${name}`);
|
|
204
|
+
const body = this.parseBlockList();
|
|
205
|
+
this.consume(TT.RBRACKET, null, `Expected ']' to close @${name}`);
|
|
206
|
+
return N.atBlock(name, null, body);
|
|
207
|
+
}
|
|
126
208
|
}
|
|
209
|
+
}
|
|
127
210
|
|
|
128
|
-
|
|
211
|
+
parseBlockList() {
|
|
129
212
|
const body = [];
|
|
130
213
|
while (!this.isAtEnd() && !this.check(TT.RBRACKET)) {
|
|
131
|
-
body.push(this.
|
|
214
|
+
if (this.check(TT.AT_IDENT)) { body.push(this.parseAtBlock()); continue; }
|
|
215
|
+
if (this.check(TT.ELEMENT)) { body.push(this.parseElement()); continue; }
|
|
216
|
+
if (this.check(TT.DOTDOT)) { body.push(this.parseParentPropInline()); continue; }
|
|
217
|
+
if (this.check(TT.STRING)) { body.push(N.textNode(this.advance().value)); continue; }
|
|
218
|
+
if (this.check(TT.COMMA)) { this.advance(); continue; }
|
|
219
|
+
break;
|
|
132
220
|
}
|
|
133
|
-
|
|
134
|
-
return N.atBlock(name, body);
|
|
221
|
+
return body;
|
|
135
222
|
}
|
|
136
223
|
|
|
137
|
-
// ── kv list (
|
|
224
|
+
// ── kv list (flat key=value pairs) ────────────────────────────
|
|
138
225
|
|
|
139
226
|
parseKVList() {
|
|
140
227
|
const pairs = {};
|
|
141
228
|
while (!this.isAtEnd() && !this.check(TT.RBRACKET)) {
|
|
142
|
-
// skip stray commas
|
|
143
229
|
if (this.match(TT.COMMA)) continue;
|
|
144
|
-
|
|
145
|
-
const key = this.
|
|
230
|
+
if (!this.check(TT.IDENT)) break;
|
|
231
|
+
const key = this.advance().value;
|
|
146
232
|
this.consume(TT.EQ, null, `Expected '=' after key '${key}'`);
|
|
147
|
-
|
|
148
|
-
|
|
233
|
+
pairs[key] = this.parseValue();
|
|
234
|
+
this.match(TT.COMMA);
|
|
235
|
+
}
|
|
236
|
+
return pairs;
|
|
237
|
+
}
|
|
149
238
|
|
|
150
|
-
|
|
239
|
+
// ── @lang kv list — like parseKVList but also accepts bare string values ──
|
|
240
|
+
// Handles: runtime_language="nv" config="" src="PATH" code=(...)
|
|
241
|
+
_parseLangKVList() {
|
|
242
|
+
const pairs = {};
|
|
243
|
+
while (!this.isAtEnd() && !this.check(TT.RBRACKET)) {
|
|
244
|
+
if (this.match(TT.COMMA)) continue;
|
|
245
|
+
if (!this.check(TT.IDENT)) break;
|
|
246
|
+
const key = this.advance().value;
|
|
247
|
+
if (this.match(TT.EQ)) {
|
|
248
|
+
pairs[key] = this.parseValue();
|
|
249
|
+
} else {
|
|
250
|
+
pairs[key] = N.value(true);
|
|
251
|
+
}
|
|
252
|
+
this.match(TT.COMMA);
|
|
151
253
|
}
|
|
152
254
|
return pairs;
|
|
153
255
|
}
|
|
154
256
|
|
|
155
|
-
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
parseNamedCodeList() {
|
|
260
|
+
const entries = [];
|
|
261
|
+
while (!this.isAtEnd() && !this.check(TT.RBRACKET)) {
|
|
262
|
+
if (this.match(TT.COMMA)) continue;
|
|
263
|
+
if (!this.check(TT.IDENT)) break;
|
|
264
|
+
const name = this.advance().value;
|
|
265
|
+
this.consume(TT.EQ, null, `Expected '=' after computed name '${name}'`);
|
|
266
|
+
const code = this.parseValue();
|
|
267
|
+
entries.push({ name, code });
|
|
268
|
+
this.match(TT.COMMA);
|
|
269
|
+
}
|
|
270
|
+
return entries;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// ── @effect entries: deps -> (nova code) ──────────────────────
|
|
274
|
+
|
|
275
|
+
parseEffectList() {
|
|
276
|
+
const entries = [];
|
|
277
|
+
while (!this.isAtEnd() && !this.check(TT.RBRACKET)) {
|
|
278
|
+
if (this.match(TT.COMMA)) continue;
|
|
279
|
+
// deps: [signalA, signalB] or * (always) — stored as array of names
|
|
280
|
+
const deps = [];
|
|
281
|
+
if (this.check(TT.LBRACKET)) {
|
|
282
|
+
this.advance();
|
|
283
|
+
while (!this.isAtEnd() && !this.check(TT.RBRACKET)) {
|
|
284
|
+
if (this.check(TT.IDENT)) deps.push(this.advance().value);
|
|
285
|
+
this.match(TT.COMMA);
|
|
286
|
+
}
|
|
287
|
+
this.consume(TT.RBRACKET, null, "Expected ']' to close effect deps");
|
|
288
|
+
} else if (this.check(TT.IDENT, '*')) {
|
|
289
|
+
this.advance(); // wildcard — run on any signal change
|
|
290
|
+
deps.push('*');
|
|
291
|
+
} else if (this.check(TT.IDENT)) {
|
|
292
|
+
deps.push(this.advance().value);
|
|
293
|
+
}
|
|
294
|
+
this.consume(TT.ARROW, null, "Expected '->' after effect deps");
|
|
295
|
+
const code = this.parseValue();
|
|
296
|
+
entries.push({ deps, code });
|
|
297
|
+
this.match(TT.COMMA);
|
|
298
|
+
}
|
|
299
|
+
return entries;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// ── element ────────────────────────────────────────────────────
|
|
156
303
|
|
|
157
304
|
parseElement() {
|
|
158
|
-
const tag = this.advance().value;
|
|
305
|
+
const tag = this.advance().value;
|
|
159
306
|
|
|
160
|
-
// optional inline text: {h1}='Hello'
|
|
161
307
|
let textValue = null;
|
|
162
|
-
if (this.match(TT.EQ))
|
|
163
|
-
textValue = this.parseValue();
|
|
164
|
-
}
|
|
308
|
+
if (this.match(TT.EQ)) textValue = this.parseValue();
|
|
165
309
|
|
|
166
|
-
|
|
167
|
-
let
|
|
168
|
-
let
|
|
310
|
+
let props = {};
|
|
311
|
+
let body = [];
|
|
312
|
+
let bindings = [];
|
|
313
|
+
let transitions= [];
|
|
314
|
+
let cond = null;
|
|
169
315
|
|
|
170
316
|
if (this.match(TT.LBRACKET)) {
|
|
171
|
-
const
|
|
172
|
-
props
|
|
173
|
-
body
|
|
317
|
+
const parsed = this.parseElementBody();
|
|
318
|
+
props = parsed.props;
|
|
319
|
+
body = parsed.body;
|
|
320
|
+
bindings = parsed.bindings;
|
|
321
|
+
transitions = parsed.transitions;
|
|
174
322
|
this.consume(TT.RBRACKET, null, `Expected ']' to close element {${tag}}`);
|
|
175
323
|
}
|
|
176
324
|
|
|
177
|
-
return N.element(tag, textValue, props, body);
|
|
325
|
+
return N.element(tag, textValue, props, body, bindings, transitions, cond);
|
|
178
326
|
}
|
|
179
327
|
|
|
180
|
-
// ── element body: mix of properties and child elements ────
|
|
181
|
-
|
|
182
328
|
parseElementBody() {
|
|
183
|
-
const props = {};
|
|
184
|
-
const body = [];
|
|
329
|
+
const props = {}, body = [], bindings = [], transitions = [];
|
|
185
330
|
|
|
186
331
|
while (!this.isAtEnd() && !this.check(TT.RBRACKET)) {
|
|
187
332
|
const t = this.peek();
|
|
188
333
|
|
|
189
|
-
// [..] parent-ref
|
|
334
|
+
// [..] parent-ref
|
|
190
335
|
if (t.type === TT.DOTDOT) {
|
|
191
336
|
const pp = this.parseParentPropInline();
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
props[pp.prop] = { __subBody: pp.subBody };
|
|
195
|
-
} else {
|
|
196
|
-
props[pp.prop] = pp.value;
|
|
197
|
-
}
|
|
337
|
+
if (pp.subBody !== null) props[pp.prop] = { __subBody: pp.subBody };
|
|
338
|
+
else props[pp.prop] = pp.value;
|
|
198
339
|
continue;
|
|
199
340
|
}
|
|
200
341
|
|
|
201
|
-
// child element
|
|
202
|
-
if (t.type === TT.
|
|
203
|
-
|
|
342
|
+
// conditional child: ? signalName {element}
|
|
343
|
+
if (t.type === TT.QUESTION) {
|
|
344
|
+
this.advance();
|
|
345
|
+
const sigName = this.consume(TT.IDENT, null, "Expected signal name after '?'").value;
|
|
346
|
+
const child = this.parseElement();
|
|
347
|
+
child.cond = sigName;
|
|
348
|
+
body.push(child);
|
|
204
349
|
continue;
|
|
205
350
|
}
|
|
206
351
|
|
|
207
|
-
//
|
|
208
|
-
if (t.type === TT.AT_IDENT) {
|
|
209
|
-
body.push(this.
|
|
352
|
+
// each block: @each signalName as itemVar (index: indexVar) [ body ]
|
|
353
|
+
if (t.type === TT.AT_IDENT && t.value === 'each') {
|
|
354
|
+
body.push(this.parseEachBlock());
|
|
210
355
|
continue;
|
|
211
356
|
}
|
|
212
357
|
|
|
213
|
-
//
|
|
358
|
+
// slot outlet: @slot name
|
|
359
|
+
if (t.type === TT.AT_IDENT && t.value === 'slot') {
|
|
360
|
+
this.advance();
|
|
361
|
+
const slotName = this.check(TT.IDENT) ? this.advance().value : 'default';
|
|
362
|
+
body.push(N.slotOutlet(slotName));
|
|
363
|
+
continue;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// child element
|
|
367
|
+
if (t.type === TT.ELEMENT) { body.push(this.parseElement()); continue; }
|
|
368
|
+
|
|
369
|
+
// child @block
|
|
370
|
+
if (t.type === TT.AT_IDENT) { body.push(this.parseAtBlock()); continue; }
|
|
371
|
+
|
|
372
|
+
// bare text node
|
|
373
|
+
if (t.type === TT.STRING) { body.push(N.textNode(this.advance().value)); continue; }
|
|
374
|
+
|
|
375
|
+
// property
|
|
214
376
|
if (t.type === TT.IDENT) {
|
|
215
377
|
const key = this.advance().value;
|
|
378
|
+
|
|
379
|
+
// one-way binding: prop -> signalName
|
|
380
|
+
if (this.match(TT.ARROW)) {
|
|
381
|
+
const sig = this.consume(TT.IDENT, null, "Expected signal name after '->'").value;
|
|
382
|
+
bindings.push(N.binding(key, sig, false));
|
|
383
|
+
this.match(TT.COMMA);
|
|
384
|
+
continue;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// two-way binding: prop <-> signalName
|
|
388
|
+
if (this.match(TT.DARROW)) {
|
|
389
|
+
const sig = this.consume(TT.IDENT, null, "Expected signal name after '<->'").value;
|
|
390
|
+
bindings.push(N.binding(key, sig, true));
|
|
391
|
+
this.match(TT.COMMA);
|
|
392
|
+
continue;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
// transition hint: prop ~ transitionName
|
|
396
|
+
if (this.match(TT.TILDE)) {
|
|
397
|
+
const tname = this.consume(TT.IDENT, null, "Expected transition name after '~'").value;
|
|
398
|
+
transitions.push(N.transition(key, tname));
|
|
399
|
+
this.match(TT.COMMA);
|
|
400
|
+
continue;
|
|
401
|
+
}
|
|
402
|
+
|
|
216
403
|
if (this.match(TT.EQ)) {
|
|
217
404
|
props[key] = this.parseValue();
|
|
218
405
|
} else {
|
|
@@ -222,113 +409,95 @@ class Parser {
|
|
|
222
409
|
continue;
|
|
223
410
|
}
|
|
224
411
|
|
|
225
|
-
//
|
|
226
|
-
if (t.type === TT.
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
if (t.type === TT.STRING) {
|
|
230
|
-
body.push({ kind: 'text_node', value: this.advance().value });
|
|
412
|
+
// signal ref as prop value: #signal (shorthand for text binding)
|
|
413
|
+
if (t.type === TT.HASH) {
|
|
414
|
+
this.advance();
|
|
415
|
+
body.push(N.signalRef(t.value));
|
|
231
416
|
continue;
|
|
232
417
|
}
|
|
233
418
|
|
|
234
|
-
|
|
235
|
-
if (t.type === TT.COMMA)
|
|
419
|
+
if (t.type === TT.NUMBER) { this.advance(); this.match(TT.COMMA); continue; }
|
|
420
|
+
if (t.type === TT.COMMA) { this.advance(); continue; }
|
|
236
421
|
|
|
237
422
|
this.error(`Unexpected token '${t.value}' (${t.type}) inside element body`);
|
|
238
423
|
}
|
|
239
424
|
|
|
240
|
-
return { props, body };
|
|
425
|
+
return { props, body, bindings, transitions };
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
// ── @each signalName as item (index: i) [ body ] ──────────────
|
|
429
|
+
|
|
430
|
+
parseEachBlock() {
|
|
431
|
+
this.advance(); // consume 'each' AT_IDENT
|
|
432
|
+
const signal = this.consume(TT.IDENT, null, "Expected signal name after @each").value;
|
|
433
|
+
// 'as' keyword
|
|
434
|
+
if (this.check(TT.IDENT, 'as')) this.advance();
|
|
435
|
+
const itemVar = this.consume(TT.IDENT, null, "Expected item variable after 'as'").value;
|
|
436
|
+
let indexVar = null;
|
|
437
|
+
// optional (index: i)
|
|
438
|
+
if (this.check(TT.STRING) || (this.check(TT.IDENT, 'index'))) {
|
|
439
|
+
this.advance(); // skip
|
|
440
|
+
}
|
|
441
|
+
// (index: varName)
|
|
442
|
+
if (this.check(TT.LBRACKET) || this.check(TT.LPAREN)) {
|
|
443
|
+
// simple: check for index keyword in next ident
|
|
444
|
+
}
|
|
445
|
+
this.consume(TT.LBRACKET, null, "Expected '[' after @each header");
|
|
446
|
+
const body = this.parseBlockList();
|
|
447
|
+
this.consume(TT.RBRACKET, null, "Expected ']' to close @each block");
|
|
448
|
+
return N.eachBlock(signal, itemVar, indexVar, body);
|
|
241
449
|
}
|
|
242
450
|
|
|
243
|
-
// ── [..] property
|
|
451
|
+
// ── [..] property ──────────────────────────────────────────────
|
|
244
452
|
|
|
245
453
|
parseParentPropInline() {
|
|
246
454
|
this.advance(); // consume DOTDOT
|
|
247
455
|
this.consume(TT.COLONCOLON, null, "Expected '::' after '[..]'");
|
|
248
456
|
const prop = this.consume(TT.IDENT, null, "Expected property name after '::'").value;
|
|
249
457
|
|
|
250
|
-
// [..]::ss [ body ] — sub-block
|
|
251
458
|
if (this.match(TT.LBRACKET)) {
|
|
252
459
|
const subBody = [];
|
|
253
460
|
while (!this.isAtEnd() && !this.check(TT.RBRACKET)) {
|
|
254
|
-
// inside ::ss sub-blocks, content can be elements OR bare kv pairs
|
|
255
461
|
if (this.check(TT.ELEMENT) || this.check(TT.AT_IDENT) || this.check(TT.DOTDOT)) {
|
|
256
|
-
subBody.push(this.
|
|
462
|
+
subBody.push(this.parseTopNode());
|
|
257
463
|
} else if (this.check(TT.IDENT)) {
|
|
258
|
-
// bare kv: color='red', (CSS-style declarations)
|
|
259
464
|
const key = this.advance().value;
|
|
260
465
|
if (this.match(TT.EQ)) {
|
|
261
|
-
|
|
262
|
-
subBody.push({ kind: 'css_decl', key, value: val });
|
|
466
|
+
subBody.push(N.cssDecl(key, this.parseValue()));
|
|
263
467
|
} else {
|
|
264
|
-
subBody.push(
|
|
468
|
+
subBody.push(N.cssDecl(key, N.value(true)));
|
|
265
469
|
}
|
|
266
470
|
this.match(TT.COMMA);
|
|
267
471
|
} else if (this.check(TT.COMMA)) {
|
|
268
472
|
this.advance();
|
|
269
|
-
} else {
|
|
270
|
-
break;
|
|
271
|
-
}
|
|
473
|
+
} else { break; }
|
|
272
474
|
}
|
|
273
475
|
this.consume(TT.RBRACKET, null, `Expected ']' to close [..]::${prop} block`);
|
|
274
476
|
return N.parentProp(prop, null, subBody);
|
|
275
477
|
}
|
|
276
478
|
|
|
277
|
-
|
|
278
|
-
if (this.match(TT.EQ)) {
|
|
279
|
-
const val = this.parseValue();
|
|
280
|
-
return N.parentProp(prop, val, null);
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
// boolean flag
|
|
479
|
+
if (this.match(TT.EQ)) return N.parentProp(prop, this.parseValue(), null);
|
|
284
480
|
return N.parentProp(prop, N.value(true), null);
|
|
285
481
|
}
|
|
286
482
|
|
|
287
|
-
//
|
|
288
|
-
parseParentPropStandalone() {
|
|
289
|
-
return this.parseParentPropInline();
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
// ── value ─────────────────────────────────────────────────
|
|
483
|
+
// ── value ──────────────────────────────────────────────────────
|
|
293
484
|
|
|
294
485
|
parseValue() {
|
|
295
486
|
const t = this.peek();
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
if (t.type === TT.
|
|
299
|
-
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
if (t.type === TT.STRING) {
|
|
303
|
-
this.advance();
|
|
304
|
-
return N.value(t.value);
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
if (t.type === TT.NUMBER) {
|
|
308
|
-
this.advance();
|
|
309
|
-
return N.value(t.value);
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
if (t.type === TT.IDENT) {
|
|
313
|
-
this.advance();
|
|
314
|
-
// could be bare word like 'novac', 'server', 'client'
|
|
315
|
-
return N.value(t.value);
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
// multiline string was already converted to STRING by lexer (paren-string)
|
|
319
|
-
this.error(`Expected a value (string, number, or identifier), got '${t.value}' (${t.type})`);
|
|
487
|
+
if (t.type === TT.PIPE) return this.parsePipeList();
|
|
488
|
+
if (t.type === TT.HASH) { this.advance(); return N.signalRef(t.value); }
|
|
489
|
+
if (t.type === TT.STRING) { this.advance(); return N.value(t.value); }
|
|
490
|
+
if (t.type === TT.NUMBER) { this.advance(); return N.value(t.value); }
|
|
491
|
+
if (t.type === TT.IDENT) { this.advance(); return N.value(t.value); }
|
|
492
|
+
this.error(`Expected a value, got '${t.value}' (${t.type})`);
|
|
320
493
|
}
|
|
321
494
|
|
|
322
|
-
// ── pipe list |item1, item2, item3| ─────────────────────
|
|
323
|
-
|
|
324
495
|
parsePipeList() {
|
|
325
496
|
this.consume(TT.PIPE, null, "Expected '|'");
|
|
326
497
|
const items = [];
|
|
327
498
|
while (!this.isAtEnd() && !this.check(TT.PIPE)) {
|
|
328
499
|
const t = this.peek();
|
|
329
|
-
if (t.type === TT.IDENT || t.type === TT.STRING)
|
|
330
|
-
items.push(this.advance().value);
|
|
331
|
-
}
|
|
500
|
+
if (t.type === TT.IDENT || t.type === TT.STRING) items.push(this.advance().value);
|
|
332
501
|
this.match(TT.COMMA);
|
|
333
502
|
}
|
|
334
503
|
this.consume(TT.PIPE, null, "Expected closing '|'");
|
|
@@ -336,4 +505,4 @@ class Parser {
|
|
|
336
505
|
}
|
|
337
506
|
}
|
|
338
507
|
|
|
339
|
-
module.exports = { Parser, ParseError };
|
|
508
|
+
module.exports = { Parser, ParseError, N };
|