naidejs 1.0.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +299 -3
- package/SPEC-X.nx +80 -7
- package/SPEC.naide +117 -9
- package/bin/naide.js +59 -2
- package/examples/fullapp.naide +74 -0
- package/examples/fullapp.nx +74 -0
- package/package.json +13 -5
- package/src/generator.js +493 -19
- package/src/index.js +2 -2
- package/src/parser.js +376 -73
- package/src/preprocess.js +8 -0
- package/src/runtime.js +425 -0
- package/src/tokens.js +24 -0
package/src/parser.js
CHANGED
|
@@ -14,14 +14,11 @@ export class Parser {
|
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
preprocessTokens(tokens) {
|
|
17
|
-
// Remove INDENT/DEDENT pairs around pipe continuation lines
|
|
18
|
-
// Pattern: NEWLINE INDENT PIPE -> NEWLINE PIPE (and remove matching DEDENT)
|
|
19
17
|
const result = [...tokens];
|
|
20
18
|
const indentsToRemove = new Set();
|
|
21
19
|
|
|
22
20
|
for (let i = 0; i < result.length; i++) {
|
|
23
21
|
if (result[i].type !== T.INDENT) continue;
|
|
24
|
-
// Look forward past any newlines for PIPE
|
|
25
22
|
let j = i + 1;
|
|
26
23
|
while (j < result.length && result[j].type === T.NEWLINE) j++;
|
|
27
24
|
if (j < result.length && result[j].type === T.PIPE) {
|
|
@@ -31,7 +28,6 @@ export class Parser {
|
|
|
31
28
|
|
|
32
29
|
if (indentsToRemove.size === 0) return result;
|
|
33
30
|
|
|
34
|
-
// For each INDENT to remove, find matching DEDENT
|
|
35
31
|
const dedentsToRemove = new Set();
|
|
36
32
|
for (const idx of indentsToRemove) {
|
|
37
33
|
let depth = 0;
|
|
@@ -91,7 +87,12 @@ export class Parser {
|
|
|
91
87
|
tok.type === T.LOG || tok.type === T.DB || tok.type === T.GET ||
|
|
92
88
|
tok.type === T.POST || tok.type === T.PUT || tok.type === T.DEL ||
|
|
93
89
|
tok.type === T.MATCH || tok.type === T.ON || tok.type === T.NEW ||
|
|
94
|
-
tok.type === T.FROM || tok.type === T.AS || tok.type === T.SELF
|
|
90
|
+
tok.type === T.FROM || tok.type === T.AS || tok.type === T.SELF ||
|
|
91
|
+
tok.type === T.SCHEMA || tok.type === T.CRUD || tok.type === T.AUTH ||
|
|
92
|
+
tok.type === T.CORS || tok.type === T.LIMIT || tok.type === T.ENV ||
|
|
93
|
+
tok.type === T.EVERY || tok.type === T.WATCH ||
|
|
94
|
+
tok.type === T.STATIC || tok.type === T.WS ||
|
|
95
|
+
tok.type === T.GROUP || tok.type === T.COOKIE) {
|
|
95
96
|
return tok.value;
|
|
96
97
|
}
|
|
97
98
|
throw this.error(`Expected property name but got ${tok.type} ('${tok.value}')`, tok);
|
|
@@ -145,9 +146,46 @@ export class Parser {
|
|
|
145
146
|
case T.BREAK: this.advance(); return new ASTNode('Break');
|
|
146
147
|
case T.CONTINUE: this.advance(); return new ASTNode('Continue');
|
|
147
148
|
case T.MUT: return this.parseMutVariable();
|
|
148
|
-
case T.DB:
|
|
149
|
+
case T.DB:
|
|
150
|
+
if (this.peek(1).type === T.DOT) return this.parseDbStatement();
|
|
151
|
+
return this.parseDbDir();
|
|
149
152
|
case T.AWAIT: return this.parseAwaitStatement();
|
|
150
153
|
case T.AWAIT_ALL: return this.parseAwaitAll();
|
|
154
|
+
case T.SCHEMA: return this.parseSchema();
|
|
155
|
+
case T.CRUD:
|
|
156
|
+
if (this.peek(1).type === T.DOT) return this.parseExpressionStatement();
|
|
157
|
+
return this.parseCrud();
|
|
158
|
+
case T.AUTH:
|
|
159
|
+
if (this.peek(1).type === T.DOT) return this.parseExpressionStatement();
|
|
160
|
+
return this.parseAuth();
|
|
161
|
+
case T.CORS:
|
|
162
|
+
if (this.peek(1).type === T.DOT) return this.parseExpressionStatement();
|
|
163
|
+
return this.parseCors();
|
|
164
|
+
case T.LIMIT:
|
|
165
|
+
if (this.peek(1).type === T.DOT) return this.parseExpressionStatement();
|
|
166
|
+
return this.parseLimit();
|
|
167
|
+
case T.ENV:
|
|
168
|
+
if (this.peek(1).type === T.DOT) return this.parseExpressionStatement();
|
|
169
|
+
return this.parseEnv();
|
|
170
|
+
case T.EVERY:
|
|
171
|
+
if (this.peek(1).type === T.DOT) return this.parseExpressionStatement();
|
|
172
|
+
return this.parseEvery();
|
|
173
|
+
case T.WATCH:
|
|
174
|
+
if (this.peek(1).type === T.DOT) return this.parseExpressionStatement();
|
|
175
|
+
return this.parseWatch();
|
|
176
|
+
case T.STATIC:
|
|
177
|
+
if (this.peek(1).type === T.DOT) return this.parseExpressionStatement();
|
|
178
|
+
return this.parseStatic();
|
|
179
|
+
case T.WS:
|
|
180
|
+
if (this.peek(1).type === T.DOT) return this.parseExpressionStatement();
|
|
181
|
+
return this.parseWs();
|
|
182
|
+
case T.GROUP:
|
|
183
|
+
if (this.peek(1).type === T.DOT) return this.parseExpressionStatement();
|
|
184
|
+
return this.parseGroup();
|
|
185
|
+
case T.COOKIE:
|
|
186
|
+
if (this.peek(1).type === T.DOT) return this.parseExpressionStatement();
|
|
187
|
+
this.advance();
|
|
188
|
+
return new ASTNode('CookieDecl');
|
|
151
189
|
default:
|
|
152
190
|
if (TYPE_TOKENS.has(tok.type)) {
|
|
153
191
|
return this.parseTypedVariable();
|
|
@@ -156,14 +194,10 @@ export class Parser {
|
|
|
156
194
|
}
|
|
157
195
|
}
|
|
158
196
|
|
|
159
|
-
// use express
|
|
160
|
-
// use express from "express"
|
|
161
|
-
// use {readFile, writeFile} from "fs/promises"
|
|
162
197
|
parseUse() {
|
|
163
198
|
this.advance(); // use
|
|
164
199
|
|
|
165
200
|
if (this.at(T.LBRACE)) {
|
|
166
|
-
// use {a, b} from "module"
|
|
167
201
|
this.advance();
|
|
168
202
|
const names = [];
|
|
169
203
|
while (!this.at(T.RBRACE) && !this.at(T.EOF)) {
|
|
@@ -201,8 +235,6 @@ export class Parser {
|
|
|
201
235
|
return tok.value;
|
|
202
236
|
}
|
|
203
237
|
|
|
204
|
-
// fn name(params) -> returnType:
|
|
205
|
-
// body
|
|
206
238
|
parseFunction(isAsync, isPublic) {
|
|
207
239
|
this.advance(); // fn or fn.async
|
|
208
240
|
const name = this.expect(T.IDENT).value;
|
|
@@ -275,7 +307,6 @@ export class Parser {
|
|
|
275
307
|
if (this.at(T.FN_ASYNC)) return this.parseFunction(true, true);
|
|
276
308
|
if (this.at(T.MODEL)) return this.parseModel(true);
|
|
277
309
|
|
|
278
|
-
// pub variable
|
|
279
310
|
if (TYPE_TOKENS.has(this.peek().type)) {
|
|
280
311
|
const node = this.parseTypedVariable();
|
|
281
312
|
node.isPublic = true;
|
|
@@ -285,24 +316,25 @@ export class Parser {
|
|
|
285
316
|
throw this.error('Expected fn, model, or type after pub');
|
|
286
317
|
}
|
|
287
318
|
|
|
288
|
-
// ret expression
|
|
289
319
|
parseReturn() {
|
|
290
320
|
this.advance(); // ret
|
|
291
321
|
if (this.at(T.NEWLINE) || this.at(T.EOF) || this.at(T.DEDENT)) {
|
|
292
322
|
return new ASTNode('Return', { value: null });
|
|
293
323
|
}
|
|
294
|
-
// ret.status 200 {...}
|
|
295
324
|
if (this.match(T.DOT)) {
|
|
296
325
|
const method = this.expect(T.IDENT).value;
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
326
|
+
if (method === 'status') {
|
|
327
|
+
const statusCode = this.parseExpression();
|
|
328
|
+
const body = this.parseExpression();
|
|
329
|
+
return new ASTNode('ReturnStatus', { method, statusCode, body });
|
|
330
|
+
}
|
|
331
|
+
const value = this.parseExpression();
|
|
332
|
+
return new ASTNode('ReturnMethod', { method, value });
|
|
300
333
|
}
|
|
301
334
|
const value = this.parseExpression();
|
|
302
335
|
return new ASTNode('Return', { value });
|
|
303
336
|
}
|
|
304
337
|
|
|
305
|
-
// str name = "hello"
|
|
306
338
|
parseTypedVariable() {
|
|
307
339
|
const varType = this.advance().value;
|
|
308
340
|
const name = this.expect(T.IDENT).value;
|
|
@@ -311,7 +343,6 @@ export class Parser {
|
|
|
311
343
|
return new ASTNode('TypedVar', { varType, name, value, isMut: false, isPublic: false });
|
|
312
344
|
}
|
|
313
345
|
|
|
314
|
-
// mut int counter = 0
|
|
315
346
|
parseMutVariable() {
|
|
316
347
|
this.advance(); // mut
|
|
317
348
|
if (TYPE_TOKENS.has(this.peek().type)) {
|
|
@@ -319,19 +350,12 @@ export class Parser {
|
|
|
319
350
|
node.isMut = true;
|
|
320
351
|
return node;
|
|
321
352
|
}
|
|
322
|
-
// mut name = value (no type)
|
|
323
353
|
const name = this.expect(T.IDENT).value;
|
|
324
354
|
this.expect(T.ASSIGN);
|
|
325
355
|
const value = this.parseExpression();
|
|
326
356
|
return new ASTNode('TypedVar', { varType: null, name, value, isMut: true });
|
|
327
357
|
}
|
|
328
358
|
|
|
329
|
-
// if condition:
|
|
330
|
-
// body
|
|
331
|
-
// elif condition:
|
|
332
|
-
// body
|
|
333
|
-
// else:
|
|
334
|
-
// body
|
|
335
359
|
parseIf() {
|
|
336
360
|
this.advance(); // if
|
|
337
361
|
const condition = this.parseExpression();
|
|
@@ -359,15 +383,12 @@ export class Parser {
|
|
|
359
383
|
return new ASTNode('If', { condition, body, elifs, elseBody });
|
|
360
384
|
}
|
|
361
385
|
|
|
362
|
-
// each item in collection:
|
|
363
|
-
// body
|
|
364
386
|
parseEach() {
|
|
365
387
|
this.advance(); // each
|
|
366
388
|
let key = null;
|
|
367
389
|
const valueName = this.expect(T.IDENT).value;
|
|
368
390
|
if (this.match(T.COMMA)) {
|
|
369
391
|
key = valueName;
|
|
370
|
-
// The next ident is actually the value
|
|
371
392
|
const val = this.expect(T.IDENT).value;
|
|
372
393
|
this.expect(T.IN);
|
|
373
394
|
const collection = this.parseExpression();
|
|
@@ -382,8 +403,6 @@ export class Parser {
|
|
|
382
403
|
return new ASTNode('Each', { key: null, value: valueName, collection, body });
|
|
383
404
|
}
|
|
384
405
|
|
|
385
|
-
// for i in 0..10:
|
|
386
|
-
// body
|
|
387
406
|
parseFor() {
|
|
388
407
|
this.advance(); // for
|
|
389
408
|
const varName = this.expect(T.IDENT).value;
|
|
@@ -396,8 +415,6 @@ export class Parser {
|
|
|
396
415
|
return new ASTNode('For', { varName, start, end, body });
|
|
397
416
|
}
|
|
398
417
|
|
|
399
|
-
// while condition:
|
|
400
|
-
// body
|
|
401
418
|
parseWhile() {
|
|
402
419
|
this.advance(); // while
|
|
403
420
|
const condition = this.parseExpression();
|
|
@@ -406,9 +423,6 @@ export class Parser {
|
|
|
406
423
|
return new ASTNode('While', { condition, body });
|
|
407
424
|
}
|
|
408
425
|
|
|
409
|
-
// match value:
|
|
410
|
-
// pattern: body
|
|
411
|
-
// _: default
|
|
412
426
|
parseMatch() {
|
|
413
427
|
this.advance(); // match
|
|
414
428
|
const value = this.parseExpression();
|
|
@@ -446,10 +460,6 @@ export class Parser {
|
|
|
446
460
|
return new ASTNode('Match', { value, cases });
|
|
447
461
|
}
|
|
448
462
|
|
|
449
|
-
// try:
|
|
450
|
-
// body
|
|
451
|
-
// fail e:
|
|
452
|
-
// handler
|
|
453
463
|
parseTry() {
|
|
454
464
|
this.advance(); // try
|
|
455
465
|
this.expect(T.COLON);
|
|
@@ -470,14 +480,10 @@ export class Parser {
|
|
|
470
480
|
return new ASTNode('Try', { body, catchVar, catchBody });
|
|
471
481
|
}
|
|
472
482
|
|
|
473
|
-
// server app port 3000:
|
|
474
|
-
// get "/path" -> type:
|
|
475
|
-
// body
|
|
476
483
|
parseServer() {
|
|
477
484
|
this.advance(); // server
|
|
478
485
|
const name = this.expect(T.IDENT).value;
|
|
479
486
|
|
|
480
|
-
let portKw = null;
|
|
481
487
|
let port = null;
|
|
482
488
|
if (this.at(T.IDENT) && this.peek().value === 'port') {
|
|
483
489
|
this.advance();
|
|
@@ -497,8 +503,26 @@ export class Parser {
|
|
|
497
503
|
routes.push(this.parseRoute());
|
|
498
504
|
} else if (this.at(T.MID)) {
|
|
499
505
|
middleware.push(this.parseMiddleware());
|
|
506
|
+
} else if (this.at(T.CRUD)) {
|
|
507
|
+
routes.push(this.parseCrud());
|
|
508
|
+
} else if (this.at(T.AUTH)) {
|
|
509
|
+
routes.push(this.parseAuth());
|
|
510
|
+
} else if (this.at(T.CORS)) {
|
|
511
|
+
routes.push(this.parseCors());
|
|
512
|
+
} else if (this.at(T.LIMIT)) {
|
|
513
|
+
routes.push(this.parseLimit());
|
|
514
|
+
} else if (this.at(T.STATIC)) {
|
|
515
|
+
routes.push(this.parseStatic());
|
|
516
|
+
} else if (this.at(T.WS)) {
|
|
517
|
+
routes.push(this.parseWs());
|
|
518
|
+
} else if (this.at(T.GROUP)) {
|
|
519
|
+
routes.push(this.parseGroup());
|
|
520
|
+
} else if (this.at(T.COOKIE)) {
|
|
521
|
+
this.advance();
|
|
522
|
+
routes.push(new ASTNode('CookieDecl'));
|
|
523
|
+
} else if (this.at(T.IDENT) && this.peek().value === 'error') {
|
|
524
|
+
routes.push(this.parseErrorHandler());
|
|
500
525
|
} else {
|
|
501
|
-
// generic statement in server block
|
|
502
526
|
routes.push(this.parseStatement());
|
|
503
527
|
}
|
|
504
528
|
this.skipNewlines();
|
|
@@ -547,11 +571,6 @@ export class Parser {
|
|
|
547
571
|
return new ASTNode('Middleware', { name, params, body });
|
|
548
572
|
}
|
|
549
573
|
|
|
550
|
-
// model User:
|
|
551
|
-
// str name
|
|
552
|
-
// int age
|
|
553
|
-
// fn greet() -> str:
|
|
554
|
-
// ret "hi"
|
|
555
574
|
parseModel(isPublic = false) {
|
|
556
575
|
this.advance(); // model
|
|
557
576
|
const name = this.expect(T.IDENT).value;
|
|
@@ -583,7 +602,6 @@ export class Parser {
|
|
|
583
602
|
}
|
|
584
603
|
fields.push({ name: fieldName, type: fieldType, defaultValue });
|
|
585
604
|
} else {
|
|
586
|
-
// skip unknown
|
|
587
605
|
this.advance();
|
|
588
606
|
}
|
|
589
607
|
this.skipNewlines();
|
|
@@ -593,8 +611,6 @@ export class Parser {
|
|
|
593
611
|
return new ASTNode('Model', { name, parent, fields, methods, isPublic });
|
|
594
612
|
}
|
|
595
613
|
|
|
596
|
-
// on event:
|
|
597
|
-
// body
|
|
598
614
|
parseOn() {
|
|
599
615
|
this.advance(); // on
|
|
600
616
|
const event = this.parseExpression();
|
|
@@ -603,8 +619,6 @@ export class Parser {
|
|
|
603
619
|
return new ASTNode('On', { event, body });
|
|
604
620
|
}
|
|
605
621
|
|
|
606
|
-
// log "message"
|
|
607
|
-
// log.error "message"
|
|
608
622
|
parseLog() {
|
|
609
623
|
this.advance(); // log
|
|
610
624
|
let level = 'log';
|
|
@@ -632,17 +646,20 @@ export class Parser {
|
|
|
632
646
|
const connectionString = this.parseExpression();
|
|
633
647
|
return new ASTNode('DbConnect', { connectionString });
|
|
634
648
|
}
|
|
635
|
-
|
|
636
|
-
this.pos -= 3; // rewind to parse as expression
|
|
649
|
+
this.pos -= 3;
|
|
637
650
|
return this.parseExpressionStatement();
|
|
638
651
|
}
|
|
639
652
|
|
|
653
|
+
parseDbDir() {
|
|
654
|
+
this.advance(); // db
|
|
655
|
+
const path = this.parseString();
|
|
656
|
+
return new ASTNode('DbDir', { path });
|
|
657
|
+
}
|
|
658
|
+
|
|
640
659
|
parseAwaitStatement() {
|
|
641
|
-
// could be await expression or standalone await call
|
|
642
660
|
return this.parseExpressionStatement();
|
|
643
661
|
}
|
|
644
662
|
|
|
645
|
-
// [a, b] = await.all [expr1, expr2]
|
|
646
663
|
parseAwaitAll() {
|
|
647
664
|
this.advance(); // await.all
|
|
648
665
|
const exprs = [];
|
|
@@ -664,7 +681,6 @@ export class Parser {
|
|
|
664
681
|
parseExpressionStatement() {
|
|
665
682
|
const expr = this.parseExpression();
|
|
666
683
|
|
|
667
|
-
// Check for assignment: expr = value
|
|
668
684
|
if (this.match(T.ASSIGN)) {
|
|
669
685
|
const value = this.parseExpression();
|
|
670
686
|
return new ASTNode('Assignment', { target: expr, value });
|
|
@@ -681,7 +697,6 @@ export class Parser {
|
|
|
681
697
|
return new ASTNode('ExprStatement', { expression: expr });
|
|
682
698
|
}
|
|
683
699
|
|
|
684
|
-
// Expression parsing with precedence climbing
|
|
685
700
|
parseExpression() {
|
|
686
701
|
return this.parsePipe();
|
|
687
702
|
}
|
|
@@ -698,14 +713,13 @@ export class Parser {
|
|
|
698
713
|
|
|
699
714
|
matchPipeAcrossLines() {
|
|
700
715
|
if (this.match(T.PIPE)) return true;
|
|
701
|
-
// Lookahead across newlines for pipe (INDENT/DEDENT already preprocessed)
|
|
702
716
|
let scanPos = this.pos;
|
|
703
717
|
while (scanPos < this.tokens.length && this.tokens[scanPos].type === T.NEWLINE) {
|
|
704
718
|
scanPos++;
|
|
705
719
|
}
|
|
706
720
|
if (scanPos < this.tokens.length && this.tokens[scanPos].type === T.PIPE) {
|
|
707
721
|
while (this.at(T.NEWLINE)) this.advance();
|
|
708
|
-
this.advance();
|
|
722
|
+
this.advance();
|
|
709
723
|
return true;
|
|
710
724
|
}
|
|
711
725
|
return false;
|
|
@@ -713,12 +727,9 @@ export class Parser {
|
|
|
713
727
|
|
|
714
728
|
parseTernary() {
|
|
715
729
|
let expr = this.parseNullish();
|
|
716
|
-
// inline if: value = if cond then a else b
|
|
717
730
|
if (this.at(T.IF)) {
|
|
718
|
-
// Only treat as ternary if we're in an expression context
|
|
719
|
-
// Lookahead: if ... then ... else
|
|
720
731
|
const savedPos = this.pos;
|
|
721
|
-
this.advance();
|
|
732
|
+
this.advance();
|
|
722
733
|
const condition = this.parseNullish();
|
|
723
734
|
if (this.match(T.THEN)) {
|
|
724
735
|
const consequent = this.parseNullish();
|
|
@@ -726,7 +737,6 @@ export class Parser {
|
|
|
726
737
|
const alternate = this.parseNullish();
|
|
727
738
|
return new ASTNode('Ternary', { condition, consequent, alternate });
|
|
728
739
|
}
|
|
729
|
-
// Not a ternary, restore
|
|
730
740
|
this.pos = savedPos;
|
|
731
741
|
}
|
|
732
742
|
return expr;
|
|
@@ -890,7 +900,12 @@ export class Parser {
|
|
|
890
900
|
case T.TYPE_STR: case T.TYPE_INT: case T.TYPE_NUM:
|
|
891
901
|
case T.TYPE_BOOL: case T.TYPE_LIST: case T.TYPE_MAP:
|
|
892
902
|
case T.TYPE_ANY: case T.TYPE_JSON: case T.TYPE_VOID:
|
|
893
|
-
|
|
903
|
+
this.advance();
|
|
904
|
+
return new ASTNode('Identifier', { name: tok.value });
|
|
905
|
+
|
|
906
|
+
case T.SCHEMA: case T.CRUD: case T.AUTH: case T.CORS:
|
|
907
|
+
case T.LIMIT: case T.ENV: case T.EVERY: case T.WATCH:
|
|
908
|
+
case T.STATIC: case T.WS: case T.GROUP: case T.COOKIE:
|
|
894
909
|
this.advance();
|
|
895
910
|
return new ASTNode('Identifier', { name: tok.value });
|
|
896
911
|
|
|
@@ -965,11 +980,9 @@ export class Parser {
|
|
|
965
980
|
}
|
|
966
981
|
|
|
967
982
|
parseGroupOrArrow() {
|
|
968
|
-
// Check if this is an arrow function: (params) => body
|
|
969
983
|
const savedPos = this.pos;
|
|
970
984
|
this.advance(); // (
|
|
971
985
|
|
|
972
|
-
// Try to parse as arrow function params
|
|
973
986
|
let isArrow = false;
|
|
974
987
|
let depth = 1;
|
|
975
988
|
let scanPos = this.pos;
|
|
@@ -999,7 +1012,6 @@ export class Parser {
|
|
|
999
1012
|
return new ASTNode('ArrowFn', { params, body });
|
|
1000
1013
|
}
|
|
1001
1014
|
|
|
1002
|
-
// Regular grouping
|
|
1003
1015
|
this.pos = savedPos;
|
|
1004
1016
|
this.advance(); // (
|
|
1005
1017
|
const expr = this.parseExpression();
|
|
@@ -1076,4 +1088,295 @@ export class Parser {
|
|
|
1076
1088
|
const body = this.parseBlock();
|
|
1077
1089
|
return new ASTNode('Lambda', { params, returnType, body, isAsync });
|
|
1078
1090
|
}
|
|
1091
|
+
|
|
1092
|
+
// ===== High-level feature parsers =====
|
|
1093
|
+
|
|
1094
|
+
parseSchema() {
|
|
1095
|
+
this.advance(); // schema
|
|
1096
|
+
const name = this.expect(T.IDENT).value;
|
|
1097
|
+
this.expect(T.COLON);
|
|
1098
|
+
this.skipNewlines();
|
|
1099
|
+
this.expect(T.INDENT);
|
|
1100
|
+
|
|
1101
|
+
const fields = [];
|
|
1102
|
+
this.skipNewlines();
|
|
1103
|
+
|
|
1104
|
+
while (!this.at(T.DEDENT) && !this.at(T.EOF)) {
|
|
1105
|
+
fields.push(this.parseSchemaField());
|
|
1106
|
+
this.skipNewlines();
|
|
1107
|
+
}
|
|
1108
|
+
this.match(T.DEDENT);
|
|
1109
|
+
|
|
1110
|
+
return new ASTNode('SchemaDecl', { name, fields });
|
|
1111
|
+
}
|
|
1112
|
+
|
|
1113
|
+
parseSchemaField() {
|
|
1114
|
+
const name = this.expect(T.IDENT).value;
|
|
1115
|
+
|
|
1116
|
+
let fieldType;
|
|
1117
|
+
let enumValues = null;
|
|
1118
|
+
|
|
1119
|
+
if (TYPE_TOKENS.has(this.peek().type)) {
|
|
1120
|
+
fieldType = this.advance().value;
|
|
1121
|
+
} else if (this.at(T.IDENT)) {
|
|
1122
|
+
fieldType = this.advance().value;
|
|
1123
|
+
if (fieldType === 'enum' && this.at(T.LPAREN)) {
|
|
1124
|
+
this.advance();
|
|
1125
|
+
enumValues = [];
|
|
1126
|
+
while (!this.at(T.RPAREN) && !this.at(T.EOF)) {
|
|
1127
|
+
enumValues.push(this.parseExpression());
|
|
1128
|
+
this.match(T.COMMA);
|
|
1129
|
+
}
|
|
1130
|
+
this.expect(T.RPAREN);
|
|
1131
|
+
}
|
|
1132
|
+
} else {
|
|
1133
|
+
throw this.error('Expected type in schema field');
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1136
|
+
const modifiers = [];
|
|
1137
|
+
while (!this.at(T.NEWLINE) && !this.at(T.DEDENT) && !this.at(T.EOF)) {
|
|
1138
|
+
if (this.at(T.IDENT) || this.at(T.IDENT)) {
|
|
1139
|
+
const modName = this.advance().value;
|
|
1140
|
+
if (this.at(T.LPAREN)) {
|
|
1141
|
+
this.advance();
|
|
1142
|
+
const args = [];
|
|
1143
|
+
while (!this.at(T.RPAREN) && !this.at(T.EOF)) {
|
|
1144
|
+
args.push(this.parseExpression());
|
|
1145
|
+
this.match(T.COMMA);
|
|
1146
|
+
}
|
|
1147
|
+
this.expect(T.RPAREN);
|
|
1148
|
+
modifiers.push({ name: modName, args });
|
|
1149
|
+
} else {
|
|
1150
|
+
modifiers.push({ name: modName, args: [] });
|
|
1151
|
+
}
|
|
1152
|
+
} else {
|
|
1153
|
+
break;
|
|
1154
|
+
}
|
|
1155
|
+
}
|
|
1156
|
+
|
|
1157
|
+
return { name, type: fieldType, enumValues, modifiers };
|
|
1158
|
+
}
|
|
1159
|
+
|
|
1160
|
+
parseCrud() {
|
|
1161
|
+
this.advance(); // crud
|
|
1162
|
+
const path = this.parseString();
|
|
1163
|
+
const schemaName = this.expect(T.IDENT).value;
|
|
1164
|
+
return new ASTNode('CrudDecl', { path, schemaName });
|
|
1165
|
+
}
|
|
1166
|
+
|
|
1167
|
+
parseAuth() {
|
|
1168
|
+
this.advance(); // auth
|
|
1169
|
+
const secret = this.parseExpression();
|
|
1170
|
+
|
|
1171
|
+
const protectedPaths = [];
|
|
1172
|
+
const publicPaths = [];
|
|
1173
|
+
|
|
1174
|
+
if (this.match(T.COLON)) {
|
|
1175
|
+
this.skipNewlines();
|
|
1176
|
+
this.expect(T.INDENT);
|
|
1177
|
+
this.skipNewlines();
|
|
1178
|
+
while (!this.at(T.DEDENT) && !this.at(T.EOF)) {
|
|
1179
|
+
if (this.at(T.IDENT) && this.peek().value === 'protect') {
|
|
1180
|
+
this.advance();
|
|
1181
|
+
protectedPaths.push(this.parseString());
|
|
1182
|
+
} else if (this.at(T.IDENT) && this.peek().value === 'public') {
|
|
1183
|
+
this.advance();
|
|
1184
|
+
publicPaths.push(this.parseString());
|
|
1185
|
+
} else {
|
|
1186
|
+
this.advance();
|
|
1187
|
+
}
|
|
1188
|
+
this.skipNewlines();
|
|
1189
|
+
}
|
|
1190
|
+
this.match(T.DEDENT);
|
|
1191
|
+
}
|
|
1192
|
+
|
|
1193
|
+
return new ASTNode('AuthDecl', { secret, protectedPaths, publicPaths });
|
|
1194
|
+
}
|
|
1195
|
+
|
|
1196
|
+
parseCors() {
|
|
1197
|
+
this.advance(); // cors
|
|
1198
|
+
const origins = this.parseExpression();
|
|
1199
|
+
return new ASTNode('CorsDecl', { origins });
|
|
1200
|
+
}
|
|
1201
|
+
|
|
1202
|
+
parseLimit() {
|
|
1203
|
+
this.advance(); // limit
|
|
1204
|
+
const path = this.parseString();
|
|
1205
|
+
const max = this.parseExpression();
|
|
1206
|
+
const window = this.parseExpression();
|
|
1207
|
+
return new ASTNode('LimitDecl', { path, max, window });
|
|
1208
|
+
}
|
|
1209
|
+
|
|
1210
|
+
parseEnv() {
|
|
1211
|
+
this.advance(); // env
|
|
1212
|
+
this.expect(T.COLON);
|
|
1213
|
+
this.skipNewlines();
|
|
1214
|
+
this.expect(T.INDENT);
|
|
1215
|
+
|
|
1216
|
+
const vars = [];
|
|
1217
|
+
this.skipNewlines();
|
|
1218
|
+
|
|
1219
|
+
while (!this.at(T.DEDENT) && !this.at(T.EOF)) {
|
|
1220
|
+
vars.push(this.parseEnvField());
|
|
1221
|
+
this.skipNewlines();
|
|
1222
|
+
}
|
|
1223
|
+
this.match(T.DEDENT);
|
|
1224
|
+
|
|
1225
|
+
return new ASTNode('EnvDecl', { vars });
|
|
1226
|
+
}
|
|
1227
|
+
|
|
1228
|
+
parseEnvField() {
|
|
1229
|
+
const name = this.expect(T.IDENT).value;
|
|
1230
|
+
|
|
1231
|
+
let fieldType = 'string';
|
|
1232
|
+
if (TYPE_TOKENS.has(this.peek().type)) {
|
|
1233
|
+
fieldType = this.advance().value;
|
|
1234
|
+
} else if (this.at(T.IDENT) && ['string', 'number', 'integer', 'boolean'].includes(this.peek().value)) {
|
|
1235
|
+
fieldType = this.advance().value;
|
|
1236
|
+
}
|
|
1237
|
+
|
|
1238
|
+
const modifiers = [];
|
|
1239
|
+
while (!this.at(T.NEWLINE) && !this.at(T.DEDENT) && !this.at(T.EOF)) {
|
|
1240
|
+
if (this.at(T.IDENT)) {
|
|
1241
|
+
const modName = this.advance().value;
|
|
1242
|
+
if (this.at(T.LPAREN)) {
|
|
1243
|
+
this.advance();
|
|
1244
|
+
const args = [];
|
|
1245
|
+
while (!this.at(T.RPAREN) && !this.at(T.EOF)) {
|
|
1246
|
+
args.push(this.parseExpression());
|
|
1247
|
+
this.match(T.COMMA);
|
|
1248
|
+
}
|
|
1249
|
+
this.expect(T.RPAREN);
|
|
1250
|
+
modifiers.push({ name: modName, args });
|
|
1251
|
+
} else {
|
|
1252
|
+
modifiers.push({ name: modName, args: [] });
|
|
1253
|
+
}
|
|
1254
|
+
} else {
|
|
1255
|
+
break;
|
|
1256
|
+
}
|
|
1257
|
+
}
|
|
1258
|
+
|
|
1259
|
+
return { name, type: fieldType, modifiers };
|
|
1260
|
+
}
|
|
1261
|
+
|
|
1262
|
+
parseEvery() {
|
|
1263
|
+
this.advance(); // every
|
|
1264
|
+
const interval = this.parseExpression();
|
|
1265
|
+
this.expect(T.COLON);
|
|
1266
|
+
const body = this.parseBlock();
|
|
1267
|
+
return new ASTNode('EveryDecl', { interval, body });
|
|
1268
|
+
}
|
|
1269
|
+
|
|
1270
|
+
parseWatch() {
|
|
1271
|
+
this.advance(); // watch
|
|
1272
|
+
let eventName = this.expect(T.IDENT).value;
|
|
1273
|
+
while (this.match(T.DOT)) {
|
|
1274
|
+
eventName += '.' + this.expectPropertyName();
|
|
1275
|
+
}
|
|
1276
|
+
let params = [];
|
|
1277
|
+
if (this.match(T.LPAREN)) {
|
|
1278
|
+
while (!this.at(T.RPAREN) && !this.at(T.EOF)) {
|
|
1279
|
+
params.push(this.expect(T.IDENT).value);
|
|
1280
|
+
this.match(T.COMMA);
|
|
1281
|
+
}
|
|
1282
|
+
this.expect(T.RPAREN);
|
|
1283
|
+
}
|
|
1284
|
+
this.expect(T.COLON);
|
|
1285
|
+
const body = this.parseBlock();
|
|
1286
|
+
return new ASTNode('WatchDecl', { eventName, params, body });
|
|
1287
|
+
}
|
|
1288
|
+
|
|
1289
|
+
// static "/public"
|
|
1290
|
+
parseStatic() {
|
|
1291
|
+
this.advance(); // static
|
|
1292
|
+
const path = this.parseString();
|
|
1293
|
+
return new ASTNode('StaticDecl', { path });
|
|
1294
|
+
}
|
|
1295
|
+
|
|
1296
|
+
// ws "/chat":
|
|
1297
|
+
// on "message" (data):
|
|
1298
|
+
// broadcast(data)
|
|
1299
|
+
// on "connect":
|
|
1300
|
+
// send({type: "welcome"})
|
|
1301
|
+
parseWs() {
|
|
1302
|
+
this.advance(); // ws
|
|
1303
|
+
const path = this.parseString();
|
|
1304
|
+
this.expect(T.COLON);
|
|
1305
|
+
this.skipNewlines();
|
|
1306
|
+
this.expect(T.INDENT);
|
|
1307
|
+
|
|
1308
|
+
const events = [];
|
|
1309
|
+
this.skipNewlines();
|
|
1310
|
+
|
|
1311
|
+
while (!this.at(T.DEDENT) && !this.at(T.EOF)) {
|
|
1312
|
+
if (this.at(T.ON)) {
|
|
1313
|
+
this.advance(); // on
|
|
1314
|
+
const eventName = this.parseString();
|
|
1315
|
+
let params = [];
|
|
1316
|
+
if (this.match(T.LPAREN)) {
|
|
1317
|
+
while (!this.at(T.RPAREN) && !this.at(T.EOF)) {
|
|
1318
|
+
params.push(this.expect(T.IDENT).value);
|
|
1319
|
+
this.match(T.COMMA);
|
|
1320
|
+
}
|
|
1321
|
+
this.expect(T.RPAREN);
|
|
1322
|
+
}
|
|
1323
|
+
this.expect(T.COLON);
|
|
1324
|
+
const body = this.parseBlock();
|
|
1325
|
+
events.push({ name: eventName, params, body });
|
|
1326
|
+
} else {
|
|
1327
|
+
this.advance();
|
|
1328
|
+
}
|
|
1329
|
+
this.skipNewlines();
|
|
1330
|
+
}
|
|
1331
|
+
this.match(T.DEDENT);
|
|
1332
|
+
|
|
1333
|
+
return new ASTNode('WsDecl', { path, events });
|
|
1334
|
+
}
|
|
1335
|
+
|
|
1336
|
+
parseGroup() {
|
|
1337
|
+
this.advance(); // group
|
|
1338
|
+
const prefix = this.parseString();
|
|
1339
|
+
this.expect(T.COLON);
|
|
1340
|
+
this.skipNewlines();
|
|
1341
|
+
this.expect(T.INDENT);
|
|
1342
|
+
|
|
1343
|
+
const routes = [];
|
|
1344
|
+
this.skipNewlines();
|
|
1345
|
+
|
|
1346
|
+
while (!this.at(T.DEDENT) && !this.at(T.EOF)) {
|
|
1347
|
+
if (this.atAny(T.GET, T.POST, T.PUT, T.DEL)) {
|
|
1348
|
+
routes.push(this.parseRoute());
|
|
1349
|
+
} else if (this.at(T.MID)) {
|
|
1350
|
+
routes.push(this.parseMiddleware());
|
|
1351
|
+
} else if (this.at(T.CRUD)) {
|
|
1352
|
+
routes.push(this.parseCrud());
|
|
1353
|
+
} else if (this.at(T.AUTH)) {
|
|
1354
|
+
routes.push(this.parseAuth());
|
|
1355
|
+
} else if (this.at(T.GROUP)) {
|
|
1356
|
+
routes.push(this.parseGroup());
|
|
1357
|
+
} else {
|
|
1358
|
+
routes.push(this.parseStatement());
|
|
1359
|
+
}
|
|
1360
|
+
this.skipNewlines();
|
|
1361
|
+
}
|
|
1362
|
+
this.match(T.DEDENT);
|
|
1363
|
+
|
|
1364
|
+
return new ASTNode('GroupDecl', { prefix, routes });
|
|
1365
|
+
}
|
|
1366
|
+
|
|
1367
|
+
parseErrorHandler() {
|
|
1368
|
+
this.advance(); // error (ident)
|
|
1369
|
+
let params = ['err', 'req', 'res'];
|
|
1370
|
+
if (this.match(T.LPAREN)) {
|
|
1371
|
+
params = [];
|
|
1372
|
+
while (!this.at(T.RPAREN) && !this.at(T.EOF)) {
|
|
1373
|
+
params.push(this.expect(T.IDENT).value);
|
|
1374
|
+
this.match(T.COMMA);
|
|
1375
|
+
}
|
|
1376
|
+
this.expect(T.RPAREN);
|
|
1377
|
+
}
|
|
1378
|
+
this.expect(T.COLON);
|
|
1379
|
+
const body = this.parseBlock();
|
|
1380
|
+
return new ASTNode('ErrorHandler', { params, body });
|
|
1381
|
+
}
|
|
1079
1382
|
}
|