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.
Files changed (161) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +1574 -597
  3. package/bin/novac +468 -171
  4. package/bin/nvc +522 -0
  5. package/bin/nvml +78 -17
  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 +69 -0
  10. package/examples/math.nv +21 -0
  11. package/kits/birdAPI/kitdef.js +954 -0
  12. package/kits/kitRNG/kitdef.js +740 -0
  13. package/kits/kitSSH/kitdef.js +1272 -0
  14. package/kits/kitadb/kitdef.js +606 -0
  15. package/kits/kitai/kitdef.js +2185 -0
  16. package/kits/kitansi/kitdef.js +1402 -0
  17. package/kits/kitcanvas/kitdef.js +914 -0
  18. package/kits/kitclippy/kitdef.js +925 -0
  19. package/kits/kitformat/kitdef.js +1485 -0
  20. package/kits/kitgps/kitdef.js +1862 -0
  21. package/kits/kitlibproc/kitdef.js +3 -2
  22. package/kits/kitmatrix/ex.js +19 -0
  23. package/kits/kitmatrix/kitdef.js +960 -0
  24. package/kits/kitmorse/kitdef.js +229 -0
  25. package/kits/kitmpatch/kitdef.js +906 -0
  26. package/kits/kitnet/kitdef.js +1401 -0
  27. package/kits/kitnovacweb/README.md +1416 -143
  28. package/kits/kitnovacweb/kitdef.js +92 -2
  29. package/kits/kitnovacweb/nvml/executor.js +578 -176
  30. package/kits/kitnovacweb/nvml/index.js +2 -2
  31. package/kits/kitnovacweb/nvml/lexer.js +72 -69
  32. package/kits/kitnovacweb/nvml/parser.js +328 -159
  33. package/kits/kitnovacweb/nvml/renderer.js +770 -270
  34. package/kits/kitparse/kitdef.js +1688 -0
  35. package/kits/kitproto/kitdef.js +613 -0
  36. package/kits/kitqr/kitdef.js +637 -0
  37. package/kits/kitregex++/kitdef.js +1353 -0
  38. package/kits/kitrequire/kitdef.js +1599 -0
  39. package/kits/kitx11/kitdef.js +1 -0
  40. package/kits/kitx11/kitx11.js +2472 -0
  41. package/kits/kitx11/kitx11_conn.js +948 -0
  42. package/kits/kitx11/kitx11_worker.js +121 -0
  43. package/kits/libtea/kitdef.js +2691 -0
  44. package/kits/libterm/ex.js +285 -0
  45. package/kits/libterm/kitdef.js +1927 -0
  46. package/novac/LICENSE +21 -0
  47. package/novac/README.md +1823 -0
  48. package/novac/bin/novac +950 -0
  49. package/novac/bin/nvc +522 -0
  50. package/novac/bin/nvml +542 -0
  51. package/novac/demo.nv +245 -0
  52. package/novac/demo_builtins.nv +209 -0
  53. package/novac/demo_http.nv +62 -0
  54. package/novac/examples/bf.nv +69 -0
  55. package/novac/examples/math.nv +21 -0
  56. package/novac/kits/kitai/kitdef.js +2185 -0
  57. package/novac/kits/kitansi/kitdef.js +1402 -0
  58. package/novac/kits/kitformat/kitdef.js +1485 -0
  59. package/novac/kits/kitgps/kitdef.js +1862 -0
  60. package/novac/kits/kitlibfs/kitdef.js +231 -0
  61. package/{examples/example-project/nova_modules → novac/kits}/kitlibproc/kitdef.js +3 -2
  62. package/novac/kits/kitmatrix/ex.js +19 -0
  63. package/novac/kits/kitmatrix/kitdef.js +960 -0
  64. package/novac/kits/kitmpatch/kitdef.js +906 -0
  65. package/novac/kits/kitnovacweb/README.md +1572 -0
  66. package/novac/kits/kitnovacweb/demo.nv +12 -0
  67. package/novac/kits/kitnovacweb/demo.nvml +71 -0
  68. package/novac/kits/kitnovacweb/index.nova +12 -0
  69. package/novac/kits/kitnovacweb/kitdef.js +692 -0
  70. package/novac/kits/kitnovacweb/nova.kit.json +8 -0
  71. package/novac/kits/kitnovacweb/nvml/executor.js +739 -0
  72. package/novac/kits/kitnovacweb/nvml/index.js +67 -0
  73. package/novac/kits/kitnovacweb/nvml/lexer.js +263 -0
  74. package/novac/kits/kitnovacweb/nvml/parser.js +508 -0
  75. package/novac/kits/kitnovacweb/nvml/renderer.js +924 -0
  76. package/novac/kits/kitparse/kitdef.js +1688 -0
  77. package/novac/kits/kitregex++/kitdef.js +1353 -0
  78. package/novac/kits/kitrequire/kitdef.js +1599 -0
  79. package/novac/kits/kitx11/kitdef.js +1 -0
  80. package/novac/kits/kitx11/kitx11.js +2472 -0
  81. package/novac/kits/kitx11/kitx11_conn.js +948 -0
  82. package/novac/kits/kitx11/kitx11_worker.js +121 -0
  83. package/novac/kits/libtea/tf.js +2691 -0
  84. package/novac/kits/libterm/ex.js +285 -0
  85. package/novac/kits/libterm/kitdef.js +1927 -0
  86. package/novac/node_modules/chalk/license +9 -0
  87. package/novac/node_modules/chalk/package.json +83 -0
  88. package/novac/node_modules/chalk/readme.md +297 -0
  89. package/novac/node_modules/chalk/source/index.d.ts +325 -0
  90. package/novac/node_modules/chalk/source/index.js +225 -0
  91. package/novac/node_modules/chalk/source/utilities.js +33 -0
  92. package/novac/node_modules/chalk/source/vendor/ansi-styles/index.d.ts +236 -0
  93. package/novac/node_modules/chalk/source/vendor/ansi-styles/index.js +223 -0
  94. package/novac/node_modules/chalk/source/vendor/supports-color/browser.d.ts +1 -0
  95. package/novac/node_modules/chalk/source/vendor/supports-color/browser.js +34 -0
  96. package/novac/node_modules/chalk/source/vendor/supports-color/index.d.ts +55 -0
  97. package/novac/node_modules/chalk/source/vendor/supports-color/index.js +190 -0
  98. package/novac/node_modules/commander/LICENSE +22 -0
  99. package/novac/node_modules/commander/Readme.md +1176 -0
  100. package/novac/node_modules/commander/esm.mjs +16 -0
  101. package/novac/node_modules/commander/index.js +24 -0
  102. package/novac/node_modules/commander/lib/argument.js +150 -0
  103. package/novac/node_modules/commander/lib/command.js +2777 -0
  104. package/novac/node_modules/commander/lib/error.js +39 -0
  105. package/novac/node_modules/commander/lib/help.js +747 -0
  106. package/novac/node_modules/commander/lib/option.js +380 -0
  107. package/novac/node_modules/commander/lib/suggestSimilar.js +101 -0
  108. package/novac/node_modules/commander/package-support.json +19 -0
  109. package/novac/node_modules/commander/package.json +82 -0
  110. package/novac/node_modules/commander/typings/esm.d.mts +3 -0
  111. package/novac/node_modules/commander/typings/index.d.ts +1113 -0
  112. package/novac/node_modules/node-addon-api/LICENSE.md +9 -0
  113. package/novac/node_modules/node-addon-api/README.md +95 -0
  114. package/novac/node_modules/node-addon-api/common.gypi +21 -0
  115. package/novac/node_modules/node-addon-api/except.gypi +25 -0
  116. package/novac/node_modules/node-addon-api/index.js +14 -0
  117. package/novac/node_modules/node-addon-api/napi-inl.deprecated.h +186 -0
  118. package/novac/node_modules/node-addon-api/napi-inl.h +7165 -0
  119. package/novac/node_modules/node-addon-api/napi.h +3364 -0
  120. package/novac/node_modules/node-addon-api/node_addon_api.gyp +42 -0
  121. package/novac/node_modules/node-addon-api/node_api.gyp +9 -0
  122. package/novac/node_modules/node-addon-api/noexcept.gypi +26 -0
  123. package/novac/node_modules/node-addon-api/package-support.json +21 -0
  124. package/novac/node_modules/node-addon-api/package.json +480 -0
  125. package/novac/node_modules/node-addon-api/tools/README.md +73 -0
  126. package/novac/node_modules/node-addon-api/tools/check-napi.js +99 -0
  127. package/novac/node_modules/node-addon-api/tools/clang-format.js +71 -0
  128. package/novac/node_modules/node-addon-api/tools/conversion.js +301 -0
  129. package/novac/node_modules/serialize-javascript/LICENSE +27 -0
  130. package/novac/node_modules/serialize-javascript/README.md +149 -0
  131. package/novac/node_modules/serialize-javascript/index.js +297 -0
  132. package/novac/node_modules/serialize-javascript/package.json +33 -0
  133. package/novac/package.json +27 -0
  134. package/novac/scripts/update-bin.js +24 -0
  135. package/novac/src/core/bstd.js +1035 -0
  136. package/novac/src/core/config.js +155 -0
  137. package/novac/src/core/describe.js +187 -0
  138. package/novac/src/core/emitter.js +499 -0
  139. package/novac/src/core/error.js +86 -0
  140. package/novac/src/core/executor.js +5606 -0
  141. package/novac/src/core/formatter.js +686 -0
  142. package/novac/src/core/lexer.js +1026 -0
  143. package/novac/src/core/nova_builtins.js +717 -0
  144. package/novac/src/core/nova_thread_worker.js +166 -0
  145. package/novac/src/core/parser.js +2181 -0
  146. package/novac/src/core/types.js +112 -0
  147. package/novac/src/index.js +28 -0
  148. package/novac/src/runtime/stdlib.js +244 -0
  149. package/package.json +6 -3
  150. package/scripts/update-bin.js +0 -0
  151. package/src/core/bstd.js +838 -362
  152. package/src/core/executor.js +2578 -170
  153. package/src/core/lexer.js +502 -54
  154. package/src/core/nova_builtins.js +21 -3
  155. package/src/core/parser.js +413 -72
  156. package/src/core/types.js +30 -2
  157. package/src/index.js +0 -0
  158. package/examples/example-project/README.md +0 -3
  159. package/examples/example-project/src/main.nova +0 -3
  160. package/src/core/environment.js +0 -0
  161. /package/{examples/example-project/bin/example-project.nv → novac/node_modules/node-addon-api/nothing.c} +0 -0
@@ -0,0 +1,508 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * NVML Parser v2
5
+ *
6
+ * Grammar:
7
+ *
8
+ * program → topNode*
9
+ *
10
+ * topNode → at_block | element_node
11
+ *
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)*
22
+ *
23
+ * element_node → ELEMENT ('=' value)? ('[' element_body ']')?
24
+ *
25
+ * element_body → (property | child | parent_ref)*
26
+ *
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 ']' | ε)
33
+ *
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
39
+ */
40
+
41
+ const { Lexer, TT } = require('./lexer');
42
+
43
+ class ParseError extends Error {
44
+ constructor(msg, token) {
45
+ super(`[NVML Parser ${token ? token.line + ':' + token.col : '?'}] ${msg}`);
46
+ this.name = 'ParseError';
47
+ this.token = token;
48
+ }
49
+ }
50
+
51
+ const N = {
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 }),
77
+ };
78
+
79
+ class Parser {
80
+ constructor(source) {
81
+ this.tokens = new Lexer(source).tokenize();
82
+ this.current = 0;
83
+ }
84
+
85
+ peek() { return this.tokens[this.current]; }
86
+ previous() { return this.tokens[this.current - 1]; }
87
+ isAtEnd() { return this.peek().type === TT.EOF; }
88
+
89
+ advance() { if (!this.isAtEnd()) this.current++; return this.previous(); }
90
+
91
+ check(type, value = null) {
92
+ if (this.isAtEnd()) return false;
93
+ const t = this.peek();
94
+ return t.type === type && (value === null || t.value === value);
95
+ }
96
+
97
+ match(...specs) {
98
+ for (const spec of specs) {
99
+ const [type, value = null] = Array.isArray(spec) ? spec : [spec];
100
+ if (this.check(type, value)) { this.advance(); return true; }
101
+ }
102
+ return false;
103
+ }
104
+
105
+ consume(type, value, msg) {
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();
108
+ throw new ParseError(msg || `Expected ${type}${value ? ' ' + value : ''}`, this.peek());
109
+ }
110
+
111
+ error(msg) { throw new ParseError(msg, this.peek()); }
112
+
113
+ // ── entry ──────────────────────────────────────────────────────
114
+
115
+ parse() {
116
+ const body = [];
117
+ while (!this.isAtEnd()) body.push(this.parseTopNode());
118
+ return N.program(body);
119
+ }
120
+
121
+ parseTopNode() {
122
+ const t = this.peek();
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`);
126
+ }
127
+
128
+ // ── @blocks ────────────────────────────────────────────────────
129
+
130
+ parseAtBlock() {
131
+ const name = this.advance().value; // consume AT_IDENT
132
+
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
+ }
200
+
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
+ }
208
+ }
209
+ }
210
+
211
+ parseBlockList() {
212
+ const body = [];
213
+ while (!this.isAtEnd() && !this.check(TT.RBRACKET)) {
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;
220
+ }
221
+ return body;
222
+ }
223
+
224
+ // ── kv list (flat key=value pairs) ────────────────────────────
225
+
226
+ parseKVList() {
227
+ const pairs = {};
228
+ while (!this.isAtEnd() && !this.check(TT.RBRACKET)) {
229
+ if (this.match(TT.COMMA)) continue;
230
+ if (!this.check(TT.IDENT)) break;
231
+ const key = this.advance().value;
232
+ this.consume(TT.EQ, null, `Expected '=' after key '${key}'`);
233
+ pairs[key] = this.parseValue();
234
+ this.match(TT.COMMA);
235
+ }
236
+ return pairs;
237
+ }
238
+
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);
253
+ }
254
+ return pairs;
255
+ }
256
+
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 ────────────────────────────────────────────────────
303
+
304
+ parseElement() {
305
+ const tag = this.advance().value;
306
+
307
+ let textValue = null;
308
+ if (this.match(TT.EQ)) textValue = this.parseValue();
309
+
310
+ let props = {};
311
+ let body = [];
312
+ let bindings = [];
313
+ let transitions= [];
314
+ let cond = null;
315
+
316
+ if (this.match(TT.LBRACKET)) {
317
+ const parsed = this.parseElementBody();
318
+ props = parsed.props;
319
+ body = parsed.body;
320
+ bindings = parsed.bindings;
321
+ transitions = parsed.transitions;
322
+ this.consume(TT.RBRACKET, null, `Expected ']' to close element {${tag}}`);
323
+ }
324
+
325
+ return N.element(tag, textValue, props, body, bindings, transitions, cond);
326
+ }
327
+
328
+ parseElementBody() {
329
+ const props = {}, body = [], bindings = [], transitions = [];
330
+
331
+ while (!this.isAtEnd() && !this.check(TT.RBRACKET)) {
332
+ const t = this.peek();
333
+
334
+ // [..] parent-ref
335
+ if (t.type === TT.DOTDOT) {
336
+ const pp = this.parseParentPropInline();
337
+ if (pp.subBody !== null) props[pp.prop] = { __subBody: pp.subBody };
338
+ else props[pp.prop] = pp.value;
339
+ continue;
340
+ }
341
+
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);
349
+ continue;
350
+ }
351
+
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());
355
+ continue;
356
+ }
357
+
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
376
+ if (t.type === TT.IDENT) {
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
+
403
+ if (this.match(TT.EQ)) {
404
+ props[key] = this.parseValue();
405
+ } else {
406
+ props[key] = N.value(true); // boolean flag
407
+ }
408
+ this.match(TT.COMMA);
409
+ continue;
410
+ }
411
+
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));
416
+ continue;
417
+ }
418
+
419
+ if (t.type === TT.NUMBER) { this.advance(); this.match(TT.COMMA); continue; }
420
+ if (t.type === TT.COMMA) { this.advance(); continue; }
421
+
422
+ this.error(`Unexpected token '${t.value}' (${t.type}) inside element body`);
423
+ }
424
+
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);
449
+ }
450
+
451
+ // ── [..] property ──────────────────────────────────────────────
452
+
453
+ parseParentPropInline() {
454
+ this.advance(); // consume DOTDOT
455
+ this.consume(TT.COLONCOLON, null, "Expected '::' after '[..]'");
456
+ const prop = this.consume(TT.IDENT, null, "Expected property name after '::'").value;
457
+
458
+ if (this.match(TT.LBRACKET)) {
459
+ const subBody = [];
460
+ while (!this.isAtEnd() && !this.check(TT.RBRACKET)) {
461
+ if (this.check(TT.ELEMENT) || this.check(TT.AT_IDENT) || this.check(TT.DOTDOT)) {
462
+ subBody.push(this.parseTopNode());
463
+ } else if (this.check(TT.IDENT)) {
464
+ const key = this.advance().value;
465
+ if (this.match(TT.EQ)) {
466
+ subBody.push(N.cssDecl(key, this.parseValue()));
467
+ } else {
468
+ subBody.push(N.cssDecl(key, N.value(true)));
469
+ }
470
+ this.match(TT.COMMA);
471
+ } else if (this.check(TT.COMMA)) {
472
+ this.advance();
473
+ } else { break; }
474
+ }
475
+ this.consume(TT.RBRACKET, null, `Expected ']' to close [..]::${prop} block`);
476
+ return N.parentProp(prop, null, subBody);
477
+ }
478
+
479
+ if (this.match(TT.EQ)) return N.parentProp(prop, this.parseValue(), null);
480
+ return N.parentProp(prop, N.value(true), null);
481
+ }
482
+
483
+ // ── value ──────────────────────────────────────────────────────
484
+
485
+ parseValue() {
486
+ const t = this.peek();
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})`);
493
+ }
494
+
495
+ parsePipeList() {
496
+ this.consume(TT.PIPE, null, "Expected '|'");
497
+ const items = [];
498
+ while (!this.isAtEnd() && !this.check(TT.PIPE)) {
499
+ const t = this.peek();
500
+ if (t.type === TT.IDENT || t.type === TT.STRING) items.push(this.advance().value);
501
+ this.match(TT.COMMA);
502
+ }
503
+ this.consume(TT.PIPE, null, "Expected closing '|'");
504
+ return N.pipeList(items);
505
+ }
506
+ }
507
+
508
+ module.exports = { Parser, ParseError, N };