novac 2.2.0 → 2.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +0 -0
- package/README.md +0 -0
- package/bin/novac +6 -3
- package/bin/nvc +0 -0
- package/bin/nvml +0 -0
- package/demo.nv +0 -0
- package/demo_builtins.nv +0 -0
- package/demo_http.nv +0 -0
- package/examples/bf.nv +5 -13
- package/examples/math.nv +2 -2
- package/kits/kitffmpeg/kitdef.js +1174 -0
- package/kits/libos/kitdef.js +3135 -0
- package/kits/libtasker/kitdef.js +125 -0
- package/package.json +1 -1
- package/scripts/update-bin.js +0 -0
- package/src/core/executor.js +7 -4
- package/src/core/lexer.js +2 -2
- package/src/index.js +0 -0
- package/novac/LICENSE +0 -21
- package/novac/README.md +0 -1823
- package/novac/bin/novac +0 -950
- package/novac/bin/nvc +0 -522
- package/novac/bin/nvml +0 -542
- package/novac/demo.nv +0 -245
- package/novac/demo_builtins.nv +0 -209
- package/novac/demo_http.nv +0 -62
- package/novac/examples/bf.nv +0 -69
- package/novac/examples/math.nv +0 -21
- package/novac/kits/kitai/kitdef.js +0 -2185
- package/novac/kits/kitansi/kitdef.js +0 -1402
- package/novac/kits/kitformat/kitdef.js +0 -1485
- package/novac/kits/kitgps/kitdef.js +0 -1862
- package/novac/kits/kitlibfs/kitdef.js +0 -231
- package/novac/kits/kitlibproc/kitdef.js +0 -78
- package/novac/kits/kitmatrix/ex.js +0 -19
- package/novac/kits/kitmatrix/kitdef.js +0 -960
- package/novac/kits/kitmpatch/kitdef.js +0 -906
- package/novac/kits/kitnovacweb/README.md +0 -1572
- package/novac/kits/kitnovacweb/demo.nv +0 -12
- package/novac/kits/kitnovacweb/demo.nvml +0 -71
- package/novac/kits/kitnovacweb/index.nova +0 -12
- package/novac/kits/kitnovacweb/kitdef.js +0 -692
- package/novac/kits/kitnovacweb/nova.kit.json +0 -8
- package/novac/kits/kitnovacweb/nvml/executor.js +0 -739
- package/novac/kits/kitnovacweb/nvml/index.js +0 -67
- package/novac/kits/kitnovacweb/nvml/lexer.js +0 -263
- package/novac/kits/kitnovacweb/nvml/parser.js +0 -508
- package/novac/kits/kitnovacweb/nvml/renderer.js +0 -924
- package/novac/kits/kitparse/kitdef.js +0 -1688
- package/novac/kits/kitregex++/kitdef.js +0 -1353
- package/novac/kits/kitrequire/kitdef.js +0 -1599
- package/novac/kits/kitx11/kitdef.js +0 -1
- package/novac/kits/kitx11/kitx11.js +0 -2472
- package/novac/kits/kitx11/kitx11_conn.js +0 -948
- package/novac/kits/kitx11/kitx11_worker.js +0 -121
- package/novac/kits/libtea/tf.js +0 -2691
- package/novac/kits/libterm/ex.js +0 -285
- package/novac/kits/libterm/kitdef.js +0 -1927
- package/novac/node_modules/chalk/license +0 -9
- package/novac/node_modules/chalk/package.json +0 -83
- package/novac/node_modules/chalk/readme.md +0 -297
- package/novac/node_modules/chalk/source/index.d.ts +0 -325
- package/novac/node_modules/chalk/source/index.js +0 -225
- package/novac/node_modules/chalk/source/utilities.js +0 -33
- package/novac/node_modules/chalk/source/vendor/ansi-styles/index.d.ts +0 -236
- package/novac/node_modules/chalk/source/vendor/ansi-styles/index.js +0 -223
- package/novac/node_modules/chalk/source/vendor/supports-color/browser.d.ts +0 -1
- package/novac/node_modules/chalk/source/vendor/supports-color/browser.js +0 -34
- package/novac/node_modules/chalk/source/vendor/supports-color/index.d.ts +0 -55
- package/novac/node_modules/chalk/source/vendor/supports-color/index.js +0 -190
- package/novac/node_modules/commander/LICENSE +0 -22
- package/novac/node_modules/commander/Readme.md +0 -1176
- package/novac/node_modules/commander/esm.mjs +0 -16
- package/novac/node_modules/commander/index.js +0 -24
- package/novac/node_modules/commander/lib/argument.js +0 -150
- package/novac/node_modules/commander/lib/command.js +0 -2777
- package/novac/node_modules/commander/lib/error.js +0 -39
- package/novac/node_modules/commander/lib/help.js +0 -747
- package/novac/node_modules/commander/lib/option.js +0 -380
- package/novac/node_modules/commander/lib/suggestSimilar.js +0 -101
- package/novac/node_modules/commander/package-support.json +0 -19
- package/novac/node_modules/commander/package.json +0 -82
- package/novac/node_modules/commander/typings/esm.d.mts +0 -3
- package/novac/node_modules/commander/typings/index.d.ts +0 -1113
- package/novac/node_modules/node-addon-api/LICENSE.md +0 -9
- package/novac/node_modules/node-addon-api/README.md +0 -95
- package/novac/node_modules/node-addon-api/common.gypi +0 -21
- package/novac/node_modules/node-addon-api/except.gypi +0 -25
- package/novac/node_modules/node-addon-api/index.js +0 -14
- package/novac/node_modules/node-addon-api/napi-inl.deprecated.h +0 -186
- package/novac/node_modules/node-addon-api/napi-inl.h +0 -7165
- package/novac/node_modules/node-addon-api/napi.h +0 -3364
- package/novac/node_modules/node-addon-api/node_addon_api.gyp +0 -42
- package/novac/node_modules/node-addon-api/node_api.gyp +0 -9
- package/novac/node_modules/node-addon-api/noexcept.gypi +0 -26
- package/novac/node_modules/node-addon-api/nothing.c +0 -0
- package/novac/node_modules/node-addon-api/package-support.json +0 -21
- package/novac/node_modules/node-addon-api/package.json +0 -480
- package/novac/node_modules/node-addon-api/tools/README.md +0 -73
- package/novac/node_modules/node-addon-api/tools/check-napi.js +0 -99
- package/novac/node_modules/node-addon-api/tools/clang-format.js +0 -71
- package/novac/node_modules/node-addon-api/tools/conversion.js +0 -301
- package/novac/node_modules/serialize-javascript/LICENSE +0 -27
- package/novac/node_modules/serialize-javascript/README.md +0 -149
- package/novac/node_modules/serialize-javascript/index.js +0 -297
- package/novac/node_modules/serialize-javascript/package.json +0 -33
- package/novac/package.json +0 -27
- package/novac/scripts/update-bin.js +0 -24
- package/novac/src/core/bstd.js +0 -1035
- package/novac/src/core/config.js +0 -155
- package/novac/src/core/describe.js +0 -187
- package/novac/src/core/emitter.js +0 -499
- package/novac/src/core/error.js +0 -86
- package/novac/src/core/executor.js +0 -5606
- package/novac/src/core/formatter.js +0 -686
- package/novac/src/core/lexer.js +0 -1026
- package/novac/src/core/nova_builtins.js +0 -717
- package/novac/src/core/nova_thread_worker.js +0 -166
- package/novac/src/core/parser.js +0 -2181
- package/novac/src/core/types.js +0 -112
- package/novac/src/index.js +0 -28
- package/novac/src/runtime/stdlib.js +0 -244
package/novac/src/core/parser.js
DELETED
|
@@ -1,2181 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Nova Parser v2
|
|
3
|
-
* Fixed: currency IDENTIFIER postfix removed (caused method-shorthand bugs)
|
|
4
|
-
* Fixed: object literal colon is PUNCTUATION not OPERATOR
|
|
5
|
-
* Added: type/struct/interface/enum/trait declarations
|
|
6
|
-
* computed property names [expr]: val
|
|
7
|
-
* getter/setter get prop(){} / set prop(v){}
|
|
8
|
-
* logical assignment &&= ||= ??=
|
|
9
|
-
* optional catch (catch without var)
|
|
10
|
-
* @decorator syntax (stored but not enforced)
|
|
11
|
-
* 'new' as expression keyword -> call with new flag
|
|
12
|
-
*/
|
|
13
|
-
const { Lexer, CLASSIC_KEYWORDS, PUNCTUATION } = require('./lexer');
|
|
14
|
-
const { CustomError } = require('./error');
|
|
15
|
-
|
|
16
|
-
class Parser {
|
|
17
|
-
constructor(source) {
|
|
18
|
-
this.tokens = new Lexer(source).tokenize();
|
|
19
|
-
this.rawsrc = source;
|
|
20
|
-
this.current = 0;
|
|
21
|
-
this.symbols = new Map();
|
|
22
|
-
this.classicMode = false; // true while parsing inside @classic { }
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
parse() {
|
|
26
|
-
const nodes = [];
|
|
27
|
-
while (!this.isAtEnd()) { const n = this.statement(); if (n) nodes.push(n); }
|
|
28
|
-
return { kind: 'program', nodes };
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
nd(obj) {
|
|
32
|
-
const t = this.peek();
|
|
33
|
-
return { ...obj, line: t.line ?? 0, column: t.column ?? 0 };
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
// ══════════════════════════ STATEMENTS ══════════════════════════
|
|
37
|
-
|
|
38
|
-
statement() {
|
|
39
|
-
// decorators
|
|
40
|
-
let decorators = [];
|
|
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
|
-
}
|
|
54
|
-
|
|
55
|
-
if (this.match('KEYWORD', 'class')) return this.classDeclaration(decorators);
|
|
56
|
-
if (this.match('KEYWORD', 'type')) return this.typeDeclaration();
|
|
57
|
-
if (this.match('KEYWORD', 'struct')) return this.structDeclaration();
|
|
58
|
-
if (this.match('KEYWORD', 'interface')) return this.interfaceDeclaration();
|
|
59
|
-
if (this.match('KEYWORD', 'enum')) return this.enumDeclaration();
|
|
60
|
-
if (this.match('KEYWORD', 'trait')) return this.traitDeclaration();
|
|
61
|
-
if (this.match('KEYWORD', 'impl')) return this.implDeclaration();
|
|
62
|
-
if (this.match('KEYWORD', 'const')) return this.varDeclaration(true);
|
|
63
|
-
if (this.match('KEYWORD', 'var') || this.match('KEYWORD', 'let')) return this.varDeclaration();
|
|
64
|
-
if (this.match('KEYWORD', 'func')) return this.funcDeclaration(true);
|
|
65
|
-
if (this.match('KEYWORD', 'function')) return this.funcDeclaration();
|
|
66
|
-
if (this.match('KEYWORD', 'if')) return this.branchBody('if');
|
|
67
|
-
if (this.match('KEYWORD', 'while')) return this.branchBody('while');
|
|
68
|
-
if (this.match('KEYWORD', 'do')) return this.doWhileStatement();
|
|
69
|
-
if (this.match('KEYWORD', 'repeat')) return this.branchBody('repeat');
|
|
70
|
-
if (this.match('KEYWORD', 'until')) return this.branchBody('until');
|
|
71
|
-
if (this.match('KEYWORD', 'unless')) return this.branchBody('unless');
|
|
72
|
-
if (this.match('KEYWORD', 'for')) return this.forStatement();
|
|
73
|
-
if (this.match('KEYWORD', 'each')) return this.eachStatement();
|
|
74
|
-
if (this.match('KEYWORD', 'switch')) return this.switchStatement();
|
|
75
|
-
if (this.match('KEYWORD', 'match')) return this.matchStatement();
|
|
76
|
-
if (this.match('KEYWORD', 'return')) return this.returnStatement(false);
|
|
77
|
-
if (this.match('KEYWORD', 'give')) return this.returnStatement(true);
|
|
78
|
-
if (this.match('KEYWORD', 'yield')) { let nf = this.nd({ kind: 'yield_stmt', value: this.expression() }); this.match('PUNCTUATION', ';'); return nf; };
|
|
79
|
-
if (this.match('KEYWORD', 'throw')) return this.throwStatement();
|
|
80
|
-
if (this.match('KEYWORD', 'try')) return this.tryStatement();
|
|
81
|
-
if (this.match('KEYWORD', 'break')) { this.match('PUNCTUATION', ';'); return this.nd({ kind: 'break' }); }
|
|
82
|
-
if (this.match('KEYWORD', 'goback')) { this.match('PUNCTUATION', ';'); return this.nd({ kind: 'goback' }); }
|
|
83
|
-
if (this.match('KEYWORD', 'continue')) { this.match('PUNCTUATION', ';'); return this.nd({ kind: 'continue' }); }
|
|
84
|
-
if (this.match('KEYWORD', 'assert')) return this.assertStatement();
|
|
85
|
-
if (this.match('KEYWORD', 'emit')) return this.emitStatement();
|
|
86
|
-
if (this.match('KEYWORD', 'on')) return this.onStatement();
|
|
87
|
-
if (this.match('KEYWORD', 'where')) return this.whereStatement();
|
|
88
|
-
if (this.match('KEYWORD', 'guard')) return this.guardStatement();
|
|
89
|
-
if (this.match('KEYWORD', 'when')) return this.whenStatement();
|
|
90
|
-
if (this.match('KEYWORD', 'with')) return this.withStatement();
|
|
91
|
-
if (this.match('KEYWORD', 'loop')) return this.loopStatement();
|
|
92
|
-
if (this.match('KEYWORD', 'wait')) return this.waitStatement();
|
|
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');
|
|
141
|
-
if (this.match('KEYWORD', 'import')) return this.importStatement();
|
|
142
|
-
if (this.match('KEYWORD', 'import_builtin')) return this.importBuiltinStatement();
|
|
143
|
-
if (this.match('KEYWORD', 'from')) return this.fromStatement();
|
|
144
|
-
if (this.match('KEYWORD', 'server')) return this.serverStatement();
|
|
145
|
-
if (this.match('KEYWORD', 'fetch')) return this.fetchStatement();
|
|
146
|
-
if (this.match('KEYWORD', 'export')) return this.exportStatement();
|
|
147
|
-
if (this.match('KEYWORD', 'default')) return this.defaultStatement();
|
|
148
|
-
if (this.match('KEYWORD', 'namespace')) return this.namespaceStatement();
|
|
149
|
-
if (this.match('PUNCTUATION', '{')) return this.blockBody(true);
|
|
150
|
-
if (this.match('PUNCTUATION', '.')) return this.dotCmd();
|
|
151
|
-
return this.expressionStatement();
|
|
152
|
-
}
|
|
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
|
-
|
|
233
|
-
// ── type declarations ──
|
|
234
|
-
|
|
235
|
-
typeDeclaration() {
|
|
236
|
-
const name = this.consume('IDENTIFIER', 'Expected type name').value;
|
|
237
|
-
// generics: type Name<T> = ...
|
|
238
|
-
const params = this.parseTypeParams();
|
|
239
|
-
this.consume('OPERATOR', '=', 'Expected = in type declaration');
|
|
240
|
-
const def = this.parseTypeExpr();
|
|
241
|
-
this.match('PUNCTUATION', ';');
|
|
242
|
-
return this.nd({ kind: 'type_decl', name, params, def });
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
parseTypeParams() {
|
|
246
|
-
const params = [];
|
|
247
|
-
if (this.check('OPERATOR', '<')) {
|
|
248
|
-
this.advance();
|
|
249
|
-
do { params.push(this.consume('IDENTIFIER', 'Expected type param').value); }
|
|
250
|
-
while (this.match('PUNCTUATION', ','));
|
|
251
|
-
this.consume('OPERATOR', '>', 'Expected >');
|
|
252
|
-
}
|
|
253
|
-
return params;
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
parseTypeExpr() {
|
|
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
|
|
259
|
-
if (this.check('PUNCTUATION', '{')) {
|
|
260
|
-
// object type shape
|
|
261
|
-
this.advance();
|
|
262
|
-
const fields = {};
|
|
263
|
-
while (!this.check('PUNCTUATION', '}') && !this.isAtEnd()) {
|
|
264
|
-
const optional = this.peek().value === '?' && this.next()?.type === 'IDENTIFIER';
|
|
265
|
-
const key = this.consume('IDENTIFIER', 'Expected field name').value;
|
|
266
|
-
let isOpt = false;
|
|
267
|
-
if (this.match('PUNCTUATION', '?')) isOpt = true;
|
|
268
|
-
this.consume('PUNCTUATION', ':', 'Expected : in type shape');
|
|
269
|
-
fields[key] = { type: this.parseTypeExpr(), optional: isOpt };
|
|
270
|
-
this.match('PUNCTUATION', ','); this.match('PUNCTUATION', ';');
|
|
271
|
-
}
|
|
272
|
-
this.consume('PUNCTUATION', '}', 'Expected }');
|
|
273
|
-
return { kind: 'shape_type', fields };
|
|
274
|
-
}
|
|
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
|
-
|
|
311
|
-
// union: |
|
|
312
|
-
if (this.check('OPERATOR', '|')) {
|
|
313
|
-
const variants = [base];
|
|
314
|
-
while (this.match('OPERATOR', '|')) variants.push(parseAtom());
|
|
315
|
-
return { kind: 'union_type', variants };
|
|
316
|
-
}
|
|
317
|
-
// intersection: &
|
|
318
|
-
if (this.check('OPERATOR', '&')) {
|
|
319
|
-
const parts = [base];
|
|
320
|
-
while (this.match('OPERATOR', '&')) parts.push(parseAtom());
|
|
321
|
-
return { kind: 'intersect_type', parts };
|
|
322
|
-
}
|
|
323
|
-
return base;
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
structDeclaration() {
|
|
327
|
-
const name = this.consume('IDENTIFIER', 'Expected struct name').value;
|
|
328
|
-
const params = this.parseTypeParams();
|
|
329
|
-
const fields = [];
|
|
330
|
-
this.consume('PUNCTUATION', '{', 'Expected {');
|
|
331
|
-
while (!this.check('PUNCTUATION', '}') && !this.isAtEnd()) {
|
|
332
|
-
let decorators = [];
|
|
333
|
-
while (this.match('PUNCTUATION', '@')) decorators.push(this.expression());
|
|
334
|
-
const fieldName = this.consume('IDENTIFIER', 'Expected field name').value;
|
|
335
|
-
this.consume('PUNCTUATION', ':', 'Expected : after field name');
|
|
336
|
-
const fieldType = this.parseTypeExpr();
|
|
337
|
-
let defaultValue = null;
|
|
338
|
-
if (this.match('OPERATOR', '=')) defaultValue = this.expression();
|
|
339
|
-
fields.push({ name: fieldName, type: fieldType, defaultValue, decorators });
|
|
340
|
-
this.match('PUNCTUATION', ','); this.match('PUNCTUATION', ';');
|
|
341
|
-
}
|
|
342
|
-
this.consume('PUNCTUATION', '}', 'Expected }');
|
|
343
|
-
return this.nd({ kind: 'struct_decl', name, params, fields });
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
interfaceDeclaration() {
|
|
347
|
-
const name = this.consume('IDENTIFIER', 'Expected interface name').value;
|
|
348
|
-
const params = this.parseTypeParams();
|
|
349
|
-
// optional extends chain
|
|
350
|
-
const exts = [];
|
|
351
|
-
if (this.match('KEYWORD', 'extends')) {
|
|
352
|
-
do { exts.push(this.consume('IDENTIFIER', 'Expected interface name').value); }
|
|
353
|
-
while (this.match('PUNCTUATION', ','));
|
|
354
|
-
}
|
|
355
|
-
this.consume('PUNCTUATION', '{', 'Expected {');
|
|
356
|
-
const members = [];
|
|
357
|
-
while (!this.check('PUNCTUATION', '}') && !this.isAtEnd()) {
|
|
358
|
-
const mname = this.consume('IDENTIFIER', 'Expected member name').value;
|
|
359
|
-
let isMethod = false, mparams = [], returnType = null, isOptional = false;
|
|
360
|
-
if (this.match('PUNCTUATION', '?')) isOptional = true;
|
|
361
|
-
if (this.check('PUNCTUATION', '(')) {
|
|
362
|
-
isMethod = true;
|
|
363
|
-
this.advance();
|
|
364
|
-
if (!this.check('PUNCTUATION', ')')) {
|
|
365
|
-
do {
|
|
366
|
-
const pname = this.consume('IDENTIFIER', 'Expected param name').value;
|
|
367
|
-
let ptype = null;
|
|
368
|
-
if (this.match('PUNCTUATION', ':')) ptype = this.parseTypeExpr();
|
|
369
|
-
mparams.push({ name: pname, type: ptype });
|
|
370
|
-
} while (this.match('PUNCTUATION', ','));
|
|
371
|
-
}
|
|
372
|
-
this.consume('PUNCTUATION', ')', 'Expected )');
|
|
373
|
-
}
|
|
374
|
-
if (this.match('PUNCTUATION', ':')) returnType = this.parseTypeExpr();
|
|
375
|
-
members.push({ name: mname, isMethod, params: mparams, returnType, isOptional });
|
|
376
|
-
this.match('PUNCTUATION', ','); this.match('PUNCTUATION', ';');
|
|
377
|
-
}
|
|
378
|
-
this.consume('PUNCTUATION', '}', 'Expected }');
|
|
379
|
-
return this.nd({ kind: 'interface_decl', name, params, extends: exts, members });
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
enumDeclaration() {
|
|
383
|
-
const name = this.consume('IDENTIFIER', 'Expected enum name').value;
|
|
384
|
-
this.consume('PUNCTUATION', '{', 'Expected {');
|
|
385
|
-
const variants = [];
|
|
386
|
-
while (!this.check('PUNCTUATION', '}') && !this.isAtEnd()) {
|
|
387
|
-
const vname = this.consume('IDENTIFIER', 'Expected variant name').value;
|
|
388
|
-
let value = null;
|
|
389
|
-
// associated value: Variant(types...) or Variant = expr
|
|
390
|
-
if (this.check('PUNCTUATION', '(')) {
|
|
391
|
-
this.advance();
|
|
392
|
-
const types = [];
|
|
393
|
-
if (!this.check('PUNCTUATION', ')')) {
|
|
394
|
-
do { types.push(this.parseTypeExpr()); } while (this.match('PUNCTUATION', ','));
|
|
395
|
-
}
|
|
396
|
-
this.consume('PUNCTUATION', ')', 'Expected )');
|
|
397
|
-
value = { kind: 'tuple', types };
|
|
398
|
-
} else if (this.match('OPERATOR', '=')) {
|
|
399
|
-
value = this.expression();
|
|
400
|
-
}
|
|
401
|
-
variants.push({ name: vname, value });
|
|
402
|
-
this.match('PUNCTUATION', ',');
|
|
403
|
-
}
|
|
404
|
-
this.consume('PUNCTUATION', '}', 'Expected }');
|
|
405
|
-
return this.nd({ kind: 'enum_decl', name, variants });
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
traitDeclaration() {
|
|
409
|
-
const name = this.consume('IDENTIFIER', 'Expected trait name').value;
|
|
410
|
-
const params = this.parseTypeParams();
|
|
411
|
-
this.consume('PUNCTUATION', '{', 'Expected {');
|
|
412
|
-
const methods = [];
|
|
413
|
-
while (!this.check('PUNCTUATION', '}') && !this.isAtEnd()) {
|
|
414
|
-
const mname = this.consume('IDENTIFIER', 'Expected method name').value;
|
|
415
|
-
const { args, body } = this.parseFuncBody();
|
|
416
|
-
methods.push({ name: mname, args, body: body || [] });
|
|
417
|
-
}
|
|
418
|
-
this.consume('PUNCTUATION', '}', 'Expected }');
|
|
419
|
-
return this.nd({ kind: 'trait_decl', name, params, methods });
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
implDeclaration() {
|
|
423
|
-
// impl TraitName for StructName { ... }
|
|
424
|
-
const traitName = this.consume('IDENTIFIER', 'Expected trait name').value;
|
|
425
|
-
let forType = null;
|
|
426
|
-
if (this.match('KEYWORD', 'of') || this.matchIdent('for')) {
|
|
427
|
-
forType = this.consume('IDENTIFIER', 'Expected type name').value;
|
|
428
|
-
}
|
|
429
|
-
this.consume('PUNCTUATION', '{', 'Expected {');
|
|
430
|
-
const methods = [];
|
|
431
|
-
while (!this.check('PUNCTUATION', '}') && !this.isAtEnd()) {
|
|
432
|
-
const mname = this.consume('IDENTIFIER', 'Expected method name').value;
|
|
433
|
-
const { args, body } = this.parseFuncBody();
|
|
434
|
-
methods.push({ name: mname, args, body });
|
|
435
|
-
}
|
|
436
|
-
this.consume('PUNCTUATION', '}', 'Expected }');
|
|
437
|
-
return this.nd({ kind: 'impl_decl', trait: traitName, for: forType, methods });
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
matchIdent(v) {
|
|
441
|
-
if (this.check('IDENTIFIER', v)) { this.advance(); return true; }
|
|
442
|
-
return false;
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
// ── control flow ──
|
|
446
|
-
|
|
447
|
-
forStatement() {
|
|
448
|
-
this.consume('PUNCTUATION', '(', 'Expected (');
|
|
449
|
-
const saved = this.current;
|
|
450
|
-
if (this.check('KEYWORD', 'var') || this.check('KEYWORD', 'let') || this.check('KEYWORD', 'const')) {
|
|
451
|
-
this.advance();
|
|
452
|
-
if (this.check('IDENTIFIER')) {
|
|
453
|
-
const varName = this.advance().value;
|
|
454
|
-
if (this.match('KEYWORD', 'of')) {
|
|
455
|
-
const iterable = this.expression();
|
|
456
|
-
this.consume('PUNCTUATION', ')', 'Expected )');
|
|
457
|
-
const body = this.check('PUNCTUATION', '{') ? this.blockBody() : [this.statement()];
|
|
458
|
-
return this.nd({ kind: 'for_of', varName, iterable, body });
|
|
459
|
-
}
|
|
460
|
-
if (this.check('OPERATOR', 'in') || this.check('KEYWORD', 'in')) {
|
|
461
|
-
this.advance();
|
|
462
|
-
const object = this.expression();
|
|
463
|
-
this.consume('PUNCTUATION', ')', 'Expected )');
|
|
464
|
-
const body = this.check('PUNCTUATION', '{') ? this.blockBody() : [this.statement()];
|
|
465
|
-
return this.nd({ kind: 'for_in', varName, object, body });
|
|
466
|
-
}
|
|
467
|
-
}
|
|
468
|
-
this.current = saved;
|
|
469
|
-
}
|
|
470
|
-
const init = this.statement();
|
|
471
|
-
this.match('PUNCTUATION', ';');
|
|
472
|
-
let cond = null;
|
|
473
|
-
if (!this.check('PUNCTUATION', ';') && !this.check('PUNCTUATION', ')')) cond = this.expression();
|
|
474
|
-
this.match('PUNCTUATION', ';');
|
|
475
|
-
let update = null;
|
|
476
|
-
if (!this.check('PUNCTUATION', ')')) update = this.expressionStatement();
|
|
477
|
-
this.consume('PUNCTUATION', ')', 'Expected )');
|
|
478
|
-
const body = this.check('PUNCTUATION', '{') ? this.blockBody() : [this.statement()];
|
|
479
|
-
this.match('PUNCTUATION', ';');
|
|
480
|
-
return this.nd({ kind: 'branch', type: 'for', args: [init, cond, update], body });
|
|
481
|
-
}
|
|
482
|
-
|
|
483
|
-
eachStatement() {
|
|
484
|
-
const varName = this.consume('IDENTIFIER', 'Expected variable name after each').value;
|
|
485
|
-
let indexName = null;
|
|
486
|
-
if (this.match('PUNCTUATION', ',')) indexName = this.consume('IDENTIFIER', 'Expected index name').value;
|
|
487
|
-
if (!this.match('KEYWORD', 'of')) this.error("Expected 'of' in each loop");
|
|
488
|
-
const iterable = this.expression();
|
|
489
|
-
const body = this.blockBody();
|
|
490
|
-
return this.nd({ kind: 'each', varName, indexName, iterable, body });
|
|
491
|
-
}
|
|
492
|
-
|
|
493
|
-
envStatement() {
|
|
494
|
-
const varName = this.consume('IDENTIFIER', 'Expected variable name after env').value;
|
|
495
|
-
this.match('PUNCTUATION', ';');
|
|
496
|
-
return this.nd({ kind: 'env', varName });
|
|
497
|
-
}
|
|
498
|
-
|
|
499
|
-
switchStatement() {
|
|
500
|
-
this.consume('PUNCTUATION', '(', 'Expected (');
|
|
501
|
-
const subject = this.expression();
|
|
502
|
-
this.consume('PUNCTUATION', ')', 'Expected )');
|
|
503
|
-
this.consume('PUNCTUATION', '{', 'Expected {');
|
|
504
|
-
const cases = [];
|
|
505
|
-
while (!this.check('PUNCTUATION', '}') && !this.isAtEnd()) {
|
|
506
|
-
if (this.match('KEYWORD', 'case')) {
|
|
507
|
-
const value = this.expression();
|
|
508
|
-
this.consume('PUNCTUATION', ':', 'Expected :');
|
|
509
|
-
const body = [];
|
|
510
|
-
while (!this.check('KEYWORD', 'case') && !this.check('KEYWORD', 'default') && !this.check('PUNCTUATION', '}') && !this.isAtEnd())
|
|
511
|
-
body.push(this.statement());
|
|
512
|
-
cases.push(this.nd({ kind: 'case', value, body }));
|
|
513
|
-
} else if (this.match('KEYWORD', 'default')) {
|
|
514
|
-
this.consume('PUNCTUATION', ':', 'Expected :');
|
|
515
|
-
const body = [];
|
|
516
|
-
while (!this.check('KEYWORD', 'case') && !this.check('PUNCTUATION', '}') && !this.isAtEnd())
|
|
517
|
-
body.push(this.statement());
|
|
518
|
-
cases.push(this.nd({ kind: 'default_case', body }));
|
|
519
|
-
} else this.error('Expected case or default');
|
|
520
|
-
}
|
|
521
|
-
this.consume('PUNCTUATION', '}', 'Expected }');
|
|
522
|
-
return this.nd({ kind: 'switch', subject, cases });
|
|
523
|
-
}
|
|
524
|
-
helloStatement() {
|
|
525
|
-
const args = [];
|
|
526
|
-
if (!this.check('PUNCTUATION', ';')) {
|
|
527
|
-
do { args.push(this.expression()); } while (this.match('PUNCTUATION', ','));
|
|
528
|
-
}
|
|
529
|
-
this.match('PUNCTUATION', ';');
|
|
530
|
-
return this.nd({ kind: 'hello', args });
|
|
531
|
-
}
|
|
532
|
-
|
|
533
|
-
evalStatement() {
|
|
534
|
-
const code = this.statement();
|
|
535
|
-
this.match('PUNCTUATION', ';');
|
|
536
|
-
return this.nd({ kind: 'eval', code });
|
|
537
|
-
}
|
|
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
|
-
|
|
549
|
-
importStatement() {
|
|
550
|
-
const source = this.expression();
|
|
551
|
-
let names = null;
|
|
552
|
-
if (this.match('KEYWORD', 'as')) {
|
|
553
|
-
names = [];
|
|
554
|
-
do { names.push(this.consume('IDENTIFIER', 'Expected imported name').value); }
|
|
555
|
-
while (this.match('PUNCTUATION', ','));
|
|
556
|
-
}
|
|
557
|
-
this.match('PUNCTUATION', ';');
|
|
558
|
-
return this.nd({ kind: 'import', source, names });
|
|
559
|
-
}
|
|
560
|
-
|
|
561
|
-
importBuiltinStatement() {
|
|
562
|
-
let names = null;
|
|
563
|
-
names = [];
|
|
564
|
-
do { names.push(this.consume('IDENTIFIER', 'Expected imported name').value); }
|
|
565
|
-
while (this.match('PUNCTUATION', ','));
|
|
566
|
-
this.match('PUNCTUATION', ';');
|
|
567
|
-
return this.nd({ kind: 'import_builtin', names });
|
|
568
|
-
}
|
|
569
|
-
|
|
570
|
-
fromStatement() {
|
|
571
|
-
const source = this.expression();
|
|
572
|
-
this.consume('KEYWORD', 'import', 'Expected import after from');
|
|
573
|
-
const names = [];
|
|
574
|
-
do { names.push(this.consume('IDENTIFIER', 'Expected imported name').value); }
|
|
575
|
-
while (this.match('PUNCTUATION', ','));
|
|
576
|
-
this.match('PUNCTUATION', ';');
|
|
577
|
-
return this.nd({ kind: 'from_import', source, names });
|
|
578
|
-
}
|
|
579
|
-
|
|
580
|
-
exportStatement() {
|
|
581
|
-
let value = null;
|
|
582
|
-
if (!this.check('PUNCTUATION', ';')) value = this.expression();
|
|
583
|
-
this.match('PUNCTUATION', ';');
|
|
584
|
-
return this.nd({ kind: 'export', value });
|
|
585
|
-
}
|
|
586
|
-
|
|
587
|
-
defaultStatement() {
|
|
588
|
-
let value = null;
|
|
589
|
-
if (!this.check('PUNCTUATION', ';')) value = this.expression();
|
|
590
|
-
this.match('PUNCTUATION', ';');
|
|
591
|
-
return this.nd({ kind: 'default_export', value });
|
|
592
|
-
}
|
|
593
|
-
|
|
594
|
-
namespaceStatement() {
|
|
595
|
-
const name = this.consume('IDENTIFIER', 'Expected namespace name').value;
|
|
596
|
-
this.consume('PUNCTUATION', '{', 'Expected {');
|
|
597
|
-
const body = [];
|
|
598
|
-
while (!this.check('PUNCTUATION', '}') && !this.isAtEnd()) body.push(this.statement());
|
|
599
|
-
this.consume('PUNCTUATION', '}', 'Expected }');
|
|
600
|
-
return this.nd({ kind: 'namespace', name, body });
|
|
601
|
-
}
|
|
602
|
-
|
|
603
|
-
matchStatement() {
|
|
604
|
-
this.consume('PUNCTUATION', '(', 'Expected (');
|
|
605
|
-
const subject = this.expression();
|
|
606
|
-
this.consume('PUNCTUATION', ')', 'Expected )');
|
|
607
|
-
this.consume('PUNCTUATION', '{', 'Expected {');
|
|
608
|
-
const cases = [];
|
|
609
|
-
while (!this.check('PUNCTUATION', '}') && !this.isAtEnd()) {
|
|
610
|
-
if (this.match('KEYWORD', 'when')) {
|
|
611
|
-
const patterns = [this.expression()];
|
|
612
|
-
while (this.match('PUNCTUATION', ',')) patterns.push(this.expression());
|
|
613
|
-
const guard = this.match('KEYWORD', 'where') ? this.expression() : null;
|
|
614
|
-
const body = this.blockBody();
|
|
615
|
-
cases.push(this.nd({ kind: 'when', patterns, guard, body }));
|
|
616
|
-
} else if (this.match('KEYWORD', 'default')) {
|
|
617
|
-
const body = this.blockBody();
|
|
618
|
-
cases.push(this.nd({ kind: 'default_when', body }));
|
|
619
|
-
} else this.error('Expected when or default');
|
|
620
|
-
}
|
|
621
|
-
this.consume('PUNCTUATION', '}', 'Expected }');
|
|
622
|
-
return this.nd({ kind: 'match', subject, cases });
|
|
623
|
-
}
|
|
624
|
-
|
|
625
|
-
assertStatement() {
|
|
626
|
-
const condition = this.expression();
|
|
627
|
-
let message = null;
|
|
628
|
-
if (this.match('PUNCTUATION', ',')) message = this.expression();
|
|
629
|
-
this.match('PUNCTUATION', ';');
|
|
630
|
-
return this.nd({ kind: 'assert', condition, message });
|
|
631
|
-
}
|
|
632
|
-
|
|
633
|
-
emitStatement() {
|
|
634
|
-
let event;
|
|
635
|
-
if (this.check('STRING') || this.check('IDENTIFIER')) event = this.nd({ kind: 'value', value: this.advance().value });
|
|
636
|
-
else event = this.expression();
|
|
637
|
-
let value = null;
|
|
638
|
-
if (this.match('PUNCTUATION', ',')) value = this.expression();
|
|
639
|
-
this.match('PUNCTUATION', ';');
|
|
640
|
-
return this.nd({ kind: 'emit_event', event, value });
|
|
641
|
-
}
|
|
642
|
-
|
|
643
|
-
onStatement() {
|
|
644
|
-
let event;
|
|
645
|
-
if (this.check('STRING') || this.check('IDENTIFIER')) event = this.nd({ kind: 'value', value: this.advance().value });
|
|
646
|
-
else event = this.expression();
|
|
647
|
-
let param = null;
|
|
648
|
-
if (this.match('PUNCTUATION', '(')) {
|
|
649
|
-
param = this.consume('IDENTIFIER', 'Expected param name').value;
|
|
650
|
-
this.consume('PUNCTUATION', ')', 'Expected )');
|
|
651
|
-
}
|
|
652
|
-
const body = this.blockBody();
|
|
653
|
-
return this.nd({ kind: 'on_event', event, param, body });
|
|
654
|
-
}
|
|
655
|
-
|
|
656
|
-
whereStatement() {
|
|
657
|
-
this.consume('PUNCTUATION', '(', 'Expected (');
|
|
658
|
-
const bindings = [];
|
|
659
|
-
if (!this.check('PUNCTUATION', ')')) {
|
|
660
|
-
do {
|
|
661
|
-
const name = this.consume('IDENTIFIER', 'Expected variable name in where').value;
|
|
662
|
-
this.consume('OPERATOR', '=', 'Expected = in where binding');
|
|
663
|
-
const value = this.expression();
|
|
664
|
-
bindings.push({ name, value });
|
|
665
|
-
} while (this.match('PUNCTUATION', ','));
|
|
666
|
-
}
|
|
667
|
-
this.consume('PUNCTUATION', ')', 'Expected )');
|
|
668
|
-
const body = this.blockBody();
|
|
669
|
-
return this.nd({ kind: 'where', bindings, body });
|
|
670
|
-
}
|
|
671
|
-
|
|
672
|
-
doWhileStatement() {
|
|
673
|
-
// do { body } while (cond);
|
|
674
|
-
const body = this.blockBody();
|
|
675
|
-
this.consume('KEYWORD', 'while', 'Expected while after do block');
|
|
676
|
-
this.consume('PUNCTUATION', '(', 'Expected (');
|
|
677
|
-
const cond = this.expression();
|
|
678
|
-
this.consume('PUNCTUATION', ')', 'Expected )');
|
|
679
|
-
this.match('PUNCTUATION', ';');
|
|
680
|
-
return this.nd({ kind: 'branch', type: 'do', args: [cond], body });
|
|
681
|
-
}
|
|
682
|
-
|
|
683
|
-
throwStatement() {
|
|
684
|
-
const value = this.expression(); this.match('PUNCTUATION', ';');
|
|
685
|
-
return this.nd({ kind: 'throw', value });
|
|
686
|
-
}
|
|
687
|
-
|
|
688
|
-
tryStatement() {
|
|
689
|
-
const tryBody = this.blockBody();
|
|
690
|
-
let catchBody = null, catchName = null, finallyBody = null;
|
|
691
|
-
if (this.match('KEYWORD', 'catch')) {
|
|
692
|
-
if (this.match('PUNCTUATION', '(')) {
|
|
693
|
-
catchName = this.consume('IDENTIFIER', 'Expected catch variable name').value;
|
|
694
|
-
this.consume('PUNCTUATION', ')', 'Expected )');
|
|
695
|
-
}
|
|
696
|
-
catchBody = this.blockBody();
|
|
697
|
-
}
|
|
698
|
-
if (this.match('KEYWORD', 'finally')) finallyBody = this.blockBody();
|
|
699
|
-
if (!catchBody && !finallyBody) this.error('Try needs catch or finally');
|
|
700
|
-
return this.nd({ kind: 'try', tryBody, catchBody, catchName, finallyBody });
|
|
701
|
-
}
|
|
702
|
-
|
|
703
|
-
varDeclaration(isConst) {
|
|
704
|
-
let nameNode = null, destructure = null, isPointer = false;
|
|
705
|
-
if (this.match('OPERATOR', '*')) isPointer = true;
|
|
706
|
-
if (this.check('PUNCTUATION', '{')) {
|
|
707
|
-
destructure = this.objectPattern();
|
|
708
|
-
} else if (this.check('PUNCTUATION', '[')) {
|
|
709
|
-
destructure = this.arrayPattern();
|
|
710
|
-
} else {
|
|
711
|
-
nameNode = this.consume('IDENTIFIER', 'Expected variable name');
|
|
712
|
-
let explicitType = null;
|
|
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();
|
|
748
|
-
nameNode = { ...nameNode, typeAnnotation: explicitType };
|
|
749
|
-
}
|
|
750
|
-
let value = null;
|
|
751
|
-
if (this.match('OPERATOR', '=')) value = this.expression();
|
|
752
|
-
this.match('PUNCTUATION', ';');
|
|
753
|
-
if (destructure) return this.nd({ kind: 'declare', destructure, value, isConst, isPointer });
|
|
754
|
-
if (nameNode.value) this.symbols.set(nameNode.value, { type: nameNode.typeAnnotation || null, isConst });
|
|
755
|
-
return this.nd({ kind: 'declare', name: nameNode.value, value, isConst, isPointer,
|
|
756
|
-
explicitType: nameNode.typeAnnotation || null,
|
|
757
|
-
modifiers: nameNode.modifiers || null });
|
|
758
|
-
}
|
|
759
|
-
|
|
760
|
-
objectPattern() {
|
|
761
|
-
this.consume('PUNCTUATION', '{', 'Expected {');
|
|
762
|
-
const props = [];
|
|
763
|
-
if (!this.check('PUNCTUATION', '}')) {
|
|
764
|
-
do {
|
|
765
|
-
const key = this.consume('IDENTIFIER', 'Expected property name').value;
|
|
766
|
-
let alias = key;
|
|
767
|
-
if (this.match('PUNCTUATION', ':')) alias = this.consume('IDENTIFIER', 'Expected alias').value;
|
|
768
|
-
let defaultValue = null;
|
|
769
|
-
if (this.match('OPERATOR', '=')) defaultValue = this.expression();
|
|
770
|
-
props.push({ key, alias, defaultValue });
|
|
771
|
-
} while (this.match('PUNCTUATION', ','));
|
|
772
|
-
}
|
|
773
|
-
this.consume('PUNCTUATION', '}', 'Expected }');
|
|
774
|
-
return { kind: 'objpattern', props };
|
|
775
|
-
}
|
|
776
|
-
|
|
777
|
-
arrayPattern(consumed) {
|
|
778
|
-
if (!consumed) this.consume('PUNCTUATION', '[', 'Expected [');
|
|
779
|
-
const elements = [];
|
|
780
|
-
if (!this.check('PUNCTUATION', ']')) {
|
|
781
|
-
do {
|
|
782
|
-
if (this.match('OPERATOR', '...')) { elements.push({ name: this.consume('IDENTIFIER', 'Expected name').value, rest: true }); break; }
|
|
783
|
-
const name = this.consume('IDENTIFIER', 'Expected element name').value;
|
|
784
|
-
let defaultValue = null;
|
|
785
|
-
if (this.match('OPERATOR', '=')) defaultValue = this.expression();
|
|
786
|
-
elements.push({ name, defaultValue });
|
|
787
|
-
} while (this.match('PUNCTUATION', ','));
|
|
788
|
-
}
|
|
789
|
-
this.consume('PUNCTUATION', ']', 'Expected ]');
|
|
790
|
-
return this.nd({ kind: 'arrpattern', elements });
|
|
791
|
-
}
|
|
792
|
-
|
|
793
|
-
returnStatement(isGive) {
|
|
794
|
-
let value = null;
|
|
795
|
-
if (!this.check('PUNCTUATION', ';') && !this.check('EOF')) value = this.expression();
|
|
796
|
-
this.match('PUNCTUATION', ';');
|
|
797
|
-
return { kind: 'return', value, terminate: isGive };
|
|
798
|
-
}
|
|
799
|
-
|
|
800
|
-
funcDeclaration(isArrow) {
|
|
801
|
-
let options = {};
|
|
802
|
-
const FLAGS = {
|
|
803
|
-
async: () => options.isAsync = true,
|
|
804
|
-
Strict: () => options.strictARGcount = true,
|
|
805
|
-
timeout: () => options.timeout = this.expression(),
|
|
806
|
-
once: () => options.once = true,
|
|
807
|
-
memo: () => options.memoize = true,
|
|
808
|
-
generator: () => options.isGenerator = true,
|
|
809
|
-
defer: () => options.defer = this.statement(),
|
|
810
|
-
};
|
|
811
|
-
while (true) {
|
|
812
|
-
const matched = Object.keys(FLAGS).find(k => this.match('IDENTIFIER', k) || this.match('KEYWORD', k));
|
|
813
|
-
if (!matched) break;
|
|
814
|
-
FLAGS[matched]();
|
|
815
|
-
}
|
|
816
|
-
const name = this.consume('IDENTIFIER', 'Expected function name');
|
|
817
|
-
let returnType = null;
|
|
818
|
-
const { args, body } = this.parseFuncBody(isArrow);
|
|
819
|
-
if (this.match('PUNCTUATION', ':')) returnType = this.parseTypeExpr();
|
|
820
|
-
return this.nd({ kind: 'function', name: name.value, args, body, ...options, returnType });
|
|
821
|
-
}
|
|
822
|
-
|
|
823
|
-
classDeclaration(decorators = []) {
|
|
824
|
-
const name = this.consume('IDENTIFIER', 'Expected class name');
|
|
825
|
-
let superClass = null;
|
|
826
|
-
if (this.match('KEYWORD', 'extends')) {
|
|
827
|
-
const sn = this.consume('IDENTIFIER', 'Expected superclass name');
|
|
828
|
-
superClass = this.nd({ kind: 'ref', name: sn.value });
|
|
829
|
-
}
|
|
830
|
-
// optional implements list
|
|
831
|
-
const impls = [];
|
|
832
|
-
if (this.matchIdent('implements')) {
|
|
833
|
-
do { impls.push(this.consume('IDENTIFIER', 'Expected interface name').value); }
|
|
834
|
-
while (this.match('PUNCTUATION', ','));
|
|
835
|
-
}
|
|
836
|
-
this.consume('PUNCTUATION', '{', 'Expected {');
|
|
837
|
-
const members = [];
|
|
838
|
-
while (!this.check('PUNCTUATION', '}') && !this.isAtEnd()) {
|
|
839
|
-
let mDecorators = [];
|
|
840
|
-
while (this.match('PUNCTUATION', '@')) mDecorators.push(this.expression());
|
|
841
|
-
// getter/setter
|
|
842
|
-
let accessor = null;
|
|
843
|
-
if ((this.check('KEYWORD', 'get') || this.check('KEYWORD', 'set')) && this.next()?.type === 'IDENTIFIER') {
|
|
844
|
-
accessor = this.advance().value;
|
|
845
|
-
}
|
|
846
|
-
const tok = this.peek();
|
|
847
|
-
if (tok.type === 'IDENTIFIER' || tok.type === 'KEYWORD') {
|
|
848
|
-
const mname = this.advance().value;
|
|
849
|
-
if (this.check('PUNCTUATION', '(')) {
|
|
850
|
-
// method
|
|
851
|
-
const { args, body } = this.parseFuncBody();
|
|
852
|
-
let returnType = null;
|
|
853
|
-
if (this.match('PUNCTUATION', ':')) returnType = this.parseTypeExpr();
|
|
854
|
-
members.push({ kind: 'function', name: mname, args, body, accessor, decorators: mDecorators, returnType });
|
|
855
|
-
} else {
|
|
856
|
-
// field
|
|
857
|
-
this.consume('PUNCTUATION', ':', 'Expected : after field name');
|
|
858
|
-
const value = this.expression();
|
|
859
|
-
let fieldType = null;
|
|
860
|
-
members.push({ kind: 'declare', name: mname, value, fieldType, decorators: mDecorators });
|
|
861
|
-
this.match('PUNCTUATION', ',');
|
|
862
|
-
}
|
|
863
|
-
} else {
|
|
864
|
-
this.error('Expected member name in class body');
|
|
865
|
-
}
|
|
866
|
-
}
|
|
867
|
-
this.consume('PUNCTUATION', '}', 'Expected }');
|
|
868
|
-
return this.nd({ kind: 'class', name: name.value, superClass, impls, members, decorators });
|
|
869
|
-
}
|
|
870
|
-
|
|
871
|
-
expressionStatement() {
|
|
872
|
-
let expr = this.expression();
|
|
873
|
-
const ASSIGN_OPS = ['=', '+=', '-=', '*=', '/=', '%=', '**=', '&&=', '||=', '??='];
|
|
874
|
-
for (const op of ASSIGN_OPS) {
|
|
875
|
-
if (this.match('OPERATOR', op)) {
|
|
876
|
-
const value = this.expression();
|
|
877
|
-
expr = op === '='
|
|
878
|
-
? this.nd({ kind: 'assign', name: expr, value })
|
|
879
|
-
: this.nd({ kind: 'compound_assign', operator: op, name: expr, value });
|
|
880
|
-
break;
|
|
881
|
-
}
|
|
882
|
-
}
|
|
883
|
-
this.match('PUNCTUATION', ';');
|
|
884
|
-
return { kind: 'exec', expr };
|
|
885
|
-
}
|
|
886
|
-
|
|
887
|
-
// ══════════════════════════ EXPRESSIONS ══════════════════════════
|
|
888
|
-
|
|
889
|
-
expression(minPrec = 0) {
|
|
890
|
-
if (this.isAtEnd()) return { kind: 'EOF' };
|
|
891
|
-
let tok = this.peek(), left;
|
|
892
|
-
|
|
893
|
-
// ── prefix / primary ──
|
|
894
|
-
if (this.check('KEYWORD', 'fetch')) {
|
|
895
|
-
this.advance();
|
|
896
|
-
this.consume('PUNCTUATION', '(', 'Expected ( after fetch');
|
|
897
|
-
const fUrl = this.expression();
|
|
898
|
-
let fOpts = null;
|
|
899
|
-
if (this.match('PUNCTUATION', ',')) fOpts = this.expression();
|
|
900
|
-
this.consume('PUNCTUATION', ')', 'Expected )');
|
|
901
|
-
left = this.nd({ kind: 'fetch_expr', url: fUrl, options: fOpts });
|
|
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
|
-
|
|
950
|
-
} else if (this.check('KEYWORD', 'await')) {
|
|
951
|
-
this.advance(); left = this.nd({ kind: 'await', operand: this.expression(14) });
|
|
952
|
-
} else if (this.check('KEYWORD', 'new')) {
|
|
953
|
-
this.advance();
|
|
954
|
-
left = this.nd({ kind: 'new_expr', callee: this.expression(16) });
|
|
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 });
|
|
965
|
-
} else if (this.check('KEYWORD', 'rate')) {
|
|
966
|
-
this.advance();
|
|
967
|
-
this.consume('PUNCTUATION', '(', 'Expected ( after rate');
|
|
968
|
-
const rateVal = this.expression();
|
|
969
|
-
this.consume('PUNCTUATION', ')', 'Expected )');
|
|
970
|
-
const rateCastType = this.consumeName('Expected cast type after rate(...)').value;
|
|
971
|
-
left = this.nd({ kind: 'rate_cast', value: rateVal, cast_type: rateCastType });
|
|
972
|
-
} else if (this.check('KEYWORD', 'classify')) {
|
|
973
|
-
this.advance();
|
|
974
|
-
const classInner = this.expression(16);
|
|
975
|
-
let classVal = classInner, classTargetType = null;
|
|
976
|
-
// If the expression consumed "as TYPE" as a cast node, extract it
|
|
977
|
-
if (classInner && classInner.kind === 'cast') {
|
|
978
|
-
classVal = classInner.value;
|
|
979
|
-
classTargetType = classInner.type;
|
|
980
|
-
} else if (this.match('KEYWORD', 'as')) {
|
|
981
|
-
classTargetType = this.parseTypeExpr();
|
|
982
|
-
}
|
|
983
|
-
left = this.nd({ kind: 'classify', value: classVal, targetType: classTargetType });
|
|
984
|
-
|
|
985
|
-
} else if (tok.type === 'OPERATOR' && (tok.value === '++' || tok.value === '--')) {
|
|
986
|
-
const op = this.advance().value;
|
|
987
|
-
left = this.nd({ kind: 'prefix', operator: op, operand: this.expression(14) });
|
|
988
|
-
|
|
989
|
-
} else if (tok.type === 'OPERATOR' && tok.isUnary && !tok.isPostfix) {
|
|
990
|
-
const op = this.advance().value;
|
|
991
|
-
if (op === '*') left = this.nd({ kind: 'deref', operand: this.expression(14) });
|
|
992
|
-
else if (op === '...') left = this.nd({ kind: 'spread', value: this.expression(14) });
|
|
993
|
-
else if (op === 'delete' && (this.check('URL') || this.check('STRING'))) {
|
|
994
|
-
// HTTP DELETE as first-class expression: delete https://api.com/res/1()
|
|
995
|
-
const urlNode = this.check('URL')
|
|
996
|
-
? this.nd({ kind: 'url_literal', value: this.advance().value })
|
|
997
|
-
: this.nd({ kind: 'value', value: this.advance().value });
|
|
998
|
-
let args = [];
|
|
999
|
-
if (this.match('PUNCTUATION', '(')) {
|
|
1000
|
-
if (!this.check('PUNCTUATION', ')')) {
|
|
1001
|
-
do { args.push(this.expression()); } while (this.match('PUNCTUATION', ','));
|
|
1002
|
-
}
|
|
1003
|
-
this.consume('PUNCTUATION', ')', 'Expected )');
|
|
1004
|
-
}
|
|
1005
|
-
left = this.nd({ kind: 'http_request', method: 'delete', url: urlNode, args });
|
|
1006
|
-
}
|
|
1007
|
-
else left = this.nd({ kind: 'unary', operator: op, operand: this.expression(14) });
|
|
1008
|
-
|
|
1009
|
-
} else if (tok.type === 'PUNCTUATION' && tok.value === '(') {
|
|
1010
|
-
this.advance();
|
|
1011
|
-
const args = [];
|
|
1012
|
-
if (!this.check('PUNCTUATION', ')')) {
|
|
1013
|
-
do { args.push(this.expression()); } while (this.match('PUNCTUATION', ','));
|
|
1014
|
-
}
|
|
1015
|
-
this.consume('PUNCTUATION', ')', 'Expected )');
|
|
1016
|
-
if (this.match('OPERATOR', '=>')) {
|
|
1017
|
-
const body = this.check('PUNCTUATION', '{') ? this.blockBody() : [this.nd({ kind: 'return', value: this.expression(), terminate: true })];
|
|
1018
|
-
return this.nd({ kind: 'arrowfunc', args: args.map(a => a?.name), body });
|
|
1019
|
-
}
|
|
1020
|
-
left = this.nd(args[0] || { kind: 'value', value: undefined });
|
|
1021
|
-
|
|
1022
|
-
} else if (this.match('PUNCTUATION', '[')) {
|
|
1023
|
-
const elements = [];
|
|
1024
|
-
if (!this.check('PUNCTUATION', ']')) {
|
|
1025
|
-
do {
|
|
1026
|
-
if (this.match('OPERATOR', '...')) elements.push(this.nd({ kind: 'spread', value: this.expression() }));
|
|
1027
|
-
else elements.push(this.expression());
|
|
1028
|
-
} while (this.match('PUNCTUATION', ','));
|
|
1029
|
-
}
|
|
1030
|
-
this.consume('PUNCTUATION', ']', 'Expected ]');
|
|
1031
|
-
left = this.nd({ kind: 'array', elements });
|
|
1032
|
-
|
|
1033
|
-
} else if (tok.type === 'PUNCTUATION' && tok.value === '{') {
|
|
1034
|
-
left = this.objectLiteral();
|
|
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 });
|
|
1043
|
-
} else if (tok.type === 'NUMBER' || tok.type === 'STRING' || tok.type === 'LITERAL') {
|
|
1044
|
-
left = this.nd({ kind: 'value', value: this.advance().value });
|
|
1045
|
-
|
|
1046
|
-
} else if (tok.type === 'FSTRING_START') {
|
|
1047
|
-
left = this.fstring();
|
|
1048
|
-
|
|
1049
|
-
} else if (tok.type === 'INTERPOLATION_END') {
|
|
1050
|
-
return undefined;
|
|
1051
|
-
|
|
1052
|
-
} else if (tok.type === 'IDENTIFIER') {
|
|
1053
|
-
const name = this.advance().value;
|
|
1054
|
-
if (this.match('OPERATOR', '=>')) {
|
|
1055
|
-
const body = this.check('PUNCTUATION', '{') ? this.blockBody() : [this.nd({ kind: 'return', value: this.expression(), terminate: true })];
|
|
1056
|
-
return this.nd({ kind: 'arrowfunc', args: [name], body });
|
|
1057
|
-
}
|
|
1058
|
-
if (name === 'PRS_goto') {
|
|
1059
|
-
// skip intrinsic for now
|
|
1060
|
-
}
|
|
1061
|
-
left = this.nd({ kind: 'ref', name, explicitType: this.symbols.get(name)?.type ?? null });
|
|
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
|
-
|
|
1068
|
-
} else if (tok.type === 'URL') {
|
|
1069
|
-
left = this.nd({ kind: 'url_literal', value: this.advance().value });
|
|
1070
|
-
|
|
1071
|
-
} else if (tok.type === 'KEYWORD' && ['get', 'post', 'put', 'delete', 'patch', 'head', 'options'].includes(tok.value)) {
|
|
1072
|
-
// First-class HTTP request: get https://api.example.com/users(body, headers)
|
|
1073
|
-
// Also handles bare: get "https://api.example.com/users"
|
|
1074
|
-
const method = this.advance().value;
|
|
1075
|
-
// Require a URL token or string as the target
|
|
1076
|
-
const urlNode = (this.check('URL') || this.check('STRING'))
|
|
1077
|
-
? this.nd({ kind: this.check('URL') ? 'url_literal' : 'value', value: this.advance().value })
|
|
1078
|
-
: this.expression(15);
|
|
1079
|
-
let args = [];
|
|
1080
|
-
if (this.match('PUNCTUATION', '(')) {
|
|
1081
|
-
if (!this.check('PUNCTUATION', ')')) {
|
|
1082
|
-
do {
|
|
1083
|
-
if (this.match('OPERATOR', '...')) args.push(this.nd({ kind: 'spread', value: this.expression() }));
|
|
1084
|
-
else args.push(this.expression());
|
|
1085
|
-
} while (this.match('PUNCTUATION', ','));
|
|
1086
|
-
}
|
|
1087
|
-
this.consume('PUNCTUATION', ')', 'Expected )');
|
|
1088
|
-
}
|
|
1089
|
-
left = this.nd({ kind: 'http_request', method, url: urlNode, args });
|
|
1090
|
-
|
|
1091
|
-
} else if (tok.type === 'EOF') {
|
|
1092
|
-
return this.nd({ kind: 'EOF' });
|
|
1093
|
-
|
|
1094
|
-
} else if (tok.type === 'KEYWORD' && tok.value === 'run') {
|
|
1095
|
-
this.advance();
|
|
1096
|
-
left = this.nd({ kind: 'run_expr', code: this.statement() })
|
|
1097
|
-
} else if (tok.value === '=') {
|
|
1098
|
-
this.advance();
|
|
1099
|
-
left = this.nd({ kind: 'ast', ast: this.statement() });
|
|
1100
|
-
} else {
|
|
1101
|
-
this.error("Unexpected token '" + tok.value + "'");
|
|
1102
|
-
}
|
|
1103
|
-
|
|
1104
|
-
// ── postfix / calls / property ──
|
|
1105
|
-
while (!this.isAtEnd()) {
|
|
1106
|
-
tok = this.peek();
|
|
1107
|
-
if (tok.type === 'PUNCTUATION' && (tok.value === ';' || tok.value === '}')) break;
|
|
1108
|
-
if (tok.type === 'PUNCTUATION' && tok.value === '{') break; // always a block
|
|
1109
|
-
if (tok.type === 'KEYWORD' && tok.value === 'if') {
|
|
1110
|
-
this.advance();
|
|
1111
|
-
let cond = this.expression();
|
|
1112
|
-
let elseExpr = null;
|
|
1113
|
-
if (this.match('KEYWORD', 'else')) elseExpr = this.expression();
|
|
1114
|
-
left = this.nd({ kind: 'if_ternary', cond, elseExpr, operand: left });
|
|
1115
|
-
}
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
// Postfix ++ / --
|
|
1119
|
-
if (tok.type === 'OPERATOR' && (tok.value === '++' || tok.value === '--')) {
|
|
1120
|
-
left = this.nd({ kind: 'postfix', operator: this.advance().value, operand: left }); continue;
|
|
1121
|
-
}
|
|
1122
|
-
|
|
1123
|
-
// Optional chaining ?.
|
|
1124
|
-
if (tok.type === 'OPERATOR' && tok.value === '?.') {
|
|
1125
|
-
this.advance();
|
|
1126
|
-
if (this.match('PUNCTUATION', '(')) {
|
|
1127
|
-
const args = this.parseCallArgs();
|
|
1128
|
-
left = this.nd({ kind: 'optional_call', callee: left, args });
|
|
1129
|
-
} else if (this.match('PUNCTUATION', '[')) {
|
|
1130
|
-
const index = this.expression();
|
|
1131
|
-
this.consume('PUNCTUATION', ']', 'Expected ]');
|
|
1132
|
-
left = this.nd({ kind: 'optional_subscript', object: left, index });
|
|
1133
|
-
} else {
|
|
1134
|
-
const prop = this.consumeName('Expected property name after ?.');
|
|
1135
|
-
left = this.nd({ kind: 'optional_prop', object: left, name: prop.value });
|
|
1136
|
-
}
|
|
1137
|
-
continue;
|
|
1138
|
-
}
|
|
1139
|
-
|
|
1140
|
-
// as cast
|
|
1141
|
-
if (tok.type === 'KEYWORD' && tok.value === 'as') {
|
|
1142
|
-
this.advance();
|
|
1143
|
-
const type = this.parseTypeExpr();
|
|
1144
|
-
left = this.nd({ kind: 'cast', value: left, type }); continue;
|
|
1145
|
-
}
|
|
1146
|
-
|
|
1147
|
-
// Function call
|
|
1148
|
-
if (tok.type === 'PUNCTUATION' && tok.value === '(') {
|
|
1149
|
-
this.advance();
|
|
1150
|
-
const args = this.parseCallArgs();
|
|
1151
|
-
left = this.nd({ kind: 'call', name: left, args }); continue;
|
|
1152
|
-
}
|
|
1153
|
-
|
|
1154
|
-
// Property access
|
|
1155
|
-
if (tok.type === 'PUNCTUATION' && tok.value === '.') {
|
|
1156
|
-
this.advance();
|
|
1157
|
-
if (this.match('PUNCTUATION', '(')) {
|
|
1158
|
-
let nameNode = this.nd({
|
|
1159
|
-
kind: 'subscript', object: left, index: this.nd({kind:'value', value: '.expr'})
|
|
1160
|
-
});
|
|
1161
|
-
const args = this.parseCallArgs();
|
|
1162
|
-
left = this.nd({ kind: 'call', name: nameNode, args })
|
|
1163
|
-
} else {
|
|
1164
|
-
const prop = this.consumeName('Expected property name after .');
|
|
1165
|
-
left = this.nd({ kind: 'prop', object: left, name: prop.value }); continue;
|
|
1166
|
-
}
|
|
1167
|
-
}
|
|
1168
|
-
|
|
1169
|
-
// Subscript
|
|
1170
|
-
if (this.match('PUNCTUATION', '[')) {
|
|
1171
|
-
const index = this.expression();
|
|
1172
|
-
this.consume('PUNCTUATION', ']', 'Expected ]');
|
|
1173
|
-
left = this.nd({ kind: 'subscript', object: left, index }); continue;
|
|
1174
|
-
}
|
|
1175
|
-
|
|
1176
|
-
break;
|
|
1177
|
-
}
|
|
1178
|
-
|
|
1179
|
-
// ── binary operators ──
|
|
1180
|
-
while (!this.isAtEnd()) {
|
|
1181
|
-
tok = this.peek();
|
|
1182
|
-
// Ternary
|
|
1183
|
-
if (tok.type === 'PUNCTUATION' && tok.value === '?' && minPrec <= 0) {
|
|
1184
|
-
this.advance();
|
|
1185
|
-
const consequent = this.expression(0);
|
|
1186
|
-
this.consume('PUNCTUATION', ':', 'Expected : in ternary');
|
|
1187
|
-
const alternate = this.expression(0);
|
|
1188
|
-
left = this.nd({ kind: 'ternary', condition: left, consequent, alternate }); continue;
|
|
1189
|
-
}
|
|
1190
|
-
if (tok.type !== 'OPERATOR') break;
|
|
1191
|
-
if (['=', '+=', '-=', '*=', '/=', '%=', '**=', '&&=', '||=', '??='].includes(tok.value)) break;
|
|
1192
|
-
if (tok.value === ';' || tok.value === '}') break;
|
|
1193
|
-
if (tok.value === '++' || tok.value === '--') break;
|
|
1194
|
-
const prec = tok.precedence ?? 0;
|
|
1195
|
-
if (prec < minPrec) break;
|
|
1196
|
-
const op = this.advance().value;
|
|
1197
|
-
const right = this.expression(tok.rightAssoc ? prec : prec + 1);
|
|
1198
|
-
left = this.nd({ kind: 'binary', operator: op, left, right });
|
|
1199
|
-
}
|
|
1200
|
-
return left;
|
|
1201
|
-
}
|
|
1202
|
-
|
|
1203
|
-
parseCallArgs(needsClosing = true) {
|
|
1204
|
-
// Caller must have consumed '('
|
|
1205
|
-
const args = [];
|
|
1206
|
-
if (!this.check('PUNCTUATION', ')')) {
|
|
1207
|
-
do {
|
|
1208
|
-
if (this.match('OPERATOR', '...')) args.push(this.nd({ kind: 'spread', value: this.expression() }));
|
|
1209
|
-
else args.push(this.expression());
|
|
1210
|
-
} while (this.match('PUNCTUATION', ','));
|
|
1211
|
-
}
|
|
1212
|
-
if (needsClosing) this.consume('PUNCTUATION', ')', 'Expected )');
|
|
1213
|
-
return args;
|
|
1214
|
-
}
|
|
1215
|
-
|
|
1216
|
-
fstring() {
|
|
1217
|
-
this.consume('FSTRING_START', 'Expected f-string start');
|
|
1218
|
-
const parts = [];
|
|
1219
|
-
while (!this.check('FSTRING_END') && !this.isAtEnd()) {
|
|
1220
|
-
if (this.match('STRING_PART')) { parts.push({ kind: 'value', value: this.previous().value }); continue; }
|
|
1221
|
-
if (this.match('INTERPOLATION_START')) {
|
|
1222
|
-
const expr = this.expression();
|
|
1223
|
-
this.consume('INTERPOLATION_END', 'Expected }');
|
|
1224
|
-
parts.push(expr); continue;
|
|
1225
|
-
}
|
|
1226
|
-
this.error("Unexpected token in f-string: " + this.peek().value);
|
|
1227
|
-
}
|
|
1228
|
-
this.consume('FSTRING_END', 'Unterminated f-string');
|
|
1229
|
-
return this.nd({ kind: 'fstring', parts });
|
|
1230
|
-
}
|
|
1231
|
-
|
|
1232
|
-
// object literal (NOT used for class body)
|
|
1233
|
-
objectLiteral() {
|
|
1234
|
-
const props = [];
|
|
1235
|
-
this.consume('PUNCTUATION', '{', 'Expected {');
|
|
1236
|
-
while (!this.check('PUNCTUATION', '}') && !this.isAtEnd()) {
|
|
1237
|
-
// Spread: { ...expr }
|
|
1238
|
-
if (this.match('OPERATOR', '...')) {
|
|
1239
|
-
props.push({ kind: 'spread', value: this.expression() });
|
|
1240
|
-
this.match('PUNCTUATION', ','); continue;
|
|
1241
|
-
}
|
|
1242
|
-
// Computed: { [expr]: val }
|
|
1243
|
-
if (this.match('PUNCTUATION', '[')) {
|
|
1244
|
-
const keyExpr = this.expression();
|
|
1245
|
-
this.consume('PUNCTUATION', ']', 'Expected ]');
|
|
1246
|
-
this.consume('PUNCTUATION', ':', 'Expected :');
|
|
1247
|
-
const val = this.expression();
|
|
1248
|
-
props.push({ kind: 'computed', key: keyExpr, value: val });
|
|
1249
|
-
this.match('PUNCTUATION', ','); continue;
|
|
1250
|
-
}
|
|
1251
|
-
// Getter/Setter: { get prop(){} } / { set prop(v){} }
|
|
1252
|
-
if ((this.check('KEYWORD', 'get') || this.check('KEYWORD', 'set')) && this.next()?.type === 'IDENTIFIER') {
|
|
1253
|
-
const acc = this.advance().value;
|
|
1254
|
-
const pname = this.advance().value;
|
|
1255
|
-
const { args, body } = this.parseFuncBody();
|
|
1256
|
-
props.push({ kind: 'prop', key: pname, value: { kind: 'function', name: pname, args, body }, accessor: acc });
|
|
1257
|
-
this.match('PUNCTUATION', ','); continue;
|
|
1258
|
-
}
|
|
1259
|
-
// Normal or method shorthand — accept any keyword-like name as a property key
|
|
1260
|
-
let nameTok;
|
|
1261
|
-
if (this.check('STRING')) { nameTok = this.advance(); }
|
|
1262
|
-
else if (this.check('NUMBER')) { nameTok = this.advance(); }
|
|
1263
|
-
else { nameTok = this.consumeName('Expected property name'); }
|
|
1264
|
-
const propName = nameTok.value;
|
|
1265
|
-
if (this.check('PUNCTUATION', '(')) {
|
|
1266
|
-
// method shorthand
|
|
1267
|
-
const { args, body } = this.parseFuncBody();
|
|
1268
|
-
props.push({ kind: 'prop', key: propName, value: { kind: 'function', name: propName, args, body } });
|
|
1269
|
-
} else if (this.match('PUNCTUATION', '?')) {
|
|
1270
|
-
// Boolean presence syntax: { key? true } or { key? false }
|
|
1271
|
-
// The value MUST be a boolean — true or false
|
|
1272
|
-
const valTok = this.peek();
|
|
1273
|
-
let boolVal;
|
|
1274
|
-
if (this.match('KEYWORD', 'true') || this.match('LITERAL', 'true')) {
|
|
1275
|
-
boolVal = { kind: 'bool', value: true };
|
|
1276
|
-
} else if (this.match('KEYWORD', 'false') || this.match('LITERAL', 'false')) {
|
|
1277
|
-
boolVal = { kind: 'bool', value: false };
|
|
1278
|
-
} else {
|
|
1279
|
-
// Allow an expression that should evaluate to bool — parse and wrap in bool_assert
|
|
1280
|
-
const expr = this.expression();
|
|
1281
|
-
boolVal = { kind: 'bool_assert', expr };
|
|
1282
|
-
}
|
|
1283
|
-
props.push({ kind: 'prop', key: propName, value: boolVal, boolProp: true });
|
|
1284
|
-
} else {
|
|
1285
|
-
this.consume('PUNCTUATION', ':', 'Expected : after property name');
|
|
1286
|
-
const val = this.expression();
|
|
1287
|
-
props.push({ kind: 'prop', key: propName, value: val });
|
|
1288
|
-
}
|
|
1289
|
-
this.match('PUNCTUATION', ',');
|
|
1290
|
-
}
|
|
1291
|
-
this.consume('PUNCTUATION', '}', 'Expected }');
|
|
1292
|
-
return this.nd({ kind: 'object', props });
|
|
1293
|
-
}
|
|
1294
|
-
|
|
1295
|
-
// ── func body helper ──
|
|
1296
|
-
parseFuncBody(isArrow) {
|
|
1297
|
-
this.consume('PUNCTUATION', '(', 'Expected (');
|
|
1298
|
-
const args = [];
|
|
1299
|
-
if (!this.check('PUNCTUATION', ')')) {
|
|
1300
|
-
do {
|
|
1301
|
-
if (this.match('OPERATOR', '...')) {
|
|
1302
|
-
const name = this.consume('IDENTIFIER', 'Expected rest param name').value;
|
|
1303
|
-
args.push({ name, rest: true, defaultValue: null }); break;
|
|
1304
|
-
}
|
|
1305
|
-
const name = this.consume('IDENTIFIER', 'Expected argument name').value;
|
|
1306
|
-
let argType = null;
|
|
1307
|
-
if (this.match('PUNCTUATION', ':')) argType = this.parseTypeExpr();
|
|
1308
|
-
let defaultValue = null;
|
|
1309
|
-
if (this.match('OPERATOR', '=')) defaultValue = this.expression();
|
|
1310
|
-
args.push({ name, rest: false, defaultValue, type: argType });
|
|
1311
|
-
} while (this.match('PUNCTUATION', ','));
|
|
1312
|
-
}
|
|
1313
|
-
this.consume('PUNCTUATION', ')', 'Expected )');
|
|
1314
|
-
if (isArrow) this.consume('OPERATOR', '=>', 'Expected =>');
|
|
1315
|
-
this.consume('PUNCTUATION', '{', 'Expected {');
|
|
1316
|
-
const body = [];
|
|
1317
|
-
while (!this.check('PUNCTUATION', '}') && !this.isAtEnd()) body.push(this.statement());
|
|
1318
|
-
this.consume('PUNCTUATION', '}', 'Expected }');
|
|
1319
|
-
return { args, body };
|
|
1320
|
-
}
|
|
1321
|
-
|
|
1322
|
-
parseParenBody(branchName, SEP = ',') {
|
|
1323
|
-
this.consume('PUNCTUATION', '(', 'Expected (');
|
|
1324
|
-
const args = [];
|
|
1325
|
-
if (branchName === 'for') {
|
|
1326
|
-
args.push(this.statement()); this.match('PUNCTUATION', ';');
|
|
1327
|
-
args.push(this.expression()); this.match('PUNCTUATION', ';');
|
|
1328
|
-
args.push(this.statement());
|
|
1329
|
-
} else {
|
|
1330
|
-
if (!this.check('PUNCTUATION', ')')) {
|
|
1331
|
-
do { args.push(this.expression()); } while (this.match('PUNCTUATION', SEP));
|
|
1332
|
-
}
|
|
1333
|
-
}
|
|
1334
|
-
this.consume('PUNCTUATION', ')', 'Expected )');
|
|
1335
|
-
const body = this.check('PUNCTUATION', '{') ? this.blockBody() : [this.statement()];
|
|
1336
|
-
this.match('PUNCTUATION', ';');
|
|
1337
|
-
return { args: branchName === 'if' ? args[0] : args, body };
|
|
1338
|
-
}
|
|
1339
|
-
|
|
1340
|
-
branchBody(name = 'if', SEP = ',') {
|
|
1341
|
-
const { args, body } = this.parseParenBody(name, SEP);
|
|
1342
|
-
const node = this.nd({ kind: 'branch', type: name, args, body });
|
|
1343
|
-
if (this.match('KEYWORD', 'else')) {
|
|
1344
|
-
node.next = this.nd({ kind: 'branch', type: 'else', args: null, body: [this.statement()] });
|
|
1345
|
-
}
|
|
1346
|
-
this.match('PUNCTUATION', ';');
|
|
1347
|
-
return node;
|
|
1348
|
-
}
|
|
1349
|
-
|
|
1350
|
-
blockBody(consumed) {
|
|
1351
|
-
if (consumed || this.match('PUNCTUATION', '{')) {
|
|
1352
|
-
const body = [];
|
|
1353
|
-
while (!this.check('PUNCTUATION', '}') && !this.isAtEnd()) body.push(this.statement());
|
|
1354
|
-
this.consume('PUNCTUATION', '}', 'Expected }');
|
|
1355
|
-
return body;
|
|
1356
|
-
} else return [this.statement()];
|
|
1357
|
-
}
|
|
1358
|
-
|
|
1359
|
-
dotCmd() {
|
|
1360
|
-
let cmd = this.consumeName('Expected command name.');
|
|
1361
|
-
let args = this.parseCallArgs(false);
|
|
1362
|
-
return this.nd({ kind: 'dot_cmd', cmd, args })
|
|
1363
|
-
}
|
|
1364
|
-
|
|
1365
|
-
// Accept any identifier-like token as a name
|
|
1366
|
-
// (identifiers, contextual keywords, AND word-based operators like step/xor/and/is/etc.)
|
|
1367
|
-
consumeName(msg) {
|
|
1368
|
-
const t = this.peek();
|
|
1369
|
-
if (t.type === 'IDENTIFIER' || t.type === 'KEYWORD') return this.advance();
|
|
1370
|
-
// Accept word-operators used as property names (e.g. obj.step, obj.and, obj.from)
|
|
1371
|
-
if (t.type === 'OPERATOR' && /^[a-zA-Z_]/.test(String(t.value))) return this.advance();
|
|
1372
|
-
this.error(msg);
|
|
1373
|
-
}
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
// ══════════════ Nova Classic Statements ══════════════
|
|
1377
|
-
|
|
1378
|
-
foreachStatement() {
|
|
1379
|
-
this.consume('PUNCTUATION', '(', 'Expected (');
|
|
1380
|
-
const iterable = this.expression();
|
|
1381
|
-
this.consume('PUNCTUATION', ')', 'Expected )');
|
|
1382
|
-
this.consume('PUNCTUATION', '(', 'Expected (');
|
|
1383
|
-
const vars = [];
|
|
1384
|
-
do { vars.push(this.consume('IDENTIFIER', 'Expected variable name').value); }
|
|
1385
|
-
while (this.match('PUNCTUATION', ','));
|
|
1386
|
-
this.consume('PUNCTUATION', ')', 'Expected )');
|
|
1387
|
-
const body = this.blockBody();
|
|
1388
|
-
return this.nd({ kind: 'foreach', iterable, vars, body });
|
|
1389
|
-
}
|
|
1390
|
-
|
|
1391
|
-
tempStatement() {
|
|
1392
|
-
const name = this.consume('IDENTIFIER', 'Expected variable name').value;
|
|
1393
|
-
this.consume('OPERATOR', '=', 'Expected =');
|
|
1394
|
-
// collect raw tokens until bare => at depth 0, to avoid parsing `val =>` as arrow fn
|
|
1395
|
-
const valueToks = [];
|
|
1396
|
-
let _depth = 0;
|
|
1397
|
-
while (!this.isAtEnd()) {
|
|
1398
|
-
const _t = this.peek();
|
|
1399
|
-
if (_t.type === 'PUNCTUATION' && ['(', '[', '{'].includes(_t.value)) _depth++;
|
|
1400
|
-
if (_t.type === 'PUNCTUATION' && [')', ']', '}'].includes(_t.value)) _depth--;
|
|
1401
|
-
if (_t.type === 'OPERATOR' && _t.value === '=>' && _depth === 0) break;
|
|
1402
|
-
valueToks.push(this.advance());
|
|
1403
|
-
}
|
|
1404
|
-
this.consume('OPERATOR', '=>', 'Expected => after temp value');
|
|
1405
|
-
const tmpP = new Parser('');
|
|
1406
|
-
tmpP.tokens = [...valueToks, { type: 'EOF', value: null, line: 0, column: 0 }];
|
|
1407
|
-
tmpP.rawsrc = ''; tmpP.current = 0; tmpP.symbols = new Map(this.symbols);
|
|
1408
|
-
const value = tmpP.expression();
|
|
1409
|
-
const body = this.blockBody();
|
|
1410
|
-
this.match('PUNCTUATION', ';');
|
|
1411
|
-
return this.nd({ kind: 'temp', name, value, body });
|
|
1412
|
-
}
|
|
1413
|
-
|
|
1414
|
-
keepStatement() {
|
|
1415
|
-
const name = this.consume('IDENTIFIER', 'Expected variable name').value;
|
|
1416
|
-
this.consume('OPERATOR', '=', 'Expected =');
|
|
1417
|
-
const value = this.expression();
|
|
1418
|
-
this.match('PUNCTUATION', ';');
|
|
1419
|
-
return this.nd({ kind: 'keep', name, value });
|
|
1420
|
-
}
|
|
1421
|
-
|
|
1422
|
-
echoStatement() {
|
|
1423
|
-
const value = this.expression();
|
|
1424
|
-
this.match('PUNCTUATION', ';');
|
|
1425
|
-
return this.nd({ kind: 'echo', value });
|
|
1426
|
-
}
|
|
1427
|
-
|
|
1428
|
-
gearStatement() {
|
|
1429
|
-
let wait = { kind: 'value', value: 0 };
|
|
1430
|
-
if (this.check('PUNCTUATION', '(')) {
|
|
1431
|
-
this.advance();
|
|
1432
|
-
wait = this.expression();
|
|
1433
|
-
this.consume('PUNCTUATION', ')', 'Expected )');
|
|
1434
|
-
}
|
|
1435
|
-
const name = this.consumeName('Expected gear name').value;
|
|
1436
|
-
const body = this.blockBody();
|
|
1437
|
-
this.match('PUNCTUATION', ';');
|
|
1438
|
-
return this.nd({ kind: 'gear_decl', name, wait, body });
|
|
1439
|
-
}
|
|
1440
|
-
|
|
1441
|
-
engageStatement() {
|
|
1442
|
-
const gears = [this.consumeName('Expected gear name').value];
|
|
1443
|
-
while (this.match('OPERATOR', '>>')) gears.push(this.consumeName('Expected gear name').value);
|
|
1444
|
-
this.match('PUNCTUATION', ';');
|
|
1445
|
-
return this.nd({ kind: 'engage', gears });
|
|
1446
|
-
}
|
|
1447
|
-
|
|
1448
|
-
sandboxStatement() {
|
|
1449
|
-
const code = this.blockBodyRaw();
|
|
1450
|
-
this.match('PUNCTUATION', ';');
|
|
1451
|
-
return this.nd({ kind: 'sandbox', code });
|
|
1452
|
-
}
|
|
1453
|
-
|
|
1454
|
-
blockBodyRaw() {
|
|
1455
|
-
this.consume('PUNCTUATION', '{', 'Expected {');
|
|
1456
|
-
let body = '', depth = 1;
|
|
1457
|
-
while (!this.isAtEnd()) {
|
|
1458
|
-
const t = this.peek();
|
|
1459
|
-
if (t.type === 'PUNCTUATION' && t.value === '{') depth++;
|
|
1460
|
-
if (t.type === 'PUNCTUATION' && t.value === '}') {
|
|
1461
|
-
depth--;
|
|
1462
|
-
if (depth === 0) { this.advance(); break; }
|
|
1463
|
-
}
|
|
1464
|
-
body += (t.value ?? '') + ' '; this.advance();
|
|
1465
|
-
}
|
|
1466
|
-
return body.trim();
|
|
1467
|
-
}
|
|
1468
|
-
|
|
1469
|
-
inferStatement() {
|
|
1470
|
-
this.consume('PUNCTUATION', '(', 'Expected (');
|
|
1471
|
-
const model = this.expression();
|
|
1472
|
-
this.consume('PUNCTUATION', ')', 'Expected )');
|
|
1473
|
-
this.consume('OPERATOR', '=>', 'Expected =>');
|
|
1474
|
-
const varName = this.consume('IDENTIFIER', 'Expected variable name').value;
|
|
1475
|
-
this.consume('PUNCTUATION', ':', 'Expected :');
|
|
1476
|
-
const prompt = this.check('PUNCTUATION', '{') ? { kind: 'value', value: this.blockBodyRaw() } : this.expression();
|
|
1477
|
-
this.match('PUNCTUATION', ';');
|
|
1478
|
-
return this.nd({ kind: 'infer', model, varName, prompt });
|
|
1479
|
-
}
|
|
1480
|
-
|
|
1481
|
-
addtoStatement() {
|
|
1482
|
-
const name = this.consume('IDENTIFIER', 'Expected variable name').value;
|
|
1483
|
-
const value = this.expression();
|
|
1484
|
-
let key = null;
|
|
1485
|
-
if (this.match('PUNCTUATION', ':')) key = this.expression();
|
|
1486
|
-
this.match('PUNCTUATION', ';');
|
|
1487
|
-
return this.nd({ kind: 'addto', name, value, key });
|
|
1488
|
-
}
|
|
1489
|
-
|
|
1490
|
-
macroStatement() {
|
|
1491
|
-
const name = this.consume('IDENTIFIER', 'Expected macro name').value;
|
|
1492
|
-
this.consume('OPERATOR', '=', 'Expected =');
|
|
1493
|
-
let value;
|
|
1494
|
-
if (this.check('STRING') || this.check('NUMBER')) value = this.nd({ kind: 'value', value: this.advance().value });
|
|
1495
|
-
else value = this.expression();
|
|
1496
|
-
this.match('PUNCTUATION', ';');
|
|
1497
|
-
return this.nd({ kind: 'macro_decl', name, value });
|
|
1498
|
-
}
|
|
1499
|
-
|
|
1500
|
-
blockDeclStatement() {
|
|
1501
|
-
const name = this.consumeName('Expected block name').value;
|
|
1502
|
-
const body = this.blockBody();
|
|
1503
|
-
this.match('PUNCTUATION', ';');
|
|
1504
|
-
return this.nd({ kind: 'block_decl', name, body });
|
|
1505
|
-
}
|
|
1506
|
-
|
|
1507
|
-
snippetStatement() {
|
|
1508
|
-
const name = this.consumeName('Expected snippet name').value;
|
|
1509
|
-
const body = this.blockBody();
|
|
1510
|
-
this.match('PUNCTUATION', ';');
|
|
1511
|
-
return this.nd({ kind: 'snippet_decl', name, body });
|
|
1512
|
-
}
|
|
1513
|
-
|
|
1514
|
-
defuncStatement() {
|
|
1515
|
-
const name = this.consume('IDENTIFIER', 'Expected function name').value;
|
|
1516
|
-
const { args, body } = this.parseFuncBody(true);
|
|
1517
|
-
this.match('PUNCTUATION', ';');
|
|
1518
|
-
return this.nd({ kind: 'defunc_decl', name, args, body });
|
|
1519
|
-
}
|
|
1520
|
-
|
|
1521
|
-
lambdaStatement() {
|
|
1522
|
-
const name = this.consume('IDENTIFIER', 'Expected lambda name').value;
|
|
1523
|
-
this.consume('OPERATOR', '=', 'Expected =');
|
|
1524
|
-
const fn = this.expression();
|
|
1525
|
-
this.match('PUNCTUATION', ';');
|
|
1526
|
-
return this.nd({ kind: 'lambda_decl', name, fn });
|
|
1527
|
-
}
|
|
1528
|
-
|
|
1529
|
-
composeStatement() {
|
|
1530
|
-
const name = this.consume('IDENTIFIER', 'Expected compose name').value;
|
|
1531
|
-
this.consume('OPERATOR', '=', 'Expected =');
|
|
1532
|
-
const fns = [this.expression()];
|
|
1533
|
-
while (this.match('OPERATOR', '>>')) fns.push(this.expression());
|
|
1534
|
-
this.match('PUNCTUATION', ';');
|
|
1535
|
-
return this.nd({ kind: 'compose_decl', name, fns });
|
|
1536
|
-
}
|
|
1537
|
-
|
|
1538
|
-
partialStatement() {
|
|
1539
|
-
const name = this.consume('IDENTIFIER', 'Expected partial name').value;
|
|
1540
|
-
this.consume('OPERATOR', '=', 'Expected =');
|
|
1541
|
-
const callee = this.expression();
|
|
1542
|
-
this.match('PUNCTUATION', ';');
|
|
1543
|
-
return this.nd({ kind: 'partial_decl', name, callee });
|
|
1544
|
-
}
|
|
1545
|
-
|
|
1546
|
-
backupStatement() {
|
|
1547
|
-
const name = this.consume('IDENTIFIER', 'Expected variable name').value;
|
|
1548
|
-
this.consume('OPERATOR', '=', 'Expected =');
|
|
1549
|
-
const value = this.expression();
|
|
1550
|
-
this.match('PUNCTUATION', ';');
|
|
1551
|
-
return this.nd({ kind: 'backup_val', name, value });
|
|
1552
|
-
}
|
|
1553
|
-
|
|
1554
|
-
retrieveStatement() {
|
|
1555
|
-
const name = this.consume('IDENTIFIER', 'Expected variable name').value;
|
|
1556
|
-
this.match('PUNCTUATION', ';');
|
|
1557
|
-
return this.nd({ kind: 'retrieve_val', name });
|
|
1558
|
-
}
|
|
1559
|
-
|
|
1560
|
-
describeStatement() {
|
|
1561
|
-
const text = this.expression();
|
|
1562
|
-
this.match('PUNCTUATION', ';');
|
|
1563
|
-
return this.nd({ kind: 'describe', text });
|
|
1564
|
-
}
|
|
1565
|
-
|
|
1566
|
-
usingStatement() {
|
|
1567
|
-
if (this.check('KEYWORD', 'namespace')) {
|
|
1568
|
-
this.advance();
|
|
1569
|
-
const ns = this.expression();
|
|
1570
|
-
this.match('PUNCTUATION', ';');
|
|
1571
|
-
return this.nd({ kind: 'using_namespace', ns });
|
|
1572
|
-
}
|
|
1573
|
-
const flag = this.consumeName('Expected feature flag name').value;
|
|
1574
|
-
this.match('PUNCTUATION', ';');
|
|
1575
|
-
return this.nd({ kind: 'using_flag', flag });
|
|
1576
|
-
}
|
|
1577
|
-
|
|
1578
|
-
unuseStatement() {
|
|
1579
|
-
const flag = this.consumeName('Expected feature flag name').value;
|
|
1580
|
-
this.match('PUNCTUATION', ';');
|
|
1581
|
-
return this.nd({ kind: 'unuse_flag', flag });
|
|
1582
|
-
}
|
|
1583
|
-
|
|
1584
|
-
classifyStatement() {
|
|
1585
|
-
const value = this.expression();
|
|
1586
|
-
let targetType = null;
|
|
1587
|
-
if (this.match('KEYWORD', 'as')) targetType = this.parseTypeExpr();
|
|
1588
|
-
this.match('PUNCTUATION', ';');
|
|
1589
|
-
return this.nd({ kind: 'classify', value, targetType });
|
|
1590
|
-
}
|
|
1591
|
-
|
|
1592
|
-
rateStatement() {
|
|
1593
|
-
this.consume('PUNCTUATION', '(', 'Expected (');
|
|
1594
|
-
const value = this.expression();
|
|
1595
|
-
this.consume('PUNCTUATION', ')', 'Expected )');
|
|
1596
|
-
const cast_type = this.consumeName('Expected cast type').value;
|
|
1597
|
-
this.match('PUNCTUATION', ';');
|
|
1598
|
-
return this.nd({ kind: 'rate_cast', value, cast_type });
|
|
1599
|
-
}
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
// ══════════════ Ported from old Nova Classic ══════════════
|
|
1603
|
-
|
|
1604
|
-
guardStatement() {
|
|
1605
|
-
this.consume('PUNCTUATION', '(', 'Expected (');
|
|
1606
|
-
const cond = this.expression();
|
|
1607
|
-
this.consume('PUNCTUATION', ')', 'Expected )');
|
|
1608
|
-
const body = this.check('PUNCTUATION', '{') ? this.blockBody() : [this.statement()];
|
|
1609
|
-
const node = this.nd({ kind: 'guard', cond, body });
|
|
1610
|
-
if (this.match('KEYWORD', 'else')) node.elseBody = [this.statement()];
|
|
1611
|
-
return node;
|
|
1612
|
-
}
|
|
1613
|
-
|
|
1614
|
-
whenStatement() {
|
|
1615
|
-
// when cond do { body } — run body once if cond is truthy
|
|
1616
|
-
const cond = this.expression();
|
|
1617
|
-
if (this.match('KEYWORD', 'do') || this.match('KEYWORD', 'then')) { }
|
|
1618
|
-
const body = this.blockBody();
|
|
1619
|
-
this.match('PUNCTUATION', ';');
|
|
1620
|
-
return this.nd({ kind: 'when_stmt', cond, body });
|
|
1621
|
-
}
|
|
1622
|
-
|
|
1623
|
-
withStatement() {
|
|
1624
|
-
// with (obj) { body } — run body with obj as context
|
|
1625
|
-
// with option featureFlag { body }
|
|
1626
|
-
if (this.check('KEYWORD', 'option') || this.check('IDENTIFIER', 'option')) {
|
|
1627
|
-
this.advance();
|
|
1628
|
-
const flag = this.consumeName('Expected option name').value;
|
|
1629
|
-
const body = this.blockBody();
|
|
1630
|
-
return this.nd({ kind: 'with_option', flag, body });
|
|
1631
|
-
}
|
|
1632
|
-
this.consume('PUNCTUATION', '(', 'Expected (');
|
|
1633
|
-
const target = this.expression();
|
|
1634
|
-
this.consume('PUNCTUATION', ')', 'Expected )');
|
|
1635
|
-
const body = this.blockBody();
|
|
1636
|
-
this.match('PUNCTUATION', ';');
|
|
1637
|
-
return this.nd({ kind: 'with_ctx', target, body });
|
|
1638
|
-
}
|
|
1639
|
-
|
|
1640
|
-
loopStatement() {
|
|
1641
|
-
// loop VAR in EXPR => { body }
|
|
1642
|
-
const varName = this.consume('IDENTIFIER', 'Expected loop variable').value;
|
|
1643
|
-
this.consume('KEYWORD', 'in') || this.consume('OPERATOR', 'in', 'Expected in');
|
|
1644
|
-
// consume 'in' — may be KEYWORD or OPERATOR
|
|
1645
|
-
const iterable = this.expression();
|
|
1646
|
-
const body = this.blockBody();
|
|
1647
|
-
this.match('PUNCTUATION', ';');
|
|
1648
|
-
return this.nd({ kind: 'loop_in', varName, iterable, body });
|
|
1649
|
-
}
|
|
1650
|
-
|
|
1651
|
-
waitStatement() {
|
|
1652
|
-
this.consume('PUNCTUATION', '(', 'Expected (');
|
|
1653
|
-
const ms = this.expression();
|
|
1654
|
-
this.consume('PUNCTUATION', ')', 'Expected )');
|
|
1655
|
-
this.match('PUNCTUATION', ';');
|
|
1656
|
-
return this.nd({ kind: 'wait_stmt', ms });
|
|
1657
|
-
}
|
|
1658
|
-
|
|
1659
|
-
lendStatement() {
|
|
1660
|
-
// lend fn source to target | lend m mapName with funcName
|
|
1661
|
-
if (this.check('KEYWORD', 'fn') || this.check('IDENTIFIER', 'fn')) {
|
|
1662
|
-
this.advance();
|
|
1663
|
-
const source = this.consume('IDENTIFIER', 'Expected source fn').value;
|
|
1664
|
-
this.consumeName('Expected to'); // 'to'
|
|
1665
|
-
const target = this.consume('IDENTIFIER', 'Expected target fn').value;
|
|
1666
|
-
this.match('PUNCTUATION', ';');
|
|
1667
|
-
return this.nd({ kind: 'lend_fn', source, target });
|
|
1668
|
-
}
|
|
1669
|
-
if (this.check('KEYWORD', 'm') || this.check('IDENTIFIER', 'm')) {
|
|
1670
|
-
this.advance();
|
|
1671
|
-
const mapName = this.consume('IDENTIFIER', 'Expected map name').value;
|
|
1672
|
-
this.consumeName('Expected with');
|
|
1673
|
-
const funcName = this.consume('IDENTIFIER', 'Expected function name').value;
|
|
1674
|
-
this.match('PUNCTUATION', ';');
|
|
1675
|
-
return this.nd({ kind: 'lend_m', mapName, funcName });
|
|
1676
|
-
}
|
|
1677
|
-
this.match('PUNCTUATION', ';');
|
|
1678
|
-
return this.nd({ kind: 'skip' });
|
|
1679
|
-
}
|
|
1680
|
-
|
|
1681
|
-
sstreamStatement() {
|
|
1682
|
-
// sstream name => { put expr; put expr; }
|
|
1683
|
-
const name = this.consume('IDENTIFIER', 'Expected stream name').value;
|
|
1684
|
-
this.consume('OPERATOR', '=>', 'Expected =>');
|
|
1685
|
-
const body = this.blockBody();
|
|
1686
|
-
this.match('PUNCTUATION', ';');
|
|
1687
|
-
return this.nd({ kind: 'sstream_decl', name, body });
|
|
1688
|
-
}
|
|
1689
|
-
|
|
1690
|
-
declareStatement() {
|
|
1691
|
-
// declare (expr) as/into/with name [log] [throw] [finalize]
|
|
1692
|
-
this.consume('PUNCTUATION', '(', 'Expected (');
|
|
1693
|
-
const value = this.expression();
|
|
1694
|
-
this.consume('PUNCTUATION', ')', 'Expected )');
|
|
1695
|
-
let name = null, mode = 'quiet';
|
|
1696
|
-
if (this.check('KEYWORD', 'as') || this.check('IDENTIFIER', 'as')) { this.advance(); mode = 'as'; name = this.consume('IDENTIFIER', 'Expected name').value; }
|
|
1697
|
-
else if (this.matchIdent('into')) { mode = 'into'; name = this.consume('IDENTIFIER', 'Expected name').value; }
|
|
1698
|
-
else if (this.matchIdent('with')) { mode = 'with'; name = this.consume('IDENTIFIER', 'Expected name').value; }
|
|
1699
|
-
const flags = [];
|
|
1700
|
-
while (this.check('IDENTIFIER') && ['log', 'throw', 'finalize', 'quietly'].includes(this.peek().value)) flags.push(this.advance().value);
|
|
1701
|
-
this.match('PUNCTUATION', ';');
|
|
1702
|
-
return this.nd({ kind: 'declare_stmt', value, name, mode, flags });
|
|
1703
|
-
}
|
|
1704
|
-
|
|
1705
|
-
sessionStatement() {
|
|
1706
|
-
this.consume('PUNCTUATION', '(', 'Expected (');
|
|
1707
|
-
const name = this.expression();
|
|
1708
|
-
this.consume('PUNCTUATION', ')', 'Expected )');
|
|
1709
|
-
const body = this.blockBodyRaw();
|
|
1710
|
-
this.match('PUNCTUATION', ';');
|
|
1711
|
-
return this.nd({ kind: 'session_decl', name, body });
|
|
1712
|
-
}
|
|
1713
|
-
|
|
1714
|
-
enterStatement() {
|
|
1715
|
-
const key = this.consumeName('Expected enter target').value;
|
|
1716
|
-
const type = this.consumeName('Expected enter type').value;
|
|
1717
|
-
this.match('PUNCTUATION', ';');
|
|
1718
|
-
return this.nd({ kind: 'enter_stmt', key, type });
|
|
1719
|
-
}
|
|
1720
|
-
|
|
1721
|
-
namespaceStatement() {
|
|
1722
|
-
const path = this.expression();
|
|
1723
|
-
this.match('PUNCTUATION', ';');
|
|
1724
|
-
return this.nd({ kind: 'namespace_decl', path });
|
|
1725
|
-
}
|
|
1726
|
-
|
|
1727
|
-
resuStatement() {
|
|
1728
|
-
// resu name(params) => { valueBlock }, ;
|
|
1729
|
-
const name = this.consume('IDENTIFIER', 'Expected resu name').value;
|
|
1730
|
-
this.consume('PUNCTUATION', '(', 'Expected (');
|
|
1731
|
-
const params = [];
|
|
1732
|
-
if (!this.check('PUNCTUATION', ')')) {
|
|
1733
|
-
do { params.push(this.consume('IDENTIFIER', 'Expected param').value); }
|
|
1734
|
-
while (this.match('PUNCTUATION', ','));
|
|
1735
|
-
}
|
|
1736
|
-
this.consume('PUNCTUATION', ')', 'Expected )');
|
|
1737
|
-
this.consume('OPERATOR', '=>', 'Expected =>');
|
|
1738
|
-
const body = this.blockBodyRaw();
|
|
1739
|
-
this.match('PUNCTUATION', ',');
|
|
1740
|
-
this.match('PUNCTUATION', ';');
|
|
1741
|
-
return this.nd({ kind: 'resu_decl', name, params, body });
|
|
1742
|
-
}
|
|
1743
|
-
|
|
1744
|
-
exportStatement() {
|
|
1745
|
-
this.consume('PUNCTUATION', '{', 'Expected {');
|
|
1746
|
-
const exports = [];
|
|
1747
|
-
while (!this.check('PUNCTUATION', '}') && !this.isAtEnd()) {
|
|
1748
|
-
const name = this.expression();
|
|
1749
|
-
this.consume('OPERATOR', '=', 'Expected =');
|
|
1750
|
-
const body = this.blockBodyRaw();
|
|
1751
|
-
exports.push({ name, body });
|
|
1752
|
-
this.match('PUNCTUATION', ',');
|
|
1753
|
-
}
|
|
1754
|
-
this.consume('PUNCTUATION', '}', 'Expected }');
|
|
1755
|
-
this.match('PUNCTUATION', ';');
|
|
1756
|
-
return this.nd({ kind: 'export_stmt', exports });
|
|
1757
|
-
}
|
|
1758
|
-
|
|
1759
|
-
warnStatement() {
|
|
1760
|
-
const msg = this.expression(); this.match('PUNCTUATION', ';');
|
|
1761
|
-
return this.nd({ kind: 'warn_stmt', msg });
|
|
1762
|
-
}
|
|
1763
|
-
|
|
1764
|
-
infoStatement() {
|
|
1765
|
-
const msg = this.expression(); this.match('PUNCTUATION', ';');
|
|
1766
|
-
return this.nd({ kind: 'info_stmt', msg });
|
|
1767
|
-
}
|
|
1768
|
-
|
|
1769
|
-
timeStatement() {
|
|
1770
|
-
const body = this.blockBody(); this.match('PUNCTUATION', ';');
|
|
1771
|
-
return this.nd({ kind: 'time_block', body });
|
|
1772
|
-
}
|
|
1773
|
-
|
|
1774
|
-
keyfuncStatement() {
|
|
1775
|
-
const name = this.consume('IDENTIFIER', 'Expected keyfunc name').value;
|
|
1776
|
-
this.consume('PUNCTUATION', '(', 'Expected (');
|
|
1777
|
-
const params = []; if (!this.check('PUNCTUATION', ')')) { do { params.push(this.consume('IDENTIFIER', 'Expected param').value); } while (this.match('PUNCTUATION', ',')); }
|
|
1778
|
-
this.consume('PUNCTUATION', ')', 'Expected )');
|
|
1779
|
-
const matchBody = this.blockBodyRaw();
|
|
1780
|
-
const logic = this.blockBodyRaw();
|
|
1781
|
-
this.match('PUNCTUATION', ';');
|
|
1782
|
-
return this.nd({ kind: 'keyfunc_decl', name, params, matchBody, logic });
|
|
1783
|
-
}
|
|
1784
|
-
|
|
1785
|
-
printStatement() {
|
|
1786
|
-
this.consume('PUNCTUATION', '(', 'Expected (');
|
|
1787
|
-
const args = []; if (!this.check('PUNCTUATION', ')')) { do { args.push(this.expression()); } while (this.match('PUNCTUATION', ',')); }
|
|
1788
|
-
this.consume('PUNCTUATION', ')', 'Expected )');
|
|
1789
|
-
this.match('PUNCTUATION', ';');
|
|
1790
|
-
return this.nd({ kind: 'print_stmt', args });
|
|
1791
|
-
}
|
|
1792
|
-
|
|
1793
|
-
logStatement() {
|
|
1794
|
-
this.consume('PUNCTUATION', '(', 'Expected (');
|
|
1795
|
-
const args = []; if (!this.check('PUNCTUATION', ')')) { do { args.push(this.expression()); } while (this.match('PUNCTUATION', ',')); }
|
|
1796
|
-
this.consume('PUNCTUATION', ')', 'Expected )');
|
|
1797
|
-
this.match('PUNCTUATION', ';');
|
|
1798
|
-
return this.nd({ kind: 'log_stmt', args });
|
|
1799
|
-
}
|
|
1800
|
-
|
|
1801
|
-
printlnStatement() {
|
|
1802
|
-
this.consume('PUNCTUATION', '(', 'Expected (');
|
|
1803
|
-
const value = this.expression();
|
|
1804
|
-
this.consume('PUNCTUATION', ')', 'Expected )');
|
|
1805
|
-
this.match('PUNCTUATION', ';');
|
|
1806
|
-
return this.nd({ kind: 'println_stmt', value });
|
|
1807
|
-
}
|
|
1808
|
-
|
|
1809
|
-
loglnStatement() {
|
|
1810
|
-
this.consume('PUNCTUATION', '(', 'Expected (');
|
|
1811
|
-
const value = this.expression();
|
|
1812
|
-
this.consume('PUNCTUATION', ')', 'Expected )');
|
|
1813
|
-
this.match('PUNCTUATION', ';');
|
|
1814
|
-
return this.nd({ kind: 'logln_stmt', value });
|
|
1815
|
-
}
|
|
1816
|
-
|
|
1817
|
-
bannerStatement() {
|
|
1818
|
-
// banner is a terminal UI — parse as expression
|
|
1819
|
-
const args = [];
|
|
1820
|
-
if (this.check('PUNCTUATION', '(')) {
|
|
1821
|
-
this.advance();
|
|
1822
|
-
if (!this.check('PUNCTUATION', ')')) { do { args.push(this.expression()); } while (this.match('PUNCTUATION', ',')); }
|
|
1823
|
-
this.consume('PUNCTUATION', ')', 'Expected )');
|
|
1824
|
-
}
|
|
1825
|
-
this.match('PUNCTUATION', ';');
|
|
1826
|
-
return this.nd({ kind: 'banner_stmt', args });
|
|
1827
|
-
}
|
|
1828
|
-
|
|
1829
|
-
readFileStatement() {
|
|
1830
|
-
const varName = this.consume('IDENTIFIER', 'Expected variable name').value;
|
|
1831
|
-
this.consume('OPERATOR', '=', 'Expected =');
|
|
1832
|
-
this.consume('PUNCTUATION', '(', 'Expected (');
|
|
1833
|
-
const path = this.expression();
|
|
1834
|
-
this.consume('PUNCTUATION', ')', 'Expected )');
|
|
1835
|
-
this.match('PUNCTUATION', ';');
|
|
1836
|
-
return this.nd({ kind: 'readfile_stmt', varName, path });
|
|
1837
|
-
}
|
|
1838
|
-
|
|
1839
|
-
execFileStatement() {
|
|
1840
|
-
this.consume('PUNCTUATION', '(', 'Expected (');
|
|
1841
|
-
const path = this.expression();
|
|
1842
|
-
this.consume('PUNCTUATION', ')', 'Expected )');
|
|
1843
|
-
this.match('PUNCTUATION', ';');
|
|
1844
|
-
return this.nd({ kind: 'execfile_stmt', path });
|
|
1845
|
-
}
|
|
1846
|
-
|
|
1847
|
-
createFileStatement() {
|
|
1848
|
-
this.consume('PUNCTUATION', '(', 'Expected (');
|
|
1849
|
-
const path = this.expression();
|
|
1850
|
-
this.consume('PUNCTUATION', ',', 'Expected ,');
|
|
1851
|
-
const content = this.expression();
|
|
1852
|
-
this.consume('PUNCTUATION', ')', 'Expected )');
|
|
1853
|
-
this.match('PUNCTUATION', ';');
|
|
1854
|
-
return this.nd({ kind: 'createfile_stmt', path, content });
|
|
1855
|
-
}
|
|
1856
|
-
|
|
1857
|
-
deleteFileStatement() {
|
|
1858
|
-
this.consume('PUNCTUATION', '(', 'Expected (');
|
|
1859
|
-
const path = this.expression();
|
|
1860
|
-
this.consume('PUNCTUATION', ')', 'Expected )');
|
|
1861
|
-
this.match('PUNCTUATION', ';');
|
|
1862
|
-
return this.nd({ kind: 'deletefile_stmt', path });
|
|
1863
|
-
}
|
|
1864
|
-
|
|
1865
|
-
listFilesStatement() {
|
|
1866
|
-
this.consume('PUNCTUATION', '(', 'Expected (');
|
|
1867
|
-
const dir = this.expression();
|
|
1868
|
-
this.consume('PUNCTUATION', ')', 'Expected )');
|
|
1869
|
-
this.match('OPERATOR', '=>');
|
|
1870
|
-
let varName = null;
|
|
1871
|
-
if (this.check('IDENTIFIER')) varName = this.advance().value;
|
|
1872
|
-
this.match('PUNCTUATION', ';');
|
|
1873
|
-
return this.nd({ kind: 'listfiles_stmt', dir, varName });
|
|
1874
|
-
}
|
|
1875
|
-
|
|
1876
|
-
termStatement() {
|
|
1877
|
-
this.consume('PUNCTUATION', '(', 'Expected (');
|
|
1878
|
-
const cmd = this.expression();
|
|
1879
|
-
this.consume('PUNCTUATION', ')', 'Expected )');
|
|
1880
|
-
const shell = this.consumeName('Expected shell name').value;
|
|
1881
|
-
this.match('PUNCTUATION', ';');
|
|
1882
|
-
return this.nd({ kind: 'term_stmt', cmd, shell });
|
|
1883
|
-
}
|
|
1884
|
-
|
|
1885
|
-
shStatement() {
|
|
1886
|
-
const code = this.blockBodyRaw();
|
|
1887
|
-
this.match('PUNCTUATION', ';');
|
|
1888
|
-
return this.nd({ kind: 'sh_stmt', code });
|
|
1889
|
-
}
|
|
1890
|
-
|
|
1891
|
-
execStatement() {
|
|
1892
|
-
this.consume('PUNCTUATION', '(', 'Expected (');
|
|
1893
|
-
const code = this.expression();
|
|
1894
|
-
this.consume('PUNCTUATION', ')', 'Expected )');
|
|
1895
|
-
this.match('PUNCTUATION', ';');
|
|
1896
|
-
return this.nd({ kind: 'exec_stmt', code });
|
|
1897
|
-
}
|
|
1898
|
-
|
|
1899
|
-
notifyStatement() {
|
|
1900
|
-
this.consume('PUNCTUATION', '(', 'Expected (');
|
|
1901
|
-
const title = this.expression();
|
|
1902
|
-
this.match('PUNCTUATION', ',');
|
|
1903
|
-
const content = this.check('PUNCTUATION', ')') ? null : this.expression();
|
|
1904
|
-
this.consume('PUNCTUATION', ')', 'Expected )');
|
|
1905
|
-
this.match('PUNCTUATION', ';');
|
|
1906
|
-
return this.nd({ kind: 'notify_stmt', title, content });
|
|
1907
|
-
}
|
|
1908
|
-
|
|
1909
|
-
toastStatement() {
|
|
1910
|
-
this.consume('PUNCTUATION', '(', 'Expected (');
|
|
1911
|
-
const msg = this.expression();
|
|
1912
|
-
this.consume('PUNCTUATION', ')', 'Expected )');
|
|
1913
|
-
this.match('PUNCTUATION', ';');
|
|
1914
|
-
return this.nd({ kind: 'toast_stmt', msg });
|
|
1915
|
-
}
|
|
1916
|
-
|
|
1917
|
-
vibrateStatement() {
|
|
1918
|
-
this.consume('PUNCTUATION', '(', 'Expected (');
|
|
1919
|
-
const ms = this.expression();
|
|
1920
|
-
this.consume('PUNCTUATION', ')', 'Expected )');
|
|
1921
|
-
this.match('PUNCTUATION', ';');
|
|
1922
|
-
return this.nd({ kind: 'vibrate_stmt', ms });
|
|
1923
|
-
}
|
|
1924
|
-
|
|
1925
|
-
clipboardStatement() {
|
|
1926
|
-
this.consume('PUNCTUATION', '(', 'Expected (');
|
|
1927
|
-
const text = this.expression();
|
|
1928
|
-
this.consume('PUNCTUATION', ')', 'Expected )');
|
|
1929
|
-
this.match('PUNCTUATION', ';');
|
|
1930
|
-
return this.nd({ kind: 'clipboard_stmt', text });
|
|
1931
|
-
}
|
|
1932
|
-
|
|
1933
|
-
cameraStatement() {
|
|
1934
|
-
this.consume('PUNCTUATION', '(', 'Expected (');
|
|
1935
|
-
const path = this.expression();
|
|
1936
|
-
this.consume('PUNCTUATION', ')', 'Expected )');
|
|
1937
|
-
this.match('PUNCTUATION', ';');
|
|
1938
|
-
return this.nd({ kind: 'camera_stmt', path });
|
|
1939
|
-
}
|
|
1940
|
-
|
|
1941
|
-
shareStatement() {
|
|
1942
|
-
this.consume('PUNCTUATION', '(', 'Expected (');
|
|
1943
|
-
const path = this.expression();
|
|
1944
|
-
this.consume('PUNCTUATION', ')', 'Expected )');
|
|
1945
|
-
this.match('PUNCTUATION', ';');
|
|
1946
|
-
return this.nd({ kind: 'share_stmt', path });
|
|
1947
|
-
}
|
|
1948
|
-
|
|
1949
|
-
openStatement() {
|
|
1950
|
-
this.consume('PUNCTUATION', '(', 'Expected (');
|
|
1951
|
-
const path = this.expression();
|
|
1952
|
-
this.consume('PUNCTUATION', ')', 'Expected )');
|
|
1953
|
-
this.match('PUNCTUATION', ';');
|
|
1954
|
-
return this.nd({ kind: 'open_stmt', path });
|
|
1955
|
-
}
|
|
1956
|
-
|
|
1957
|
-
sysinfoStatement(kind) {
|
|
1958
|
-
let varName = null;
|
|
1959
|
-
if (this.match('OPERATOR', '=>')) varName = this.consume('IDENTIFIER', 'Expected variable name').value;
|
|
1960
|
-
this.match('PUNCTUATION', ';');
|
|
1961
|
-
return this.nd({ kind: 'sysinfo_stmt', info: kind, varName });
|
|
1962
|
-
}
|
|
1963
|
-
|
|
1964
|
-
pathStatement(sub) {
|
|
1965
|
-
this.consume('PUNCTUATION', '(', 'Expected (');
|
|
1966
|
-
const target = this.expression();
|
|
1967
|
-
this.consume('PUNCTUATION', ')', 'Expected )');
|
|
1968
|
-
this.match('PUNCTUATION', ';');
|
|
1969
|
-
return this.nd({ kind: 'path_stmt', sub, target });
|
|
1970
|
-
}
|
|
1971
|
-
|
|
1972
|
-
cryptoStatement(fn) {
|
|
1973
|
-
this.consume('PUNCTUATION', '(', 'Expected (');
|
|
1974
|
-
const arg = this.expression();
|
|
1975
|
-
let varName = null;
|
|
1976
|
-
if (this.match('PUNCTUATION', ',')) varName = this.consume('IDENTIFIER', 'Expected variable').value;
|
|
1977
|
-
this.consume('PUNCTUATION', ')', 'Expected )');
|
|
1978
|
-
if (!varName && this.match('OPERATOR', '=>')) varName = this.consume('IDENTIFIER', 'Expected variable').value;
|
|
1979
|
-
this.match('PUNCTUATION', ';');
|
|
1980
|
-
return this.nd({ kind: 'crypto_stmt', fn, arg, varName });
|
|
1981
|
-
}
|
|
1982
|
-
|
|
1983
|
-
parseurlStatement() {
|
|
1984
|
-
this.consume('PUNCTUATION', '(', 'Expected (');
|
|
1985
|
-
const url = this.expression();
|
|
1986
|
-
this.consume('PUNCTUATION', ')', 'Expected )');
|
|
1987
|
-
let varName = null;
|
|
1988
|
-
if (this.match('OPERATOR', '=>')) varName = this.consume('IDENTIFIER', 'Expected variable').value;
|
|
1989
|
-
this.match('PUNCTUATION', ';');
|
|
1990
|
-
return this.nd({ kind: 'parseurl_stmt', url, varName });
|
|
1991
|
-
}
|
|
1992
|
-
|
|
1993
|
-
existsStatement() {
|
|
1994
|
-
this.consume('PUNCTUATION', '(', 'Expected (');
|
|
1995
|
-
const path = this.expression();
|
|
1996
|
-
this.consume('PUNCTUATION', ')', 'Expected )');
|
|
1997
|
-
let varName = null;
|
|
1998
|
-
if (this.match('OPERATOR', '=>')) varName = this.consume('IDENTIFIER', 'Expected variable').value;
|
|
1999
|
-
this.match('PUNCTUATION', ';');
|
|
2000
|
-
return this.nd({ kind: 'exists_stmt', path, varName });
|
|
2001
|
-
}
|
|
2002
|
-
|
|
2003
|
-
exptStatement() {
|
|
2004
|
-
// expt expected from { block }
|
|
2005
|
-
const expected = this.consumeName('Expected value').value;
|
|
2006
|
-
this.consumeName('Expected from');
|
|
2007
|
-
const body = this.blockBody();
|
|
2008
|
-
this.match('PUNCTUATION', ';');
|
|
2009
|
-
return this.nd({ kind: 'expt_stmt', expected, body });
|
|
2010
|
-
}
|
|
2011
|
-
|
|
2012
|
-
optionStatement() {
|
|
2013
|
-
// option name = expr
|
|
2014
|
-
const name = this.consumeName('Expected option name').value;
|
|
2015
|
-
this.consume('OPERATOR', '=', 'Expected =');
|
|
2016
|
-
const value = this.expression();
|
|
2017
|
-
this.match('PUNCTUATION', ';');
|
|
2018
|
-
return this.nd({ kind: 'option_stmt', name, value });
|
|
2019
|
-
}
|
|
2020
|
-
|
|
2021
|
-
// ══════════════ HTTP / Server ══════════════
|
|
2022
|
-
|
|
2023
|
-
/**
|
|
2024
|
-
* server(port) {
|
|
2025
|
-
* get "/path"(req) { ... }
|
|
2026
|
-
* post "/path"(req) { ... }
|
|
2027
|
-
* }
|
|
2028
|
-
*/
|
|
2029
|
-
serverStatement() {
|
|
2030
|
-
this.consume('PUNCTUATION', '(', 'Expected ( after server');
|
|
2031
|
-
const port = this.expression();
|
|
2032
|
-
this.consume('PUNCTUATION', ')', 'Expected )');
|
|
2033
|
-
this.consume('PUNCTUATION', '{', 'Expected {');
|
|
2034
|
-
const routes = [];
|
|
2035
|
-
while (!this.check('PUNCTUATION', '}') && !this.isAtEnd()) {
|
|
2036
|
-
// Accept HTTP method keywords AND plain identifiers (e.g. 'websocket')
|
|
2037
|
-
const methodTok = this.peek();
|
|
2038
|
-
const HTTP_METHODS = new Set(['get', 'post', 'put', 'delete', 'patch', 'head', 'options']);
|
|
2039
|
-
if ((methodTok.type === 'KEYWORD' && HTTP_METHODS.has(methodTok.value)) ||
|
|
2040
|
-
(methodTok.type === 'OPERATOR' && HTTP_METHODS.has(methodTok.value)) ||
|
|
2041
|
-
methodTok.type === 'IDENTIFIER') {
|
|
2042
|
-
const method = this.advance().value;
|
|
2043
|
-
// Path can be a string, URL literal, or an expression
|
|
2044
|
-
let pathNode;
|
|
2045
|
-
if (this.check('URL')) pathNode = this.nd({ kind: 'url_literal', value: this.advance().value });
|
|
2046
|
-
else if (this.check('STRING')) pathNode = this.nd({ kind: 'value', value: this.advance().value });
|
|
2047
|
-
else pathNode = this.expression();
|
|
2048
|
-
// Optional (req, res) params
|
|
2049
|
-
const params = [];
|
|
2050
|
-
if (this.match('PUNCTUATION', '(')) {
|
|
2051
|
-
if (!this.check('PUNCTUATION', ')')) {
|
|
2052
|
-
do { params.push(this.consume('IDENTIFIER', 'Expected param name').value); }
|
|
2053
|
-
while (this.match('PUNCTUATION', ','));
|
|
2054
|
-
}
|
|
2055
|
-
this.consume('PUNCTUATION', ')', 'Expected )');
|
|
2056
|
-
}
|
|
2057
|
-
const body = this.blockBody();
|
|
2058
|
-
routes.push(this.nd({ kind: 'server_route', method, path: pathNode, params, body }));
|
|
2059
|
-
} else {
|
|
2060
|
-
this.error('Expected HTTP method keyword in server block');
|
|
2061
|
-
}
|
|
2062
|
-
}
|
|
2063
|
-
this.consume('PUNCTUATION', '}', 'Expected }');
|
|
2064
|
-
return this.nd({ kind: 'server_decl', port, routes });
|
|
2065
|
-
}
|
|
2066
|
-
|
|
2067
|
-
/**
|
|
2068
|
-
* fetch(url)
|
|
2069
|
-
* fetch(url, { method, headers, body })
|
|
2070
|
-
* Used as an expression too — handled via built-in function in executor.
|
|
2071
|
-
* As a statement: fetch(url) => varName (sync shorthand)
|
|
2072
|
-
*/
|
|
2073
|
-
fetchStatement() {
|
|
2074
|
-
this.consume('PUNCTUATION', '(', 'Expected ( after fetch');
|
|
2075
|
-
const url = this.expression();
|
|
2076
|
-
let options = null;
|
|
2077
|
-
if (this.match('PUNCTUATION', ',')) options = this.expression();
|
|
2078
|
-
this.consume('PUNCTUATION', ')', 'Expected )');
|
|
2079
|
-
let varName = null;
|
|
2080
|
-
if (this.match('OPERATOR', '=>')) varName = this.consume('IDENTIFIER', 'Expected variable name').value;
|
|
2081
|
-
this.match('PUNCTUATION', ';');
|
|
2082
|
-
return this.nd({ kind: 'fetch_stmt', url, options, varName });
|
|
2083
|
-
}
|
|
2084
|
-
peek() { return this.tokens[this.current]; }
|
|
2085
|
-
previous() { return this.tokens[this.current - 1]; }
|
|
2086
|
-
next() { return this.tokens[this.current + 1]; }
|
|
2087
|
-
isAtEnd() { return this.peek().type === 'EOF'; }
|
|
2088
|
-
|
|
2089
|
-
// Returns true when the current position looks like the start of a lambda
|
|
2090
|
-
// parameter list — used to distinguish `generator(...)` call from
|
|
2091
|
-
// `generator (...) => ...` or `generator id => ...`.
|
|
2092
|
-
// We look ahead past optional whitespace for: `(`, an IDENTIFIER not followed
|
|
2093
|
-
// by `(`, or the `async` keyword — all of which signal a lambda head.
|
|
2094
|
-
// Like _peekIsLambda but for the `async` keyword at current position.
|
|
2095
|
-
// `async` is a lambda modifier when followed by: `(` + eventual `)=>`,
|
|
2096
|
-
// an identifier + `=>`, or the `generator` identifier.
|
|
2097
|
-
_peekAsyncIsLambda() {
|
|
2098
|
-
const n1 = this.tokens[this.current + 1];
|
|
2099
|
-
if (!n1) return false;
|
|
2100
|
-
if (n1.type === 'IDENTIFIER' && n1.value === 'generator') return true;
|
|
2101
|
-
if (n1.type === 'PUNCTUATION' && n1.value === '(') {
|
|
2102
|
-
let depth = 0, i = this.current + 1;
|
|
2103
|
-
while (i < this.tokens.length) {
|
|
2104
|
-
const t = this.tokens[i++];
|
|
2105
|
-
if (t.type === 'PUNCTUATION' && t.value === '(') depth++;
|
|
2106
|
-
else if (t.type === 'PUNCTUATION' && t.value === ')') {
|
|
2107
|
-
depth--;
|
|
2108
|
-
if (depth === 0) break;
|
|
2109
|
-
}
|
|
2110
|
-
}
|
|
2111
|
-
const afterParen = this.tokens[i];
|
|
2112
|
-
return afterParen && afterParen.type === 'OPERATOR' && afterParen.value === '=>';
|
|
2113
|
-
}
|
|
2114
|
-
if (n1.type === 'IDENTIFIER') {
|
|
2115
|
-
const n2 = this.tokens[this.current + 2];
|
|
2116
|
-
return n2 && n2.type === 'OPERATOR' && n2.value === '=>';
|
|
2117
|
-
}
|
|
2118
|
-
return false;
|
|
2119
|
-
}
|
|
2120
|
-
|
|
2121
|
-
_peekIsLambda() {
|
|
2122
|
-
// After `generator`, the next token decides:
|
|
2123
|
-
const n1 = this.tokens[this.current + 1]; // token after `generator`
|
|
2124
|
-
if (!n1) return false;
|
|
2125
|
-
// `generator async ...` or `generator (` — lambda
|
|
2126
|
-
if (n1.type === 'KEYWORD' && n1.value === 'async') return true;
|
|
2127
|
-
if (n1.type === 'PUNCTUATION' && n1.value === '(') {
|
|
2128
|
-
// Could be a call `generator(...)` or a lambda `generator (...) => ...`
|
|
2129
|
-
// Look further: find the matching `)` then check if `=>` follows
|
|
2130
|
-
let depth = 0, i = this.current + 1;
|
|
2131
|
-
while (i < this.tokens.length) {
|
|
2132
|
-
const t = this.tokens[i++];
|
|
2133
|
-
if (t.type === 'PUNCTUATION' && t.value === '(') depth++;
|
|
2134
|
-
else if (t.type === 'PUNCTUATION' && t.value === ')') {
|
|
2135
|
-
depth--;
|
|
2136
|
-
if (depth === 0) break;
|
|
2137
|
-
}
|
|
2138
|
-
}
|
|
2139
|
-
const afterParen = this.tokens[i];
|
|
2140
|
-
return afterParen && afterParen.type === 'OPERATOR' && afterParen.value === '=>';
|
|
2141
|
-
}
|
|
2142
|
-
// `generator identifier =>` — single-arg lambda
|
|
2143
|
-
if (n1.type === 'IDENTIFIER') {
|
|
2144
|
-
const n2 = this.tokens[this.current + 2];
|
|
2145
|
-
return n2 && n2.type === 'OPERATOR' && n2.value === '=>';
|
|
2146
|
-
}
|
|
2147
|
-
return false;
|
|
2148
|
-
}
|
|
2149
|
-
advance() { if (!this.isAtEnd()) this.current++; return this.previous(); }
|
|
2150
|
-
check(type, value = null) {
|
|
2151
|
-
if (this.isAtEnd()) return false;
|
|
2152
|
-
const t = this.peek();
|
|
2153
|
-
// Allow DVAR 'default' to match wherever KEYWORD 'default' is expected
|
|
2154
|
-
if (type === 'KEYWORD' && value === 'default' && t.type === 'DVAR' && t.value === 'default') return true;
|
|
2155
|
-
return t.type === type && (value === null || t.value === value);
|
|
2156
|
-
}
|
|
2157
|
-
match(type, value = null) { if (this.check(type, value)) { this.advance(); return true; } return false; }
|
|
2158
|
-
consume(type, valueOrMsg, msgIfVal) {
|
|
2159
|
-
const hasVal = typeof valueOrMsg === 'string' && msgIfVal;
|
|
2160
|
-
const expVal = hasVal ? valueOrMsg : null;
|
|
2161
|
-
const msg = hasVal ? msgIfVal : valueOrMsg;
|
|
2162
|
-
if (this.check(type, expVal)) return this.advance();
|
|
2163
|
-
this.error(msg);
|
|
2164
|
-
}
|
|
2165
|
-
error(msg) {
|
|
2166
|
-
const t = this.peek();
|
|
2167
|
-
const line = this.rawsrc.split('\n')[t.line - 1] ?? '';
|
|
2168
|
-
throw new (new CustomError('ParseError'))(
|
|
2169
|
-
"[NovaParser " + t.line + ":" + t.column + "] " + msg + "\n error at:\n " + line + "\n " + " ".repeat(Math.max(t.column - 1, 0)) + "^^"
|
|
2170
|
-
);
|
|
2171
|
-
}
|
|
2172
|
-
walk(tokens) {
|
|
2173
|
-
this.tokens = tokens;
|
|
2174
|
-
this.current = 0;
|
|
2175
|
-
const body = [];
|
|
2176
|
-
while (!this.isAtEnd()) body.push(this.statement());
|
|
2177
|
-
return { kind: 'program', body };
|
|
2178
|
-
}
|
|
2179
|
-
}
|
|
2180
|
-
|
|
2181
|
-
module.exports = { Parser };
|