pulse-js-framework 1.0.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.
@@ -0,0 +1,900 @@
1
+ /**
2
+ * Pulse Parser - AST builder for .pulse files
3
+ *
4
+ * Converts tokens into an Abstract Syntax Tree
5
+ */
6
+
7
+ import { TokenType, tokenize } from './lexer.js';
8
+
9
+ // AST Node types
10
+ export const NodeType = {
11
+ Program: 'Program',
12
+ PageDeclaration: 'PageDeclaration',
13
+ RouteDeclaration: 'RouteDeclaration',
14
+ StateBlock: 'StateBlock',
15
+ ViewBlock: 'ViewBlock',
16
+ ActionsBlock: 'ActionsBlock',
17
+ StyleBlock: 'StyleBlock',
18
+ Element: 'Element',
19
+ TextNode: 'TextNode',
20
+ Interpolation: 'Interpolation',
21
+ Directive: 'Directive',
22
+ IfDirective: 'IfDirective',
23
+ EachDirective: 'EachDirective',
24
+ EventDirective: 'EventDirective',
25
+ Property: 'Property',
26
+ ObjectLiteral: 'ObjectLiteral',
27
+ ArrayLiteral: 'ArrayLiteral',
28
+ Identifier: 'Identifier',
29
+ MemberExpression: 'MemberExpression',
30
+ CallExpression: 'CallExpression',
31
+ BinaryExpression: 'BinaryExpression',
32
+ UnaryExpression: 'UnaryExpression',
33
+ UpdateExpression: 'UpdateExpression',
34
+ Literal: 'Literal',
35
+ FunctionDeclaration: 'FunctionDeclaration',
36
+ StyleRule: 'StyleRule',
37
+ StyleProperty: 'StyleProperty'
38
+ };
39
+
40
+ /**
41
+ * AST Node class
42
+ */
43
+ export class ASTNode {
44
+ constructor(type, props = {}) {
45
+ this.type = type;
46
+ Object.assign(this, props);
47
+ }
48
+ }
49
+
50
+ /**
51
+ * Parser class
52
+ */
53
+ export class Parser {
54
+ constructor(tokens) {
55
+ this.tokens = tokens;
56
+ this.pos = 0;
57
+ }
58
+
59
+ /**
60
+ * Get current token
61
+ */
62
+ current() {
63
+ return this.tokens[this.pos];
64
+ }
65
+
66
+ /**
67
+ * Peek at token at offset
68
+ */
69
+ peek(offset = 1) {
70
+ return this.tokens[this.pos + offset];
71
+ }
72
+
73
+ /**
74
+ * Check if current token matches type
75
+ */
76
+ is(type) {
77
+ return this.current()?.type === type;
78
+ }
79
+
80
+ /**
81
+ * Check if current token matches any of types
82
+ */
83
+ isAny(...types) {
84
+ return types.includes(this.current()?.type);
85
+ }
86
+
87
+ /**
88
+ * Advance to next token and return current
89
+ */
90
+ advance() {
91
+ return this.tokens[this.pos++];
92
+ }
93
+
94
+ /**
95
+ * Expect a specific token type
96
+ */
97
+ expect(type, message = null) {
98
+ if (!this.is(type)) {
99
+ const token = this.current();
100
+ throw new Error(
101
+ message ||
102
+ `Expected ${type} but got ${token?.type} at line ${token?.line}:${token?.column}`
103
+ );
104
+ }
105
+ return this.advance();
106
+ }
107
+
108
+ /**
109
+ * Parse the entire program
110
+ */
111
+ parse() {
112
+ const program = new ASTNode(NodeType.Program, {
113
+ page: null,
114
+ route: null,
115
+ state: null,
116
+ view: null,
117
+ actions: null,
118
+ style: null
119
+ });
120
+
121
+ while (!this.is(TokenType.EOF)) {
122
+ if (this.is(TokenType.AT)) {
123
+ this.advance();
124
+ if (this.is(TokenType.PAGE)) {
125
+ program.page = this.parsePageDeclaration();
126
+ } else if (this.is(TokenType.ROUTE)) {
127
+ program.route = this.parseRouteDeclaration();
128
+ }
129
+ } else if (this.is(TokenType.STATE)) {
130
+ program.state = this.parseStateBlock();
131
+ } else if (this.is(TokenType.VIEW)) {
132
+ program.view = this.parseViewBlock();
133
+ } else if (this.is(TokenType.ACTIONS)) {
134
+ program.actions = this.parseActionsBlock();
135
+ } else if (this.is(TokenType.STYLE)) {
136
+ program.style = this.parseStyleBlock();
137
+ } else {
138
+ throw new Error(
139
+ `Unexpected token ${this.current()?.type} at line ${this.current()?.line}`
140
+ );
141
+ }
142
+ }
143
+
144
+ return program;
145
+ }
146
+
147
+ /**
148
+ * Parse @page declaration
149
+ */
150
+ parsePageDeclaration() {
151
+ this.expect(TokenType.PAGE);
152
+ const name = this.expect(TokenType.IDENT);
153
+ return new ASTNode(NodeType.PageDeclaration, { name: name.value });
154
+ }
155
+
156
+ /**
157
+ * Parse @route declaration
158
+ */
159
+ parseRouteDeclaration() {
160
+ this.expect(TokenType.ROUTE);
161
+ const path = this.expect(TokenType.STRING);
162
+ return new ASTNode(NodeType.RouteDeclaration, { path: path.value });
163
+ }
164
+
165
+ /**
166
+ * Parse state block
167
+ */
168
+ parseStateBlock() {
169
+ this.expect(TokenType.STATE);
170
+ this.expect(TokenType.LBRACE);
171
+
172
+ const properties = [];
173
+ while (!this.is(TokenType.RBRACE) && !this.is(TokenType.EOF)) {
174
+ properties.push(this.parseStateProperty());
175
+ }
176
+
177
+ this.expect(TokenType.RBRACE);
178
+ return new ASTNode(NodeType.StateBlock, { properties });
179
+ }
180
+
181
+ /**
182
+ * Parse a state property
183
+ */
184
+ parseStateProperty() {
185
+ const name = this.expect(TokenType.IDENT);
186
+ this.expect(TokenType.COLON);
187
+ const value = this.parseValue();
188
+ return new ASTNode(NodeType.Property, { name: name.value, value });
189
+ }
190
+
191
+ /**
192
+ * Parse a value (literal, object, array, etc.)
193
+ */
194
+ parseValue() {
195
+ if (this.is(TokenType.LBRACE)) {
196
+ return this.parseObjectLiteral();
197
+ }
198
+ if (this.is(TokenType.LBRACKET)) {
199
+ return this.parseArrayLiteral();
200
+ }
201
+ if (this.is(TokenType.STRING)) {
202
+ const token = this.advance();
203
+ return new ASTNode(NodeType.Literal, { value: token.value, raw: token.raw });
204
+ }
205
+ if (this.is(TokenType.NUMBER)) {
206
+ const token = this.advance();
207
+ return new ASTNode(NodeType.Literal, { value: token.value });
208
+ }
209
+ if (this.is(TokenType.TRUE)) {
210
+ this.advance();
211
+ return new ASTNode(NodeType.Literal, { value: true });
212
+ }
213
+ if (this.is(TokenType.FALSE)) {
214
+ this.advance();
215
+ return new ASTNode(NodeType.Literal, { value: false });
216
+ }
217
+ if (this.is(TokenType.NULL)) {
218
+ this.advance();
219
+ return new ASTNode(NodeType.Literal, { value: null });
220
+ }
221
+ if (this.is(TokenType.IDENT)) {
222
+ return this.parseIdentifierOrExpression();
223
+ }
224
+
225
+ throw new Error(
226
+ `Unexpected token ${this.current()?.type} in value at line ${this.current()?.line}`
227
+ );
228
+ }
229
+
230
+ /**
231
+ * Parse object literal
232
+ */
233
+ parseObjectLiteral() {
234
+ this.expect(TokenType.LBRACE);
235
+ const properties = [];
236
+
237
+ while (!this.is(TokenType.RBRACE) && !this.is(TokenType.EOF)) {
238
+ const name = this.expect(TokenType.IDENT);
239
+ this.expect(TokenType.COLON);
240
+ const value = this.parseValue();
241
+ properties.push(new ASTNode(NodeType.Property, { name: name.value, value }));
242
+
243
+ if (this.is(TokenType.COMMA)) {
244
+ this.advance();
245
+ }
246
+ }
247
+
248
+ this.expect(TokenType.RBRACE);
249
+ return new ASTNode(NodeType.ObjectLiteral, { properties });
250
+ }
251
+
252
+ /**
253
+ * Parse array literal
254
+ */
255
+ parseArrayLiteral() {
256
+ this.expect(TokenType.LBRACKET);
257
+ const elements = [];
258
+
259
+ while (!this.is(TokenType.RBRACKET) && !this.is(TokenType.EOF)) {
260
+ elements.push(this.parseValue());
261
+
262
+ if (this.is(TokenType.COMMA)) {
263
+ this.advance();
264
+ }
265
+ }
266
+
267
+ this.expect(TokenType.RBRACKET);
268
+ return new ASTNode(NodeType.ArrayLiteral, { elements });
269
+ }
270
+
271
+ /**
272
+ * Parse view block
273
+ */
274
+ parseViewBlock() {
275
+ this.expect(TokenType.VIEW);
276
+ this.expect(TokenType.LBRACE);
277
+
278
+ const children = [];
279
+ while (!this.is(TokenType.RBRACE) && !this.is(TokenType.EOF)) {
280
+ children.push(this.parseViewChild());
281
+ }
282
+
283
+ this.expect(TokenType.RBRACE);
284
+ return new ASTNode(NodeType.ViewBlock, { children });
285
+ }
286
+
287
+ /**
288
+ * Parse a view child (element, directive, or text)
289
+ */
290
+ parseViewChild() {
291
+ if (this.is(TokenType.AT)) {
292
+ return this.parseDirective();
293
+ }
294
+ if (this.is(TokenType.SELECTOR) || this.is(TokenType.IDENT)) {
295
+ return this.parseElement();
296
+ }
297
+ if (this.is(TokenType.STRING)) {
298
+ return this.parseTextNode();
299
+ }
300
+
301
+ throw new Error(
302
+ `Unexpected token ${this.current()?.type} in view at line ${this.current()?.line}`
303
+ );
304
+ }
305
+
306
+ /**
307
+ * Parse an element
308
+ */
309
+ parseElement() {
310
+ const selector = this.isAny(TokenType.SELECTOR, TokenType.IDENT)
311
+ ? this.advance().value
312
+ : '';
313
+
314
+ const directives = [];
315
+ const textContent = [];
316
+ const children = [];
317
+
318
+ // Parse inline directives and text
319
+ while (!this.is(TokenType.LBRACE) && !this.is(TokenType.RBRACE) &&
320
+ !this.is(TokenType.SELECTOR) && !this.is(TokenType.EOF)) {
321
+ if (this.is(TokenType.AT)) {
322
+ directives.push(this.parseInlineDirective());
323
+ } else if (this.is(TokenType.STRING)) {
324
+ textContent.push(this.parseTextNode());
325
+ } else if (this.is(TokenType.IDENT) && !this.couldBeElement()) {
326
+ break;
327
+ } else {
328
+ break;
329
+ }
330
+ }
331
+
332
+ // Parse children if there's a block
333
+ if (this.is(TokenType.LBRACE)) {
334
+ this.advance();
335
+ while (!this.is(TokenType.RBRACE) && !this.is(TokenType.EOF)) {
336
+ children.push(this.parseViewChild());
337
+ }
338
+ this.expect(TokenType.RBRACE);
339
+ }
340
+
341
+ return new ASTNode(NodeType.Element, {
342
+ selector,
343
+ directives,
344
+ textContent,
345
+ children
346
+ });
347
+ }
348
+
349
+ /**
350
+ * Check if current position could be an element
351
+ */
352
+ couldBeElement() {
353
+ const next = this.peek();
354
+ return next?.type === TokenType.LBRACE ||
355
+ next?.type === TokenType.AT ||
356
+ next?.type === TokenType.STRING;
357
+ }
358
+
359
+ /**
360
+ * Parse a text node
361
+ */
362
+ parseTextNode() {
363
+ const token = this.expect(TokenType.STRING);
364
+ const parts = this.parseInterpolatedString(token.value);
365
+ return new ASTNode(NodeType.TextNode, { parts });
366
+ }
367
+
368
+ /**
369
+ * Parse interpolated string into parts
370
+ * "Hello, {name}!" -> ["Hello, ", { expr: "name" }, "!"]
371
+ */
372
+ parseInterpolatedString(str) {
373
+ const parts = [];
374
+ let current = '';
375
+ let i = 0;
376
+
377
+ while (i < str.length) {
378
+ if (str[i] === '{') {
379
+ if (current) {
380
+ parts.push(current);
381
+ current = '';
382
+ }
383
+ i++; // skip {
384
+ let expr = '';
385
+ let braceCount = 1;
386
+ while (i < str.length && braceCount > 0) {
387
+ if (str[i] === '{') braceCount++;
388
+ else if (str[i] === '}') braceCount--;
389
+ if (braceCount > 0) expr += str[i];
390
+ i++;
391
+ }
392
+ parts.push(new ASTNode(NodeType.Interpolation, { expression: expr.trim() }));
393
+ } else {
394
+ current += str[i];
395
+ i++;
396
+ }
397
+ }
398
+
399
+ if (current) {
400
+ parts.push(current);
401
+ }
402
+
403
+ return parts;
404
+ }
405
+
406
+ /**
407
+ * Parse a directive (@if, @each, @click, etc.)
408
+ */
409
+ parseDirective() {
410
+ this.expect(TokenType.AT);
411
+ const name = this.expect(TokenType.IDENT).value;
412
+
413
+ if (name === 'if') {
414
+ return this.parseIfDirective();
415
+ }
416
+ if (name === 'each') {
417
+ return this.parseEachDirective();
418
+ }
419
+
420
+ // Event directive like @click
421
+ return this.parseEventDirective(name);
422
+ }
423
+
424
+ /**
425
+ * Parse inline directive
426
+ */
427
+ parseInlineDirective() {
428
+ this.expect(TokenType.AT);
429
+ const name = this.expect(TokenType.IDENT).value;
430
+
431
+ // Event directive
432
+ this.expect(TokenType.LPAREN);
433
+ const expression = this.parseExpression();
434
+ this.expect(TokenType.RPAREN);
435
+
436
+ return new ASTNode(NodeType.EventDirective, { event: name, handler: expression });
437
+ }
438
+
439
+ /**
440
+ * Parse @if directive
441
+ */
442
+ parseIfDirective() {
443
+ this.expect(TokenType.LPAREN);
444
+ const condition = this.parseExpression();
445
+ this.expect(TokenType.RPAREN);
446
+
447
+ this.expect(TokenType.LBRACE);
448
+ const consequent = [];
449
+ while (!this.is(TokenType.RBRACE) && !this.is(TokenType.EOF)) {
450
+ consequent.push(this.parseViewChild());
451
+ }
452
+ this.expect(TokenType.RBRACE);
453
+
454
+ let alternate = null;
455
+ if (this.is(TokenType.AT) && this.peek()?.value === 'else') {
456
+ this.advance(); // @
457
+ this.advance(); // else
458
+ this.expect(TokenType.LBRACE);
459
+ alternate = [];
460
+ while (!this.is(TokenType.RBRACE) && !this.is(TokenType.EOF)) {
461
+ alternate.push(this.parseViewChild());
462
+ }
463
+ this.expect(TokenType.RBRACE);
464
+ }
465
+
466
+ return new ASTNode(NodeType.IfDirective, { condition, consequent, alternate });
467
+ }
468
+
469
+ /**
470
+ * Parse @each directive
471
+ */
472
+ parseEachDirective() {
473
+ this.expect(TokenType.LPAREN);
474
+ const itemName = this.expect(TokenType.IDENT).value;
475
+ this.expect(TokenType.IN);
476
+ const iterable = this.parseExpression();
477
+ this.expect(TokenType.RPAREN);
478
+
479
+ this.expect(TokenType.LBRACE);
480
+ const template = [];
481
+ while (!this.is(TokenType.RBRACE) && !this.is(TokenType.EOF)) {
482
+ template.push(this.parseViewChild());
483
+ }
484
+ this.expect(TokenType.RBRACE);
485
+
486
+ return new ASTNode(NodeType.EachDirective, { itemName, iterable, template });
487
+ }
488
+
489
+ /**
490
+ * Parse event directive
491
+ */
492
+ parseEventDirective(event) {
493
+ this.expect(TokenType.LPAREN);
494
+ const handler = this.parseExpression();
495
+ this.expect(TokenType.RPAREN);
496
+
497
+ const children = [];
498
+ if (this.is(TokenType.LBRACE)) {
499
+ this.advance();
500
+ while (!this.is(TokenType.RBRACE) && !this.is(TokenType.EOF)) {
501
+ children.push(this.parseViewChild());
502
+ }
503
+ this.expect(TokenType.RBRACE);
504
+ }
505
+
506
+ return new ASTNode(NodeType.EventDirective, { event, handler, children });
507
+ }
508
+
509
+ /**
510
+ * Parse expression (simplified)
511
+ */
512
+ parseExpression() {
513
+ return this.parseOrExpression();
514
+ }
515
+
516
+ /**
517
+ * Parse OR expression
518
+ */
519
+ parseOrExpression() {
520
+ let left = this.parseAndExpression();
521
+
522
+ while (this.is(TokenType.OR)) {
523
+ this.advance();
524
+ const right = this.parseAndExpression();
525
+ left = new ASTNode(NodeType.BinaryExpression, { operator: '||', left, right });
526
+ }
527
+
528
+ return left;
529
+ }
530
+
531
+ /**
532
+ * Parse AND expression
533
+ */
534
+ parseAndExpression() {
535
+ let left = this.parseComparisonExpression();
536
+
537
+ while (this.is(TokenType.AND)) {
538
+ this.advance();
539
+ const right = this.parseComparisonExpression();
540
+ left = new ASTNode(NodeType.BinaryExpression, { operator: '&&', left, right });
541
+ }
542
+
543
+ return left;
544
+ }
545
+
546
+ /**
547
+ * Parse comparison expression
548
+ */
549
+ parseComparisonExpression() {
550
+ let left = this.parseAdditiveExpression();
551
+
552
+ while (this.isAny(TokenType.EQEQ, TokenType.NEQ, TokenType.LT, TokenType.GT,
553
+ TokenType.LTE, TokenType.GTE)) {
554
+ const operator = this.advance().value;
555
+ const right = this.parseAdditiveExpression();
556
+ left = new ASTNode(NodeType.BinaryExpression, { operator, left, right });
557
+ }
558
+
559
+ return left;
560
+ }
561
+
562
+ /**
563
+ * Parse additive expression
564
+ */
565
+ parseAdditiveExpression() {
566
+ let left = this.parseMultiplicativeExpression();
567
+
568
+ while (this.isAny(TokenType.PLUS, TokenType.MINUS)) {
569
+ const operator = this.advance().value;
570
+ const right = this.parseMultiplicativeExpression();
571
+ left = new ASTNode(NodeType.BinaryExpression, { operator, left, right });
572
+ }
573
+
574
+ return left;
575
+ }
576
+
577
+ /**
578
+ * Parse multiplicative expression
579
+ */
580
+ parseMultiplicativeExpression() {
581
+ let left = this.parseUnaryExpression();
582
+
583
+ while (this.isAny(TokenType.STAR, TokenType.SLASH)) {
584
+ const operator = this.advance().value;
585
+ const right = this.parseUnaryExpression();
586
+ left = new ASTNode(NodeType.BinaryExpression, { operator, left, right });
587
+ }
588
+
589
+ return left;
590
+ }
591
+
592
+ /**
593
+ * Parse unary expression
594
+ */
595
+ parseUnaryExpression() {
596
+ if (this.is(TokenType.NOT)) {
597
+ this.advance();
598
+ const argument = this.parseUnaryExpression();
599
+ return new ASTNode(NodeType.UnaryExpression, { operator: '!', argument });
600
+ }
601
+ if (this.is(TokenType.MINUS)) {
602
+ this.advance();
603
+ const argument = this.parseUnaryExpression();
604
+ return new ASTNode(NodeType.UnaryExpression, { operator: '-', argument });
605
+ }
606
+
607
+ return this.parsePostfixExpression();
608
+ }
609
+
610
+ /**
611
+ * Parse postfix expression (++, --)
612
+ */
613
+ parsePostfixExpression() {
614
+ let expr = this.parsePrimaryExpression();
615
+
616
+ while (this.isAny(TokenType.PLUSPLUS, TokenType.MINUSMINUS)) {
617
+ const operator = this.advance().value;
618
+ expr = new ASTNode(NodeType.UpdateExpression, {
619
+ operator,
620
+ argument: expr,
621
+ prefix: false
622
+ });
623
+ }
624
+
625
+ return expr;
626
+ }
627
+
628
+ /**
629
+ * Parse primary expression
630
+ */
631
+ parsePrimaryExpression() {
632
+ if (this.is(TokenType.LPAREN)) {
633
+ this.advance();
634
+ const expr = this.parseExpression();
635
+ this.expect(TokenType.RPAREN);
636
+ return expr;
637
+ }
638
+
639
+ if (this.is(TokenType.NUMBER)) {
640
+ const token = this.advance();
641
+ return new ASTNode(NodeType.Literal, { value: token.value });
642
+ }
643
+
644
+ if (this.is(TokenType.STRING)) {
645
+ const token = this.advance();
646
+ return new ASTNode(NodeType.Literal, { value: token.value, raw: token.raw });
647
+ }
648
+
649
+ if (this.is(TokenType.TRUE)) {
650
+ this.advance();
651
+ return new ASTNode(NodeType.Literal, { value: true });
652
+ }
653
+
654
+ if (this.is(TokenType.FALSE)) {
655
+ this.advance();
656
+ return new ASTNode(NodeType.Literal, { value: false });
657
+ }
658
+
659
+ if (this.is(TokenType.NULL)) {
660
+ this.advance();
661
+ return new ASTNode(NodeType.Literal, { value: null });
662
+ }
663
+
664
+ if (this.is(TokenType.IDENT)) {
665
+ return this.parseIdentifierOrExpression();
666
+ }
667
+
668
+ throw new Error(
669
+ `Unexpected token ${this.current()?.type} in expression at line ${this.current()?.line}`
670
+ );
671
+ }
672
+
673
+ /**
674
+ * Parse identifier with possible member access and calls
675
+ */
676
+ parseIdentifierOrExpression() {
677
+ let expr = new ASTNode(NodeType.Identifier, { name: this.advance().value });
678
+
679
+ while (true) {
680
+ if (this.is(TokenType.DOT)) {
681
+ this.advance();
682
+ const property = this.expect(TokenType.IDENT);
683
+ expr = new ASTNode(NodeType.MemberExpression, {
684
+ object: expr,
685
+ property: property.value
686
+ });
687
+ } else if (this.is(TokenType.LBRACKET)) {
688
+ this.advance();
689
+ const property = this.parseExpression();
690
+ this.expect(TokenType.RBRACKET);
691
+ expr = new ASTNode(NodeType.MemberExpression, {
692
+ object: expr,
693
+ property,
694
+ computed: true
695
+ });
696
+ } else if (this.is(TokenType.LPAREN)) {
697
+ this.advance();
698
+ const args = [];
699
+ while (!this.is(TokenType.RPAREN) && !this.is(TokenType.EOF)) {
700
+ args.push(this.parseExpression());
701
+ if (this.is(TokenType.COMMA)) {
702
+ this.advance();
703
+ }
704
+ }
705
+ this.expect(TokenType.RPAREN);
706
+ expr = new ASTNode(NodeType.CallExpression, { callee: expr, arguments: args });
707
+ } else {
708
+ break;
709
+ }
710
+ }
711
+
712
+ return expr;
713
+ }
714
+
715
+ /**
716
+ * Parse actions block
717
+ */
718
+ parseActionsBlock() {
719
+ this.expect(TokenType.ACTIONS);
720
+ this.expect(TokenType.LBRACE);
721
+
722
+ const functions = [];
723
+ while (!this.is(TokenType.RBRACE) && !this.is(TokenType.EOF)) {
724
+ functions.push(this.parseFunctionDeclaration());
725
+ }
726
+
727
+ this.expect(TokenType.RBRACE);
728
+ return new ASTNode(NodeType.ActionsBlock, { functions });
729
+ }
730
+
731
+ /**
732
+ * Parse function declaration
733
+ */
734
+ parseFunctionDeclaration() {
735
+ let async = false;
736
+ if (this.is(TokenType.IDENT) && this.current().value === 'async') {
737
+ this.advance();
738
+ async = true;
739
+ }
740
+
741
+ const name = this.expect(TokenType.IDENT).value;
742
+ this.expect(TokenType.LPAREN);
743
+
744
+ const params = [];
745
+ while (!this.is(TokenType.RPAREN) && !this.is(TokenType.EOF)) {
746
+ params.push(this.expect(TokenType.IDENT).value);
747
+ if (this.is(TokenType.COMMA)) {
748
+ this.advance();
749
+ }
750
+ }
751
+ this.expect(TokenType.RPAREN);
752
+
753
+ // Parse function body as raw JS
754
+ this.expect(TokenType.LBRACE);
755
+ const body = this.parseFunctionBody();
756
+ this.expect(TokenType.RBRACE);
757
+
758
+ return new ASTNode(NodeType.FunctionDeclaration, { name, params, body, async });
759
+ }
760
+
761
+ /**
762
+ * Parse function body (raw content between braces)
763
+ */
764
+ parseFunctionBody() {
765
+ // Simplified: collect all tokens until matching }
766
+ const statements = [];
767
+ let braceCount = 1;
768
+
769
+ while (!this.is(TokenType.EOF)) {
770
+ if (this.is(TokenType.LBRACE)) {
771
+ braceCount++;
772
+ } else if (this.is(TokenType.RBRACE)) {
773
+ braceCount--;
774
+ if (braceCount === 0) break;
775
+ }
776
+
777
+ // Collect raw token for reconstruction
778
+ statements.push(this.current());
779
+ this.advance();
780
+ }
781
+
782
+ return statements;
783
+ }
784
+
785
+ /**
786
+ * Parse style block
787
+ */
788
+ parseStyleBlock() {
789
+ this.expect(TokenType.STYLE);
790
+ this.expect(TokenType.LBRACE);
791
+
792
+ const rules = [];
793
+ while (!this.is(TokenType.RBRACE) && !this.is(TokenType.EOF)) {
794
+ rules.push(this.parseStyleRule());
795
+ }
796
+
797
+ this.expect(TokenType.RBRACE);
798
+ return new ASTNode(NodeType.StyleBlock, { rules });
799
+ }
800
+
801
+ /**
802
+ * Parse style rule
803
+ */
804
+ parseStyleRule() {
805
+ // Parse selector
806
+ let selector = '';
807
+ while (!this.is(TokenType.LBRACE) && !this.is(TokenType.EOF)) {
808
+ selector += this.advance().value;
809
+ }
810
+ selector = selector.trim();
811
+
812
+ this.expect(TokenType.LBRACE);
813
+
814
+ const properties = [];
815
+ const nestedRules = [];
816
+
817
+ while (!this.is(TokenType.RBRACE) && !this.is(TokenType.EOF)) {
818
+ // Check if this is a nested rule or a property
819
+ if (this.isNestedRule()) {
820
+ nestedRules.push(this.parseStyleRule());
821
+ } else {
822
+ properties.push(this.parseStyleProperty());
823
+ }
824
+ }
825
+
826
+ this.expect(TokenType.RBRACE);
827
+ return new ASTNode(NodeType.StyleRule, { selector, properties, nestedRules });
828
+ }
829
+
830
+ /**
831
+ * Check if current position is a nested rule
832
+ */
833
+ isNestedRule() {
834
+ // Look ahead to see if there's a { before a : or newline
835
+ let i = 0;
836
+ while (this.peek(i) && this.peek(i).type !== TokenType.EOF) {
837
+ const token = this.peek(i);
838
+ if (token.type === TokenType.LBRACE) return true;
839
+ if (token.type === TokenType.COLON) return false;
840
+ if (token.type === TokenType.RBRACE) return false;
841
+ i++;
842
+ }
843
+ return false;
844
+ }
845
+
846
+ /**
847
+ * Parse style property
848
+ */
849
+ parseStyleProperty() {
850
+ let name = '';
851
+ while (!this.is(TokenType.COLON) && !this.is(TokenType.EOF)) {
852
+ name += this.advance().value;
853
+ }
854
+ name = name.trim();
855
+
856
+ this.expect(TokenType.COLON);
857
+
858
+ let value = '';
859
+ while (!this.is(TokenType.SEMICOLON) && !this.is(TokenType.RBRACE) &&
860
+ !this.is(TokenType.EOF) && !this.isPropertyStart()) {
861
+ value += this.advance().value + ' ';
862
+ }
863
+ value = value.trim();
864
+
865
+ if (this.is(TokenType.SEMICOLON)) {
866
+ this.advance();
867
+ }
868
+
869
+ return new ASTNode(NodeType.StyleProperty, { name, value });
870
+ }
871
+
872
+ /**
873
+ * Check if current position starts a new property
874
+ */
875
+ isPropertyStart() {
876
+ // Check if it looks like: identifier followed by :
877
+ if (!this.is(TokenType.IDENT)) return false;
878
+ let i = 1;
879
+ while (this.peek(i) && this.peek(i).type === TokenType.IDENT) {
880
+ i++;
881
+ }
882
+ return this.peek(i)?.type === TokenType.COLON;
883
+ }
884
+ }
885
+
886
+ /**
887
+ * Parse source code into AST
888
+ */
889
+ export function parse(source) {
890
+ const tokens = tokenize(source);
891
+ const parser = new Parser(tokens);
892
+ return parser.parse();
893
+ }
894
+
895
+ export default {
896
+ NodeType,
897
+ ASTNode,
898
+ Parser,
899
+ parse
900
+ };