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/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: return this.parseDbStatement();
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
- const statusCode = this.parseExpression();
298
- const body = this.parseExpression();
299
- return new ASTNode('ReturnStatus', { method, statusCode, body });
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
- // db.query, db.find, etc -> treat as expression
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(); // consume PIPE
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(); // if
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
- // In expression context, type keywords act as identifiers (e.g., arr.map(), JSON.parse())
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
  }