naidejs 1.1.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 +183 -4
- package/SPEC-X.nx +80 -7
- package/SPEC.naide +117 -9
- package/bin/naide.js +55 -1
- package/examples/fullapp.naide +41 -4
- package/examples/fullapp.nx +41 -4
- package/package.json +9 -5
- package/src/generator.js +248 -5
- package/src/parser.js +143 -90
- package/src/preprocess.js +8 -0
- package/src/runtime.js +162 -4
- package/src/tokens.js +8 -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;
|
|
@@ -94,7 +90,9 @@ export class Parser {
|
|
|
94
90
|
tok.type === T.FROM || tok.type === T.AS || tok.type === T.SELF ||
|
|
95
91
|
tok.type === T.SCHEMA || tok.type === T.CRUD || tok.type === T.AUTH ||
|
|
96
92
|
tok.type === T.CORS || tok.type === T.LIMIT || tok.type === T.ENV ||
|
|
97
|
-
tok.type === T.EVERY || tok.type === T.WATCH
|
|
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) {
|
|
98
96
|
return tok.value;
|
|
99
97
|
}
|
|
100
98
|
throw this.error(`Expected property name but got ${tok.type} ('${tok.value}')`, tok);
|
|
@@ -148,7 +146,9 @@ export class Parser {
|
|
|
148
146
|
case T.BREAK: this.advance(); return new ASTNode('Break');
|
|
149
147
|
case T.CONTINUE: this.advance(); return new ASTNode('Continue');
|
|
150
148
|
case T.MUT: return this.parseMutVariable();
|
|
151
|
-
case T.DB:
|
|
149
|
+
case T.DB:
|
|
150
|
+
if (this.peek(1).type === T.DOT) return this.parseDbStatement();
|
|
151
|
+
return this.parseDbDir();
|
|
152
152
|
case T.AWAIT: return this.parseAwaitStatement();
|
|
153
153
|
case T.AWAIT_ALL: return this.parseAwaitAll();
|
|
154
154
|
case T.SCHEMA: return this.parseSchema();
|
|
@@ -173,6 +173,19 @@ export class Parser {
|
|
|
173
173
|
case T.WATCH:
|
|
174
174
|
if (this.peek(1).type === T.DOT) return this.parseExpressionStatement();
|
|
175
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');
|
|
176
189
|
default:
|
|
177
190
|
if (TYPE_TOKENS.has(tok.type)) {
|
|
178
191
|
return this.parseTypedVariable();
|
|
@@ -181,14 +194,10 @@ export class Parser {
|
|
|
181
194
|
}
|
|
182
195
|
}
|
|
183
196
|
|
|
184
|
-
// use express
|
|
185
|
-
// use express from "express"
|
|
186
|
-
// use {readFile, writeFile} from "fs/promises"
|
|
187
197
|
parseUse() {
|
|
188
198
|
this.advance(); // use
|
|
189
199
|
|
|
190
200
|
if (this.at(T.LBRACE)) {
|
|
191
|
-
// use {a, b} from "module"
|
|
192
201
|
this.advance();
|
|
193
202
|
const names = [];
|
|
194
203
|
while (!this.at(T.RBRACE) && !this.at(T.EOF)) {
|
|
@@ -226,8 +235,6 @@ export class Parser {
|
|
|
226
235
|
return tok.value;
|
|
227
236
|
}
|
|
228
237
|
|
|
229
|
-
// fn name(params) -> returnType:
|
|
230
|
-
// body
|
|
231
238
|
parseFunction(isAsync, isPublic) {
|
|
232
239
|
this.advance(); // fn or fn.async
|
|
233
240
|
const name = this.expect(T.IDENT).value;
|
|
@@ -300,7 +307,6 @@ export class Parser {
|
|
|
300
307
|
if (this.at(T.FN_ASYNC)) return this.parseFunction(true, true);
|
|
301
308
|
if (this.at(T.MODEL)) return this.parseModel(true);
|
|
302
309
|
|
|
303
|
-
// pub variable
|
|
304
310
|
if (TYPE_TOKENS.has(this.peek().type)) {
|
|
305
311
|
const node = this.parseTypedVariable();
|
|
306
312
|
node.isPublic = true;
|
|
@@ -310,24 +316,25 @@ export class Parser {
|
|
|
310
316
|
throw this.error('Expected fn, model, or type after pub');
|
|
311
317
|
}
|
|
312
318
|
|
|
313
|
-
// ret expression
|
|
314
319
|
parseReturn() {
|
|
315
320
|
this.advance(); // ret
|
|
316
321
|
if (this.at(T.NEWLINE) || this.at(T.EOF) || this.at(T.DEDENT)) {
|
|
317
322
|
return new ASTNode('Return', { value: null });
|
|
318
323
|
}
|
|
319
|
-
// ret.status 200 {...}
|
|
320
324
|
if (this.match(T.DOT)) {
|
|
321
325
|
const method = this.expect(T.IDENT).value;
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
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 });
|
|
325
333
|
}
|
|
326
334
|
const value = this.parseExpression();
|
|
327
335
|
return new ASTNode('Return', { value });
|
|
328
336
|
}
|
|
329
337
|
|
|
330
|
-
// str name = "hello"
|
|
331
338
|
parseTypedVariable() {
|
|
332
339
|
const varType = this.advance().value;
|
|
333
340
|
const name = this.expect(T.IDENT).value;
|
|
@@ -336,7 +343,6 @@ export class Parser {
|
|
|
336
343
|
return new ASTNode('TypedVar', { varType, name, value, isMut: false, isPublic: false });
|
|
337
344
|
}
|
|
338
345
|
|
|
339
|
-
// mut int counter = 0
|
|
340
346
|
parseMutVariable() {
|
|
341
347
|
this.advance(); // mut
|
|
342
348
|
if (TYPE_TOKENS.has(this.peek().type)) {
|
|
@@ -344,19 +350,12 @@ export class Parser {
|
|
|
344
350
|
node.isMut = true;
|
|
345
351
|
return node;
|
|
346
352
|
}
|
|
347
|
-
// mut name = value (no type)
|
|
348
353
|
const name = this.expect(T.IDENT).value;
|
|
349
354
|
this.expect(T.ASSIGN);
|
|
350
355
|
const value = this.parseExpression();
|
|
351
356
|
return new ASTNode('TypedVar', { varType: null, name, value, isMut: true });
|
|
352
357
|
}
|
|
353
358
|
|
|
354
|
-
// if condition:
|
|
355
|
-
// body
|
|
356
|
-
// elif condition:
|
|
357
|
-
// body
|
|
358
|
-
// else:
|
|
359
|
-
// body
|
|
360
359
|
parseIf() {
|
|
361
360
|
this.advance(); // if
|
|
362
361
|
const condition = this.parseExpression();
|
|
@@ -384,15 +383,12 @@ export class Parser {
|
|
|
384
383
|
return new ASTNode('If', { condition, body, elifs, elseBody });
|
|
385
384
|
}
|
|
386
385
|
|
|
387
|
-
// each item in collection:
|
|
388
|
-
// body
|
|
389
386
|
parseEach() {
|
|
390
387
|
this.advance(); // each
|
|
391
388
|
let key = null;
|
|
392
389
|
const valueName = this.expect(T.IDENT).value;
|
|
393
390
|
if (this.match(T.COMMA)) {
|
|
394
391
|
key = valueName;
|
|
395
|
-
// The next ident is actually the value
|
|
396
392
|
const val = this.expect(T.IDENT).value;
|
|
397
393
|
this.expect(T.IN);
|
|
398
394
|
const collection = this.parseExpression();
|
|
@@ -407,8 +403,6 @@ export class Parser {
|
|
|
407
403
|
return new ASTNode('Each', { key: null, value: valueName, collection, body });
|
|
408
404
|
}
|
|
409
405
|
|
|
410
|
-
// for i in 0..10:
|
|
411
|
-
// body
|
|
412
406
|
parseFor() {
|
|
413
407
|
this.advance(); // for
|
|
414
408
|
const varName = this.expect(T.IDENT).value;
|
|
@@ -421,8 +415,6 @@ export class Parser {
|
|
|
421
415
|
return new ASTNode('For', { varName, start, end, body });
|
|
422
416
|
}
|
|
423
417
|
|
|
424
|
-
// while condition:
|
|
425
|
-
// body
|
|
426
418
|
parseWhile() {
|
|
427
419
|
this.advance(); // while
|
|
428
420
|
const condition = this.parseExpression();
|
|
@@ -431,9 +423,6 @@ export class Parser {
|
|
|
431
423
|
return new ASTNode('While', { condition, body });
|
|
432
424
|
}
|
|
433
425
|
|
|
434
|
-
// match value:
|
|
435
|
-
// pattern: body
|
|
436
|
-
// _: default
|
|
437
426
|
parseMatch() {
|
|
438
427
|
this.advance(); // match
|
|
439
428
|
const value = this.parseExpression();
|
|
@@ -471,10 +460,6 @@ export class Parser {
|
|
|
471
460
|
return new ASTNode('Match', { value, cases });
|
|
472
461
|
}
|
|
473
462
|
|
|
474
|
-
// try:
|
|
475
|
-
// body
|
|
476
|
-
// fail e:
|
|
477
|
-
// handler
|
|
478
463
|
parseTry() {
|
|
479
464
|
this.advance(); // try
|
|
480
465
|
this.expect(T.COLON);
|
|
@@ -495,14 +480,10 @@ export class Parser {
|
|
|
495
480
|
return new ASTNode('Try', { body, catchVar, catchBody });
|
|
496
481
|
}
|
|
497
482
|
|
|
498
|
-
// server app port 3000:
|
|
499
|
-
// get "/path" -> type:
|
|
500
|
-
// body
|
|
501
483
|
parseServer() {
|
|
502
484
|
this.advance(); // server
|
|
503
485
|
const name = this.expect(T.IDENT).value;
|
|
504
486
|
|
|
505
|
-
let portKw = null;
|
|
506
487
|
let port = null;
|
|
507
488
|
if (this.at(T.IDENT) && this.peek().value === 'port') {
|
|
508
489
|
this.advance();
|
|
@@ -530,6 +511,17 @@ export class Parser {
|
|
|
530
511
|
routes.push(this.parseCors());
|
|
531
512
|
} else if (this.at(T.LIMIT)) {
|
|
532
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());
|
|
533
525
|
} else {
|
|
534
526
|
routes.push(this.parseStatement());
|
|
535
527
|
}
|
|
@@ -579,11 +571,6 @@ export class Parser {
|
|
|
579
571
|
return new ASTNode('Middleware', { name, params, body });
|
|
580
572
|
}
|
|
581
573
|
|
|
582
|
-
// model User:
|
|
583
|
-
// str name
|
|
584
|
-
// int age
|
|
585
|
-
// fn greet() -> str:
|
|
586
|
-
// ret "hi"
|
|
587
574
|
parseModel(isPublic = false) {
|
|
588
575
|
this.advance(); // model
|
|
589
576
|
const name = this.expect(T.IDENT).value;
|
|
@@ -615,7 +602,6 @@ export class Parser {
|
|
|
615
602
|
}
|
|
616
603
|
fields.push({ name: fieldName, type: fieldType, defaultValue });
|
|
617
604
|
} else {
|
|
618
|
-
// skip unknown
|
|
619
605
|
this.advance();
|
|
620
606
|
}
|
|
621
607
|
this.skipNewlines();
|
|
@@ -625,8 +611,6 @@ export class Parser {
|
|
|
625
611
|
return new ASTNode('Model', { name, parent, fields, methods, isPublic });
|
|
626
612
|
}
|
|
627
613
|
|
|
628
|
-
// on event:
|
|
629
|
-
// body
|
|
630
614
|
parseOn() {
|
|
631
615
|
this.advance(); // on
|
|
632
616
|
const event = this.parseExpression();
|
|
@@ -635,8 +619,6 @@ export class Parser {
|
|
|
635
619
|
return new ASTNode('On', { event, body });
|
|
636
620
|
}
|
|
637
621
|
|
|
638
|
-
// log "message"
|
|
639
|
-
// log.error "message"
|
|
640
622
|
parseLog() {
|
|
641
623
|
this.advance(); // log
|
|
642
624
|
let level = 'log';
|
|
@@ -664,17 +646,20 @@ export class Parser {
|
|
|
664
646
|
const connectionString = this.parseExpression();
|
|
665
647
|
return new ASTNode('DbConnect', { connectionString });
|
|
666
648
|
}
|
|
667
|
-
|
|
668
|
-
this.pos -= 3; // rewind to parse as expression
|
|
649
|
+
this.pos -= 3;
|
|
669
650
|
return this.parseExpressionStatement();
|
|
670
651
|
}
|
|
671
652
|
|
|
653
|
+
parseDbDir() {
|
|
654
|
+
this.advance(); // db
|
|
655
|
+
const path = this.parseString();
|
|
656
|
+
return new ASTNode('DbDir', { path });
|
|
657
|
+
}
|
|
658
|
+
|
|
672
659
|
parseAwaitStatement() {
|
|
673
|
-
// could be await expression or standalone await call
|
|
674
660
|
return this.parseExpressionStatement();
|
|
675
661
|
}
|
|
676
662
|
|
|
677
|
-
// [a, b] = await.all [expr1, expr2]
|
|
678
663
|
parseAwaitAll() {
|
|
679
664
|
this.advance(); // await.all
|
|
680
665
|
const exprs = [];
|
|
@@ -696,7 +681,6 @@ export class Parser {
|
|
|
696
681
|
parseExpressionStatement() {
|
|
697
682
|
const expr = this.parseExpression();
|
|
698
683
|
|
|
699
|
-
// Check for assignment: expr = value
|
|
700
684
|
if (this.match(T.ASSIGN)) {
|
|
701
685
|
const value = this.parseExpression();
|
|
702
686
|
return new ASTNode('Assignment', { target: expr, value });
|
|
@@ -713,7 +697,6 @@ export class Parser {
|
|
|
713
697
|
return new ASTNode('ExprStatement', { expression: expr });
|
|
714
698
|
}
|
|
715
699
|
|
|
716
|
-
// Expression parsing with precedence climbing
|
|
717
700
|
parseExpression() {
|
|
718
701
|
return this.parsePipe();
|
|
719
702
|
}
|
|
@@ -730,14 +713,13 @@ export class Parser {
|
|
|
730
713
|
|
|
731
714
|
matchPipeAcrossLines() {
|
|
732
715
|
if (this.match(T.PIPE)) return true;
|
|
733
|
-
// Lookahead across newlines for pipe (INDENT/DEDENT already preprocessed)
|
|
734
716
|
let scanPos = this.pos;
|
|
735
717
|
while (scanPos < this.tokens.length && this.tokens[scanPos].type === T.NEWLINE) {
|
|
736
718
|
scanPos++;
|
|
737
719
|
}
|
|
738
720
|
if (scanPos < this.tokens.length && this.tokens[scanPos].type === T.PIPE) {
|
|
739
721
|
while (this.at(T.NEWLINE)) this.advance();
|
|
740
|
-
this.advance();
|
|
722
|
+
this.advance();
|
|
741
723
|
return true;
|
|
742
724
|
}
|
|
743
725
|
return false;
|
|
@@ -745,12 +727,9 @@ export class Parser {
|
|
|
745
727
|
|
|
746
728
|
parseTernary() {
|
|
747
729
|
let expr = this.parseNullish();
|
|
748
|
-
// inline if: value = if cond then a else b
|
|
749
730
|
if (this.at(T.IF)) {
|
|
750
|
-
// Only treat as ternary if we're in an expression context
|
|
751
|
-
// Lookahead: if ... then ... else
|
|
752
731
|
const savedPos = this.pos;
|
|
753
|
-
this.advance();
|
|
732
|
+
this.advance();
|
|
754
733
|
const condition = this.parseNullish();
|
|
755
734
|
if (this.match(T.THEN)) {
|
|
756
735
|
const consequent = this.parseNullish();
|
|
@@ -758,7 +737,6 @@ export class Parser {
|
|
|
758
737
|
const alternate = this.parseNullish();
|
|
759
738
|
return new ASTNode('Ternary', { condition, consequent, alternate });
|
|
760
739
|
}
|
|
761
|
-
// Not a ternary, restore
|
|
762
740
|
this.pos = savedPos;
|
|
763
741
|
}
|
|
764
742
|
return expr;
|
|
@@ -922,12 +900,12 @@ export class Parser {
|
|
|
922
900
|
case T.TYPE_STR: case T.TYPE_INT: case T.TYPE_NUM:
|
|
923
901
|
case T.TYPE_BOOL: case T.TYPE_LIST: case T.TYPE_MAP:
|
|
924
902
|
case T.TYPE_ANY: case T.TYPE_JSON: case T.TYPE_VOID:
|
|
925
|
-
// In expression context, type keywords act as identifiers (e.g., arr.map(), JSON.parse())
|
|
926
903
|
this.advance();
|
|
927
904
|
return new ASTNode('Identifier', { name: tok.value });
|
|
928
905
|
|
|
929
906
|
case T.SCHEMA: case T.CRUD: case T.AUTH: case T.CORS:
|
|
930
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:
|
|
931
909
|
this.advance();
|
|
932
910
|
return new ASTNode('Identifier', { name: tok.value });
|
|
933
911
|
|
|
@@ -1002,11 +980,9 @@ export class Parser {
|
|
|
1002
980
|
}
|
|
1003
981
|
|
|
1004
982
|
parseGroupOrArrow() {
|
|
1005
|
-
// Check if this is an arrow function: (params) => body
|
|
1006
983
|
const savedPos = this.pos;
|
|
1007
984
|
this.advance(); // (
|
|
1008
985
|
|
|
1009
|
-
// Try to parse as arrow function params
|
|
1010
986
|
let isArrow = false;
|
|
1011
987
|
let depth = 1;
|
|
1012
988
|
let scanPos = this.pos;
|
|
@@ -1036,7 +1012,6 @@ export class Parser {
|
|
|
1036
1012
|
return new ASTNode('ArrowFn', { params, body });
|
|
1037
1013
|
}
|
|
1038
1014
|
|
|
1039
|
-
// Regular grouping
|
|
1040
1015
|
this.pos = savedPos;
|
|
1041
1016
|
this.advance(); // (
|
|
1042
1017
|
const expr = this.parseExpression();
|
|
@@ -1114,10 +1089,8 @@ export class Parser {
|
|
|
1114
1089
|
return new ASTNode('Lambda', { params, returnType, body, isAsync });
|
|
1115
1090
|
}
|
|
1116
1091
|
|
|
1117
|
-
//
|
|
1118
|
-
|
|
1119
|
-
// name str required min(2) max(50)
|
|
1120
|
-
// email str required email unique
|
|
1092
|
+
// ===== High-level feature parsers =====
|
|
1093
|
+
|
|
1121
1094
|
parseSchema() {
|
|
1122
1095
|
this.advance(); // schema
|
|
1123
1096
|
const name = this.expect(T.IDENT).value;
|
|
@@ -1184,7 +1157,6 @@ export class Parser {
|
|
|
1184
1157
|
return { name, type: fieldType, enumValues, modifiers };
|
|
1185
1158
|
}
|
|
1186
1159
|
|
|
1187
|
-
// crud "/api/users" User
|
|
1188
1160
|
parseCrud() {
|
|
1189
1161
|
this.advance(); // crud
|
|
1190
1162
|
const path = this.parseString();
|
|
@@ -1192,9 +1164,6 @@ export class Parser {
|
|
|
1192
1164
|
return new ASTNode('CrudDecl', { path, schemaName });
|
|
1193
1165
|
}
|
|
1194
1166
|
|
|
1195
|
-
// auth SECRET:
|
|
1196
|
-
// protect "/api/*"
|
|
1197
|
-
// public "/api/auth/*"
|
|
1198
1167
|
parseAuth() {
|
|
1199
1168
|
this.advance(); // auth
|
|
1200
1169
|
const secret = this.parseExpression();
|
|
@@ -1224,15 +1193,12 @@ export class Parser {
|
|
|
1224
1193
|
return new ASTNode('AuthDecl', { secret, protectedPaths, publicPaths });
|
|
1225
1194
|
}
|
|
1226
1195
|
|
|
1227
|
-
// cors "*"
|
|
1228
|
-
// cors ["origin1", "origin2"]
|
|
1229
1196
|
parseCors() {
|
|
1230
1197
|
this.advance(); // cors
|
|
1231
1198
|
const origins = this.parseExpression();
|
|
1232
1199
|
return new ASTNode('CorsDecl', { origins });
|
|
1233
1200
|
}
|
|
1234
1201
|
|
|
1235
|
-
// limit "/api/*" 100 "1m"
|
|
1236
1202
|
parseLimit() {
|
|
1237
1203
|
this.advance(); // limit
|
|
1238
1204
|
const path = this.parseString();
|
|
@@ -1241,9 +1207,6 @@ export class Parser {
|
|
|
1241
1207
|
return new ASTNode('LimitDecl', { path, max, window });
|
|
1242
1208
|
}
|
|
1243
1209
|
|
|
1244
|
-
// env:
|
|
1245
|
-
// PORT int default(3000)
|
|
1246
|
-
// JWT_SECRET str required
|
|
1247
1210
|
parseEnv() {
|
|
1248
1211
|
this.advance(); // env
|
|
1249
1212
|
this.expect(T.COLON);
|
|
@@ -1296,8 +1259,6 @@ export class Parser {
|
|
|
1296
1259
|
return { name, type: fieldType, modifiers };
|
|
1297
1260
|
}
|
|
1298
1261
|
|
|
1299
|
-
// every "5m":
|
|
1300
|
-
// log "tick"
|
|
1301
1262
|
parseEvery() {
|
|
1302
1263
|
this.advance(); // every
|
|
1303
1264
|
const interval = this.parseExpression();
|
|
@@ -1306,8 +1267,6 @@ export class Parser {
|
|
|
1306
1267
|
return new ASTNode('EveryDecl', { interval, body });
|
|
1307
1268
|
}
|
|
1308
1269
|
|
|
1309
|
-
// watch User.create (event):
|
|
1310
|
-
// log event
|
|
1311
1270
|
parseWatch() {
|
|
1312
1271
|
this.advance(); // watch
|
|
1313
1272
|
let eventName = this.expect(T.IDENT).value;
|
|
@@ -1326,4 +1285,98 @@ export class Parser {
|
|
|
1326
1285
|
const body = this.parseBlock();
|
|
1327
1286
|
return new ASTNode('WatchDecl', { eventName, params, body });
|
|
1328
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
|
+
}
|
|
1329
1382
|
}
|
package/src/preprocess.js
CHANGED
|
@@ -107,6 +107,14 @@ export function preprocess(source) {
|
|
|
107
107
|
const rest = line.slice(1).trim();
|
|
108
108
|
if (rest.startsWith('.s ') || rest.startsWith('.s(')) {
|
|
109
109
|
out = 'ret.status ' + transformContent(rest.slice(3));
|
|
110
|
+
} else if (rest.startsWith('.r ')) {
|
|
111
|
+
out = 'ret.redirect ' + transformContent(rest.slice(3));
|
|
112
|
+
} else if (rest.startsWith('.h ')) {
|
|
113
|
+
out = 'ret.html ' + transformContent(rest.slice(3));
|
|
114
|
+
} else if (rest.startsWith('.t ')) {
|
|
115
|
+
out = 'ret.text ' + transformContent(rest.slice(3));
|
|
116
|
+
} else if (rest.startsWith('.f ')) {
|
|
117
|
+
out = 'ret.file ' + transformContent(rest.slice(3));
|
|
110
118
|
} else {
|
|
111
119
|
out = rest ? 'ret ' + transformContent(rest) : 'ret';
|
|
112
120
|
}
|