@angular-wave/angular.ts 0.0.49 → 0.0.51

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.
@@ -1,30 +1,83 @@
1
- import { $parseMinErr } from "./parse";
2
- import { isAssignable } from "./shared";
1
+ import { isAssignable } from "./interpreter";
3
2
  import { ASTType } from "./ast-type";
3
+ import { minErr } from "../../shared/utils";
4
+
5
+ const $parseMinErr = minErr("$parse");
4
6
 
5
7
  /**
6
- * @param {import('./lexer').Lexer} lexer
7
- * @param {*} options
8
+ * @typedef {Object} ASTNode
9
+ * @property {number} type - The type of the AST node.
10
+ * @property {string} [name] - The name of the identifier.
11
+ * @property {string} [kind] - The kind of the property (e.g., 'init').
12
+ * @property {*} [value] - The value of the node if it is a literal.
13
+ * @property {ASTNode[]} [elements] - The elements of an array node.
14
+ * @property {ASTNode[]} [properties] - The properties of an object node.
15
+ * @property {ASTNode} [key] - The key of an object property.
16
+ * @property {ASTNode} [value] - The value of an object property.
17
+ * @property {ASTNode} [left] - The left-hand side of a binary expression.
18
+ * @property {ASTNode} [right] - The right-hand side of a binary expression.
19
+ * @property {ASTNode} [argument] - The argument of a unary expression.
20
+ * @property {ASTNode} [test] - The test expression of a conditional expression.
21
+ * @property {ASTNode} [alternate] - The alternate expression of a conditional expression.
22
+ * @property {ASTNode} [consequent] - The consequent expression of a conditional expression.
23
+ * @property {ASTNode[]} [body] - The body of a program or block statement.
24
+ * @property {ASTNode} [expression] - The expression of an expression statement.
25
+ * @property {ASTNode} [callee] - The callee of a call expression.
26
+ * @property {ASTNode[]} [arguments] - The arguments of a call expression.
27
+ * @property {boolean} [prefix] - Indicates if a unary operator is a prefix.
28
+ * @property {ASTNode} [object] - The object of a member expression.
29
+ * @property {ASTNode} [property] - The property of a member expression.
30
+ * @property {boolean} [computed] - Indicates if a member expression is computed.
31
+ * @property {string} [operator] - The operator of a binary or logical expression.
32
+ * @property {boolean} [filter]
8
33
  */
9
- export function AST(lexer, options) {
10
- this.lexer = lexer;
11
- this.options = options;
12
- }
13
34
 
14
- AST.prototype = {
35
+ // Keep this exported in case modification is required
36
+ /** @type {Map<string,any>} */
37
+ export const literals = new Map(
38
+ Object.entries({
39
+ true: true,
40
+ false: false,
41
+ null: null,
42
+ undefined,
43
+ }),
44
+ );
45
+
46
+ /**
47
+ * @class
48
+ */
49
+ export class AST {
50
+ /**
51
+ * @param {import('./lexer').Lexer} lexer - The lexer instance for tokenizing input
52
+ */
53
+ constructor(lexer) {
54
+ /** @type {import('./lexer').Lexer} */
55
+ this.lexer = lexer;
56
+ this.selfReferential = {
57
+ this: { type: ASTType.ThisExpression },
58
+ $locals: { type: ASTType.LocalsExpression },
59
+ };
60
+ }
61
+
62
+ /**
63
+ * Parses the input text and generates an AST.
64
+ * @param {string} text - The input text to parse.
65
+ * @returns {ASTNode} The root node of the AST.
66
+ */
15
67
  ast(text) {
16
68
  this.text = text;
17
69
  this.tokens = this.lexer.lex(text);
18
-
19
70
  const value = this.program();
20
-
21
71
  if (this.tokens.length !== 0) {
22
72
  this.throwError("is an unexpected token", this.tokens[0]);
23
73
  }
24
-
25
74
  return value;
26
- },
75
+ }
27
76
 
77
+ /**
78
+ * Parses a program.
79
+ * @returns {ASTNode} The program node.
80
+ */
28
81
  program() {
29
82
  const body = [];
30
83
  let hasMore = true;
@@ -36,27 +89,35 @@ AST.prototype = {
36
89
  }
37
90
  }
38
91
  return { type: ASTType.Program, body };
39
- },
92
+ }
40
93
 
94
+ /**
95
+ * Parses an expression statement.
96
+ * @returns {ASTNode} The expression statement node.
97
+ */
41
98
  expressionStatement() {
42
99
  return {
43
100
  type: ASTType.ExpressionStatement,
44
101
  expression: this.filterChain(),
45
102
  };
46
- },
103
+ }
47
104
 
105
+ /**
106
+ * Parses a filter chain.
107
+ * @returns {ASTNode} The filter chain node.
108
+ */
48
109
  filterChain() {
49
- let left = this.expression();
110
+ let left = this.assignment();
50
111
  while (this.expect("|")) {
51
112
  left = this.filter(left);
52
113
  }
53
114
  return left;
54
- },
55
-
56
- expression() {
57
- return this.assignment();
58
- },
115
+ }
59
116
 
117
+ /**
118
+ * Parses an assignment expression.
119
+ * @returns {ASTNode} The assignment expression node.
120
+ */
60
121
  assignment() {
61
122
  let result = this.ternary();
62
123
  if (this.expect("=")) {
@@ -72,16 +133,20 @@ AST.prototype = {
72
133
  };
73
134
  }
74
135
  return result;
75
- },
136
+ }
76
137
 
138
+ /**
139
+ * Parses a ternary expression.
140
+ * @returns {ASTNode} The ternary expression node.
141
+ */
77
142
  ternary() {
78
143
  const test = this.logicalOR();
79
144
  let alternate;
80
145
  let consequent;
81
146
  if (this.expect("?")) {
82
- alternate = this.expression();
147
+ alternate = this.assignment();
83
148
  if (this.consume(":")) {
84
- consequent = this.expression();
149
+ consequent = this.assignment();
85
150
  return {
86
151
  type: ASTType.ConditionalExpression,
87
152
  test,
@@ -91,8 +156,12 @@ AST.prototype = {
91
156
  }
92
157
  }
93
158
  return test;
94
- },
159
+ }
95
160
 
161
+ /**
162
+ * Parses a logical OR expression.
163
+ * @returns {ASTNode} The logical OR expression node.
164
+ */
96
165
  logicalOR() {
97
166
  let left = this.logicalAND();
98
167
  while (this.expect("||")) {
@@ -104,8 +173,12 @@ AST.prototype = {
104
173
  };
105
174
  }
106
175
  return left;
107
- },
176
+ }
108
177
 
178
+ /**
179
+ * Parses a logical AND expression.
180
+ * @returns {ASTNode} The logical AND expression node.
181
+ */
109
182
  logicalAND() {
110
183
  let left = this.equality();
111
184
  while (this.expect("&&")) {
@@ -117,77 +190,101 @@ AST.prototype = {
117
190
  };
118
191
  }
119
192
  return left;
120
- },
193
+ }
121
194
 
195
+ /**
196
+ * Parses an equality expression.
197
+ * @returns {ASTNode} The equality expression node.
198
+ */
122
199
  equality() {
123
200
  let left = this.relational();
124
201
  let token;
125
202
  while ((token = this.expect("==", "!=", "===", "!=="))) {
126
203
  left = {
127
204
  type: ASTType.BinaryExpression,
128
- operator: token.text,
205
+ operator: /** @type {import("./lexer").Token} */ (token).text,
129
206
  left,
130
207
  right: this.relational(),
131
208
  };
132
209
  }
133
210
  return left;
134
- },
211
+ }
135
212
 
213
+ /**
214
+ * Parses a relational expression.
215
+ * @returns {ASTNode} The relational expression node.
216
+ */
136
217
  relational() {
137
218
  let left = this.additive();
138
219
  let token;
139
220
  while ((token = this.expect("<", ">", "<=", ">="))) {
140
221
  left = {
141
222
  type: ASTType.BinaryExpression,
142
- operator: token.text,
223
+ operator: /** @type {import("./lexer").Token} */ (token).text,
143
224
  left,
144
225
  right: this.additive(),
145
226
  };
146
227
  }
147
228
  return left;
148
- },
229
+ }
149
230
 
231
+ /**
232
+ * Parses an additive expression.
233
+ * @returns {ASTNode} The additive expression node.
234
+ */
150
235
  additive() {
151
236
  let left = this.multiplicative();
152
237
  let token;
153
238
  while ((token = this.expect("+", "-"))) {
154
239
  left = {
155
240
  type: ASTType.BinaryExpression,
156
- operator: token.text,
241
+ operator: /** @type {import("./lexer").Token} */ (token).text,
157
242
  left,
158
243
  right: this.multiplicative(),
159
244
  };
160
245
  }
161
246
  return left;
162
- },
247
+ }
163
248
 
249
+ /**
250
+ * Parses a multiplicative expression.
251
+ * @returns {ASTNode} The multiplicative expression node.
252
+ */
164
253
  multiplicative() {
165
254
  let left = this.unary();
166
255
  let token;
167
256
  while ((token = this.expect("*", "/", "%"))) {
168
257
  left = {
169
258
  type: ASTType.BinaryExpression,
170
- operator: token.text,
259
+ operator: /** @type {import("./lexer").Token} */ (token).text,
171
260
  left,
172
261
  right: this.unary(),
173
262
  };
174
263
  }
175
264
  return left;
176
- },
265
+ }
177
266
 
267
+ /**
268
+ * Parses a unary expression.
269
+ * @returns {ASTNode} The unary expression node.
270
+ */
178
271
  unary() {
179
272
  let token;
180
273
  if ((token = this.expect("+", "-", "!"))) {
181
274
  return {
182
275
  type: ASTType.UnaryExpression,
183
- operator: token.text,
276
+ operator: /** @type {import("./lexer").Token} */ (token).text,
184
277
  prefix: true,
185
278
  argument: this.unary(),
186
279
  };
187
280
  }
188
281
  return this.primary();
189
- },
282
+ }
190
283
 
284
+ /**
285
+ * Parses a primary expression.
286
+ * @returns {ASTNode} The primary expression node.
287
+ */
191
288
  primary() {
192
289
  let primary;
193
290
  if (this.expect("(")) {
@@ -200,46 +297,48 @@ AST.prototype = {
200
297
  } else if (
201
298
  Object.prototype.hasOwnProperty.call(
202
299
  this.selfReferential,
203
- this.peek().text,
300
+ /** @type {import("./lexer").Token} */ (this.peek()).text,
204
301
  )
205
302
  ) {
206
303
  primary = structuredClone(this.selfReferential[this.consume().text]);
207
304
  } else if (
208
- Object.prototype.hasOwnProperty.call(
209
- this.options.literals,
210
- this.peek().text,
211
- )
305
+ literals.has(/** @type {import("./lexer").Token} */ (this.peek()).text)
212
306
  ) {
213
307
  primary = {
214
308
  type: ASTType.Literal,
215
- value: this.options.literals[this.consume().text],
309
+ value: literals.get(this.consume().text),
216
310
  };
217
- } else if (this.peek().identifier) {
311
+ } else if (
312
+ /** @type {import("./lexer").Token} */ (this.peek()).identifier
313
+ ) {
218
314
  primary = this.identifier();
219
- } else if (this.peek().constant) {
315
+ } else if (/** @type {import("./lexer").Token} */ (this.peek()).constant) {
220
316
  primary = this.constant();
221
317
  } else {
222
- this.throwError("not a primary expression", this.peek());
318
+ this.throwError(
319
+ "not a primary expression",
320
+ /** @type {import("./lexer").Token} */ (this.peek()),
321
+ );
223
322
  }
224
323
 
225
324
  let next;
226
325
  while ((next = this.expect("(", "[", "."))) {
227
- if (next.text === "(") {
326
+ if (/** @type {import("./lexer").Token} */ (next).text === "(") {
228
327
  primary = {
229
328
  type: ASTType.CallExpression,
230
329
  callee: primary,
231
330
  arguments: this.parseArguments(),
232
331
  };
233
332
  this.consume(")");
234
- } else if (next.text === "[") {
333
+ } else if (/** @type {import("./lexer").Token} */ (next).text === "[") {
235
334
  primary = {
236
335
  type: ASTType.MemberExpression,
237
336
  object: primary,
238
- property: this.expression(),
337
+ property: this.assignment(),
239
338
  computed: true,
240
339
  };
241
340
  this.consume("]");
242
- } else if (next.text === ".") {
341
+ } else if (/** @type {import("./lexer").Token} */ (next).text === ".") {
243
342
  primary = {
244
343
  type: ASTType.MemberExpression,
245
344
  object: primary,
@@ -251,9 +350,15 @@ AST.prototype = {
251
350
  }
252
351
  }
253
352
  return primary;
254
- },
353
+ }
255
354
 
355
+ /**
356
+ * Parses a filter.
357
+ * @param {ASTNode} baseExpression - The base expression to apply the filter to.
358
+ * @returns {ASTNode} The filter node.
359
+ */
256
360
  filter(baseExpression) {
361
+ /** @type {ASTNode[]} */
257
362
  const args = [baseExpression];
258
363
  const result = {
259
364
  type: ASTType.CallExpression,
@@ -263,13 +368,18 @@ AST.prototype = {
263
368
  };
264
369
 
265
370
  while (this.expect(":")) {
266
- args.push(this.expression());
371
+ args.push(this.assignment());
267
372
  }
268
373
 
269
374
  return result;
270
- },
375
+ }
271
376
 
377
+ /**
378
+ * Parses function arguments.
379
+ * @returns {ASTNode[]} The arguments array.
380
+ */
272
381
  parseArguments() {
382
+ /** @type {ASTNode[]} */
273
383
  const args = [];
274
384
  if (this.peekToken().text !== ")") {
275
385
  do {
@@ -277,22 +387,35 @@ AST.prototype = {
277
387
  } while (this.expect(","));
278
388
  }
279
389
  return args;
280
- },
390
+ }
281
391
 
392
+ /**
393
+ * Parses an identifier.
394
+ * @returns {ASTNode} The identifier node.
395
+ */
282
396
  identifier() {
283
397
  const token = this.consume();
284
398
  if (!token.identifier) {
285
399
  this.throwError("is not a valid identifier", token);
286
400
  }
287
401
  return { type: ASTType.Identifier, name: token.text };
288
- },
402
+ }
289
403
 
404
+ /**
405
+ * Parses a constant.
406
+ * @returns {ASTNode} The constant node.
407
+ */
290
408
  constant() {
291
409
  // TODO check that it is a constant
292
410
  return { type: ASTType.Literal, value: this.consume().value };
293
- },
411
+ }
294
412
 
413
+ /**
414
+ * Parses an array declaration.
415
+ * @returns {ASTNode} The array declaration node.
416
+ */
295
417
  arrayDeclaration() {
418
+ /** @type {ASTNode[]} */
296
419
  const elements = [];
297
420
  if (this.peekToken().text !== "]") {
298
421
  do {
@@ -300,16 +423,22 @@ AST.prototype = {
300
423
  // Support trailing commas per ES5.1.
301
424
  break;
302
425
  }
303
- elements.push(this.expression());
426
+ elements.push(this.assignment());
304
427
  } while (this.expect(","));
305
428
  }
306
429
  this.consume("]");
307
430
 
308
431
  return { type: ASTType.ArrayExpression, elements };
309
- },
432
+ }
310
433
 
434
+ /**
435
+ * Parses an object.
436
+ * @returns {ASTNode} The object node.
437
+ */
311
438
  object() {
439
+ /** @type {ASTNode[]} */
312
440
  const properties = [];
441
+ /** @type {ASTNode} */
313
442
  let property;
314
443
  if (this.peekToken().text !== "}") {
315
444
  do {
@@ -318,29 +447,34 @@ AST.prototype = {
318
447
  break;
319
448
  }
320
449
  property = { type: ASTType.Property, kind: "init" };
321
- if (this.peek().constant) {
450
+ if (/** @type {import("./lexer").Token} */ (this.peek()).constant) {
322
451
  property.key = this.constant();
323
452
  property.computed = false;
324
453
  this.consume(":");
325
- property.value = this.expression();
326
- } else if (this.peek().identifier) {
454
+ property.value = this.assignment();
455
+ } else if (
456
+ /** @type {import("./lexer").Token} */ (this.peek()).identifier
457
+ ) {
327
458
  property.key = this.identifier();
328
459
  property.computed = false;
329
460
  if (this.peek(":")) {
330
461
  this.consume(":");
331
- property.value = this.expression();
462
+ property.value = this.assignment();
332
463
  } else {
333
464
  property.value = property.key;
334
465
  }
335
466
  } else if (this.peek("[")) {
336
467
  this.consume("[");
337
- property.key = this.expression();
468
+ property.key = this.assignment();
338
469
  this.consume("]");
339
470
  property.computed = true;
340
471
  this.consume(":");
341
- property.value = this.expression();
472
+ property.value = this.assignment();
342
473
  } else {
343
- this.throwError("invalid key", this.peek());
474
+ this.throwError(
475
+ "invalid key",
476
+ /** @type {import("./lexer").Token} */ (this.peek()),
477
+ );
344
478
  }
345
479
  properties.push(property);
346
480
  } while (this.expect(","));
@@ -348,8 +482,13 @@ AST.prototype = {
348
482
  this.consume("}");
349
483
 
350
484
  return { type: ASTType.ObjectExpression, properties };
351
- },
485
+ }
352
486
 
487
+ /**
488
+ * Throws a syntax error.
489
+ * @param {string} msg - The error message.
490
+ * @param {import("./lexer").Token} [token] - The token that caused the error.
491
+ */
353
492
  throwError(msg, token) {
354
493
  throw $parseMinErr(
355
494
  "syntax",
@@ -360,8 +499,13 @@ AST.prototype = {
360
499
  this.text,
361
500
  this.text.substring(token.index),
362
501
  );
363
- },
502
+ }
364
503
 
504
+ /**
505
+ * Consumes a token if it matches the expected type.
506
+ * @param {string} [e1] - The expected token type.
507
+ * @returns {import("./lexer").Token} The consumed token.
508
+ */
365
509
  consume(e1) {
366
510
  if (this.tokens.length === 0) {
367
511
  throw $parseMinErr(
@@ -373,11 +517,19 @@ AST.prototype = {
373
517
 
374
518
  const token = this.expect(e1);
375
519
  if (!token) {
376
- this.throwError(`is unexpected, expecting [${e1}]`, this.peek());
520
+ this.throwError(
521
+ `is unexpected, expecting [${e1}]`,
522
+ /** @type {import("./lexer").Token} */ (this.peek()),
523
+ );
524
+ } else {
525
+ return /** @type {import("./lexer").Token} */ (token);
377
526
  }
378
- return token;
379
- },
527
+ }
380
528
 
529
+ /**
530
+ * Returns the next token without consuming it.
531
+ * @returns {import("./lexer").Token} The next token.
532
+ */
381
533
  peekToken() {
382
534
  if (this.tokens.length === 0) {
383
535
  throw $parseMinErr(
@@ -387,40 +539,48 @@ AST.prototype = {
387
539
  );
388
540
  }
389
541
  return this.tokens[0];
390
- },
391
-
392
- peek(e1, e2, e3, e4) {
393
- return this.peekAhead(0, e1, e2, e3, e4);
394
- },
395
-
396
- peekAhead(i, e1, e2, e3, e4) {
542
+ }
543
+
544
+ /**
545
+ * Checks if the next token matches any of the expected types.
546
+ * @param {...string} [expected] - The expected token types.
547
+ * @returns {import('./lexer').Token|boolean} The next token if it matches, otherwise false.
548
+ */
549
+ peek(...expected) {
550
+ return this.peekAhead(0, ...expected);
551
+ }
552
+
553
+ /**
554
+ * Checks if the token at the specified index matches any of the expected types.
555
+ * @param {number} i - The index to check.
556
+ * @param {...string} [expected] - The expected token types.
557
+ * @returns {import("./lexer").Token|boolean} The token at the specified index if it matches, otherwise false.
558
+ */
559
+ peekAhead(i, ...expected) {
397
560
  if (this.tokens.length > i) {
398
561
  const token = this.tokens[i];
399
562
  const t = token.text;
400
563
  if (
401
- t === e1 ||
402
- t === e2 ||
403
- t === e3 ||
404
- t === e4 ||
405
- (!e1 && !e2 && !e3 && !e4)
564
+ expected.includes(t) ||
565
+ (!expected[0] && !expected[1] && !expected[2] && !expected[3])
406
566
  ) {
407
567
  return token;
408
568
  }
409
569
  }
410
570
  return false;
411
- },
412
-
413
- expect(e1, e2, e3, e4) {
414
- const token = this.peek(e1, e2, e3, e4);
571
+ }
572
+
573
+ /**
574
+ * Consumes the next token if it matches any of the expected types.
575
+ * @param {...string} [expected] - The expected token types.
576
+ * @returns {import("./lexer").Token|boolean} The consumed token if it matches, otherwise false.
577
+ */
578
+ expect(...expected) {
579
+ const token = this.peek(...expected);
415
580
  if (token) {
416
581
  this.tokens.shift();
417
582
  return token;
418
583
  }
419
584
  return false;
420
- },
421
-
422
- selfReferential: {
423
- this: { type: ASTType.ThisExpression },
424
- $locals: { type: ASTType.LocalsExpression },
425
- },
426
- };
585
+ }
586
+ }