starlight-cli 1.0.32 → 1.0.34
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 +406 -344
- package/package.json +1 -1
- package/src/evaluator.js +4 -5
- package/src/parser.js +403 -340
package/dist/index.js
CHANGED
|
@@ -1500,19 +1500,18 @@ async evaluate(node, env = this.global) {
|
|
|
1500
1500
|
const callee = await this.evaluate(node.callee, env);
|
|
1501
1501
|
|
|
1502
1502
|
if (typeof callee === 'object' && callee.body) {
|
|
1503
|
-
|
|
1503
|
+
const evaluator = this; // <- capture the current evaluator
|
|
1504
1504
|
const Constructor = function(...args) {
|
|
1505
1505
|
const newEnv = new Environment(callee.env);
|
|
1506
|
-
newEnv.define('this', this);
|
|
1506
|
+
newEnv.define('this', this);
|
|
1507
1507
|
for (let i = 0; i < callee.params.length; i++) {
|
|
1508
1508
|
newEnv.define(callee.params[i], args[i]);
|
|
1509
1509
|
}
|
|
1510
|
-
return
|
|
1511
|
-
}
|
|
1510
|
+
return evaluator.evaluate(callee.body, newEnv); // use captured evaluator
|
|
1511
|
+
};
|
|
1512
1512
|
|
|
1513
1513
|
const args = [];
|
|
1514
1514
|
for (const a of node.arguments) args.push(await this.evaluate(a, env));
|
|
1515
|
-
|
|
1516
1515
|
return new Constructor(...args);
|
|
1517
1516
|
}
|
|
1518
1517
|
|
|
@@ -2114,164 +2113,212 @@ class Parser {
|
|
|
2114
2113
|
}
|
|
2115
2114
|
|
|
2116
2115
|
varDeclaration() {
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2116
|
+
this.eat('LET');
|
|
2117
|
+
const id = this.current.value;
|
|
2118
|
+
this.eat('IDENTIFIER');
|
|
2119
|
+
|
|
2120
|
+
let expr = null;
|
|
2121
|
+
if (this.current.type === 'EQUAL') {
|
|
2120
2122
|
this.eat('EQUAL');
|
|
2121
|
-
|
|
2122
|
-
if (this.current.type === 'SEMICOLON') this.eat('SEMICOLON');
|
|
2123
|
-
return { type: 'VarDeclaration', id, expr };
|
|
2123
|
+
expr = this.expression();
|
|
2124
2124
|
}
|
|
2125
2125
|
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
const expr = this.expression();
|
|
2129
|
-
if (this.current.type === 'SEMICOLON') this.eat('SEMICOLON');
|
|
2130
|
-
return { type: 'SldeployStatement', expr };
|
|
2131
|
-
}
|
|
2126
|
+
// semicolon optional
|
|
2127
|
+
if (this.current.type === 'SEMICOLON') this.eat('SEMICOLON');
|
|
2132
2128
|
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2129
|
+
return { type: 'VarDeclaration', id, expr };
|
|
2130
|
+
}
|
|
2131
|
+
|
|
2132
|
+
sldeployStatement() {
|
|
2133
|
+
this.eat('SLDEPLOY');
|
|
2134
|
+
const expr = this.expression();
|
|
2135
|
+
if (this.current.type === 'SEMICOLON') this.eat('SEMICOLON');
|
|
2136
|
+
return { type: 'SldeployStatement', expr };
|
|
2137
|
+
}
|
|
2138
|
+
|
|
2139
|
+
defineStatement() {
|
|
2140
|
+
this.eat('DEFINE');
|
|
2141
|
+
const id = this.current.value;
|
|
2142
|
+
this.eat('IDENTIFIER');
|
|
2143
|
+
|
|
2144
|
+
let expr = null;
|
|
2145
|
+
if (this.current.type === 'EQUAL') {
|
|
2146
|
+
this.eat('EQUAL');
|
|
2147
|
+
expr = this.expression();
|
|
2144
2148
|
}
|
|
2149
|
+
|
|
2150
|
+
if (this.current.type === 'SEMICOLON') this.eat('SEMICOLON');
|
|
2151
|
+
|
|
2152
|
+
return { type: 'DefineStatement', id, expr };
|
|
2153
|
+
}
|
|
2154
|
+
|
|
2145
2155
|
asyncFuncDeclaration() {
|
|
2146
2156
|
const name = this.current.value;
|
|
2147
2157
|
this.eat('IDENTIFIER');
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
if (this.current.type
|
|
2151
|
-
|
|
2152
|
-
this.
|
|
2153
|
-
|
|
2154
|
-
this.eat('
|
|
2158
|
+
|
|
2159
|
+
let params = [];
|
|
2160
|
+
if (this.current.type === 'LPAREN') {
|
|
2161
|
+
this.eat('LPAREN');
|
|
2162
|
+
if (this.current.type !== 'RPAREN') {
|
|
2163
|
+
params.push(this.current.value);
|
|
2164
|
+
this.eat('IDENTIFIER');
|
|
2165
|
+
while (this.current.type === 'COMMA') {
|
|
2166
|
+
this.eat('COMMA');
|
|
2167
|
+
params.push(this.current.value);
|
|
2168
|
+
this.eat('IDENTIFIER');
|
|
2169
|
+
}
|
|
2170
|
+
}
|
|
2171
|
+
this.eat('RPAREN');
|
|
2172
|
+
} else {
|
|
2173
|
+
// no parentheses: single param as Python style
|
|
2174
|
+
if (this.current.type === 'IDENTIFIER') {
|
|
2155
2175
|
params.push(this.current.value);
|
|
2156
2176
|
this.eat('IDENTIFIER');
|
|
2157
2177
|
}
|
|
2158
2178
|
}
|
|
2159
|
-
|
|
2179
|
+
|
|
2160
2180
|
const body = this.block();
|
|
2161
2181
|
return { type: 'FunctionDeclaration', name, params, body, async: true };
|
|
2162
2182
|
}
|
|
2163
2183
|
|
|
2164
|
-
|
|
2165
|
-
|
|
2184
|
+
ifStatement() {
|
|
2185
|
+
this.eat('IF');
|
|
2186
|
+
|
|
2187
|
+
let test;
|
|
2188
|
+
if (this.current.type === 'LPAREN') {
|
|
2166
2189
|
this.eat('LPAREN');
|
|
2167
|
-
|
|
2190
|
+
test = this.expression();
|
|
2168
2191
|
this.eat('RPAREN');
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2192
|
+
} else {
|
|
2193
|
+
// python style: no parentheses
|
|
2194
|
+
test = this.expression();
|
|
2195
|
+
}
|
|
2196
|
+
|
|
2197
|
+
const consequent = this.block();
|
|
2198
|
+
|
|
2199
|
+
let alternate = null;
|
|
2200
|
+
if (this.current.type === 'ELSE') {
|
|
2201
|
+
this.eat('ELSE');
|
|
2202
|
+
if (this.current.type === 'IF') alternate = this.ifStatement();
|
|
2203
|
+
else alternate = this.block();
|
|
2177
2204
|
}
|
|
2178
2205
|
|
|
2179
|
-
|
|
2180
|
-
|
|
2206
|
+
return { type: 'IfStatement', test, consequent, alternate };
|
|
2207
|
+
}
|
|
2208
|
+
|
|
2209
|
+
whileStatement() {
|
|
2210
|
+
this.eat('WHILE');
|
|
2211
|
+
|
|
2212
|
+
let test;
|
|
2213
|
+
if (this.current.type === 'LPAREN') {
|
|
2181
2214
|
this.eat('LPAREN');
|
|
2182
|
-
|
|
2215
|
+
test = this.expression();
|
|
2183
2216
|
this.eat('RPAREN');
|
|
2184
|
-
|
|
2185
|
-
|
|
2217
|
+
} else {
|
|
2218
|
+
test = this.expression();
|
|
2186
2219
|
}
|
|
2187
|
-
importStatement() {
|
|
2188
|
-
this.eat('IMPORT');
|
|
2189
2220
|
|
|
2190
|
-
|
|
2221
|
+
const body = this.block();
|
|
2222
|
+
return { type: 'WhileStatement', test, body };
|
|
2223
|
+
}
|
|
2224
|
+
|
|
2225
|
+
importStatement() {
|
|
2226
|
+
this.eat('IMPORT');
|
|
2227
|
+
|
|
2228
|
+
let specifiers = [];
|
|
2229
|
+
if (this.current.type === 'STAR') {
|
|
2230
|
+
this.eat('STAR');
|
|
2231
|
+
this.eat('AS');
|
|
2232
|
+
const name = this.current.value;
|
|
2233
|
+
this.eat('IDENTIFIER');
|
|
2234
|
+
specifiers.push({ type: 'NamespaceImport', local: name });
|
|
2235
|
+
} else if (this.current.type === 'LBRACE') {
|
|
2236
|
+
this.eat('LBRACE');
|
|
2237
|
+
while (this.current.type !== 'RBRACE') {
|
|
2238
|
+
const importedName = this.current.value;
|
|
2239
|
+
this.eat('IDENTIFIER');
|
|
2191
2240
|
|
|
2192
|
-
|
|
2193
|
-
|
|
2241
|
+
let localName = importedName;
|
|
2242
|
+
if (this.current.type === 'AS') {
|
|
2194
2243
|
this.eat('AS');
|
|
2195
|
-
|
|
2196
|
-
this.eat('IDENTIFIER');
|
|
2197
|
-
specifiers.push({ type: 'NamespaceImport', local: name });
|
|
2198
|
-
}
|
|
2199
|
-
else if (this.current.type === 'LBRACE') {
|
|
2200
|
-
this.eat('LBRACE');
|
|
2201
|
-
while (this.current.type !== 'RBRACE') {
|
|
2202
|
-
const importedName = this.current.value;
|
|
2203
|
-
this.eat('IDENTIFIER');
|
|
2204
|
-
let localName = importedName;
|
|
2205
|
-
if (this.current.type === 'AS') {
|
|
2206
|
-
this.eat('AS');
|
|
2207
|
-
localName = this.current.value;
|
|
2208
|
-
this.eat('IDENTIFIER');
|
|
2209
|
-
}
|
|
2210
|
-
specifiers.push({ type: 'NamedImport', imported: importedName, local: localName });
|
|
2211
|
-
if (this.current.type === 'COMMA') this.eat('COMMA');
|
|
2212
|
-
}
|
|
2213
|
-
this.eat('RBRACE');
|
|
2214
|
-
}
|
|
2215
|
-
else if (this.current.type === 'IDENTIFIER') {
|
|
2216
|
-
const localName = this.current.value;
|
|
2244
|
+
localName = this.current.value;
|
|
2217
2245
|
this.eat('IDENTIFIER');
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2246
|
+
}
|
|
2247
|
+
|
|
2248
|
+
specifiers.push({ type: 'NamedImport', imported: importedName, local: localName });
|
|
2249
|
+
if (this.current.type === 'COMMA') this.eat('COMMA');
|
|
2221
2250
|
}
|
|
2251
|
+
this.eat('RBRACE');
|
|
2252
|
+
} else if (this.current.type === 'IDENTIFIER') {
|
|
2253
|
+
const localName = this.current.value;
|
|
2254
|
+
this.eat('IDENTIFIER');
|
|
2255
|
+
specifiers.push({ type: 'DefaultImport', local: localName });
|
|
2256
|
+
} else {
|
|
2257
|
+
throw new Error(`Unexpected token in import at line ${this.current.line}, column ${this.current.column}`);
|
|
2258
|
+
}
|
|
2222
2259
|
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2260
|
+
this.eat('FROM');
|
|
2261
|
+
const pathToken = this.current;
|
|
2262
|
+
if (pathToken.type !== 'STRING') throw new Error(`Expected string after FROM at line ${this.current.line}, column ${this.current.column}`);
|
|
2263
|
+
this.eat('STRING');
|
|
2227
2264
|
|
|
2228
|
-
|
|
2265
|
+
if (this.current.type === 'SEMICOLON') this.eat('SEMICOLON');
|
|
2229
2266
|
|
|
2230
|
-
|
|
2267
|
+
return { type: 'ImportStatement', path: pathToken.value, specifiers };
|
|
2231
2268
|
}
|
|
2232
2269
|
|
|
2270
|
+
forStatement() {
|
|
2271
|
+
this.eat('FOR');
|
|
2233
2272
|
|
|
2234
|
-
|
|
2235
|
-
|
|
2273
|
+
let init = null;
|
|
2274
|
+
let test = null;
|
|
2275
|
+
let update = null;
|
|
2276
|
+
|
|
2277
|
+
if (this.current.type === 'LPAREN') {
|
|
2236
2278
|
this.eat('LPAREN');
|
|
2237
2279
|
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
init = this.current.type === 'LET' ? this.varDeclaration() : this.expressionStatement();
|
|
2241
|
-
} else {
|
|
2242
|
-
this.eat('SEMICOLON');
|
|
2243
|
-
}
|
|
2280
|
+
if (this.current.type !== 'SEMICOLON') init = this.current.type === 'LET' ? this.varDeclaration() : this.expressionStatement();
|
|
2281
|
+
else this.eat('SEMICOLON');
|
|
2244
2282
|
|
|
2245
|
-
let test = null;
|
|
2246
2283
|
if (this.current.type !== 'SEMICOLON') test = this.expression();
|
|
2247
2284
|
this.eat('SEMICOLON');
|
|
2248
2285
|
|
|
2249
|
-
let update = null;
|
|
2250
2286
|
if (this.current.type !== 'RPAREN') update = this.expression();
|
|
2251
2287
|
this.eat('RPAREN');
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2288
|
+
} else {
|
|
2289
|
+
init = this.expression();
|
|
2290
|
+
if (this.current.type === 'IN') {
|
|
2291
|
+
this.eat('IN');
|
|
2292
|
+
test = this.expression(); // iterable
|
|
2293
|
+
}
|
|
2255
2294
|
}
|
|
2256
2295
|
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2296
|
+
const body = this.block();
|
|
2297
|
+
return { type: 'ForStatement', init, test, update, body };
|
|
2298
|
+
}
|
|
2299
|
+
|
|
2300
|
+
breakStatement() {
|
|
2301
|
+
this.eat('BREAK');
|
|
2302
|
+
if (this.current.type === 'SEMICOLON') this.eat('SEMICOLON');
|
|
2303
|
+
return { type: 'BreakStatement' };
|
|
2304
|
+
}
|
|
2305
|
+
|
|
2262
2306
|
|
|
2263
2307
|
continueStatement() {
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
}
|
|
2308
|
+
this.eat('CONTINUE');
|
|
2309
|
+
// semicolon optional
|
|
2310
|
+
if (this.current.type === 'SEMICOLON') this.eat('SEMICOLON');
|
|
2311
|
+
return { type: 'ContinueStatement' };
|
|
2312
|
+
}
|
|
2268
2313
|
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2314
|
+
funcDeclaration() {
|
|
2315
|
+
this.eat('FUNC');
|
|
2316
|
+
const name = this.current.value;
|
|
2317
|
+
this.eat('IDENTIFIER');
|
|
2318
|
+
|
|
2319
|
+
let params = [];
|
|
2320
|
+
if (this.current.type === 'LPAREN') {
|
|
2273
2321
|
this.eat('LPAREN');
|
|
2274
|
-
const params = [];
|
|
2275
2322
|
if (this.current.type !== 'RPAREN') {
|
|
2276
2323
|
params.push(this.current.value);
|
|
2277
2324
|
this.eat('IDENTIFIER');
|
|
@@ -2282,176 +2329,203 @@ asyncFuncDeclaration() {
|
|
|
2282
2329
|
}
|
|
2283
2330
|
}
|
|
2284
2331
|
this.eat('RPAREN');
|
|
2285
|
-
|
|
2286
|
-
|
|
2332
|
+
} else {
|
|
2333
|
+
// Python-style: single param without parentheses
|
|
2334
|
+
if (this.current.type === 'IDENTIFIER') {
|
|
2335
|
+
params.push(this.current.value);
|
|
2336
|
+
this.eat('IDENTIFIER');
|
|
2337
|
+
}
|
|
2287
2338
|
}
|
|
2288
2339
|
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2340
|
+
const body = this.block();
|
|
2341
|
+
return { type: 'FunctionDeclaration', name, params, body };
|
|
2342
|
+
}
|
|
2343
|
+
|
|
2344
|
+
returnStatement() {
|
|
2345
|
+
this.eat('RETURN');
|
|
2346
|
+
|
|
2347
|
+
let argument = null;
|
|
2348
|
+
if (this.current.type !== 'SEMICOLON') {
|
|
2349
|
+
argument = this.expression();
|
|
2295
2350
|
}
|
|
2296
2351
|
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2352
|
+
// semicolon optional
|
|
2353
|
+
if (this.current.type === 'SEMICOLON') this.eat('SEMICOLON');
|
|
2354
|
+
|
|
2355
|
+
return { type: 'ReturnStatement', argument };
|
|
2356
|
+
}
|
|
2357
|
+
|
|
2358
|
+
block() {
|
|
2359
|
+
this.eat('LBRACE');
|
|
2360
|
+
const body = [];
|
|
2361
|
+
while (this.current.type !== 'RBRACE') {
|
|
2362
|
+
body.push(this.statement());
|
|
2305
2363
|
}
|
|
2364
|
+
this.eat('RBRACE');
|
|
2365
|
+
return { type: 'BlockStatement', body };
|
|
2366
|
+
}
|
|
2367
|
+
|
|
2368
|
+
expressionStatement() {
|
|
2369
|
+
const expr = this.expression();
|
|
2370
|
+
// semicolon optional
|
|
2371
|
+
if (this.current.type === 'SEMICOLON') this.eat('SEMICOLON');
|
|
2372
|
+
return { type: 'ExpressionStatement', expression: expr };
|
|
2373
|
+
}
|
|
2374
|
+
|
|
2375
|
+
expression() {
|
|
2376
|
+
return this.assignment();
|
|
2377
|
+
}
|
|
2306
2378
|
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2379
|
+
assignment() {
|
|
2380
|
+
const node = this.logicalOr();
|
|
2381
|
+
const compoundOps = ['PLUSEQ', 'MINUSEQ', 'STAREQ', 'SLASHEQ', 'MODEQ'];
|
|
2382
|
+
|
|
2383
|
+
if (compoundOps.includes(this.current.type)) {
|
|
2384
|
+
const op = this.current.type;
|
|
2385
|
+
this.eat(op);
|
|
2386
|
+
const right = this.assignment();
|
|
2387
|
+
return { type: 'CompoundAssignment', operator: op, left: node, right };
|
|
2311
2388
|
}
|
|
2312
2389
|
|
|
2313
|
-
|
|
2314
|
-
|
|
2390
|
+
if (this.current.type === 'EQUAL') {
|
|
2391
|
+
this.eat('EQUAL');
|
|
2392
|
+
const right = this.assignment();
|
|
2393
|
+
return { type: 'AssignmentExpression', left: node, right };
|
|
2315
2394
|
}
|
|
2316
2395
|
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
const compoundOps = ['PLUSEQ', 'MINUSEQ', 'STAREQ', 'SLASHEQ', 'MODEQ'];
|
|
2320
|
-
if (compoundOps.includes(this.current.type)) {
|
|
2321
|
-
const op = this.current.type;
|
|
2322
|
-
this.eat(op);
|
|
2323
|
-
const right = this.assignment();
|
|
2324
|
-
return { type: 'CompoundAssignment', operator: op, left: node, right };
|
|
2325
|
-
}
|
|
2396
|
+
return node;
|
|
2397
|
+
}
|
|
2326
2398
|
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2399
|
+
logicalOr() {
|
|
2400
|
+
let node = this.logicalAnd();
|
|
2401
|
+
while (this.current.type === 'OR') {
|
|
2402
|
+
const op = this.current.type;
|
|
2403
|
+
this.eat(op);
|
|
2404
|
+
node = { type: 'LogicalExpression', operator: op, left: node, right: this.logicalAnd() };
|
|
2405
|
+
}
|
|
2406
|
+
return node;
|
|
2407
|
+
}
|
|
2332
2408
|
|
|
2333
|
-
|
|
2409
|
+
logicalAnd() {
|
|
2410
|
+
let node = this.equality();
|
|
2411
|
+
while (this.current.type === 'AND') {
|
|
2412
|
+
const op = this.current.type;
|
|
2413
|
+
this.eat(op);
|
|
2414
|
+
node = { type: 'LogicalExpression', operator: op, left: node, right: this.equality() };
|
|
2334
2415
|
}
|
|
2416
|
+
return node;
|
|
2417
|
+
}
|
|
2418
|
+
equality() {
|
|
2419
|
+
let node = this.comparison();
|
|
2420
|
+
while (['EQEQ', 'NOTEQ'].includes(this.current.type)) {
|
|
2421
|
+
const op = this.current.type;
|
|
2422
|
+
this.eat(op);
|
|
2423
|
+
node = { type: 'BinaryExpression', operator: op, left: node, right: this.comparison() };
|
|
2424
|
+
}
|
|
2425
|
+
return node;
|
|
2426
|
+
}
|
|
2335
2427
|
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
}
|
|
2343
|
-
return node;
|
|
2428
|
+
comparison() {
|
|
2429
|
+
let node = this.term();
|
|
2430
|
+
while (['LT', 'LTE', 'GT', 'GTE'].includes(this.current.type)) {
|
|
2431
|
+
const op = this.current.type;
|
|
2432
|
+
this.eat(op);
|
|
2433
|
+
node = { type: 'BinaryExpression', operator: op, left: node, right: this.term() };
|
|
2344
2434
|
}
|
|
2435
|
+
return node;
|
|
2436
|
+
}
|
|
2345
2437
|
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
}
|
|
2353
|
-
return node;
|
|
2438
|
+
term() {
|
|
2439
|
+
let node = this.factor();
|
|
2440
|
+
while (['PLUS', 'MINUS'].includes(this.current.type)) {
|
|
2441
|
+
const op = this.current.type;
|
|
2442
|
+
this.eat(op);
|
|
2443
|
+
node = { type: 'BinaryExpression', operator: op, left: node, right: this.factor() };
|
|
2354
2444
|
}
|
|
2445
|
+
return node;
|
|
2446
|
+
}
|
|
2355
2447
|
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
}
|
|
2363
|
-
return node;
|
|
2448
|
+
factor() {
|
|
2449
|
+
let node = this.unary();
|
|
2450
|
+
while (['STAR', 'SLASH', 'MOD'].includes(this.current.type)) {
|
|
2451
|
+
const op = this.current.type;
|
|
2452
|
+
this.eat(op);
|
|
2453
|
+
node = { type: 'BinaryExpression', operator: op, left: node, right: this.unary() };
|
|
2364
2454
|
}
|
|
2455
|
+
return node;
|
|
2456
|
+
}
|
|
2365
2457
|
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
node = { type: 'BinaryExpression', operator: op, left: node, right: this.term() };
|
|
2372
|
-
}
|
|
2373
|
-
return node;
|
|
2458
|
+
unary() {
|
|
2459
|
+
if (['NOT', 'MINUS', 'PLUS'].includes(this.current.type)) {
|
|
2460
|
+
const op = this.current.type;
|
|
2461
|
+
this.eat(op);
|
|
2462
|
+
return { type: 'UnaryExpression', operator: op, argument: this.unary() };
|
|
2374
2463
|
}
|
|
2375
2464
|
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
}
|
|
2383
|
-
return node;
|
|
2465
|
+
// Python-like: ignore ++ and -- if not used
|
|
2466
|
+
if (this.current.type === 'PLUSPLUS' || this.current.type === 'MINUSMINUS') {
|
|
2467
|
+
const op = this.current.type;
|
|
2468
|
+
this.eat(op);
|
|
2469
|
+
const argument = this.unary();
|
|
2470
|
+
return { type: 'UpdateExpression', operator: op, argument, prefix: true };
|
|
2384
2471
|
}
|
|
2385
2472
|
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
|
|
2473
|
+
return this.postfix();
|
|
2474
|
+
}
|
|
2475
|
+
|
|
2476
|
+
postfix() {
|
|
2477
|
+
let node = this.primary();
|
|
2478
|
+
while (true) {
|
|
2479
|
+
if (this.current.type === 'LBRACKET') {
|
|
2480
|
+
this.eat('LBRACKET');
|
|
2481
|
+
const index = this.expression();
|
|
2482
|
+
if (this.current.type === 'RBRACKET') this.eat('RBRACKET');
|
|
2483
|
+
node = { type: 'IndexExpression', object: node, indexer: index };
|
|
2484
|
+
continue;
|
|
2392
2485
|
}
|
|
2393
|
-
return node;
|
|
2394
|
-
}
|
|
2395
2486
|
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
const
|
|
2399
|
-
this.
|
|
2400
|
-
|
|
2487
|
+
if (this.current.type === 'LPAREN') {
|
|
2488
|
+
this.eat('LPAREN');
|
|
2489
|
+
const args = [];
|
|
2490
|
+
while (this.current.type !== 'RPAREN' && this.current.type !== 'EOF') {
|
|
2491
|
+
args.push(this.expression());
|
|
2492
|
+
if (this.current.type === 'COMMA') this.eat('COMMA'); // optional comma
|
|
2493
|
+
}
|
|
2494
|
+
if (this.current.type === 'RPAREN') this.eat('RPAREN');
|
|
2495
|
+
node = { type: 'CallExpression', callee: node, arguments: args };
|
|
2496
|
+
continue;
|
|
2401
2497
|
}
|
|
2498
|
+
|
|
2499
|
+
if (this.current.type === 'DOT') {
|
|
2500
|
+
this.eat('DOT');
|
|
2501
|
+
const property = this.current.value || '';
|
|
2502
|
+
if (this.current.type === 'IDENTIFIER') this.eat('IDENTIFIER');
|
|
2503
|
+
node = { type: 'MemberExpression', object: node, property };
|
|
2504
|
+
continue;
|
|
2505
|
+
}
|
|
2506
|
+
|
|
2402
2507
|
if (this.current.type === 'PLUSPLUS' || this.current.type === 'MINUSMINUS') {
|
|
2403
2508
|
const op = this.current.type;
|
|
2404
2509
|
this.eat(op);
|
|
2405
|
-
|
|
2406
|
-
|
|
2510
|
+
node = { type: 'UpdateExpression', operator: op, argument: node, prefix: false };
|
|
2511
|
+
continue;
|
|
2407
2512
|
}
|
|
2408
|
-
return this.postfix();
|
|
2409
|
-
}
|
|
2410
2513
|
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
this.eat('RBRACKET');
|
|
2418
|
-
node = { type: 'IndexExpression', object: node, indexer: index };
|
|
2419
|
-
continue;
|
|
2420
|
-
}
|
|
2421
|
-
if (this.current.type === 'LPAREN') {
|
|
2422
|
-
this.eat('LPAREN');
|
|
2423
|
-
const args = [];
|
|
2424
|
-
if (this.current.type !== 'RPAREN') {
|
|
2425
|
-
args.push(this.expression());
|
|
2426
|
-
while (this.current.type === 'COMMA') {
|
|
2427
|
-
this.eat('COMMA');
|
|
2428
|
-
args.push(this.expression());
|
|
2429
|
-
}
|
|
2430
|
-
}
|
|
2431
|
-
this.eat('RPAREN');
|
|
2432
|
-
node = { type: 'CallExpression', callee: node, arguments: args };
|
|
2433
|
-
continue;
|
|
2434
|
-
}
|
|
2435
|
-
if (this.current.type === 'DOT') {
|
|
2436
|
-
this.eat('DOT');
|
|
2437
|
-
if (this.current.type !== 'IDENTIFIER') throw new Error(`Expected property name after dot at line ${this.current.line}, column ${this.current.column}`);
|
|
2438
|
-
const property = this.current.value;
|
|
2439
|
-
this.eat('IDENTIFIER');
|
|
2440
|
-
node = { type: 'MemberExpression', object: node, property };
|
|
2441
|
-
continue;
|
|
2442
|
-
}
|
|
2443
|
-
if (this.current.type === 'PLUSPLUS' || this.current.type === 'MINUSMINUS') {
|
|
2444
|
-
const op = this.current.type;
|
|
2445
|
-
this.eat(op);
|
|
2446
|
-
node = { type: 'UpdateExpression', operator: op, argument: node, prefix: false };
|
|
2447
|
-
continue;
|
|
2448
|
-
}
|
|
2449
|
-
break;
|
|
2514
|
+
// Python-style: implicit function calls (if identifier followed by identifier)
|
|
2515
|
+
if (node.type === 'Identifier' && this.current.type === 'IDENTIFIER') {
|
|
2516
|
+
const argNode = { type: 'Identifier', name: this.current.value };
|
|
2517
|
+
this.eat('IDENTIFIER');
|
|
2518
|
+
node = { type: 'CallExpression', callee: node, arguments: [argNode] };
|
|
2519
|
+
continue;
|
|
2450
2520
|
}
|
|
2451
|
-
|
|
2521
|
+
|
|
2522
|
+
break;
|
|
2452
2523
|
}
|
|
2524
|
+
return node;
|
|
2525
|
+
}
|
|
2526
|
+
|
|
2453
2527
|
arrowFunction(params) {
|
|
2454
|
-
this.eat('ARROW');
|
|
2528
|
+
if (this.current.type === 'ARROW') this.eat('ARROW');
|
|
2455
2529
|
const body = this.expression();
|
|
2456
2530
|
return {
|
|
2457
2531
|
type: 'ArrowFunctionExpression',
|
|
@@ -2460,126 +2534,114 @@ arrowFunction(params) {
|
|
|
2460
2534
|
};
|
|
2461
2535
|
}
|
|
2462
2536
|
|
|
2537
|
+
|
|
2463
2538
|
primary() {
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2539
|
+
const t = this.current;
|
|
2540
|
+
|
|
2541
|
+
if (t.type === 'NUMBER') { this.eat('NUMBER'); return { type: 'Literal', value: t.value }; }
|
|
2542
|
+
if (t.type === 'STRING') { this.eat('STRING'); return { type: 'Literal', value: t.value }; }
|
|
2543
|
+
if (t.type === 'TRUE') { this.eat('TRUE'); return { type: 'Literal', value: true }; }
|
|
2544
|
+
if (t.type === 'FALSE') { this.eat('FALSE'); return { type: 'Literal', value: false }; }
|
|
2545
|
+
if (t.type === 'NULL') { this.eat('NULL'); return { type: 'Literal', value: null }; }
|
|
2546
|
+
|
|
2547
|
+
if (t.type === 'AWAIT') {
|
|
2472
2548
|
this.eat('AWAIT');
|
|
2473
2549
|
const argument = this.expression();
|
|
2474
2550
|
return { type: 'AwaitExpression', argument };
|
|
2475
2551
|
}
|
|
2476
|
-
if (t.type === 'NEW') {
|
|
2477
|
-
this.eat('NEW');
|
|
2478
|
-
const callee = this.primary(); // the class or function being called
|
|
2479
|
-
this.eat('LPAREN');
|
|
2480
|
-
const args = [];
|
|
2481
|
-
if (this.current.type !== 'RPAREN') {
|
|
2482
|
-
args.push(this.expression());
|
|
2483
|
-
while (this.current.type === 'COMMA') {
|
|
2484
|
-
this.eat('COMMA');
|
|
2485
|
-
args.push(this.expression());
|
|
2486
|
-
}
|
|
2487
|
-
}
|
|
2488
|
-
this.eat('RPAREN');
|
|
2489
|
-
return { type: 'NewExpression', callee, arguments: args };
|
|
2490
|
-
}
|
|
2491
2552
|
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
|
|
2496
|
-
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
|
|
2553
|
+
if (t.type === 'NEW') {
|
|
2554
|
+
this.eat('NEW');
|
|
2555
|
+
const callee = this.primary();
|
|
2556
|
+
this.eat('LPAREN');
|
|
2557
|
+
const args = [];
|
|
2558
|
+
if (this.current.type !== 'RPAREN') {
|
|
2559
|
+
args.push(this.expression());
|
|
2560
|
+
while (this.current.type === 'COMMA') {
|
|
2561
|
+
this.eat('COMMA');
|
|
2562
|
+
if (this.current.type !== 'RPAREN') args.push(this.expression());
|
|
2502
2563
|
}
|
|
2503
|
-
this.eat('RPAREN');
|
|
2504
|
-
return { type: 'CallExpression', callee: { type: 'Identifier', name: 'ask' }, arguments: args };
|
|
2505
2564
|
}
|
|
2565
|
+
this.eat('RPAREN');
|
|
2566
|
+
return { type: 'NewExpression', callee, arguments: args };
|
|
2567
|
+
}
|
|
2506
2568
|
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
|
|
2569
|
+
if (t.type === 'ASK') {
|
|
2570
|
+
this.eat('ASK');
|
|
2571
|
+
this.eat('LPAREN');
|
|
2572
|
+
const args = [];
|
|
2573
|
+
if (this.current.type !== 'RPAREN') {
|
|
2574
|
+
args.push(this.expression());
|
|
2575
|
+
while (this.current.type === 'COMMA') {
|
|
2576
|
+
this.eat('COMMA');
|
|
2577
|
+
if (this.current.type !== 'RPAREN') args.push(this.expression());
|
|
2578
|
+
}
|
|
2579
|
+
}
|
|
2580
|
+
this.eat('RPAREN');
|
|
2581
|
+
return { type: 'CallExpression', callee: { type: 'Identifier', name: 'ask' }, arguments: args };
|
|
2513
2582
|
}
|
|
2514
2583
|
|
|
2515
|
-
|
|
2516
|
-
|
|
2584
|
+
if (t.type === 'IDENTIFIER') {
|
|
2585
|
+
const name = t.value;
|
|
2586
|
+
this.eat('IDENTIFIER');
|
|
2517
2587
|
|
|
2588
|
+
if (this.current.type === 'ARROW') {
|
|
2589
|
+
return this.arrowFunction([name]);
|
|
2590
|
+
}
|
|
2518
2591
|
|
|
2519
|
-
|
|
2520
|
-
|
|
2592
|
+
return { type: 'Identifier', name };
|
|
2593
|
+
}
|
|
2521
2594
|
|
|
2522
|
-
|
|
2595
|
+
if (t.type === 'LPAREN') {
|
|
2596
|
+
this.eat('LPAREN');
|
|
2597
|
+
const elements = [];
|
|
2523
2598
|
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
this.eat('IDENTIFIER');
|
|
2599
|
+
if (this.current.type !== 'RPAREN') {
|
|
2600
|
+
elements.push(this.expression());
|
|
2601
|
+
while (this.current.type === 'COMMA') {
|
|
2602
|
+
this.eat('COMMA');
|
|
2603
|
+
if (this.current.type !== 'RPAREN') elements.push(this.expression());
|
|
2604
|
+
}
|
|
2531
2605
|
}
|
|
2532
|
-
}
|
|
2533
2606
|
|
|
2534
|
-
|
|
2607
|
+
this.eat('RPAREN');
|
|
2535
2608
|
|
|
2536
|
-
|
|
2537
|
-
return this.arrowFunction(params);
|
|
2538
|
-
}
|
|
2609
|
+
if (this.current.type === 'ARROW') return this.arrowFunction(elements);
|
|
2539
2610
|
|
|
2540
|
-
|
|
2541
|
-
return { type: 'Identifier', name: params[0] };
|
|
2611
|
+
return elements.length === 1 ? elements[0] : { type: 'ArrayExpression', elements };
|
|
2542
2612
|
}
|
|
2543
2613
|
|
|
2544
|
-
|
|
2545
|
-
|
|
2546
|
-
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
const elements = [];
|
|
2553
|
-
if (this.current.type !== 'RBRACKET') {
|
|
2554
|
-
elements.push(this.expression());
|
|
2555
|
-
while (this.current.type === 'COMMA') {
|
|
2556
|
-
this.eat('COMMA');
|
|
2557
|
-
elements.push(this.expression());
|
|
2558
|
-
}
|
|
2614
|
+
if (t.type === 'LBRACKET') {
|
|
2615
|
+
this.eat('LBRACKET');
|
|
2616
|
+
const elements = [];
|
|
2617
|
+
if (this.current.type !== 'RBRACKET') {
|
|
2618
|
+
elements.push(this.expression());
|
|
2619
|
+
while (this.current.type === 'COMMA') {
|
|
2620
|
+
this.eat('COMMA');
|
|
2621
|
+
if (this.current.type !== 'RBRACKET') elements.push(this.expression());
|
|
2559
2622
|
}
|
|
2560
|
-
this.eat('RBRACKET');
|
|
2561
|
-
return { type: 'ArrayExpression', elements };
|
|
2562
2623
|
}
|
|
2624
|
+
this.eat('RBRACKET');
|
|
2625
|
+
return { type: 'ArrayExpression', elements };
|
|
2626
|
+
}
|
|
2563
2627
|
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
const value = this.expression();
|
|
2574
|
-
props.push({ key, value });
|
|
2575
|
-
if (this.current.type === 'COMMA') this.eat('COMMA');
|
|
2576
|
-
}
|
|
2577
|
-
this.eat('RBRACE');
|
|
2578
|
-
return { type: 'ObjectExpression', props };
|
|
2628
|
+
if (t.type === 'LBRACE') {
|
|
2629
|
+
this.eat('LBRACE');
|
|
2630
|
+
const props = [];
|
|
2631
|
+
while (this.current.type !== 'RBRACE') {
|
|
2632
|
+
const key = this.expression(); // Flexible key: can be any expression
|
|
2633
|
+
this.eat('COLON');
|
|
2634
|
+
const value = this.expression();
|
|
2635
|
+
props.push({ key, value });
|
|
2636
|
+
if (this.current.type === 'COMMA') this.eat('COMMA'); // optional trailing comma
|
|
2579
2637
|
}
|
|
2580
|
-
|
|
2581
|
-
|
|
2638
|
+
this.eat('RBRACE');
|
|
2639
|
+
return { type: 'ObjectExpression', props };
|
|
2582
2640
|
}
|
|
2641
|
+
|
|
2642
|
+
throw new Error(`Unexpected token in primary: ${t.type} at line ${this.current.line}, column ${this.current.column}`);
|
|
2643
|
+
}
|
|
2644
|
+
|
|
2583
2645
|
}
|
|
2584
2646
|
|
|
2585
2647
|
module.exports = Parser;
|