pulse-js-framework 1.10.0 → 1.10.3

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.
Files changed (37) hide show
  1. package/compiler/parser/_extract.js +393 -0
  2. package/compiler/parser/blocks.js +361 -0
  3. package/compiler/parser/core.js +306 -0
  4. package/compiler/parser/expressions.js +386 -0
  5. package/compiler/parser/imports.js +108 -0
  6. package/compiler/parser/index.js +47 -0
  7. package/compiler/parser/state.js +155 -0
  8. package/compiler/parser/style.js +445 -0
  9. package/compiler/parser/view.js +632 -0
  10. package/compiler/parser.js +15 -2372
  11. package/compiler/parser.js.original +2376 -0
  12. package/package.json +2 -1
  13. package/runtime/a11y/announcements.js +213 -0
  14. package/runtime/a11y/contrast.js +125 -0
  15. package/runtime/a11y/focus.js +412 -0
  16. package/runtime/a11y/index.js +35 -0
  17. package/runtime/a11y/preferences.js +121 -0
  18. package/runtime/a11y/utils.js +164 -0
  19. package/runtime/a11y/validation.js +258 -0
  20. package/runtime/a11y/widgets.js +545 -0
  21. package/runtime/a11y.js +15 -1840
  22. package/runtime/a11y.js.original +1844 -0
  23. package/runtime/graphql/cache.js +69 -0
  24. package/runtime/graphql/client.js +563 -0
  25. package/runtime/graphql/hooks.js +492 -0
  26. package/runtime/graphql/index.js +62 -0
  27. package/runtime/graphql/subscriptions.js +241 -0
  28. package/runtime/graphql.js +12 -1322
  29. package/runtime/graphql.js.original +1326 -0
  30. package/runtime/router/core.js +956 -0
  31. package/runtime/router/guards.js +90 -0
  32. package/runtime/router/history.js +204 -0
  33. package/runtime/router/index.js +36 -0
  34. package/runtime/router/lazy.js +180 -0
  35. package/runtime/router/utils.js +226 -0
  36. package/runtime/router.js +12 -1600
  37. package/runtime/router.js.original +1605 -0
@@ -0,0 +1,386 @@
1
+ /**
2
+ * Pulse Parser - Expression Parsing
3
+ *
4
+ * Handles parsing of JavaScript expressions including assignments, conditionals,
5
+ * binary expressions, unary expressions, and primary expressions
6
+ *
7
+ * @module compiler/parser/expressions
8
+ */
9
+
10
+ import { TokenType } from '../lexer.js';
11
+ import { NodeType, ASTNode, Parser } from './core.js';
12
+
13
+ // ============================================================
14
+ // Expression Parsing
15
+ // ============================================================
16
+
17
+ /**
18
+ * Parse expression - delegates to assignment expression
19
+ */
20
+ Parser.prototype.parseExpression = function() {
21
+ return this.parseAssignmentExpression();
22
+ };
23
+
24
+ /**
25
+ * Parse assignment expression (a = b, a += b, a -= b, etc.)
26
+ */
27
+ Parser.prototype.parseAssignmentExpression = function() {
28
+ const left = this.parseConditionalExpression();
29
+
30
+ // Check for assignment operators
31
+ const assignmentOps = [
32
+ TokenType.EQ, // =
33
+ TokenType.PLUS_ASSIGN, // +=
34
+ TokenType.MINUS_ASSIGN, // -=
35
+ TokenType.STAR_ASSIGN, // *=
36
+ TokenType.SLASH_ASSIGN, // /=
37
+ TokenType.AND_ASSIGN, // &&=
38
+ TokenType.OR_ASSIGN, // ||=
39
+ TokenType.NULLISH_ASSIGN // ??=
40
+ ];
41
+
42
+ if (this.isAny(...assignmentOps)) {
43
+ const operator = this.advance().value;
44
+ const right = this.parseAssignmentExpression();
45
+ return new ASTNode(NodeType.AssignmentExpression, {
46
+ left,
47
+ right,
48
+ operator
49
+ });
50
+ }
51
+
52
+ return left;
53
+ };
54
+
55
+ /**
56
+ * Parse conditional (ternary) expression
57
+ */
58
+ Parser.prototype.parseConditionalExpression = function() {
59
+ const test = this.parseOrExpression();
60
+
61
+ if (this.is(TokenType.QUESTION)) {
62
+ this.advance();
63
+ const consequent = this.parseAssignmentExpression();
64
+ this.expect(TokenType.COLON);
65
+ const alternate = this.parseAssignmentExpression();
66
+ return new ASTNode(NodeType.ConditionalExpression, { test, consequent, alternate });
67
+ }
68
+
69
+ return test;
70
+ };
71
+
72
+ /**
73
+ * Binary operator precedence table (higher index = binds tighter)
74
+ */
75
+ Parser.BINARY_OPS = [
76
+ { ops: [TokenType.OR], name: 'or' },
77
+ { ops: [TokenType.AND], name: 'and' },
78
+ { ops: [TokenType.EQEQ, TokenType.EQEQEQ, TokenType.NEQ, TokenType.NEQEQ,
79
+ TokenType.LT, TokenType.GT, TokenType.LTE, TokenType.GTE], name: 'comparison' },
80
+ { ops: [TokenType.PLUS, TokenType.MINUS], name: 'additive' },
81
+ { ops: [TokenType.STAR, TokenType.SLASH, TokenType.PERCENT], name: 'multiplicative' }
82
+ ];
83
+
84
+ /**
85
+ * Generic binary expression parser using precedence climbing
86
+ */
87
+ Parser.prototype.parseBinaryExpr = function(level = 0) {
88
+ if (level >= Parser.BINARY_OPS.length) {
89
+ return this.parseUnaryExpression();
90
+ }
91
+
92
+ let left = this.parseBinaryExpr(level + 1);
93
+ const { ops } = Parser.BINARY_OPS[level];
94
+
95
+ while (this.isAny(...ops)) {
96
+ const operator = this.advance().value;
97
+ const right = this.parseBinaryExpr(level + 1);
98
+ left = new ASTNode(NodeType.BinaryExpression, { operator, left, right });
99
+ }
100
+
101
+ return left;
102
+ };
103
+
104
+ /**
105
+ * Parse OR expression (entry point for binary expressions)
106
+ */
107
+ Parser.prototype.parseOrExpression = function() {
108
+ return this.parseBinaryExpr(0);
109
+ };
110
+
111
+ /**
112
+ * Parse unary expression
113
+ */
114
+ Parser.prototype.parseUnaryExpression = function() {
115
+ if (this.is(TokenType.NOT)) {
116
+ this.advance();
117
+ const argument = this.parseUnaryExpression();
118
+ return new ASTNode(NodeType.UnaryExpression, { operator: '!', argument });
119
+ }
120
+ if (this.is(TokenType.MINUS)) {
121
+ this.advance();
122
+ const argument = this.parseUnaryExpression();
123
+ return new ASTNode(NodeType.UnaryExpression, { operator: '-', argument });
124
+ }
125
+
126
+ return this.parsePostfixExpression();
127
+ };
128
+
129
+ /**
130
+ * Parse postfix expression (++, --)
131
+ */
132
+ Parser.prototype.parsePostfixExpression = function() {
133
+ let expr = this.parsePrimaryExpression();
134
+
135
+ while (this.isAny(TokenType.PLUSPLUS, TokenType.MINUSMINUS)) {
136
+ const operator = this.advance().value;
137
+ expr = new ASTNode(NodeType.UpdateExpression, {
138
+ operator,
139
+ argument: expr,
140
+ prefix: false
141
+ });
142
+ }
143
+
144
+ return expr;
145
+ };
146
+
147
+ /**
148
+ * Parse primary expression
149
+ */
150
+ Parser.prototype.parsePrimaryExpression = function() {
151
+ // Check for arrow function: (params) => expr or () => expr
152
+ if (this.is(TokenType.LPAREN)) {
153
+ // Try to parse as arrow function by looking ahead
154
+ const savedPos = this.pos;
155
+ if (this.tryParseArrowFunction()) {
156
+ this.pos = savedPos;
157
+ return this.parseArrowFunction();
158
+ }
159
+ // Not an arrow function, parse as grouped expression
160
+ this.advance();
161
+ const expr = this.parseExpression();
162
+ this.expect(TokenType.RPAREN);
163
+ // Check if this grouped expression is actually arrow function params
164
+ if (this.is(TokenType.ARROW)) {
165
+ this.pos = savedPos;
166
+ return this.parseArrowFunction();
167
+ }
168
+ return expr;
169
+ }
170
+
171
+ // Single param arrow function: x => expr
172
+ if ((this.is(TokenType.IDENT) || this.is(TokenType.SELECTOR)) && this.peek()?.type === TokenType.ARROW) {
173
+ return this.parseArrowFunction();
174
+ }
175
+
176
+ // Array literal
177
+ if (this.is(TokenType.LBRACKET)) {
178
+ return this.parseArrayLiteralExpr();
179
+ }
180
+
181
+ // Object literal in expression context
182
+ if (this.is(TokenType.LBRACE)) {
183
+ return this.parseObjectLiteralExpr();
184
+ }
185
+
186
+ // Template literal
187
+ if (this.is(TokenType.TEMPLATE)) {
188
+ const token = this.advance();
189
+ return new ASTNode(NodeType.TemplateLiteral, { value: token.value, raw: token.raw });
190
+ }
191
+
192
+ // Spread operator
193
+ if (this.is(TokenType.SPREAD)) {
194
+ this.advance();
195
+ const argument = this.parseAssignmentExpression();
196
+ return new ASTNode(NodeType.SpreadElement, { argument });
197
+ }
198
+
199
+ // Try parsing a literal (NUMBER, STRING, TRUE, FALSE, NULL)
200
+ const literal = this.tryParseLiteral();
201
+ if (literal) return literal;
202
+
203
+ // In expressions, SELECTOR tokens should be treated as IDENT
204
+ // This happens when identifiers like 'selectedCategory' are followed by space in view context
205
+ if (this.is(TokenType.IDENT) || this.is(TokenType.SELECTOR)) {
206
+ return this.parseIdentifierOrExpression();
207
+ }
208
+
209
+ throw this.createError(
210
+ `Unexpected token ${this.current()?.type} in expression`
211
+ );
212
+ };
213
+
214
+ /**
215
+ * Try to determine if we're looking at an arrow function
216
+ */
217
+ Parser.prototype.tryParseArrowFunction = function() {
218
+ if (!this.is(TokenType.LPAREN)) return false;
219
+
220
+ let depth = 0;
221
+ let i = 0;
222
+
223
+ while (this.peek(i)) {
224
+ const token = this.peek(i);
225
+ if (token.type === TokenType.LPAREN) depth++;
226
+ else if (token.type === TokenType.RPAREN) {
227
+ depth--;
228
+ if (depth === 0) {
229
+ // Check if next token is =>
230
+ const next = this.peek(i + 1);
231
+ return next?.type === TokenType.ARROW;
232
+ }
233
+ }
234
+ i++;
235
+ }
236
+ return false;
237
+ };
238
+
239
+ /**
240
+ * Parse arrow function: (params) => expr or param => expr
241
+ */
242
+ Parser.prototype.parseArrowFunction = function() {
243
+ const params = [];
244
+
245
+ // Single param without parens: x => expr
246
+ if ((this.is(TokenType.IDENT) || this.is(TokenType.SELECTOR)) && this.peek()?.type === TokenType.ARROW) {
247
+ params.push(this.advance().value);
248
+ } else {
249
+ // Params in parens: (a, b) => expr or () => expr
250
+ this.expect(TokenType.LPAREN);
251
+ while (!this.is(TokenType.RPAREN) && !this.is(TokenType.EOF)) {
252
+ if (this.is(TokenType.SPREAD)) {
253
+ this.advance();
254
+ params.push('...' + this.expect(TokenType.IDENT).value);
255
+ } else {
256
+ params.push(this.expect(TokenType.IDENT).value);
257
+ }
258
+ if (this.is(TokenType.COMMA)) {
259
+ this.advance();
260
+ }
261
+ }
262
+ this.expect(TokenType.RPAREN);
263
+ }
264
+
265
+ this.expect(TokenType.ARROW);
266
+
267
+ // Body can be expression or block
268
+ let body;
269
+ if (this.is(TokenType.LBRACE)) {
270
+ // Block body - collect tokens
271
+ this.advance();
272
+ body = this.parseFunctionBody();
273
+ this.expect(TokenType.RBRACE);
274
+ return new ASTNode(NodeType.ArrowFunction, { params, body, block: true });
275
+ } else {
276
+ // Expression body
277
+ body = this.parseAssignmentExpression();
278
+ return new ASTNode(NodeType.ArrowFunction, { params, body, block: false });
279
+ }
280
+ };
281
+
282
+ /**
283
+ * Parse array literal in expression context
284
+ */
285
+ Parser.prototype.parseArrayLiteralExpr = function() {
286
+ this.expect(TokenType.LBRACKET);
287
+ const elements = [];
288
+
289
+ while (!this.is(TokenType.RBRACKET) && !this.is(TokenType.EOF)) {
290
+ if (this.is(TokenType.SPREAD)) {
291
+ this.advance();
292
+ elements.push(new ASTNode(NodeType.SpreadElement, {
293
+ argument: this.parseAssignmentExpression()
294
+ }));
295
+ } else {
296
+ elements.push(this.parseAssignmentExpression());
297
+ }
298
+ if (this.is(TokenType.COMMA)) {
299
+ this.advance();
300
+ }
301
+ }
302
+
303
+ this.expect(TokenType.RBRACKET);
304
+ return new ASTNode(NodeType.ArrayLiteral, { elements });
305
+ };
306
+
307
+ /**
308
+ * Parse object literal in expression context
309
+ */
310
+ Parser.prototype.parseObjectLiteralExpr = function() {
311
+ this.expect(TokenType.LBRACE);
312
+ const properties = [];
313
+
314
+ while (!this.is(TokenType.RBRACE) && !this.is(TokenType.EOF)) {
315
+ if (this.is(TokenType.SPREAD)) {
316
+ this.advance();
317
+ properties.push(new ASTNode(NodeType.SpreadElement, {
318
+ argument: this.parseAssignmentExpression()
319
+ }));
320
+ } else {
321
+ const key = this.expect(TokenType.IDENT);
322
+ if (this.is(TokenType.COLON)) {
323
+ this.advance();
324
+ const value = this.parseAssignmentExpression();
325
+ properties.push(new ASTNode(NodeType.Property, { name: key.value, value }));
326
+ } else {
327
+ // Shorthand property: { x } is same as { x: x }
328
+ properties.push(new ASTNode(NodeType.Property, {
329
+ name: key.value,
330
+ value: new ASTNode(NodeType.Identifier, { name: key.value }),
331
+ shorthand: true
332
+ }));
333
+ }
334
+ }
335
+ if (this.is(TokenType.COMMA)) {
336
+ this.advance();
337
+ }
338
+ }
339
+
340
+ this.expect(TokenType.RBRACE);
341
+ return new ASTNode(NodeType.ObjectLiteral, { properties });
342
+ };
343
+
344
+ /**
345
+ * Parse identifier with possible member access and calls
346
+ */
347
+ Parser.prototype.parseIdentifierOrExpression = function() {
348
+ // Accept both IDENT and SELECTOR (selector tokens can be identifiers in expression context)
349
+ const token = this.advance();
350
+ let expr = new ASTNode(NodeType.Identifier, { name: token.value });
351
+
352
+ while (true) {
353
+ if (this.is(TokenType.DOT)) {
354
+ this.advance();
355
+ const property = this.expect(TokenType.IDENT);
356
+ expr = new ASTNode(NodeType.MemberExpression, {
357
+ object: expr,
358
+ property: property.value
359
+ });
360
+ } else if (this.is(TokenType.LBRACKET)) {
361
+ this.advance();
362
+ const property = this.parseExpression();
363
+ this.expect(TokenType.RBRACKET);
364
+ expr = new ASTNode(NodeType.MemberExpression, {
365
+ object: expr,
366
+ property,
367
+ computed: true
368
+ });
369
+ } else if (this.is(TokenType.LPAREN)) {
370
+ this.advance();
371
+ const args = [];
372
+ while (!this.is(TokenType.RPAREN) && !this.is(TokenType.EOF)) {
373
+ args.push(this.parseExpression());
374
+ if (this.is(TokenType.COMMA)) {
375
+ this.advance();
376
+ }
377
+ }
378
+ this.expect(TokenType.RPAREN);
379
+ expr = new ASTNode(NodeType.CallExpression, { callee: expr, arguments: args });
380
+ } else {
381
+ break;
382
+ }
383
+ }
384
+
385
+ return expr;
386
+ };
@@ -0,0 +1,108 @@
1
+ /**
2
+ * Pulse Parser - Import/Page/Route Declaration Parsing
3
+ *
4
+ * Handles parsing of import statements and @page/@route decorators
5
+ *
6
+ * @module compiler/parser/imports
7
+ */
8
+
9
+ import { TokenType } from '../lexer.js';
10
+ import { NodeType, ASTNode, Parser } from './core.js';
11
+
12
+ // ============================================================
13
+ // Import Declaration Parsing
14
+ // ============================================================
15
+
16
+ /**
17
+ * Parse import declaration
18
+ * Supports:
19
+ * import Component from './Component.pulse'
20
+ * import { helper, util } from './utils.pulse'
21
+ * import { helper as h } from './utils.pulse'
22
+ * import * as Utils from './utils.pulse'
23
+ */
24
+ Parser.prototype.parseImportDeclaration = function() {
25
+ const startToken = this.expect(TokenType.IMPORT);
26
+ const specifiers = [];
27
+ let source = null;
28
+
29
+ // import * as Name from '...'
30
+ if (this.is(TokenType.STAR)) {
31
+ this.advance();
32
+ this.expect(TokenType.AS);
33
+ const local = this.expect(TokenType.IDENT);
34
+ specifiers.push(new ASTNode(NodeType.ImportSpecifier, {
35
+ type: 'namespace',
36
+ local: local.value,
37
+ imported: '*'
38
+ }));
39
+ }
40
+ // import { a, b } from '...'
41
+ else if (this.is(TokenType.LBRACE)) {
42
+ this.advance();
43
+ while (!this.is(TokenType.RBRACE) && !this.is(TokenType.EOF)) {
44
+ const imported = this.expect(TokenType.IDENT);
45
+ let local = imported.value;
46
+
47
+ // Handle 'as' alias
48
+ if (this.is(TokenType.AS)) {
49
+ this.advance();
50
+ local = this.expect(TokenType.IDENT).value;
51
+ }
52
+
53
+ specifiers.push(new ASTNode(NodeType.ImportSpecifier, {
54
+ type: 'named',
55
+ local,
56
+ imported: imported.value
57
+ }));
58
+
59
+ if (this.is(TokenType.COMMA)) {
60
+ this.advance();
61
+ }
62
+ }
63
+ this.expect(TokenType.RBRACE);
64
+ }
65
+ // import DefaultExport from '...'
66
+ else if (this.is(TokenType.IDENT)) {
67
+ const name = this.advance();
68
+ specifiers.push(new ASTNode(NodeType.ImportSpecifier, {
69
+ type: 'default',
70
+ local: name.value,
71
+ imported: 'default'
72
+ }));
73
+ }
74
+
75
+ // from '...'
76
+ this.expect(TokenType.FROM);
77
+ const sourceToken = this.expect(TokenType.STRING);
78
+ source = sourceToken.value;
79
+
80
+ return new ASTNode(NodeType.ImportDeclaration, {
81
+ specifiers,
82
+ source,
83
+ line: startToken.line,
84
+ column: startToken.column
85
+ });
86
+ };
87
+
88
+ // ============================================================
89
+ // Page/Route Declaration Parsing
90
+ // ============================================================
91
+
92
+ /**
93
+ * Parse @page declaration
94
+ */
95
+ Parser.prototype.parsePageDeclaration = function() {
96
+ this.expect(TokenType.PAGE);
97
+ const name = this.expect(TokenType.IDENT);
98
+ return new ASTNode(NodeType.PageDeclaration, { name: name.value });
99
+ };
100
+
101
+ /**
102
+ * Parse @route declaration
103
+ */
104
+ Parser.prototype.parseRouteDeclaration = function() {
105
+ this.expect(TokenType.ROUTE);
106
+ const path = this.expect(TokenType.STRING);
107
+ return new ASTNode(NodeType.RouteDeclaration, { path: path.value });
108
+ };
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Pulse Parser - Main Entry Point
3
+ *
4
+ * Barrel export for all parser modules
5
+ * Assembles the complete Parser class by importing all method modules
6
+ *
7
+ * @module compiler/parser
8
+ */
9
+
10
+ import { tokenize } from '../lexer.js';
11
+ import { NodeType, ASTNode, Parser } from './core.js';
12
+
13
+ // Import all parser method modules (these add methods to Parser.prototype)
14
+ import './imports.js'; // parseImportDeclaration, parsePageDeclaration, parseRouteDeclaration
15
+ import './state.js'; // parsePropsBlock, parseStateBlock, parseValue, etc.
16
+ import './view.js'; // parseViewBlock, parseElement, parseDirective, etc.
17
+ import './expressions.js'; // parseExpression, parseAssignmentExpression, etc.
18
+ import './blocks.js'; // parseActionsBlock, parseRouterBlock, parseStoreBlock, etc.
19
+ import './style.js'; // parseStyleBlock, parseStyleRule, parseStyleProperty
20
+
21
+ // ============================================================
22
+ // Main Parse Function
23
+ // ============================================================
24
+
25
+ /**
26
+ * Parse .pulse source code into an AST
27
+ * @param {string} source - Source code to parse
28
+ * @returns {ASTNode} Program AST node
29
+ */
30
+ export function parse(source) {
31
+ const tokens = tokenize(source);
32
+ const parser = new Parser(tokens);
33
+ return parser.parse();
34
+ }
35
+
36
+ // ============================================================
37
+ // Exports
38
+ // ============================================================
39
+
40
+ export { NodeType, ASTNode, Parser };
41
+
42
+ export default {
43
+ NodeType,
44
+ ASTNode,
45
+ Parser,
46
+ parse
47
+ };
@@ -0,0 +1,155 @@
1
+ /**
2
+ * Pulse Parser - Props/State Block Parsing
3
+ *
4
+ * Handles parsing of props and state blocks with their properties
5
+ *
6
+ * @module compiler/parser/state
7
+ */
8
+
9
+ import { TokenType } from '../lexer.js';
10
+ import { NodeType, ASTNode, Parser } from './core.js';
11
+
12
+ // ============================================================
13
+ // Props Block Parsing
14
+ // ============================================================
15
+
16
+ /**
17
+ * Parse props block
18
+ * props {
19
+ * label: "Default"
20
+ * disabled: false
21
+ * }
22
+ */
23
+ Parser.prototype.parsePropsBlock = function() {
24
+ this.expect(TokenType.PROPS);
25
+ this.expect(TokenType.LBRACE);
26
+
27
+ const properties = [];
28
+ while (!this.is(TokenType.RBRACE) && !this.is(TokenType.EOF)) {
29
+ properties.push(this.parsePropsProperty());
30
+ }
31
+
32
+ this.expect(TokenType.RBRACE);
33
+ return new ASTNode(NodeType.PropsBlock, { properties });
34
+ };
35
+
36
+ /**
37
+ * Parse a props property (name: defaultValue)
38
+ */
39
+ Parser.prototype.parsePropsProperty = function() {
40
+ const name = this.expect(TokenType.IDENT);
41
+ this.expect(TokenType.COLON);
42
+ const value = this.parseValue();
43
+ return new ASTNode(NodeType.Property, { name: name.value, value });
44
+ };
45
+
46
+ // ============================================================
47
+ // State Block Parsing
48
+ // ============================================================
49
+
50
+ /**
51
+ * Parse state block
52
+ */
53
+ Parser.prototype.parseStateBlock = function() {
54
+ this.expect(TokenType.STATE);
55
+ this.expect(TokenType.LBRACE);
56
+
57
+ const properties = [];
58
+ while (!this.is(TokenType.RBRACE) && !this.is(TokenType.EOF)) {
59
+ properties.push(this.parseStateProperty());
60
+ }
61
+
62
+ this.expect(TokenType.RBRACE);
63
+ return new ASTNode(NodeType.StateBlock, { properties });
64
+ };
65
+
66
+ /**
67
+ * Parse a state property
68
+ */
69
+ Parser.prototype.parseStateProperty = function() {
70
+ const name = this.expect(TokenType.IDENT);
71
+ this.expect(TokenType.COLON);
72
+ const value = this.parseValue();
73
+ return new ASTNode(NodeType.Property, { name: name.value, value });
74
+ };
75
+
76
+ // ============================================================
77
+ // Value Parsing Utilities
78
+ // ============================================================
79
+
80
+ /**
81
+ * Try to parse a literal token (STRING, NUMBER, TRUE, FALSE, NULL)
82
+ * Returns the AST node or null if not a literal
83
+ */
84
+ Parser.prototype.tryParseLiteral = function() {
85
+ const token = this.current();
86
+ if (!token) return null;
87
+
88
+ const literalMap = {
89
+ [TokenType.STRING]: () => new ASTNode(NodeType.Literal, { value: this.advance().value, raw: token.raw }),
90
+ [TokenType.NUMBER]: () => new ASTNode(NodeType.Literal, { value: this.advance().value }),
91
+ [TokenType.TRUE]: () => (this.advance(), new ASTNode(NodeType.Literal, { value: true })),
92
+ [TokenType.FALSE]: () => (this.advance(), new ASTNode(NodeType.Literal, { value: false })),
93
+ [TokenType.NULL]: () => (this.advance(), new ASTNode(NodeType.Literal, { value: null }))
94
+ };
95
+
96
+ return literalMap[token.type]?.() || null;
97
+ };
98
+
99
+ /**
100
+ * Parse a value (literal, object, array, etc.)
101
+ */
102
+ Parser.prototype.parseValue = function() {
103
+ if (this.is(TokenType.LBRACE)) return this.parseObjectLiteral();
104
+ if (this.is(TokenType.LBRACKET)) return this.parseArrayLiteral();
105
+
106
+ const literal = this.tryParseLiteral();
107
+ if (literal) return literal;
108
+
109
+ if (this.is(TokenType.IDENT)) return this.parseIdentifierOrExpression();
110
+
111
+ throw this.createError(
112
+ `Unexpected token ${this.current()?.type} in value`
113
+ );
114
+ };
115
+
116
+ /**
117
+ * Parse object literal
118
+ */
119
+ Parser.prototype.parseObjectLiteral = function() {
120
+ this.expect(TokenType.LBRACE);
121
+ const properties = [];
122
+
123
+ while (!this.is(TokenType.RBRACE) && !this.is(TokenType.EOF)) {
124
+ const name = this.expect(TokenType.IDENT);
125
+ this.expect(TokenType.COLON);
126
+ const value = this.parseValue();
127
+ properties.push(new ASTNode(NodeType.Property, { name: name.value, value }));
128
+
129
+ if (this.is(TokenType.COMMA)) {
130
+ this.advance();
131
+ }
132
+ }
133
+
134
+ this.expect(TokenType.RBRACE);
135
+ return new ASTNode(NodeType.ObjectLiteral, { properties });
136
+ };
137
+
138
+ /**
139
+ * Parse array literal
140
+ */
141
+ Parser.prototype.parseArrayLiteral = function() {
142
+ this.expect(TokenType.LBRACKET);
143
+ const elements = [];
144
+
145
+ while (!this.is(TokenType.RBRACKET) && !this.is(TokenType.EOF)) {
146
+ elements.push(this.parseValue());
147
+
148
+ if (this.is(TokenType.COMMA)) {
149
+ this.advance();
150
+ }
151
+ }
152
+
153
+ this.expect(TokenType.RBRACKET);
154
+ return new ASTNode(NodeType.ArrayLiteral, { elements });
155
+ };