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
package/src/core/parser.js
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* @decorator syntax (stored but not enforced)
|
|
11
11
|
* 'new' as expression keyword -> call with new flag
|
|
12
12
|
*/
|
|
13
|
-
const { Lexer, PUNCTUATION } = require('./lexer');
|
|
13
|
+
const { Lexer, CLASSIC_KEYWORDS, PUNCTUATION } = require('./lexer');
|
|
14
14
|
const { CustomError } = require('./error');
|
|
15
15
|
|
|
16
16
|
class Parser {
|
|
@@ -19,6 +19,7 @@ class Parser {
|
|
|
19
19
|
this.rawsrc = source;
|
|
20
20
|
this.current = 0;
|
|
21
21
|
this.symbols = new Map();
|
|
22
|
+
this.classicMode = false; // true while parsing inside @classic { }
|
|
22
23
|
}
|
|
23
24
|
|
|
24
25
|
parse() {
|
|
@@ -37,7 +38,19 @@ class Parser {
|
|
|
37
38
|
statement() {
|
|
38
39
|
// decorators
|
|
39
40
|
let decorators = [];
|
|
40
|
-
while (this.check('PUNCTUATION', '@')) {
|
|
41
|
+
while (this.check('PUNCTUATION', '@')) {
|
|
42
|
+
// peek if this is @classic — if so, parse as classic block not decorator
|
|
43
|
+
const savedCurrent = this.current;
|
|
44
|
+
this.advance(); // consume @
|
|
45
|
+
if (this.check('KEYWORD', 'classic') || (this.check('IDENTIFIER', 'classic'))) {
|
|
46
|
+
this.advance(); // consume 'classic'
|
|
47
|
+
return this.classicBlock();
|
|
48
|
+
}
|
|
49
|
+
// not @classic — restore and treat as decorator
|
|
50
|
+
this.current = savedCurrent;
|
|
51
|
+
this.advance();
|
|
52
|
+
decorators.push(this.expression());
|
|
53
|
+
}
|
|
41
54
|
|
|
42
55
|
if (this.match('KEYWORD', 'class')) return this.classDeclaration(decorators);
|
|
43
56
|
if (this.match('KEYWORD', 'type')) return this.typeDeclaration();
|
|
@@ -72,59 +85,59 @@ class Parser {
|
|
|
72
85
|
if (this.match('KEYWORD', 'emit')) return this.emitStatement();
|
|
73
86
|
if (this.match('KEYWORD', 'on')) return this.onStatement();
|
|
74
87
|
if (this.match('KEYWORD', 'where')) return this.whereStatement();
|
|
75
|
-
// ── Nova Classic features ──
|
|
76
|
-
if (this.match('KEYWORD', 'foreach')) return this.foreachStatement();
|
|
77
|
-
if (this.match('KEYWORD', 'temp')) return this.tempStatement();
|
|
78
|
-
if (this.match('KEYWORD', 'keep')) return this.keepStatement();
|
|
79
|
-
if (this.match('KEYWORD', 'echo')) return this.echoStatement();
|
|
80
|
-
if (this.match('KEYWORD', 'gear')) return this.gearStatement();
|
|
81
|
-
if (this.match('KEYWORD', 'engage')) return this.engageStatement();
|
|
82
|
-
if (this.match('KEYWORD', 'sandbox')) return this.sandboxStatement();
|
|
83
|
-
if (this.match('KEYWORD', 'infer')) return this.inferStatement();
|
|
84
|
-
if (this.match('KEYWORD', 'addto')) return this.addtoStatement();
|
|
85
|
-
if (this.match('KEYWORD', 'macro')) return this.macroStatement();
|
|
86
|
-
if (this.match('KEYWORD', 'block')) return this.blockDeclStatement();
|
|
87
|
-
if (this.match('KEYWORD', 'snippet')) return this.snippetStatement();
|
|
88
|
-
if (this.match('KEYWORD', 'defunc')) return this.defuncStatement();
|
|
89
|
-
if (this.match('KEYWORD', 'lambda')) return this.lambdaStatement();
|
|
90
|
-
if (this.match('KEYWORD', 'compose')) return this.composeStatement();
|
|
91
|
-
if (this.match('KEYWORD', 'partial')) return this.partialStatement();
|
|
92
|
-
if (this.match('KEYWORD', 'backup')) return this.backupStatement();
|
|
93
|
-
if (this.match('KEYWORD', 'retrieve')) return this.retrieveStatement();
|
|
94
|
-
if (this.match('KEYWORD', 'describe')) return this.describeStatement();
|
|
95
|
-
if (this.match('KEYWORD', 'using')) return this.usingStatement();
|
|
96
|
-
if (this.match('KEYWORD', 'unuse')) return this.unuseStatement();
|
|
97
|
-
if (this.match('KEYWORD', 'classify')) return this.classifyStatement();
|
|
98
|
-
if (this.match('KEYWORD', 'rate')) return this.rateStatement();
|
|
99
|
-
if (this.check('EXEC_COMMENT')) { const t = this.advance(); return this.nd({ kind: 'exec_comment', code: t.value }); }
|
|
100
|
-
// ── Nova Classic (ported from old impl) ──
|
|
101
88
|
if (this.match('KEYWORD', 'guard')) return this.guardStatement();
|
|
102
89
|
if (this.match('KEYWORD', 'when')) return this.whenStatement();
|
|
103
90
|
if (this.match('KEYWORD', 'with')) return this.withStatement();
|
|
104
91
|
if (this.match('KEYWORD', 'loop')) return this.loopStatement();
|
|
105
92
|
if (this.match('KEYWORD', 'wait')) return this.waitStatement();
|
|
106
|
-
if (this.
|
|
107
|
-
|
|
108
|
-
if (this.match('KEYWORD', '
|
|
109
|
-
if (this.match('KEYWORD', '
|
|
110
|
-
if (this.match('KEYWORD', '
|
|
111
|
-
if (this.match('KEYWORD', '
|
|
112
|
-
if (this.match('KEYWORD', '
|
|
113
|
-
if (this.match('KEYWORD', '
|
|
114
|
-
if (this.match('KEYWORD', '
|
|
115
|
-
if (this.match('KEYWORD', '
|
|
116
|
-
if (this.match('KEYWORD', '
|
|
117
|
-
if (this.match('KEYWORD', '
|
|
118
|
-
if (this.match('KEYWORD', '
|
|
119
|
-
if (this.match('KEYWORD', '
|
|
120
|
-
if (this.match('KEYWORD', '
|
|
121
|
-
if (this.match('KEYWORD', '
|
|
122
|
-
if (this.match('KEYWORD', '
|
|
123
|
-
if (this.match('KEYWORD', '
|
|
124
|
-
if (this.match('KEYWORD', '
|
|
125
|
-
if (this.match('KEYWORD', '
|
|
126
|
-
if (this.match('KEYWORD', '
|
|
127
|
-
if (this.match('KEYWORD', '
|
|
93
|
+
if (this.check('EXEC_COMMENT')) { const t = this.advance(); return this.nd({ kind: 'exec_comment', code: t.value }); }
|
|
94
|
+
// ── Nova Classic features — only valid inside @classic { } ──
|
|
95
|
+
if (this.match('KEYWORD', 'foreach')) return this.classicMode ? this.foreachStatement() : this._classicError('foreach');
|
|
96
|
+
if (this.match('KEYWORD', 'temp')) return this.classicMode ? this.tempStatement() : this._classicError('temp');
|
|
97
|
+
if (this.match('KEYWORD', 'keep')) return this.classicMode ? this.keepStatement() : this._classicError('keep');
|
|
98
|
+
if (this.match('KEYWORD', 'echo')) return this.classicMode ? this.echoStatement() : this._classicError('echo');
|
|
99
|
+
if (this.match('KEYWORD', 'gear')) return this.classicMode ? this.gearStatement() : this._classicError('gear');
|
|
100
|
+
if (this.match('KEYWORD', 'engage')) return this.classicMode ? this.engageStatement() : this._classicError('engage');
|
|
101
|
+
if (this.match('KEYWORD', 'sandbox')) return this.classicMode ? this.sandboxStatement() : this._classicError('sandbox');
|
|
102
|
+
if (this.match('KEYWORD', 'infer')) return this.classicMode ? this.inferStatement() : this._classicError('infer');
|
|
103
|
+
if (this.match('KEYWORD', 'addto')) return this.classicMode ? this.addtoStatement() : this._classicError('addto');
|
|
104
|
+
if (this.match('KEYWORD', 'macro')) return this.classicMode ? this.macroStatement() : this._classicError('macro');
|
|
105
|
+
if (this.match('KEYWORD', 'block')) return this.blockDeclStatement();
|
|
106
|
+
if (this.match('KEYWORD', 'snippet')) return this.classicMode ? this.snippetStatement() : this._classicError('snippet');
|
|
107
|
+
if (this.match('KEYWORD', 'defunc')) return this.classicMode ? this.defuncStatement() : this._classicError('defunc');
|
|
108
|
+
if (this.match('KEYWORD', 'lambda')) return this.classicMode ? this.lambdaStatement() : this._classicError('lambda');
|
|
109
|
+
if (this.match('KEYWORD', 'compose')) return this.classicMode ? this.composeStatement() : this._classicError('compose');
|
|
110
|
+
if (this.match('KEYWORD', 'partial')) return this.classicMode ? this.partialStatement() : this._classicError('partial');
|
|
111
|
+
if (this.match('KEYWORD', 'backup')) return this.classicMode ? this.backupStatement() : this._classicError('backup');
|
|
112
|
+
if (this.match('KEYWORD', 'retrieve')) return this.classicMode ? this.retrieveStatement() : this._classicError('retrieve');
|
|
113
|
+
if (this.match('KEYWORD', 'describe')) return this.classicMode ? this.describeStatement() : this._classicError('describe');
|
|
114
|
+
if (this.match('KEYWORD', 'using')) return this.classicMode ? this.usingStatement() : this._classicError('using');
|
|
115
|
+
if (this.match('KEYWORD', 'unuse')) return this.classicMode ? this.unuseStatement() : this._classicError('unuse');
|
|
116
|
+
if (this.match('KEYWORD', 'classify')) return this.classicMode ? this.classifyStatement() : this._classicError('classify');
|
|
117
|
+
if (this.match('KEYWORD', 'rate')) return this.classicMode ? this.rateStatement() : this._classicError('rate');
|
|
118
|
+
if (this.match('KEYWORD', 'lend')) return this.classicMode ? this.lendStatement() : this._classicError('lend');
|
|
119
|
+
if (this.match('KEYWORD', 'sstream')) return this.classicMode ? this.sstreamStatement() : this._classicError('sstream');
|
|
120
|
+
if (this.match('KEYWORD', 'declare')) return this.classicMode ? this.declareStatement() : this._classicError('declare');
|
|
121
|
+
if (this.match('KEYWORD', 'session')) return this.classicMode ? this.sessionStatement() : this._classicError('session');
|
|
122
|
+
if (this.match('KEYWORD', 'enter')) return this.classicMode ? this.enterStatement() : this._classicError('enter');
|
|
123
|
+
if (this.match('KEYWORD', 'resu')) return this.classicMode ? this.resuStatement() : this._classicError('resu');
|
|
124
|
+
if (this.match('KEYWORD', 'warn')) return this.classicMode ? this.warnStatement() : this._classicError('warn');
|
|
125
|
+
if (this.match('KEYWORD', 'info')) return this.classicMode ? this.infoStatement() : this._classicError('info');
|
|
126
|
+
if (this.match('KEYWORD', 'skip')) { if (!this.classicMode) return this._classicError('skip'); this.match('PUNCTUATION', ';'); return this.nd({ kind: 'skip' }); }
|
|
127
|
+
if (this.match('KEYWORD', 'end')) { if (!this.classicMode) return this._classicError('end'); this.match('PUNCTUATION', ';'); return this.nd({ kind: 'end_stmt' }); }
|
|
128
|
+
if (this.match('KEYWORD', 'clear')) { if (!this.classicMode) return this._classicError('clear'); this.match('PUNCTUATION', ';'); return this.nd({ kind: 'clear_env' }); }
|
|
129
|
+
if (this.match('KEYWORD', 'time')) return this.classicMode ? this.timeStatement() : this._classicError('time');
|
|
130
|
+
if (this.match('KEYWORD', 'keyfunc')) return this.classicMode ? this.keyfuncStatement() : this._classicError('keyfunc');
|
|
131
|
+
if (this.match('KEYWORD', 'print')) return this.classicMode ? this.printStatement() : this._classicError('print');
|
|
132
|
+
if (this.match('KEYWORD', 'log')) return this.classicMode ? this.logStatement() : this._classicError('log');
|
|
133
|
+
if (this.match('KEYWORD', 'println')) return this.classicMode ? this.printlnStatement() : this._classicError('println');
|
|
134
|
+
if (this.match('KEYWORD', 'logln')) return this.classicMode ? this.loglnStatement() : this._classicError('logln');
|
|
135
|
+
if (this.match('KEYWORD', 'expt')) return this.classicMode ? this.exptStatement() : this._classicError('expt');
|
|
136
|
+
if (this.match('KEYWORD', 'option')) return this.classicMode ? this.optionStatement() : this._classicError('option');
|
|
137
|
+
if (this.match('KEYWORD', 'env')) return this.classicMode ? this.envStatement() : this._classicError('env');
|
|
138
|
+
if (this.match('KEYWORD', 'hello')) return this.classicMode ? this.helloStatement() : this._classicError('hello');
|
|
139
|
+
if (this.match('KEYWORD', 'eval')) return this.classicMode ? this.evalStatement() : this._classicError('eval');
|
|
140
|
+
if (this.match('KEYWORD', 'register_escape')) return this.classicMode ? this.registerEscapeStatement() : this._classicError('register_escape');
|
|
128
141
|
if (this.match('KEYWORD', 'import')) return this.importStatement();
|
|
129
142
|
if (this.match('KEYWORD', 'import_builtin')) return this.importBuiltinStatement();
|
|
130
143
|
if (this.match('KEYWORD', 'from')) return this.fromStatement();
|
|
@@ -134,9 +147,89 @@ class Parser {
|
|
|
134
147
|
if (this.match('KEYWORD', 'default')) return this.defaultStatement();
|
|
135
148
|
if (this.match('KEYWORD', 'namespace')) return this.namespaceStatement();
|
|
136
149
|
if (this.match('PUNCTUATION', '{')) return this.blockBody(true);
|
|
150
|
+
if (this.match('PUNCTUATION', '.')) return this.dotCmd();
|
|
137
151
|
return this.expressionStatement();
|
|
138
152
|
}
|
|
139
153
|
|
|
154
|
+
// ── @classic { } block ──
|
|
155
|
+
// Re-tokenizes the raw source of the block body using the lexer in classicMode,
|
|
156
|
+
// then parses all the statements inside with classicMode = true.
|
|
157
|
+
classicBlock() {
|
|
158
|
+
this.consume('PUNCTUATION', '{', 'Expected { after @classic');
|
|
159
|
+
// Collect raw source of the block body by scanning the original source
|
|
160
|
+
// from the current token's position to the matching '}'
|
|
161
|
+
const nodes = [];
|
|
162
|
+
const prevClassic = this.classicMode;
|
|
163
|
+
this.classicMode = true;
|
|
164
|
+
// Re-inject classic keywords: re-tokenize remaining source in classic mode
|
|
165
|
+
// then splice into this.tokens from current position onward.
|
|
166
|
+
const startPos = this.current;
|
|
167
|
+
// Find the raw source span for the block body
|
|
168
|
+
const startToken = this.tokens[startPos];
|
|
169
|
+
// Walk tokens to find matching } depth
|
|
170
|
+
let depth = 1;
|
|
171
|
+
let endPos = startPos;
|
|
172
|
+
while (endPos < this.tokens.length && depth > 0) {
|
|
173
|
+
const t = this.tokens[endPos];
|
|
174
|
+
if (t.type === 'PUNCTUATION' && t.value === '{') depth++;
|
|
175
|
+
if (t.type === 'PUNCTUATION' && t.value === '}') depth--;
|
|
176
|
+
if (depth > 0) endPos++;
|
|
177
|
+
}
|
|
178
|
+
// Extract raw source substring for the block body
|
|
179
|
+
const srcStart = startToken ? startToken.column : 0;
|
|
180
|
+
const endToken = this.tokens[endPos];
|
|
181
|
+
// Use line/column info to re-lex just the block body in classic mode
|
|
182
|
+
// Simpler: re-lex from the raw source, find the block span by matching braces
|
|
183
|
+
const rawBody = this._extractBlockSource();
|
|
184
|
+
const classicLexer = new Lexer(rawBody, true);
|
|
185
|
+
classicLexer.definitions = new Map(); // fresh definitions
|
|
186
|
+
const classicTokens = classicLexer.tokenize();
|
|
187
|
+
// Replace our remaining tokens (up to and including the closing }) with classic-retokenized ones
|
|
188
|
+
this.tokens.splice(this.current, endPos - this.current + 1, ...classicTokens.slice(0, -1)); // drop EOF
|
|
189
|
+
// Now parse statements until we hit the matching }
|
|
190
|
+
while (!this.check('PUNCTUATION', '}') && !this.isAtEnd()) {
|
|
191
|
+
const n = this.statement();
|
|
192
|
+
if (n) nodes.push(n);
|
|
193
|
+
}
|
|
194
|
+
this.classicMode = prevClassic;
|
|
195
|
+
return this.nd({ kind: 'classic_block', body: nodes });
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Extract the raw source text of the { } block at the current position
|
|
199
|
+
// by scanning this.rawsrc for matching braces from the first { token's position.
|
|
200
|
+
_extractBlockSource() {
|
|
201
|
+
// Find character offset of the next { in rawsrc starting near current token's line/col
|
|
202
|
+
const firstTok = this.tokens[this.current];
|
|
203
|
+
// Walk rawsrc to find the opening { that corresponds to current token position
|
|
204
|
+
// Use line tracking to approximate position
|
|
205
|
+
let pos = 0, line = 1;
|
|
206
|
+
while (pos < this.rawsrc.length) {
|
|
207
|
+
if (line === firstTok.line || (firstTok.line <= 1)) {
|
|
208
|
+
// scan forward for {
|
|
209
|
+
const braceIdx = this.rawsrc.indexOf('{', pos);
|
|
210
|
+
if (braceIdx === -1) break;
|
|
211
|
+
// Extract body between matching braces
|
|
212
|
+
let depth = 0, i = braceIdx;
|
|
213
|
+
while (i < this.rawsrc.length) {
|
|
214
|
+
if (this.rawsrc[i] === '{') depth++;
|
|
215
|
+
else if (this.rawsrc[i] === '}') { depth--; if (depth === 0) { return this.rawsrc.slice(braceIdx + 1, i); } }
|
|
216
|
+
i++;
|
|
217
|
+
}
|
|
218
|
+
break;
|
|
219
|
+
}
|
|
220
|
+
if (this.rawsrc[pos] === '\n') line++;
|
|
221
|
+
pos++;
|
|
222
|
+
}
|
|
223
|
+
return '';
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
_classicError(keyword) {
|
|
227
|
+
throw new (require('./error').CustomError('ClassicError'))(
|
|
228
|
+
`'${keyword}' is a Classic novac keyword and must be used inside an @classic { } block.\n` +
|
|
229
|
+
`Wrap your legacy code: @classic { ${keyword} ... }`
|
|
230
|
+
);
|
|
231
|
+
}
|
|
232
|
+
|
|
140
233
|
// ── type declarations ──
|
|
141
234
|
|
|
142
235
|
typeDeclaration() {
|
|
@@ -162,6 +255,7 @@ class Parser {
|
|
|
162
255
|
|
|
163
256
|
parseTypeExpr() {
|
|
164
257
|
// Parse: TypeA | TypeB | TypeC or TypeA & TypeB or { field: Type } or Name or Name<T>
|
|
258
|
+
// Also handles literal value unions: 1 | "a" | true | someType
|
|
165
259
|
if (this.check('PUNCTUATION', '{')) {
|
|
166
260
|
// object type shape
|
|
167
261
|
this.advance();
|
|
@@ -178,27 +272,52 @@ class Parser {
|
|
|
178
272
|
this.consume('PUNCTUATION', '}', 'Expected }');
|
|
179
273
|
return { kind: 'shape_type', fields };
|
|
180
274
|
}
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
this.
|
|
190
|
-
|
|
191
|
-
|
|
275
|
+
|
|
276
|
+
// Helper: parse a single type atom (identifier or literal)
|
|
277
|
+
const parseAtom = () => {
|
|
278
|
+
// Literal value atoms: number, string, bool keywords
|
|
279
|
+
if (this.check('NUMBER')) {
|
|
280
|
+
const v = this.advance().value;
|
|
281
|
+
return { kind: 'value_type', value: v };
|
|
282
|
+
}
|
|
283
|
+
if (this.check('STRING')) {
|
|
284
|
+
const v = this.advance().value;
|
|
285
|
+
return { kind: 'value_type', value: v };
|
|
286
|
+
}
|
|
287
|
+
if (this.check('LITERAL')) {
|
|
288
|
+
const v = this.advance().value;
|
|
289
|
+
return { kind: 'value_type', value: v };
|
|
290
|
+
}
|
|
291
|
+
// {string}, {bool}, {number} — type-in-braces shorthand
|
|
292
|
+
if (this.check('PUNCTUATION', '{')) {
|
|
293
|
+
this.advance();
|
|
294
|
+
const name = this.consume('IDENTIFIER', 'Expected type name').value;
|
|
295
|
+
this.consume('PUNCTUATION', '}', 'Expected }');
|
|
296
|
+
return { kind: 'named_type', name, args: [] };
|
|
297
|
+
}
|
|
298
|
+
const name = this.consume('IDENTIFIER', 'Expected type name').value;
|
|
299
|
+
const args = this.parseTypeParams();
|
|
300
|
+
let base = { kind: 'named_type', name, args };
|
|
301
|
+
// [] suffix for array type
|
|
302
|
+
while (this.check('PUNCTUATION', '[') && this.next()?.value === ']') {
|
|
303
|
+
this.advance(); this.advance();
|
|
304
|
+
base = { kind: 'array_type', of: base };
|
|
305
|
+
}
|
|
306
|
+
return base;
|
|
307
|
+
};
|
|
308
|
+
|
|
309
|
+
let base = parseAtom();
|
|
310
|
+
|
|
192
311
|
// union: |
|
|
193
312
|
if (this.check('OPERATOR', '|')) {
|
|
194
313
|
const variants = [base];
|
|
195
|
-
while (this.match('OPERATOR', '|')) variants.push(
|
|
314
|
+
while (this.match('OPERATOR', '|')) variants.push(parseAtom());
|
|
196
315
|
return { kind: 'union_type', variants };
|
|
197
316
|
}
|
|
198
317
|
// intersection: &
|
|
199
318
|
if (this.check('OPERATOR', '&')) {
|
|
200
319
|
const parts = [base];
|
|
201
|
-
while (this.match('OPERATOR', '&')) parts.push(
|
|
320
|
+
while (this.match('OPERATOR', '&')) parts.push(parseAtom());
|
|
202
321
|
return { kind: 'intersect_type', parts };
|
|
203
322
|
}
|
|
204
323
|
return base;
|
|
@@ -417,6 +536,16 @@ class Parser {
|
|
|
417
536
|
return this.nd({ kind: 'eval', code });
|
|
418
537
|
}
|
|
419
538
|
|
|
539
|
+
// register_escape NAME expr
|
|
540
|
+
// NAME — escape name (identifier)
|
|
541
|
+
// expr — expression whose value becomes the replacement (string or function)
|
|
542
|
+
registerEscapeStatement() {
|
|
543
|
+
const name = this.consume('IDENTIFIER', 'Expected escape name after register_escape').value;
|
|
544
|
+
const value = this.expression();
|
|
545
|
+
this.match('PUNCTUATION', ';');
|
|
546
|
+
return this.nd({ kind: 'register_escape', name, value });
|
|
547
|
+
}
|
|
548
|
+
|
|
420
549
|
importStatement() {
|
|
421
550
|
const source = this.expression();
|
|
422
551
|
let names = null;
|
|
@@ -581,7 +710,41 @@ class Parser {
|
|
|
581
710
|
} else {
|
|
582
711
|
nameNode = this.consume('IDENTIFIER', 'Expected variable name');
|
|
583
712
|
let explicitType = null;
|
|
584
|
-
|
|
713
|
+
// ── smart modifiers ──
|
|
714
|
+
const modifiers = {};
|
|
715
|
+
while (true) {
|
|
716
|
+
if (this.match('KEYWORD', 'setter')) {
|
|
717
|
+
// setter <expr> — expression that returns a function (v, old) => newVal
|
|
718
|
+
modifiers.setter = this.expression(15); // high prec so we only grab one token/call
|
|
719
|
+
} else if (this.match('KEYWORD', 'getter')) {
|
|
720
|
+
// getter <expr>
|
|
721
|
+
modifiers.getter = this.expression(15);
|
|
722
|
+
} else if (this.match('KEYWORD', 'frozen')) {
|
|
723
|
+
modifiers.frozen = true;
|
|
724
|
+
} else if (this.match('KEYWORD', 'lazy')) {
|
|
725
|
+
modifiers.lazy = true;
|
|
726
|
+
} else if (this.match('KEYWORD', 'tracked')) {
|
|
727
|
+
modifiers.tracked = true;
|
|
728
|
+
} else if (this.match('KEYWORD', 'nonull')) {
|
|
729
|
+
modifiers.nonull = true;
|
|
730
|
+
} else if (this.match('KEYWORD', 'once')) {
|
|
731
|
+
modifiers.once = true;
|
|
732
|
+
} else if (this.match('KEYWORD', 'as')) {
|
|
733
|
+
// as fnum <expr> | as fint <expr>
|
|
734
|
+
if (this.match('KEYWORD', 'fnum') || this.match('KEYWORD', 'fint')) {
|
|
735
|
+
modifiers.clampType = this.previous().value; // 'fnum' or 'fint'
|
|
736
|
+
modifiers.clampExpr = this.expression(15); // expr returning [min, max]
|
|
737
|
+
} else {
|
|
738
|
+
// fallback: treat as regular type cast — re-read type
|
|
739
|
+
const type = this.parseTypeExpr();
|
|
740
|
+
explicitType = type;
|
|
741
|
+
}
|
|
742
|
+
} else {
|
|
743
|
+
break;
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
if (Object.keys(modifiers).length > 0) nameNode = { ...nameNode, modifiers };
|
|
747
|
+
if (!explicitType && this.match('PUNCTUATION', ':')) explicitType = this.parseTypeExpr();
|
|
585
748
|
nameNode = { ...nameNode, typeAnnotation: explicitType };
|
|
586
749
|
}
|
|
587
750
|
let value = null;
|
|
@@ -589,7 +752,9 @@ class Parser {
|
|
|
589
752
|
this.match('PUNCTUATION', ';');
|
|
590
753
|
if (destructure) return this.nd({ kind: 'declare', destructure, value, isConst, isPointer });
|
|
591
754
|
if (nameNode.value) this.symbols.set(nameNode.value, { type: nameNode.typeAnnotation || null, isConst });
|
|
592
|
-
return this.nd({ kind: 'declare', name: nameNode.value, value, isConst, isPointer,
|
|
755
|
+
return this.nd({ kind: 'declare', name: nameNode.value, value, isConst, isPointer,
|
|
756
|
+
explicitType: nameNode.typeAnnotation || null,
|
|
757
|
+
modifiers: nameNode.modifiers || null });
|
|
593
758
|
}
|
|
594
759
|
|
|
595
760
|
objectPattern() {
|
|
@@ -735,13 +900,68 @@ class Parser {
|
|
|
735
900
|
this.consume('PUNCTUATION', ')', 'Expected )');
|
|
736
901
|
left = this.nd({ kind: 'fetch_expr', url: fUrl, options: fOpts });
|
|
737
902
|
|
|
903
|
+
} else if (
|
|
904
|
+
(this.check('KEYWORD', 'async') && this._peekAsyncIsLambda()) ||
|
|
905
|
+
(this.check('IDENTIFIER', 'generator') && this._peekIsLambda())
|
|
906
|
+
) {
|
|
907
|
+
// ── async/generator lambda expressions ──────────────────────────────
|
|
908
|
+
// Supported forms:
|
|
909
|
+
// async (a, b) => body
|
|
910
|
+
// async v => body
|
|
911
|
+
// generator (a, b) => body
|
|
912
|
+
// generator v => body
|
|
913
|
+
// generator async (a, b) => body (mix)
|
|
914
|
+
// async generator (a, b) => body (mix, generator is an identifier)
|
|
915
|
+
let isAsync = false, isGenerator = false;
|
|
916
|
+
// Consume modifier keywords — allow both orderings.
|
|
917
|
+
// `async` is a KEYWORD; `generator` is an IDENTIFIER in the lexer.
|
|
918
|
+
let consumed = true;
|
|
919
|
+
while (consumed) {
|
|
920
|
+
consumed = false;
|
|
921
|
+
if (this.match('KEYWORD', 'async')) { isAsync = true; consumed = true; }
|
|
922
|
+
if (this.match('IDENTIFIER', 'generator')) { isGenerator = true; consumed = true; }
|
|
923
|
+
}
|
|
924
|
+
// Now expect either `(args)` or a single identifier, followed by `=>`
|
|
925
|
+
let arrowArgs;
|
|
926
|
+
if (this.check('PUNCTUATION', '(')) {
|
|
927
|
+
this.advance();
|
|
928
|
+
const rawArgs = [];
|
|
929
|
+
if (!this.check('PUNCTUATION', ')')) {
|
|
930
|
+
// Parse simple identifier list (no full expressions — avoids comma ambiguity)
|
|
931
|
+
do {
|
|
932
|
+
const t = this.peek();
|
|
933
|
+
if (t.type === 'IDENTIFIER') { this.advance(); rawArgs.push(t.value); }
|
|
934
|
+
else rawArgs.push(this.expression());
|
|
935
|
+
} while (this.match('PUNCTUATION', ','));
|
|
936
|
+
}
|
|
937
|
+
this.consume('PUNCTUATION', ')', 'Expected ) in async/generator lambda');
|
|
938
|
+
arrowArgs = rawArgs.map(a => typeof a === 'string' ? a : (a?.name ?? a));
|
|
939
|
+
} else {
|
|
940
|
+
// single bare identifier
|
|
941
|
+
const id = this.consume('IDENTIFIER', 'Expected argument or ( after async/generator');
|
|
942
|
+
arrowArgs = [id.value];
|
|
943
|
+
}
|
|
944
|
+
this.consume('OPERATOR', '=>', 'Expected => in async/generator lambda');
|
|
945
|
+
const arrowBody = this.check('PUNCTUATION', '{')
|
|
946
|
+
? this.blockBody()
|
|
947
|
+
: [this.nd({ kind: 'return', value: this.expression(), terminate: true })];
|
|
948
|
+
left = this.nd({ kind: 'arrowfunc', args: arrowArgs, body: arrowBody, isAsync, isGenerator });
|
|
949
|
+
|
|
738
950
|
} else if (this.check('KEYWORD', 'await')) {
|
|
739
951
|
this.advance(); left = this.nd({ kind: 'await', operand: this.expression(14) });
|
|
740
|
-
|
|
741
952
|
} else if (this.check('KEYWORD', 'new')) {
|
|
742
953
|
this.advance();
|
|
743
954
|
left = this.nd({ kind: 'new_expr', callee: this.expression(16) });
|
|
744
|
-
|
|
955
|
+
} else if (this.check('OPERATOR', '$')) {
|
|
956
|
+
this.advance();
|
|
957
|
+
const code = this.expression();
|
|
958
|
+
left = this.nd({ kind: 'dexpr', code });
|
|
959
|
+
} else if (this.check('KEYWORD', 'link')) {
|
|
960
|
+
this.advance();
|
|
961
|
+
this.consume('PUNCTUATION', '(', 'Expected ( after link');
|
|
962
|
+
const id = this.expression();
|
|
963
|
+
this.consume('PUNCTUATION', ')', 'Expected )');
|
|
964
|
+
left = this.nd({ kind: 'link', linked: id });
|
|
745
965
|
} else if (this.check('KEYWORD', 'rate')) {
|
|
746
966
|
this.advance();
|
|
747
967
|
this.consume('PUNCTUATION', '(', 'Expected ( after rate');
|
|
@@ -749,7 +969,6 @@ class Parser {
|
|
|
749
969
|
this.consume('PUNCTUATION', ')', 'Expected )');
|
|
750
970
|
const rateCastType = this.consumeName('Expected cast type after rate(...)').value;
|
|
751
971
|
left = this.nd({ kind: 'rate_cast', value: rateVal, cast_type: rateCastType });
|
|
752
|
-
|
|
753
972
|
} else if (this.check('KEYWORD', 'classify')) {
|
|
754
973
|
this.advance();
|
|
755
974
|
const classInner = this.expression(16);
|
|
@@ -814,6 +1033,13 @@ class Parser {
|
|
|
814
1033
|
} else if (tok.type === 'PUNCTUATION' && tok.value === '{') {
|
|
815
1034
|
left = this.objectLiteral();
|
|
816
1035
|
|
|
1036
|
+
} else if (tok.type === 'PUNCTUATION' && tok.value === '\\') {
|
|
1037
|
+
this.advance();
|
|
1038
|
+
let pattern = this.expression();
|
|
1039
|
+
this.consume('PUNCTUATION', '\\', 'Expected \\ in regex literal');
|
|
1040
|
+
let flags = '';
|
|
1041
|
+
if (this.check('IDENTIFIER')) flags = this.advance().value;
|
|
1042
|
+
left = this.nd({ kind: 'regex_literal', pattern, flags });
|
|
817
1043
|
} else if (tok.type === 'NUMBER' || tok.type === 'STRING' || tok.type === 'LITERAL') {
|
|
818
1044
|
left = this.nd({ kind: 'value', value: this.advance().value });
|
|
819
1045
|
|
|
@@ -834,6 +1060,11 @@ class Parser {
|
|
|
834
1060
|
}
|
|
835
1061
|
left = this.nd({ kind: 'ref', name, explicitType: this.symbols.get(name)?.type ?? null });
|
|
836
1062
|
|
|
1063
|
+
} else if (tok.type === 'DVAR') {
|
|
1064
|
+
// Dynamic / meta variable — carry source location for __line__, __col__
|
|
1065
|
+
const t = this.advance();
|
|
1066
|
+
left = this.nd({ kind: 'dvar', name: t.value, srcLine: t.srcLine, srcCol: t.srcCol });
|
|
1067
|
+
|
|
837
1068
|
} else if (tok.type === 'URL') {
|
|
838
1069
|
left = this.nd({ kind: 'url_literal', value: this.advance().value });
|
|
839
1070
|
|
|
@@ -860,6 +1091,15 @@ class Parser {
|
|
|
860
1091
|
} else if (tok.type === 'EOF') {
|
|
861
1092
|
return this.nd({ kind: 'EOF' });
|
|
862
1093
|
|
|
1094
|
+
} else if (tok.type === 'KEYWORD' && tok.value === 'nat') {
|
|
1095
|
+
this.advance();
|
|
1096
|
+
left = this.nd({ kind: 'native', value: this.expression() })
|
|
1097
|
+
} else if (tok.type === 'KEYWORD' && tok.value === 'run') {
|
|
1098
|
+
this.advance();
|
|
1099
|
+
left = this.nd({ kind: 'run_expr', code: this.statement() })
|
|
1100
|
+
} else if (tok.value === '=') {
|
|
1101
|
+
this.advance();
|
|
1102
|
+
left = this.nd({ kind: 'ast', ast: this.statement() });
|
|
863
1103
|
} else {
|
|
864
1104
|
this.error("Unexpected token '" + tok.value + "'");
|
|
865
1105
|
}
|
|
@@ -869,6 +1109,14 @@ class Parser {
|
|
|
869
1109
|
tok = this.peek();
|
|
870
1110
|
if (tok.type === 'PUNCTUATION' && (tok.value === ';' || tok.value === '}')) break;
|
|
871
1111
|
if (tok.type === 'PUNCTUATION' && tok.value === '{') break; // always a block
|
|
1112
|
+
if (tok.type === 'KEYWORD' && tok.value === 'if') {
|
|
1113
|
+
this.advance();
|
|
1114
|
+
let cond = this.expression();
|
|
1115
|
+
let elseExpr = null;
|
|
1116
|
+
if (this.match('KEYWORD', 'else')) elseExpr = this.expression();
|
|
1117
|
+
left = this.nd({ kind: 'if_ternary', cond, elseExpr, operand: left });
|
|
1118
|
+
}
|
|
1119
|
+
|
|
872
1120
|
|
|
873
1121
|
// Postfix ++ / --
|
|
874
1122
|
if (tok.type === 'OPERATOR' && (tok.value === '++' || tok.value === '--')) {
|
|
@@ -909,8 +1157,16 @@ class Parser {
|
|
|
909
1157
|
// Property access
|
|
910
1158
|
if (tok.type === 'PUNCTUATION' && tok.value === '.') {
|
|
911
1159
|
this.advance();
|
|
1160
|
+
if (this.match('PUNCTUATION', '(')) {
|
|
1161
|
+
let nameNode = this.nd({
|
|
1162
|
+
kind: 'subscript', object: left, index: this.nd({kind:'value', value: '.expr'})
|
|
1163
|
+
});
|
|
1164
|
+
const args = this.parseCallArgs();
|
|
1165
|
+
left = this.nd({ kind: 'call', name: nameNode, args })
|
|
1166
|
+
} else {
|
|
912
1167
|
const prop = this.consumeName('Expected property name after .');
|
|
913
1168
|
left = this.nd({ kind: 'prop', object: left, name: prop.value }); continue;
|
|
1169
|
+
}
|
|
914
1170
|
}
|
|
915
1171
|
|
|
916
1172
|
// Subscript
|
|
@@ -947,7 +1203,7 @@ class Parser {
|
|
|
947
1203
|
return left;
|
|
948
1204
|
}
|
|
949
1205
|
|
|
950
|
-
parseCallArgs() {
|
|
1206
|
+
parseCallArgs(needsClosing = true) {
|
|
951
1207
|
// Caller must have consumed '('
|
|
952
1208
|
const args = [];
|
|
953
1209
|
if (!this.check('PUNCTUATION', ')')) {
|
|
@@ -956,7 +1212,7 @@ class Parser {
|
|
|
956
1212
|
else args.push(this.expression());
|
|
957
1213
|
} while (this.match('PUNCTUATION', ','));
|
|
958
1214
|
}
|
|
959
|
-
this.consume('PUNCTUATION', ')', 'Expected )');
|
|
1215
|
+
if (needsClosing) this.consume('PUNCTUATION', ')', 'Expected )');
|
|
960
1216
|
return args;
|
|
961
1217
|
}
|
|
962
1218
|
|
|
@@ -1013,6 +1269,21 @@ class Parser {
|
|
|
1013
1269
|
// method shorthand
|
|
1014
1270
|
const { args, body } = this.parseFuncBody();
|
|
1015
1271
|
props.push({ kind: 'prop', key: propName, value: { kind: 'function', name: propName, args, body } });
|
|
1272
|
+
} else if (this.match('PUNCTUATION', '?')) {
|
|
1273
|
+
// Boolean presence syntax: { key? true } or { key? false }
|
|
1274
|
+
// The value MUST be a boolean — true or false
|
|
1275
|
+
const valTok = this.peek();
|
|
1276
|
+
let boolVal;
|
|
1277
|
+
if (this.match('KEYWORD', 'true') || this.match('LITERAL', 'true')) {
|
|
1278
|
+
boolVal = { kind: 'bool', value: true };
|
|
1279
|
+
} else if (this.match('KEYWORD', 'false') || this.match('LITERAL', 'false')) {
|
|
1280
|
+
boolVal = { kind: 'bool', value: false };
|
|
1281
|
+
} else {
|
|
1282
|
+
// Allow an expression that should evaluate to bool — parse and wrap in bool_assert
|
|
1283
|
+
const expr = this.expression();
|
|
1284
|
+
boolVal = { kind: 'bool_assert', expr };
|
|
1285
|
+
}
|
|
1286
|
+
props.push({ kind: 'prop', key: propName, value: boolVal, boolProp: true });
|
|
1016
1287
|
} else {
|
|
1017
1288
|
this.consume('PUNCTUATION', ':', 'Expected : after property name');
|
|
1018
1289
|
const val = this.expression();
|
|
@@ -1080,11 +1351,18 @@ class Parser {
|
|
|
1080
1351
|
}
|
|
1081
1352
|
|
|
1082
1353
|
blockBody(consumed) {
|
|
1083
|
-
if (
|
|
1354
|
+
if (consumed || this.match('PUNCTUATION', '{')) {
|
|
1084
1355
|
const body = [];
|
|
1085
1356
|
while (!this.check('PUNCTUATION', '}') && !this.isAtEnd()) body.push(this.statement());
|
|
1086
1357
|
this.consume('PUNCTUATION', '}', 'Expected }');
|
|
1087
1358
|
return body;
|
|
1359
|
+
} else return [this.statement()];
|
|
1360
|
+
}
|
|
1361
|
+
|
|
1362
|
+
dotCmd() {
|
|
1363
|
+
let cmd = this.consumeName('Expected command name.');
|
|
1364
|
+
let args = this.parseCallArgs(false);
|
|
1365
|
+
return this.nd({ kind: 'dot_cmd', cmd, args })
|
|
1088
1366
|
}
|
|
1089
1367
|
|
|
1090
1368
|
// Accept any identifier-like token as a name
|
|
@@ -1810,10 +2088,73 @@ class Parser {
|
|
|
1810
2088
|
previous() { return this.tokens[this.current - 1]; }
|
|
1811
2089
|
next() { return this.tokens[this.current + 1]; }
|
|
1812
2090
|
isAtEnd() { return this.peek().type === 'EOF'; }
|
|
2091
|
+
|
|
2092
|
+
// Returns true when the current position looks like the start of a lambda
|
|
2093
|
+
// parameter list — used to distinguish `generator(...)` call from
|
|
2094
|
+
// `generator (...) => ...` or `generator id => ...`.
|
|
2095
|
+
// We look ahead past optional whitespace for: `(`, an IDENTIFIER not followed
|
|
2096
|
+
// by `(`, or the `async` keyword — all of which signal a lambda head.
|
|
2097
|
+
// Like _peekIsLambda but for the `async` keyword at current position.
|
|
2098
|
+
// `async` is a lambda modifier when followed by: `(` + eventual `)=>`,
|
|
2099
|
+
// an identifier + `=>`, or the `generator` identifier.
|
|
2100
|
+
_peekAsyncIsLambda() {
|
|
2101
|
+
const n1 = this.tokens[this.current + 1];
|
|
2102
|
+
if (!n1) return false;
|
|
2103
|
+
if (n1.type === 'IDENTIFIER' && n1.value === 'generator') return true;
|
|
2104
|
+
if (n1.type === 'PUNCTUATION' && n1.value === '(') {
|
|
2105
|
+
let depth = 0, i = this.current + 1;
|
|
2106
|
+
while (i < this.tokens.length) {
|
|
2107
|
+
const t = this.tokens[i++];
|
|
2108
|
+
if (t.type === 'PUNCTUATION' && t.value === '(') depth++;
|
|
2109
|
+
else if (t.type === 'PUNCTUATION' && t.value === ')') {
|
|
2110
|
+
depth--;
|
|
2111
|
+
if (depth === 0) break;
|
|
2112
|
+
}
|
|
2113
|
+
}
|
|
2114
|
+
const afterParen = this.tokens[i];
|
|
2115
|
+
return afterParen && afterParen.type === 'OPERATOR' && afterParen.value === '=>';
|
|
2116
|
+
}
|
|
2117
|
+
if (n1.type === 'IDENTIFIER') {
|
|
2118
|
+
const n2 = this.tokens[this.current + 2];
|
|
2119
|
+
return n2 && n2.type === 'OPERATOR' && n2.value === '=>';
|
|
2120
|
+
}
|
|
2121
|
+
return false;
|
|
2122
|
+
}
|
|
2123
|
+
|
|
2124
|
+
_peekIsLambda() {
|
|
2125
|
+
// After `generator`, the next token decides:
|
|
2126
|
+
const n1 = this.tokens[this.current + 1]; // token after `generator`
|
|
2127
|
+
if (!n1) return false;
|
|
2128
|
+
// `generator async ...` or `generator (` — lambda
|
|
2129
|
+
if (n1.type === 'KEYWORD' && n1.value === 'async') return true;
|
|
2130
|
+
if (n1.type === 'PUNCTUATION' && n1.value === '(') {
|
|
2131
|
+
// Could be a call `generator(...)` or a lambda `generator (...) => ...`
|
|
2132
|
+
// Look further: find the matching `)` then check if `=>` follows
|
|
2133
|
+
let depth = 0, i = this.current + 1;
|
|
2134
|
+
while (i < this.tokens.length) {
|
|
2135
|
+
const t = this.tokens[i++];
|
|
2136
|
+
if (t.type === 'PUNCTUATION' && t.value === '(') depth++;
|
|
2137
|
+
else if (t.type === 'PUNCTUATION' && t.value === ')') {
|
|
2138
|
+
depth--;
|
|
2139
|
+
if (depth === 0) break;
|
|
2140
|
+
}
|
|
2141
|
+
}
|
|
2142
|
+
const afterParen = this.tokens[i];
|
|
2143
|
+
return afterParen && afterParen.type === 'OPERATOR' && afterParen.value === '=>';
|
|
2144
|
+
}
|
|
2145
|
+
// `generator identifier =>` — single-arg lambda
|
|
2146
|
+
if (n1.type === 'IDENTIFIER') {
|
|
2147
|
+
const n2 = this.tokens[this.current + 2];
|
|
2148
|
+
return n2 && n2.type === 'OPERATOR' && n2.value === '=>';
|
|
2149
|
+
}
|
|
2150
|
+
return false;
|
|
2151
|
+
}
|
|
1813
2152
|
advance() { if (!this.isAtEnd()) this.current++; return this.previous(); }
|
|
1814
2153
|
check(type, value = null) {
|
|
1815
2154
|
if (this.isAtEnd()) return false;
|
|
1816
2155
|
const t = this.peek();
|
|
2156
|
+
// Allow DVAR 'default' to match wherever KEYWORD 'default' is expected
|
|
2157
|
+
if (type === 'KEYWORD' && value === 'default' && t.type === 'DVAR' && t.value === 'default') return true;
|
|
1817
2158
|
return t.type === type && (value === null || t.value === value);
|
|
1818
2159
|
}
|
|
1819
2160
|
match(type, value = null) { if (this.check(type, value)) { this.advance(); return true; } return false; }
|
|
@@ -1840,4 +2181,4 @@ class Parser {
|
|
|
1840
2181
|
}
|
|
1841
2182
|
}
|
|
1842
2183
|
|
|
1843
|
-
module.exports = { Parser };
|
|
2184
|
+
module.exports = { Parser };
|