starlight-cli 1.0.33 → 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 +402 -339
- package/package.json +1 -1
- package/src/parser.js +403 -340
package/dist/index.js
CHANGED
|
@@ -2113,164 +2113,212 @@ class Parser {
|
|
|
2113
2113
|
}
|
|
2114
2114
|
|
|
2115
2115
|
varDeclaration() {
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
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') {
|
|
2119
2122
|
this.eat('EQUAL');
|
|
2120
|
-
|
|
2121
|
-
if (this.current.type === 'SEMICOLON') this.eat('SEMICOLON');
|
|
2122
|
-
return { type: 'VarDeclaration', id, expr };
|
|
2123
|
+
expr = this.expression();
|
|
2123
2124
|
}
|
|
2124
2125
|
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
const expr = this.expression();
|
|
2128
|
-
if (this.current.type === 'SEMICOLON') this.eat('SEMICOLON');
|
|
2129
|
-
return { type: 'SldeployStatement', expr };
|
|
2130
|
-
}
|
|
2126
|
+
// semicolon optional
|
|
2127
|
+
if (this.current.type === 'SEMICOLON') this.eat('SEMICOLON');
|
|
2131
2128
|
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
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();
|
|
2143
2148
|
}
|
|
2149
|
+
|
|
2150
|
+
if (this.current.type === 'SEMICOLON') this.eat('SEMICOLON');
|
|
2151
|
+
|
|
2152
|
+
return { type: 'DefineStatement', id, expr };
|
|
2153
|
+
}
|
|
2154
|
+
|
|
2144
2155
|
asyncFuncDeclaration() {
|
|
2145
2156
|
const name = this.current.value;
|
|
2146
2157
|
this.eat('IDENTIFIER');
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
if (this.current.type
|
|
2150
|
-
|
|
2151
|
-
this.
|
|
2152
|
-
|
|
2153
|
-
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') {
|
|
2154
2175
|
params.push(this.current.value);
|
|
2155
2176
|
this.eat('IDENTIFIER');
|
|
2156
2177
|
}
|
|
2157
2178
|
}
|
|
2158
|
-
|
|
2179
|
+
|
|
2159
2180
|
const body = this.block();
|
|
2160
2181
|
return { type: 'FunctionDeclaration', name, params, body, async: true };
|
|
2161
2182
|
}
|
|
2162
2183
|
|
|
2163
|
-
|
|
2164
|
-
|
|
2184
|
+
ifStatement() {
|
|
2185
|
+
this.eat('IF');
|
|
2186
|
+
|
|
2187
|
+
let test;
|
|
2188
|
+
if (this.current.type === 'LPAREN') {
|
|
2165
2189
|
this.eat('LPAREN');
|
|
2166
|
-
|
|
2190
|
+
test = this.expression();
|
|
2167
2191
|
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 };
|
|
2192
|
+
} else {
|
|
2193
|
+
// python style: no parentheses
|
|
2194
|
+
test = this.expression();
|
|
2176
2195
|
}
|
|
2177
2196
|
|
|
2178
|
-
|
|
2179
|
-
|
|
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();
|
|
2204
|
+
}
|
|
2205
|
+
|
|
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') {
|
|
2180
2214
|
this.eat('LPAREN');
|
|
2181
|
-
|
|
2215
|
+
test = this.expression();
|
|
2182
2216
|
this.eat('RPAREN');
|
|
2183
|
-
|
|
2184
|
-
|
|
2217
|
+
} else {
|
|
2218
|
+
test = this.expression();
|
|
2185
2219
|
}
|
|
2186
|
-
importStatement() {
|
|
2187
|
-
this.eat('IMPORT');
|
|
2188
2220
|
|
|
2189
|
-
|
|
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');
|
|
2190
2240
|
|
|
2191
|
-
|
|
2192
|
-
|
|
2241
|
+
let localName = importedName;
|
|
2242
|
+
if (this.current.type === 'AS') {
|
|
2193
2243
|
this.eat('AS');
|
|
2194
|
-
|
|
2244
|
+
localName = this.current.value;
|
|
2195
2245
|
this.eat('IDENTIFIER');
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
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;
|
|
2216
|
-
this.eat('IDENTIFIER');
|
|
2217
|
-
specifiers.push({ type: 'DefaultImport', local: localName });
|
|
2218
|
-
} else {
|
|
2219
|
-
throw new Error(`Unexpected token in import statement at line ${this.current.line}, column ${this.current.column}`);
|
|
2246
|
+
}
|
|
2247
|
+
|
|
2248
|
+
specifiers.push({ type: 'NamedImport', imported: importedName, local: localName });
|
|
2249
|
+
if (this.current.type === 'COMMA') this.eat('COMMA');
|
|
2220
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
|
+
}
|
|
2221
2259
|
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
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');
|
|
2226
2264
|
|
|
2227
|
-
|
|
2265
|
+
if (this.current.type === 'SEMICOLON') this.eat('SEMICOLON');
|
|
2228
2266
|
|
|
2229
|
-
|
|
2267
|
+
return { type: 'ImportStatement', path: pathToken.value, specifiers };
|
|
2230
2268
|
}
|
|
2231
2269
|
|
|
2270
|
+
forStatement() {
|
|
2271
|
+
this.eat('FOR');
|
|
2272
|
+
|
|
2273
|
+
let init = null;
|
|
2274
|
+
let test = null;
|
|
2275
|
+
let update = null;
|
|
2232
2276
|
|
|
2233
|
-
|
|
2234
|
-
this.eat('FOR');
|
|
2277
|
+
if (this.current.type === 'LPAREN') {
|
|
2235
2278
|
this.eat('LPAREN');
|
|
2236
2279
|
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
init = this.current.type === 'LET' ? this.varDeclaration() : this.expressionStatement();
|
|
2240
|
-
} else {
|
|
2241
|
-
this.eat('SEMICOLON');
|
|
2242
|
-
}
|
|
2280
|
+
if (this.current.type !== 'SEMICOLON') init = this.current.type === 'LET' ? this.varDeclaration() : this.expressionStatement();
|
|
2281
|
+
else this.eat('SEMICOLON');
|
|
2243
2282
|
|
|
2244
|
-
let test = null;
|
|
2245
2283
|
if (this.current.type !== 'SEMICOLON') test = this.expression();
|
|
2246
2284
|
this.eat('SEMICOLON');
|
|
2247
2285
|
|
|
2248
|
-
let update = null;
|
|
2249
2286
|
if (this.current.type !== 'RPAREN') update = this.expression();
|
|
2250
2287
|
this.eat('RPAREN');
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2288
|
+
} else {
|
|
2289
|
+
init = this.expression();
|
|
2290
|
+
if (this.current.type === 'IN') {
|
|
2291
|
+
this.eat('IN');
|
|
2292
|
+
test = this.expression(); // iterable
|
|
2293
|
+
}
|
|
2254
2294
|
}
|
|
2255
2295
|
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
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
|
+
|
|
2261
2306
|
|
|
2262
2307
|
continueStatement() {
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
}
|
|
2308
|
+
this.eat('CONTINUE');
|
|
2309
|
+
// semicolon optional
|
|
2310
|
+
if (this.current.type === 'SEMICOLON') this.eat('SEMICOLON');
|
|
2311
|
+
return { type: 'ContinueStatement' };
|
|
2312
|
+
}
|
|
2267
2313
|
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
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') {
|
|
2272
2321
|
this.eat('LPAREN');
|
|
2273
|
-
const params = [];
|
|
2274
2322
|
if (this.current.type !== 'RPAREN') {
|
|
2275
2323
|
params.push(this.current.value);
|
|
2276
2324
|
this.eat('IDENTIFIER');
|
|
@@ -2281,176 +2329,203 @@ asyncFuncDeclaration() {
|
|
|
2281
2329
|
}
|
|
2282
2330
|
}
|
|
2283
2331
|
this.eat('RPAREN');
|
|
2284
|
-
|
|
2285
|
-
|
|
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
|
+
}
|
|
2286
2338
|
}
|
|
2287
2339
|
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
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();
|
|
2294
2350
|
}
|
|
2295
2351
|
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
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());
|
|
2304
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
|
+
}
|
|
2378
|
+
|
|
2379
|
+
assignment() {
|
|
2380
|
+
const node = this.logicalOr();
|
|
2381
|
+
const compoundOps = ['PLUSEQ', 'MINUSEQ', 'STAREQ', 'SLASHEQ', 'MODEQ'];
|
|
2305
2382
|
|
|
2306
|
-
|
|
2307
|
-
const
|
|
2308
|
-
|
|
2309
|
-
|
|
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 };
|
|
2310
2388
|
}
|
|
2311
2389
|
|
|
2312
|
-
|
|
2313
|
-
|
|
2390
|
+
if (this.current.type === 'EQUAL') {
|
|
2391
|
+
this.eat('EQUAL');
|
|
2392
|
+
const right = this.assignment();
|
|
2393
|
+
return { type: 'AssignmentExpression', left: node, right };
|
|
2314
2394
|
}
|
|
2315
2395
|
|
|
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
|
-
}
|
|
2396
|
+
return node;
|
|
2397
|
+
}
|
|
2325
2398
|
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
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
|
+
}
|
|
2331
2408
|
|
|
2332
|
-
|
|
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() };
|
|
2333
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
|
+
}
|
|
2334
2427
|
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
}
|
|
2342
|
-
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() };
|
|
2343
2434
|
}
|
|
2435
|
+
return node;
|
|
2436
|
+
}
|
|
2344
2437
|
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
}
|
|
2352
|
-
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() };
|
|
2353
2444
|
}
|
|
2445
|
+
return node;
|
|
2446
|
+
}
|
|
2354
2447
|
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
}
|
|
2362
|
-
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() };
|
|
2363
2454
|
}
|
|
2455
|
+
return node;
|
|
2456
|
+
}
|
|
2364
2457
|
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
node = { type: 'BinaryExpression', operator: op, left: node, right: this.term() };
|
|
2371
|
-
}
|
|
2372
|
-
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() };
|
|
2373
2463
|
}
|
|
2374
2464
|
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
}
|
|
2382
|
-
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 };
|
|
2383
2471
|
}
|
|
2384
2472
|
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
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;
|
|
2391
2485
|
}
|
|
2392
|
-
return node;
|
|
2393
|
-
}
|
|
2394
2486
|
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
const
|
|
2398
|
-
this.
|
|
2399
|
-
|
|
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;
|
|
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;
|
|
2400
2505
|
}
|
|
2506
|
+
|
|
2401
2507
|
if (this.current.type === 'PLUSPLUS' || this.current.type === 'MINUSMINUS') {
|
|
2402
2508
|
const op = this.current.type;
|
|
2403
2509
|
this.eat(op);
|
|
2404
|
-
|
|
2405
|
-
|
|
2510
|
+
node = { type: 'UpdateExpression', operator: op, argument: node, prefix: false };
|
|
2511
|
+
continue;
|
|
2406
2512
|
}
|
|
2407
|
-
return this.postfix();
|
|
2408
|
-
}
|
|
2409
2513
|
|
|
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;
|
|
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;
|
|
2449
2520
|
}
|
|
2450
|
-
|
|
2521
|
+
|
|
2522
|
+
break;
|
|
2451
2523
|
}
|
|
2524
|
+
return node;
|
|
2525
|
+
}
|
|
2526
|
+
|
|
2452
2527
|
arrowFunction(params) {
|
|
2453
|
-
this.eat('ARROW');
|
|
2528
|
+
if (this.current.type === 'ARROW') this.eat('ARROW');
|
|
2454
2529
|
const body = this.expression();
|
|
2455
2530
|
return {
|
|
2456
2531
|
type: 'ArrowFunctionExpression',
|
|
@@ -2459,126 +2534,114 @@ arrowFunction(params) {
|
|
|
2459
2534
|
};
|
|
2460
2535
|
}
|
|
2461
2536
|
|
|
2537
|
+
|
|
2462
2538
|
primary() {
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
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') {
|
|
2471
2548
|
this.eat('AWAIT');
|
|
2472
2549
|
const argument = this.expression();
|
|
2473
2550
|
return { type: 'AwaitExpression', argument };
|
|
2474
2551
|
}
|
|
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
2552
|
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
|
|
2496
|
-
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
|
|
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());
|
|
2501
2563
|
}
|
|
2502
|
-
this.eat('RPAREN');
|
|
2503
|
-
return { type: 'CallExpression', callee: { type: 'Identifier', name: 'ask' }, arguments: args };
|
|
2504
2564
|
}
|
|
2565
|
+
this.eat('RPAREN');
|
|
2566
|
+
return { type: 'NewExpression', callee, arguments: args };
|
|
2567
|
+
}
|
|
2505
2568
|
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
|
|
2510
|
-
|
|
2511
|
-
|
|
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 };
|
|
2512
2582
|
}
|
|
2513
2583
|
|
|
2514
|
-
|
|
2515
|
-
|
|
2584
|
+
if (t.type === 'IDENTIFIER') {
|
|
2585
|
+
const name = t.value;
|
|
2586
|
+
this.eat('IDENTIFIER');
|
|
2516
2587
|
|
|
2588
|
+
if (this.current.type === 'ARROW') {
|
|
2589
|
+
return this.arrowFunction([name]);
|
|
2590
|
+
}
|
|
2517
2591
|
|
|
2518
|
-
|
|
2519
|
-
|
|
2592
|
+
return { type: 'Identifier', name };
|
|
2593
|
+
}
|
|
2520
2594
|
|
|
2521
|
-
|
|
2595
|
+
if (t.type === 'LPAREN') {
|
|
2596
|
+
this.eat('LPAREN');
|
|
2597
|
+
const elements = [];
|
|
2522
2598
|
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
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
|
+
}
|
|
2530
2605
|
}
|
|
2531
|
-
}
|
|
2532
2606
|
|
|
2533
|
-
|
|
2607
|
+
this.eat('RPAREN');
|
|
2534
2608
|
|
|
2535
|
-
|
|
2536
|
-
return this.arrowFunction(params);
|
|
2537
|
-
}
|
|
2609
|
+
if (this.current.type === 'ARROW') return this.arrowFunction(elements);
|
|
2538
2610
|
|
|
2539
|
-
|
|
2540
|
-
return { type: 'Identifier', name: params[0] };
|
|
2611
|
+
return elements.length === 1 ? elements[0] : { type: 'ArrayExpression', elements };
|
|
2541
2612
|
}
|
|
2542
2613
|
|
|
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
|
-
}
|
|
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());
|
|
2558
2622
|
}
|
|
2559
|
-
this.eat('RBRACKET');
|
|
2560
|
-
return { type: 'ArrayExpression', elements };
|
|
2561
2623
|
}
|
|
2624
|
+
this.eat('RBRACKET');
|
|
2625
|
+
return { type: 'ArrayExpression', elements };
|
|
2626
|
+
}
|
|
2562
2627
|
|
|
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 };
|
|
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
|
|
2578
2637
|
}
|
|
2579
|
-
|
|
2580
|
-
|
|
2638
|
+
this.eat('RBRACE');
|
|
2639
|
+
return { type: 'ObjectExpression', props };
|
|
2581
2640
|
}
|
|
2641
|
+
|
|
2642
|
+
throw new Error(`Unexpected token in primary: ${t.type} at line ${this.current.line}, column ${this.current.column}`);
|
|
2643
|
+
}
|
|
2644
|
+
|
|
2582
2645
|
}
|
|
2583
2646
|
|
|
2584
2647
|
module.exports = Parser;
|