starlight-cli 1.0.33 → 1.0.35
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/dist/index.js +464 -337
- package/package.json +1 -1
- package/src/evaluator.js +47 -2
- package/src/parser.js +418 -336
package/dist/index.js
CHANGED
|
@@ -1747,11 +1747,54 @@ async evalWhile(node, env) {
|
|
|
1747
1747
|
}
|
|
1748
1748
|
|
|
1749
1749
|
async evalFor(node, env) {
|
|
1750
|
+
// --- Python-style for x in iterable ---
|
|
1751
|
+
if (node.type === 'ForInStatement') {
|
|
1752
|
+
const iterable = await this.evaluate(node.iterable, env);
|
|
1753
|
+
|
|
1754
|
+
if (iterable == null || typeof iterable !== 'object') {
|
|
1755
|
+
throw new Error('Cannot iterate over non-iterable');
|
|
1756
|
+
}
|
|
1757
|
+
|
|
1758
|
+
// Handle arrays
|
|
1759
|
+
if (Array.isArray(iterable)) {
|
|
1760
|
+
for (let i = 0; i < iterable.length; i++) {
|
|
1761
|
+
const loopEnv = new Environment(env);
|
|
1762
|
+
loopEnv.define(node.variable, iterable[i]);
|
|
1763
|
+
|
|
1764
|
+
try {
|
|
1765
|
+
await this.evaluate(node.body, loopEnv);
|
|
1766
|
+
} catch (e) {
|
|
1767
|
+
if (e instanceof BreakSignal) break;
|
|
1768
|
+
if (e instanceof ContinueSignal) continue;
|
|
1769
|
+
throw e;
|
|
1770
|
+
}
|
|
1771
|
+
}
|
|
1772
|
+
} else {
|
|
1773
|
+
// Handle objects: iterate over keys
|
|
1774
|
+
for (const key of Object.keys(iterable)) {
|
|
1775
|
+
const loopEnv = new Environment(env);
|
|
1776
|
+
loopEnv.define(node.variable, key);
|
|
1777
|
+
|
|
1778
|
+
try {
|
|
1779
|
+
await this.evaluate(node.body, loopEnv);
|
|
1780
|
+
} catch (e) {
|
|
1781
|
+
if (e instanceof BreakSignal) break;
|
|
1782
|
+
if (e instanceof ContinueSignal) continue;
|
|
1783
|
+
throw e;
|
|
1784
|
+
}
|
|
1785
|
+
}
|
|
1786
|
+
}
|
|
1787
|
+
|
|
1788
|
+
return null;
|
|
1789
|
+
}
|
|
1790
|
+
|
|
1750
1791
|
const local = new Environment(env);
|
|
1751
1792
|
if (node.init) await this.evaluate(node.init, local);
|
|
1793
|
+
|
|
1752
1794
|
while (!node.test || await this.evaluate(node.test, local)) {
|
|
1753
|
-
try {
|
|
1754
|
-
|
|
1795
|
+
try {
|
|
1796
|
+
await this.evaluate(node.body, local);
|
|
1797
|
+
} catch (e) {
|
|
1755
1798
|
if (e instanceof BreakSignal) break;
|
|
1756
1799
|
if (e instanceof ContinueSignal) {
|
|
1757
1800
|
if (node.update) await this.evaluate(node.update, local);
|
|
@@ -1759,8 +1802,10 @@ async evalFor(node, env) {
|
|
|
1759
1802
|
}
|
|
1760
1803
|
throw e;
|
|
1761
1804
|
}
|
|
1805
|
+
|
|
1762
1806
|
if (node.update) await this.evaluate(node.update, local);
|
|
1763
1807
|
}
|
|
1808
|
+
|
|
1764
1809
|
return null;
|
|
1765
1810
|
}
|
|
1766
1811
|
|
|
@@ -2113,164 +2158,231 @@ class Parser {
|
|
|
2113
2158
|
}
|
|
2114
2159
|
|
|
2115
2160
|
varDeclaration() {
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2161
|
+
this.eat('LET');
|
|
2162
|
+
const id = this.current.value;
|
|
2163
|
+
this.eat('IDENTIFIER');
|
|
2164
|
+
|
|
2165
|
+
let expr = null;
|
|
2166
|
+
if (this.current.type === 'EQUAL') {
|
|
2119
2167
|
this.eat('EQUAL');
|
|
2120
|
-
|
|
2121
|
-
if (this.current.type === 'SEMICOLON') this.eat('SEMICOLON');
|
|
2122
|
-
return { type: 'VarDeclaration', id, expr };
|
|
2168
|
+
expr = this.expression();
|
|
2123
2169
|
}
|
|
2124
2170
|
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
const expr = this.expression();
|
|
2128
|
-
if (this.current.type === 'SEMICOLON') this.eat('SEMICOLON');
|
|
2129
|
-
return { type: 'SldeployStatement', expr };
|
|
2130
|
-
}
|
|
2171
|
+
// semicolon optional
|
|
2172
|
+
if (this.current.type === 'SEMICOLON') this.eat('SEMICOLON');
|
|
2131
2173
|
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2174
|
+
return { type: 'VarDeclaration', id, expr };
|
|
2175
|
+
}
|
|
2176
|
+
|
|
2177
|
+
sldeployStatement() {
|
|
2178
|
+
this.eat('SLDEPLOY');
|
|
2179
|
+
const expr = this.expression();
|
|
2180
|
+
if (this.current.type === 'SEMICOLON') this.eat('SEMICOLON');
|
|
2181
|
+
return { type: 'SldeployStatement', expr };
|
|
2182
|
+
}
|
|
2183
|
+
|
|
2184
|
+
defineStatement() {
|
|
2185
|
+
this.eat('DEFINE');
|
|
2186
|
+
const id = this.current.value;
|
|
2187
|
+
this.eat('IDENTIFIER');
|
|
2188
|
+
|
|
2189
|
+
let expr = null;
|
|
2190
|
+
if (this.current.type === 'EQUAL') {
|
|
2191
|
+
this.eat('EQUAL');
|
|
2192
|
+
expr = this.expression();
|
|
2143
2193
|
}
|
|
2194
|
+
|
|
2195
|
+
if (this.current.type === 'SEMICOLON') this.eat('SEMICOLON');
|
|
2196
|
+
|
|
2197
|
+
return { type: 'DefineStatement', id, expr };
|
|
2198
|
+
}
|
|
2199
|
+
|
|
2144
2200
|
asyncFuncDeclaration() {
|
|
2145
2201
|
const name = this.current.value;
|
|
2146
2202
|
this.eat('IDENTIFIER');
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
if (this.current.type
|
|
2150
|
-
|
|
2151
|
-
this.
|
|
2152
|
-
|
|
2153
|
-
this.eat('
|
|
2203
|
+
|
|
2204
|
+
let params = [];
|
|
2205
|
+
if (this.current.type === 'LPAREN') {
|
|
2206
|
+
this.eat('LPAREN');
|
|
2207
|
+
if (this.current.type !== 'RPAREN') {
|
|
2208
|
+
params.push(this.current.value);
|
|
2209
|
+
this.eat('IDENTIFIER');
|
|
2210
|
+
while (this.current.type === 'COMMA') {
|
|
2211
|
+
this.eat('COMMA');
|
|
2212
|
+
params.push(this.current.value);
|
|
2213
|
+
this.eat('IDENTIFIER');
|
|
2214
|
+
}
|
|
2215
|
+
}
|
|
2216
|
+
this.eat('RPAREN');
|
|
2217
|
+
} else {
|
|
2218
|
+
// no parentheses: single param as Python style
|
|
2219
|
+
if (this.current.type === 'IDENTIFIER') {
|
|
2154
2220
|
params.push(this.current.value);
|
|
2155
2221
|
this.eat('IDENTIFIER');
|
|
2156
2222
|
}
|
|
2157
2223
|
}
|
|
2158
|
-
|
|
2224
|
+
|
|
2159
2225
|
const body = this.block();
|
|
2160
2226
|
return { type: 'FunctionDeclaration', name, params, body, async: true };
|
|
2161
2227
|
}
|
|
2162
2228
|
|
|
2163
|
-
|
|
2164
|
-
|
|
2229
|
+
ifStatement() {
|
|
2230
|
+
this.eat('IF');
|
|
2231
|
+
|
|
2232
|
+
let test;
|
|
2233
|
+
if (this.current.type === 'LPAREN') {
|
|
2165
2234
|
this.eat('LPAREN');
|
|
2166
|
-
|
|
2235
|
+
test = this.expression();
|
|
2167
2236
|
this.eat('RPAREN');
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
this.eat('ELSE');
|
|
2172
|
-
if (this.current.type === 'IF') alternate = this.ifStatement();
|
|
2173
|
-
else alternate = this.block();
|
|
2174
|
-
}
|
|
2175
|
-
return { type: 'IfStatement', test, consequent, alternate };
|
|
2237
|
+
} else {
|
|
2238
|
+
// python style: no parentheses
|
|
2239
|
+
test = this.expression();
|
|
2176
2240
|
}
|
|
2177
2241
|
|
|
2178
|
-
|
|
2179
|
-
|
|
2242
|
+
const consequent = this.block();
|
|
2243
|
+
|
|
2244
|
+
let alternate = null;
|
|
2245
|
+
if (this.current.type === 'ELSE') {
|
|
2246
|
+
this.eat('ELSE');
|
|
2247
|
+
if (this.current.type === 'IF') alternate = this.ifStatement();
|
|
2248
|
+
else alternate = this.block();
|
|
2249
|
+
}
|
|
2250
|
+
|
|
2251
|
+
return { type: 'IfStatement', test, consequent, alternate };
|
|
2252
|
+
}
|
|
2253
|
+
|
|
2254
|
+
whileStatement() {
|
|
2255
|
+
this.eat('WHILE');
|
|
2256
|
+
|
|
2257
|
+
let test;
|
|
2258
|
+
if (this.current.type === 'LPAREN') {
|
|
2180
2259
|
this.eat('LPAREN');
|
|
2181
|
-
|
|
2260
|
+
test = this.expression();
|
|
2182
2261
|
this.eat('RPAREN');
|
|
2183
|
-
|
|
2184
|
-
|
|
2262
|
+
} else {
|
|
2263
|
+
test = this.expression();
|
|
2185
2264
|
}
|
|
2186
|
-
importStatement() {
|
|
2187
|
-
this.eat('IMPORT');
|
|
2188
2265
|
|
|
2189
|
-
|
|
2266
|
+
const body = this.block();
|
|
2267
|
+
return { type: 'WhileStatement', test, body };
|
|
2268
|
+
}
|
|
2269
|
+
|
|
2270
|
+
importStatement() {
|
|
2271
|
+
this.eat('IMPORT');
|
|
2272
|
+
|
|
2273
|
+
let specifiers = [];
|
|
2274
|
+
if (this.current.type === 'STAR') {
|
|
2275
|
+
this.eat('STAR');
|
|
2276
|
+
this.eat('AS');
|
|
2277
|
+
const name = this.current.value;
|
|
2278
|
+
this.eat('IDENTIFIER');
|
|
2279
|
+
specifiers.push({ type: 'NamespaceImport', local: name });
|
|
2280
|
+
} else if (this.current.type === 'LBRACE') {
|
|
2281
|
+
this.eat('LBRACE');
|
|
2282
|
+
while (this.current.type !== 'RBRACE') {
|
|
2283
|
+
const importedName = this.current.value;
|
|
2284
|
+
this.eat('IDENTIFIER');
|
|
2190
2285
|
|
|
2191
|
-
|
|
2192
|
-
|
|
2286
|
+
let localName = importedName;
|
|
2287
|
+
if (this.current.type === 'AS') {
|
|
2193
2288
|
this.eat('AS');
|
|
2194
|
-
|
|
2195
|
-
this.eat('IDENTIFIER');
|
|
2196
|
-
specifiers.push({ type: 'NamespaceImport', local: name });
|
|
2197
|
-
}
|
|
2198
|
-
else if (this.current.type === 'LBRACE') {
|
|
2199
|
-
this.eat('LBRACE');
|
|
2200
|
-
while (this.current.type !== 'RBRACE') {
|
|
2201
|
-
const importedName = this.current.value;
|
|
2202
|
-
this.eat('IDENTIFIER');
|
|
2203
|
-
let localName = importedName;
|
|
2204
|
-
if (this.current.type === 'AS') {
|
|
2205
|
-
this.eat('AS');
|
|
2206
|
-
localName = this.current.value;
|
|
2207
|
-
this.eat('IDENTIFIER');
|
|
2208
|
-
}
|
|
2209
|
-
specifiers.push({ type: 'NamedImport', imported: importedName, local: localName });
|
|
2210
|
-
if (this.current.type === 'COMMA') this.eat('COMMA');
|
|
2211
|
-
}
|
|
2212
|
-
this.eat('RBRACE');
|
|
2213
|
-
}
|
|
2214
|
-
else if (this.current.type === 'IDENTIFIER') {
|
|
2215
|
-
const localName = this.current.value;
|
|
2289
|
+
localName = this.current.value;
|
|
2216
2290
|
this.eat('IDENTIFIER');
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2291
|
+
}
|
|
2292
|
+
|
|
2293
|
+
specifiers.push({ type: 'NamedImport', imported: importedName, local: localName });
|
|
2294
|
+
if (this.current.type === 'COMMA') this.eat('COMMA');
|
|
2220
2295
|
}
|
|
2296
|
+
this.eat('RBRACE');
|
|
2297
|
+
} else if (this.current.type === 'IDENTIFIER') {
|
|
2298
|
+
const localName = this.current.value;
|
|
2299
|
+
this.eat('IDENTIFIER');
|
|
2300
|
+
specifiers.push({ type: 'DefaultImport', local: localName });
|
|
2301
|
+
} else {
|
|
2302
|
+
throw new Error(`Unexpected token in import at line ${this.current.line}, column ${this.current.column}`);
|
|
2303
|
+
}
|
|
2221
2304
|
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2305
|
+
this.eat('FROM');
|
|
2306
|
+
const pathToken = this.current;
|
|
2307
|
+
if (pathToken.type !== 'STRING') throw new Error(`Expected string after FROM at line ${this.current.line}, column ${this.current.column}`);
|
|
2308
|
+
this.eat('STRING');
|
|
2226
2309
|
|
|
2227
|
-
|
|
2310
|
+
if (this.current.type === 'SEMICOLON') this.eat('SEMICOLON');
|
|
2228
2311
|
|
|
2229
|
-
|
|
2312
|
+
return { type: 'ImportStatement', path: pathToken.value, specifiers };
|
|
2230
2313
|
}
|
|
2314
|
+
forStatement() {
|
|
2315
|
+
this.eat('FOR');
|
|
2231
2316
|
|
|
2317
|
+
// --- Python-style: for variable in iterable ---
|
|
2318
|
+
if (this.current.type === 'IDENTIFIER' && this.peekType() === 'IN') {
|
|
2319
|
+
const variable = this.current.value; // loop variable
|
|
2320
|
+
this.eat('IDENTIFIER');
|
|
2321
|
+
|
|
2322
|
+
this.eat('IN');
|
|
2323
|
+
const iterable = this.expression(); // the array/object to loop over
|
|
2232
2324
|
|
|
2233
|
-
|
|
2234
|
-
|
|
2325
|
+
const body = this.block();
|
|
2326
|
+
return { type: 'ForInStatement', variable, iterable, body };
|
|
2327
|
+
}
|
|
2328
|
+
|
|
2329
|
+
// --- C-style: for(init; test; update) ---
|
|
2330
|
+
let init = null;
|
|
2331
|
+
let test = null;
|
|
2332
|
+
let update = null;
|
|
2333
|
+
|
|
2334
|
+
if (this.current.type === 'LPAREN') {
|
|
2235
2335
|
this.eat('LPAREN');
|
|
2236
2336
|
|
|
2237
|
-
|
|
2337
|
+
// init
|
|
2238
2338
|
if (this.current.type !== 'SEMICOLON') {
|
|
2239
2339
|
init = this.current.type === 'LET' ? this.varDeclaration() : this.expressionStatement();
|
|
2240
2340
|
} else {
|
|
2241
2341
|
this.eat('SEMICOLON');
|
|
2242
2342
|
}
|
|
2243
2343
|
|
|
2244
|
-
|
|
2344
|
+
// test
|
|
2245
2345
|
if (this.current.type !== 'SEMICOLON') test = this.expression();
|
|
2246
2346
|
this.eat('SEMICOLON');
|
|
2247
2347
|
|
|
2248
|
-
|
|
2348
|
+
// update
|
|
2249
2349
|
if (this.current.type !== 'RPAREN') update = this.expression();
|
|
2250
2350
|
this.eat('RPAREN');
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2351
|
+
} else {
|
|
2352
|
+
// fallback: single expression (could be used for Python-style with "in", already handled above)
|
|
2353
|
+
init = this.expression();
|
|
2354
|
+
if (this.current.type === 'IN') {
|
|
2355
|
+
this.eat('IN');
|
|
2356
|
+
test = this.expression(); // iterable
|
|
2357
|
+
}
|
|
2254
2358
|
}
|
|
2255
2359
|
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
return { type: 'BreakStatement' };
|
|
2260
|
-
}
|
|
2360
|
+
const body = this.block();
|
|
2361
|
+
return { type: 'ForStatement', init, test, update, body };
|
|
2362
|
+
}
|
|
2261
2363
|
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
}
|
|
2364
|
+
breakStatement() {
|
|
2365
|
+
this.eat('BREAK');
|
|
2366
|
+
// Python-style: no semicolon needed, ignore if present
|
|
2367
|
+
if (this.current.type === 'SEMICOLON') this.advance();
|
|
2368
|
+
return { type: 'BreakStatement' };
|
|
2369
|
+
}
|
|
2267
2370
|
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2371
|
+
continueStatement() {
|
|
2372
|
+
this.eat('CONTINUE');
|
|
2373
|
+
// Python-style: no semicolon needed, ignore if present
|
|
2374
|
+
if (this.current.type === 'SEMICOLON') this.advance();
|
|
2375
|
+
return { type: 'ContinueStatement' };
|
|
2376
|
+
}
|
|
2377
|
+
|
|
2378
|
+
funcDeclaration() {
|
|
2379
|
+
this.eat('FUNC');
|
|
2380
|
+
const name = this.current.value;
|
|
2381
|
+
this.eat('IDENTIFIER');
|
|
2382
|
+
|
|
2383
|
+
let params = [];
|
|
2384
|
+
if (this.current.type === 'LPAREN') {
|
|
2272
2385
|
this.eat('LPAREN');
|
|
2273
|
-
const params = [];
|
|
2274
2386
|
if (this.current.type !== 'RPAREN') {
|
|
2275
2387
|
params.push(this.current.value);
|
|
2276
2388
|
this.eat('IDENTIFIER');
|
|
@@ -2281,176 +2393,203 @@ asyncFuncDeclaration() {
|
|
|
2281
2393
|
}
|
|
2282
2394
|
}
|
|
2283
2395
|
this.eat('RPAREN');
|
|
2284
|
-
|
|
2285
|
-
|
|
2396
|
+
} else {
|
|
2397
|
+
// Python-style: single param without parentheses
|
|
2398
|
+
if (this.current.type === 'IDENTIFIER') {
|
|
2399
|
+
params.push(this.current.value);
|
|
2400
|
+
this.eat('IDENTIFIER');
|
|
2401
|
+
}
|
|
2286
2402
|
}
|
|
2287
2403
|
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2404
|
+
const body = this.block();
|
|
2405
|
+
return { type: 'FunctionDeclaration', name, params, body };
|
|
2406
|
+
}
|
|
2407
|
+
|
|
2408
|
+
returnStatement() {
|
|
2409
|
+
this.eat('RETURN');
|
|
2410
|
+
|
|
2411
|
+
let argument = null;
|
|
2412
|
+
if (this.current.type !== 'SEMICOLON') {
|
|
2413
|
+
argument = this.expression();
|
|
2294
2414
|
}
|
|
2295
2415
|
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2416
|
+
// semicolon optional
|
|
2417
|
+
if (this.current.type === 'SEMICOLON') this.eat('SEMICOLON');
|
|
2418
|
+
|
|
2419
|
+
return { type: 'ReturnStatement', argument };
|
|
2420
|
+
}
|
|
2421
|
+
|
|
2422
|
+
block() {
|
|
2423
|
+
this.eat('LBRACE');
|
|
2424
|
+
const body = [];
|
|
2425
|
+
while (this.current.type !== 'RBRACE') {
|
|
2426
|
+
body.push(this.statement());
|
|
2304
2427
|
}
|
|
2428
|
+
this.eat('RBRACE');
|
|
2429
|
+
return { type: 'BlockStatement', body };
|
|
2430
|
+
}
|
|
2431
|
+
|
|
2432
|
+
expressionStatement() {
|
|
2433
|
+
const expr = this.expression();
|
|
2434
|
+
// semicolon optional
|
|
2435
|
+
if (this.current.type === 'SEMICOLON') this.eat('SEMICOLON');
|
|
2436
|
+
return { type: 'ExpressionStatement', expression: expr };
|
|
2437
|
+
}
|
|
2438
|
+
|
|
2439
|
+
expression() {
|
|
2440
|
+
return this.assignment();
|
|
2441
|
+
}
|
|
2442
|
+
|
|
2443
|
+
assignment() {
|
|
2444
|
+
const node = this.logicalOr();
|
|
2445
|
+
const compoundOps = ['PLUSEQ', 'MINUSEQ', 'STAREQ', 'SLASHEQ', 'MODEQ'];
|
|
2305
2446
|
|
|
2306
|
-
|
|
2307
|
-
const
|
|
2308
|
-
|
|
2309
|
-
|
|
2447
|
+
if (compoundOps.includes(this.current.type)) {
|
|
2448
|
+
const op = this.current.type;
|
|
2449
|
+
this.eat(op);
|
|
2450
|
+
const right = this.assignment();
|
|
2451
|
+
return { type: 'CompoundAssignment', operator: op, left: node, right };
|
|
2310
2452
|
}
|
|
2311
2453
|
|
|
2312
|
-
|
|
2313
|
-
|
|
2454
|
+
if (this.current.type === 'EQUAL') {
|
|
2455
|
+
this.eat('EQUAL');
|
|
2456
|
+
const right = this.assignment();
|
|
2457
|
+
return { type: 'AssignmentExpression', left: node, right };
|
|
2314
2458
|
}
|
|
2315
2459
|
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
const compoundOps = ['PLUSEQ', 'MINUSEQ', 'STAREQ', 'SLASHEQ', 'MODEQ'];
|
|
2319
|
-
if (compoundOps.includes(this.current.type)) {
|
|
2320
|
-
const op = this.current.type;
|
|
2321
|
-
this.eat(op);
|
|
2322
|
-
const right = this.assignment();
|
|
2323
|
-
return { type: 'CompoundAssignment', operator: op, left: node, right };
|
|
2324
|
-
}
|
|
2460
|
+
return node;
|
|
2461
|
+
}
|
|
2325
2462
|
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2463
|
+
logicalOr() {
|
|
2464
|
+
let node = this.logicalAnd();
|
|
2465
|
+
while (this.current.type === 'OR') {
|
|
2466
|
+
const op = this.current.type;
|
|
2467
|
+
this.eat(op);
|
|
2468
|
+
node = { type: 'LogicalExpression', operator: op, left: node, right: this.logicalAnd() };
|
|
2469
|
+
}
|
|
2470
|
+
return node;
|
|
2471
|
+
}
|
|
2331
2472
|
|
|
2332
|
-
|
|
2473
|
+
logicalAnd() {
|
|
2474
|
+
let node = this.equality();
|
|
2475
|
+
while (this.current.type === 'AND') {
|
|
2476
|
+
const op = this.current.type;
|
|
2477
|
+
this.eat(op);
|
|
2478
|
+
node = { type: 'LogicalExpression', operator: op, left: node, right: this.equality() };
|
|
2333
2479
|
}
|
|
2480
|
+
return node;
|
|
2481
|
+
}
|
|
2482
|
+
equality() {
|
|
2483
|
+
let node = this.comparison();
|
|
2484
|
+
while (['EQEQ', 'NOTEQ'].includes(this.current.type)) {
|
|
2485
|
+
const op = this.current.type;
|
|
2486
|
+
this.eat(op);
|
|
2487
|
+
node = { type: 'BinaryExpression', operator: op, left: node, right: this.comparison() };
|
|
2488
|
+
}
|
|
2489
|
+
return node;
|
|
2490
|
+
}
|
|
2334
2491
|
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
}
|
|
2342
|
-
return node;
|
|
2492
|
+
comparison() {
|
|
2493
|
+
let node = this.term();
|
|
2494
|
+
while (['LT', 'LTE', 'GT', 'GTE'].includes(this.current.type)) {
|
|
2495
|
+
const op = this.current.type;
|
|
2496
|
+
this.eat(op);
|
|
2497
|
+
node = { type: 'BinaryExpression', operator: op, left: node, right: this.term() };
|
|
2343
2498
|
}
|
|
2499
|
+
return node;
|
|
2500
|
+
}
|
|
2344
2501
|
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
}
|
|
2352
|
-
return node;
|
|
2502
|
+
term() {
|
|
2503
|
+
let node = this.factor();
|
|
2504
|
+
while (['PLUS', 'MINUS'].includes(this.current.type)) {
|
|
2505
|
+
const op = this.current.type;
|
|
2506
|
+
this.eat(op);
|
|
2507
|
+
node = { type: 'BinaryExpression', operator: op, left: node, right: this.factor() };
|
|
2353
2508
|
}
|
|
2509
|
+
return node;
|
|
2510
|
+
}
|
|
2354
2511
|
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
}
|
|
2362
|
-
return node;
|
|
2512
|
+
factor() {
|
|
2513
|
+
let node = this.unary();
|
|
2514
|
+
while (['STAR', 'SLASH', 'MOD'].includes(this.current.type)) {
|
|
2515
|
+
const op = this.current.type;
|
|
2516
|
+
this.eat(op);
|
|
2517
|
+
node = { type: 'BinaryExpression', operator: op, left: node, right: this.unary() };
|
|
2363
2518
|
}
|
|
2519
|
+
return node;
|
|
2520
|
+
}
|
|
2364
2521
|
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
node = { type: 'BinaryExpression', operator: op, left: node, right: this.term() };
|
|
2371
|
-
}
|
|
2372
|
-
return node;
|
|
2522
|
+
unary() {
|
|
2523
|
+
if (['NOT', 'MINUS', 'PLUS'].includes(this.current.type)) {
|
|
2524
|
+
const op = this.current.type;
|
|
2525
|
+
this.eat(op);
|
|
2526
|
+
return { type: 'UnaryExpression', operator: op, argument: this.unary() };
|
|
2373
2527
|
}
|
|
2374
2528
|
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
}
|
|
2382
|
-
return node;
|
|
2529
|
+
// Python-like: ignore ++ and -- if not used
|
|
2530
|
+
if (this.current.type === 'PLUSPLUS' || this.current.type === 'MINUSMINUS') {
|
|
2531
|
+
const op = this.current.type;
|
|
2532
|
+
this.eat(op);
|
|
2533
|
+
const argument = this.unary();
|
|
2534
|
+
return { type: 'UpdateExpression', operator: op, argument, prefix: true };
|
|
2383
2535
|
}
|
|
2384
2536
|
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
2537
|
+
return this.postfix();
|
|
2538
|
+
}
|
|
2539
|
+
|
|
2540
|
+
postfix() {
|
|
2541
|
+
let node = this.primary();
|
|
2542
|
+
while (true) {
|
|
2543
|
+
if (this.current.type === 'LBRACKET') {
|
|
2544
|
+
this.eat('LBRACKET');
|
|
2545
|
+
const index = this.expression();
|
|
2546
|
+
if (this.current.type === 'RBRACKET') this.eat('RBRACKET');
|
|
2547
|
+
node = { type: 'IndexExpression', object: node, indexer: index };
|
|
2548
|
+
continue;
|
|
2391
2549
|
}
|
|
2392
|
-
return node;
|
|
2393
|
-
}
|
|
2394
2550
|
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
const
|
|
2398
|
-
this.
|
|
2399
|
-
|
|
2551
|
+
if (this.current.type === 'LPAREN') {
|
|
2552
|
+
this.eat('LPAREN');
|
|
2553
|
+
const args = [];
|
|
2554
|
+
while (this.current.type !== 'RPAREN' && this.current.type !== 'EOF') {
|
|
2555
|
+
args.push(this.expression());
|
|
2556
|
+
if (this.current.type === 'COMMA') this.eat('COMMA'); // optional comma
|
|
2557
|
+
}
|
|
2558
|
+
if (this.current.type === 'RPAREN') this.eat('RPAREN');
|
|
2559
|
+
node = { type: 'CallExpression', callee: node, arguments: args };
|
|
2560
|
+
continue;
|
|
2400
2561
|
}
|
|
2562
|
+
|
|
2563
|
+
if (this.current.type === 'DOT') {
|
|
2564
|
+
this.eat('DOT');
|
|
2565
|
+
const property = this.current.value || '';
|
|
2566
|
+
if (this.current.type === 'IDENTIFIER') this.eat('IDENTIFIER');
|
|
2567
|
+
node = { type: 'MemberExpression', object: node, property };
|
|
2568
|
+
continue;
|
|
2569
|
+
}
|
|
2570
|
+
|
|
2401
2571
|
if (this.current.type === 'PLUSPLUS' || this.current.type === 'MINUSMINUS') {
|
|
2402
2572
|
const op = this.current.type;
|
|
2403
2573
|
this.eat(op);
|
|
2404
|
-
|
|
2405
|
-
|
|
2574
|
+
node = { type: 'UpdateExpression', operator: op, argument: node, prefix: false };
|
|
2575
|
+
continue;
|
|
2406
2576
|
}
|
|
2407
|
-
return this.postfix();
|
|
2408
|
-
}
|
|
2409
2577
|
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
this.eat('RBRACKET');
|
|
2417
|
-
node = { type: 'IndexExpression', object: node, indexer: index };
|
|
2418
|
-
continue;
|
|
2419
|
-
}
|
|
2420
|
-
if (this.current.type === 'LPAREN') {
|
|
2421
|
-
this.eat('LPAREN');
|
|
2422
|
-
const args = [];
|
|
2423
|
-
if (this.current.type !== 'RPAREN') {
|
|
2424
|
-
args.push(this.expression());
|
|
2425
|
-
while (this.current.type === 'COMMA') {
|
|
2426
|
-
this.eat('COMMA');
|
|
2427
|
-
args.push(this.expression());
|
|
2428
|
-
}
|
|
2429
|
-
}
|
|
2430
|
-
this.eat('RPAREN');
|
|
2431
|
-
node = { type: 'CallExpression', callee: node, arguments: args };
|
|
2432
|
-
continue;
|
|
2433
|
-
}
|
|
2434
|
-
if (this.current.type === 'DOT') {
|
|
2435
|
-
this.eat('DOT');
|
|
2436
|
-
if (this.current.type !== 'IDENTIFIER') throw new Error(`Expected property name after dot at line ${this.current.line}, column ${this.current.column}`);
|
|
2437
|
-
const property = this.current.value;
|
|
2438
|
-
this.eat('IDENTIFIER');
|
|
2439
|
-
node = { type: 'MemberExpression', object: node, property };
|
|
2440
|
-
continue;
|
|
2441
|
-
}
|
|
2442
|
-
if (this.current.type === 'PLUSPLUS' || this.current.type === 'MINUSMINUS') {
|
|
2443
|
-
const op = this.current.type;
|
|
2444
|
-
this.eat(op);
|
|
2445
|
-
node = { type: 'UpdateExpression', operator: op, argument: node, prefix: false };
|
|
2446
|
-
continue;
|
|
2447
|
-
}
|
|
2448
|
-
break;
|
|
2578
|
+
// Python-style: implicit function calls (if identifier followed by identifier)
|
|
2579
|
+
if (node.type === 'Identifier' && this.current.type === 'IDENTIFIER') {
|
|
2580
|
+
const argNode = { type: 'Identifier', name: this.current.value };
|
|
2581
|
+
this.eat('IDENTIFIER');
|
|
2582
|
+
node = { type: 'CallExpression', callee: node, arguments: [argNode] };
|
|
2583
|
+
continue;
|
|
2449
2584
|
}
|
|
2450
|
-
|
|
2585
|
+
|
|
2586
|
+
break;
|
|
2451
2587
|
}
|
|
2588
|
+
return node;
|
|
2589
|
+
}
|
|
2590
|
+
|
|
2452
2591
|
arrowFunction(params) {
|
|
2453
|
-
this.eat('ARROW');
|
|
2592
|
+
if (this.current.type === 'ARROW') this.eat('ARROW');
|
|
2454
2593
|
const body = this.expression();
|
|
2455
2594
|
return {
|
|
2456
2595
|
type: 'ArrowFunctionExpression',
|
|
@@ -2459,126 +2598,114 @@ arrowFunction(params) {
|
|
|
2459
2598
|
};
|
|
2460
2599
|
}
|
|
2461
2600
|
|
|
2601
|
+
|
|
2462
2602
|
primary() {
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2603
|
+
const t = this.current;
|
|
2604
|
+
|
|
2605
|
+
if (t.type === 'NUMBER') { this.eat('NUMBER'); return { type: 'Literal', value: t.value }; }
|
|
2606
|
+
if (t.type === 'STRING') { this.eat('STRING'); return { type: 'Literal', value: t.value }; }
|
|
2607
|
+
if (t.type === 'TRUE') { this.eat('TRUE'); return { type: 'Literal', value: true }; }
|
|
2608
|
+
if (t.type === 'FALSE') { this.eat('FALSE'); return { type: 'Literal', value: false }; }
|
|
2609
|
+
if (t.type === 'NULL') { this.eat('NULL'); return { type: 'Literal', value: null }; }
|
|
2610
|
+
|
|
2611
|
+
if (t.type === 'AWAIT') {
|
|
2471
2612
|
this.eat('AWAIT');
|
|
2472
2613
|
const argument = this.expression();
|
|
2473
2614
|
return { type: 'AwaitExpression', argument };
|
|
2474
2615
|
}
|
|
2475
|
-
if (t.type === 'NEW') {
|
|
2476
|
-
this.eat('NEW');
|
|
2477
|
-
const callee = this.primary(); // the class or function being called
|
|
2478
|
-
this.eat('LPAREN');
|
|
2479
|
-
const args = [];
|
|
2480
|
-
if (this.current.type !== 'RPAREN') {
|
|
2481
|
-
args.push(this.expression());
|
|
2482
|
-
while (this.current.type === 'COMMA') {
|
|
2483
|
-
this.eat('COMMA');
|
|
2484
|
-
args.push(this.expression());
|
|
2485
|
-
}
|
|
2486
|
-
}
|
|
2487
|
-
this.eat('RPAREN');
|
|
2488
|
-
return { type: 'NewExpression', callee, arguments: args };
|
|
2489
|
-
}
|
|
2490
2616
|
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
|
|
2496
|
-
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
|
|
2617
|
+
if (t.type === 'NEW') {
|
|
2618
|
+
this.eat('NEW');
|
|
2619
|
+
const callee = this.primary();
|
|
2620
|
+
this.eat('LPAREN');
|
|
2621
|
+
const args = [];
|
|
2622
|
+
if (this.current.type !== 'RPAREN') {
|
|
2623
|
+
args.push(this.expression());
|
|
2624
|
+
while (this.current.type === 'COMMA') {
|
|
2625
|
+
this.eat('COMMA');
|
|
2626
|
+
if (this.current.type !== 'RPAREN') args.push(this.expression());
|
|
2501
2627
|
}
|
|
2502
|
-
this.eat('RPAREN');
|
|
2503
|
-
return { type: 'CallExpression', callee: { type: 'Identifier', name: 'ask' }, arguments: args };
|
|
2504
2628
|
}
|
|
2629
|
+
this.eat('RPAREN');
|
|
2630
|
+
return { type: 'NewExpression', callee, arguments: args };
|
|
2631
|
+
}
|
|
2505
2632
|
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
|
|
2510
|
-
|
|
2511
|
-
|
|
2633
|
+
if (t.type === 'ASK') {
|
|
2634
|
+
this.eat('ASK');
|
|
2635
|
+
this.eat('LPAREN');
|
|
2636
|
+
const args = [];
|
|
2637
|
+
if (this.current.type !== 'RPAREN') {
|
|
2638
|
+
args.push(this.expression());
|
|
2639
|
+
while (this.current.type === 'COMMA') {
|
|
2640
|
+
this.eat('COMMA');
|
|
2641
|
+
if (this.current.type !== 'RPAREN') args.push(this.expression());
|
|
2642
|
+
}
|
|
2643
|
+
}
|
|
2644
|
+
this.eat('RPAREN');
|
|
2645
|
+
return { type: 'CallExpression', callee: { type: 'Identifier', name: 'ask' }, arguments: args };
|
|
2512
2646
|
}
|
|
2513
2647
|
|
|
2514
|
-
|
|
2515
|
-
|
|
2648
|
+
if (t.type === 'IDENTIFIER') {
|
|
2649
|
+
const name = t.value;
|
|
2650
|
+
this.eat('IDENTIFIER');
|
|
2516
2651
|
|
|
2652
|
+
if (this.current.type === 'ARROW') {
|
|
2653
|
+
return this.arrowFunction([name]);
|
|
2654
|
+
}
|
|
2517
2655
|
|
|
2518
|
-
|
|
2519
|
-
|
|
2656
|
+
return { type: 'Identifier', name };
|
|
2657
|
+
}
|
|
2520
2658
|
|
|
2521
|
-
|
|
2659
|
+
if (t.type === 'LPAREN') {
|
|
2660
|
+
this.eat('LPAREN');
|
|
2661
|
+
const elements = [];
|
|
2522
2662
|
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
this.eat('IDENTIFIER');
|
|
2663
|
+
if (this.current.type !== 'RPAREN') {
|
|
2664
|
+
elements.push(this.expression());
|
|
2665
|
+
while (this.current.type === 'COMMA') {
|
|
2666
|
+
this.eat('COMMA');
|
|
2667
|
+
if (this.current.type !== 'RPAREN') elements.push(this.expression());
|
|
2668
|
+
}
|
|
2530
2669
|
}
|
|
2531
|
-
}
|
|
2532
2670
|
|
|
2533
|
-
|
|
2671
|
+
this.eat('RPAREN');
|
|
2534
2672
|
|
|
2535
|
-
|
|
2536
|
-
return this.arrowFunction(params);
|
|
2537
|
-
}
|
|
2673
|
+
if (this.current.type === 'ARROW') return this.arrowFunction(elements);
|
|
2538
2674
|
|
|
2539
|
-
|
|
2540
|
-
return { type: 'Identifier', name: params[0] };
|
|
2675
|
+
return elements.length === 1 ? elements[0] : { type: 'ArrayExpression', elements };
|
|
2541
2676
|
}
|
|
2542
2677
|
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
|
|
2546
|
-
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
const elements = [];
|
|
2552
|
-
if (this.current.type !== 'RBRACKET') {
|
|
2553
|
-
elements.push(this.expression());
|
|
2554
|
-
while (this.current.type === 'COMMA') {
|
|
2555
|
-
this.eat('COMMA');
|
|
2556
|
-
elements.push(this.expression());
|
|
2557
|
-
}
|
|
2678
|
+
if (t.type === 'LBRACKET') {
|
|
2679
|
+
this.eat('LBRACKET');
|
|
2680
|
+
const elements = [];
|
|
2681
|
+
if (this.current.type !== 'RBRACKET') {
|
|
2682
|
+
elements.push(this.expression());
|
|
2683
|
+
while (this.current.type === 'COMMA') {
|
|
2684
|
+
this.eat('COMMA');
|
|
2685
|
+
if (this.current.type !== 'RBRACKET') elements.push(this.expression());
|
|
2558
2686
|
}
|
|
2559
|
-
this.eat('RBRACKET');
|
|
2560
|
-
return { type: 'ArrayExpression', elements };
|
|
2561
2687
|
}
|
|
2688
|
+
this.eat('RBRACKET');
|
|
2689
|
+
return { type: 'ArrayExpression', elements };
|
|
2690
|
+
}
|
|
2562
2691
|
|
|
2563
|
-
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
const value = this.expression();
|
|
2573
|
-
props.push({ key, value });
|
|
2574
|
-
if (this.current.type === 'COMMA') this.eat('COMMA');
|
|
2575
|
-
}
|
|
2576
|
-
this.eat('RBRACE');
|
|
2577
|
-
return { type: 'ObjectExpression', props };
|
|
2692
|
+
if (t.type === 'LBRACE') {
|
|
2693
|
+
this.eat('LBRACE');
|
|
2694
|
+
const props = [];
|
|
2695
|
+
while (this.current.type !== 'RBRACE') {
|
|
2696
|
+
const key = this.expression(); // Flexible key: can be any expression
|
|
2697
|
+
this.eat('COLON');
|
|
2698
|
+
const value = this.expression();
|
|
2699
|
+
props.push({ key, value });
|
|
2700
|
+
if (this.current.type === 'COMMA') this.eat('COMMA'); // optional trailing comma
|
|
2578
2701
|
}
|
|
2579
|
-
|
|
2580
|
-
|
|
2702
|
+
this.eat('RBRACE');
|
|
2703
|
+
return { type: 'ObjectExpression', props };
|
|
2581
2704
|
}
|
|
2705
|
+
|
|
2706
|
+
throw new Error(`Unexpected token in primary: ${t.type} at line ${this.current.line}, column ${this.current.column}`);
|
|
2707
|
+
}
|
|
2708
|
+
|
|
2582
2709
|
}
|
|
2583
2710
|
|
|
2584
2711
|
module.exports = Parser;
|