hispano-lang 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.
package/dist/parser.js ADDED
@@ -0,0 +1,1050 @@
1
+ /**
2
+ * Parser for HispanoLang
3
+ * Converts tokens into an Abstract Syntax Tree (AST)
4
+ */
5
+
6
+ class Parser {
7
+ constructor(tokens) {
8
+ this.tokens = tokens;
9
+ this.current = 0;
10
+ }
11
+
12
+ /**
13
+ * Parses tokens and returns a list of statements
14
+ * @returns {Array} List of statements (AST)
15
+ */
16
+ parse() {
17
+ const statements = [];
18
+
19
+ while (!this.isAtEnd()) {
20
+ statements.push(this.declaration());
21
+
22
+ // Consume semicolon if present between statements
23
+ if (this.match('SEMICOLON')) {
24
+ // Semicolon consumed
25
+ }
26
+ }
27
+
28
+ return statements;
29
+ }
30
+
31
+ /**
32
+ * Parses a declaration
33
+ * @returns {Object} Parsed declaration
34
+ */
35
+ declaration() {
36
+ try {
37
+ if (this.match('VARIABLE')) {
38
+ return this.variableDeclaration();
39
+ }
40
+
41
+ if (this.match('FUNCION')) {
42
+ return this.functionDeclaration();
43
+ }
44
+
45
+ if (this.match('MOSTRAR')) {
46
+ return this.mostrarStatement();
47
+ }
48
+
49
+ if (this.match('LEER')) {
50
+ return this.leerStatement();
51
+ }
52
+
53
+ if (this.match('SI')) {
54
+ return this.ifStatement();
55
+ }
56
+
57
+ if (this.match('MIENTRAS')) {
58
+ return this.whileStatement();
59
+ }
60
+
61
+ if (this.match('PARA')) {
62
+ return this.forStatement();
63
+ }
64
+
65
+ if (this.match('RETORNAR')) {
66
+ return this.returnStatement();
67
+ }
68
+
69
+ if (this.match('ROMPER')) {
70
+ return this.breakStatement();
71
+ }
72
+
73
+ if (this.match('CONTINUAR')) {
74
+ return this.continueStatement();
75
+ }
76
+
77
+ if (this.match('INTENTAR')) {
78
+ return this.tryStatement();
79
+ }
80
+
81
+ return this.expressionStatement();
82
+ } catch (error) {
83
+ this.synchronize();
84
+ throw error;
85
+ }
86
+ }
87
+
88
+ /**
89
+ * Parses a variable declaration
90
+ * @returns {Object} Variable declaration
91
+ */
92
+ variableDeclaration() {
93
+ let name;
94
+ if (this.match('IDENTIFIER')) {
95
+ name = this.previous();
96
+ } else if (this.match('AND')) {
97
+ name = this.previous();
98
+ } else {
99
+ throw new Error('Expected variable name');
100
+ }
101
+
102
+ let initializer = null;
103
+ if (this.match('EQUAL')) {
104
+ initializer = this.expression();
105
+ }
106
+
107
+ return {
108
+ type: 'VariableDeclaration',
109
+ name: name.lexeme,
110
+ initializer,
111
+ };
112
+ }
113
+
114
+ /**
115
+ * Parses a function declaration
116
+ * @returns {Object} Function declaration
117
+ */
118
+ functionDeclaration() {
119
+ const name = this.consume('IDENTIFIER', 'Expected function name');
120
+ this.consume('LEFT_PAREN', 'Expected ( after function name');
121
+
122
+ const parameters = [];
123
+ if (!this.check('RIGHT_PAREN')) {
124
+ do {
125
+ if (parameters.length >= 255) {
126
+ throw new Error('Cannot have more than 255 parameters');
127
+ }
128
+ let param;
129
+ if (this.match('IDENTIFIER')) {
130
+ param = this.previous();
131
+ } else if (this.match('AND')) {
132
+ param = this.previous();
133
+ } else {
134
+ throw new Error('Expected parameter name');
135
+ }
136
+ parameters.push(param.lexeme);
137
+ } while (this.match('COMMA'));
138
+ }
139
+
140
+ this.consume('RIGHT_PAREN', 'Expected ) after parameters');
141
+ this.consume('LEFT_BRACE', 'Expected { before function body');
142
+ const body = this.block();
143
+ this.consume('RIGHT_BRACE', 'Expected } after function body');
144
+
145
+ return {
146
+ type: 'FunctionDeclaration',
147
+ name: name.lexeme,
148
+ parameters,
149
+ body,
150
+ };
151
+ }
152
+
153
+ /**
154
+ * Parses a show statement
155
+ * @returns {Object} Show statement
156
+ */
157
+ mostrarStatement() {
158
+ const value = this.expression();
159
+
160
+ return {
161
+ type: 'MostrarStatement',
162
+ expression: value,
163
+ };
164
+ }
165
+
166
+ /**
167
+ * Parses a read statement
168
+ * @returns {Object} Read statement
169
+ */
170
+ leerStatement() {
171
+ // Parse the variable name to store the input
172
+ const variableName = this.consume(
173
+ 'IDENTIFIER',
174
+ 'Expected variable name after leer'
175
+ );
176
+
177
+ // Optional prompt message
178
+ let prompt = null;
179
+ if (this.match('STRING')) {
180
+ prompt = this.previous().literal;
181
+ }
182
+
183
+ return {
184
+ type: 'LeerStatement',
185
+ variable: variableName.lexeme,
186
+ prompt: prompt,
187
+ };
188
+ }
189
+
190
+ /**
191
+ * Parses an if statement
192
+ * @returns {Object} If statement
193
+ */
194
+ ifStatement() {
195
+ const condition = this.expression();
196
+ this.consume('LEFT_BRACE', 'Expected { after condition');
197
+ const thenBranch = this.block();
198
+ this.consume('RIGHT_BRACE', 'Expected } after block');
199
+ let elseBranch = null;
200
+
201
+ if (this.match('SINO')) {
202
+ this.consume('LEFT_BRACE', 'Expected { after else');
203
+ elseBranch = this.block();
204
+ this.consume('RIGHT_BRACE', 'Expected } after else block');
205
+ }
206
+
207
+ return {
208
+ type: 'IfStatement',
209
+ condition,
210
+ thenBranch,
211
+ elseBranch,
212
+ };
213
+ }
214
+
215
+ /**
216
+ * Parses a while statement
217
+ * @returns {Object} While statement
218
+ */
219
+ whileStatement() {
220
+ const condition = this.expression();
221
+ this.consume('LEFT_BRACE', 'Expected { after condition');
222
+ const body = this.block();
223
+ this.consume('RIGHT_BRACE', 'Expected } after block');
224
+
225
+ return {
226
+ type: 'WhileStatement',
227
+ condition,
228
+ body,
229
+ };
230
+ }
231
+
232
+ /**
233
+ * Parses a for statement
234
+ * @returns {Object} For statement
235
+ */
236
+ forStatement() {
237
+ this.consume('LEFT_PAREN', 'Expected ( after para');
238
+
239
+ // Initializer (optional)
240
+ let initializer = null;
241
+ if (!this.check('SEMICOLON')) {
242
+ if (this.match('VARIABLE')) {
243
+ initializer = this.variableDeclaration();
244
+ } else {
245
+ initializer = this.expressionStatement();
246
+ }
247
+ // Consume the semicolon after initializer
248
+ this.consume('SEMICOLON', 'Expected ; after for initializer');
249
+ } else {
250
+ // Skip the first semicolon if no initializer
251
+ this.advance();
252
+ }
253
+
254
+ // Condition (optional)
255
+ let condition = null;
256
+ if (!this.check('SEMICOLON')) {
257
+ condition = this.expression();
258
+ }
259
+ this.consume('SEMICOLON', 'Expected ; after condition');
260
+
261
+ // Increment (optional)
262
+ let increment = null;
263
+ if (!this.check('RIGHT_PAREN')) {
264
+ increment = this.expression();
265
+ }
266
+ this.consume('RIGHT_PAREN', 'Expected ) after for clause');
267
+
268
+ this.consume('LEFT_BRACE', 'Expected { after for clause');
269
+ const body = this.block();
270
+ this.consume('RIGHT_BRACE', 'Expected } after block');
271
+
272
+ return {
273
+ type: 'ForStatement',
274
+ initializer,
275
+ condition,
276
+ increment,
277
+ body,
278
+ };
279
+ }
280
+
281
+ /**
282
+ * Parses a return statement
283
+ * @returns {Object} Return statement
284
+ */
285
+ returnStatement() {
286
+ const keyword = this.previous();
287
+ let value = null;
288
+ if (!this.check('RIGHT_BRACE')) {
289
+ value = this.expression();
290
+ }
291
+
292
+ return {
293
+ type: 'ReturnStatement',
294
+ keyword,
295
+ value,
296
+ };
297
+ }
298
+
299
+ /**
300
+ * Parses a break statement
301
+ * @returns {Object} Break statement
302
+ */
303
+ breakStatement() {
304
+ const keyword = this.previous();
305
+ return {
306
+ type: 'BreakStatement',
307
+ keyword,
308
+ };
309
+ }
310
+
311
+ /**
312
+ * Parses a continue statement
313
+ * @returns {Object} Continue statement
314
+ */
315
+ continueStatement() {
316
+ const keyword = this.previous();
317
+ return {
318
+ type: 'ContinueStatement',
319
+ keyword,
320
+ };
321
+ }
322
+
323
+ /**
324
+ * Parses a code block
325
+ * @returns {Array} List of statements in the block
326
+ */
327
+ block() {
328
+ const statements = [];
329
+
330
+ while (!this.check('RIGHT_BRACE') && !this.isAtEnd()) {
331
+ statements.push(this.declaration());
332
+ }
333
+
334
+ return statements;
335
+ }
336
+
337
+ /**
338
+ * Parses an expression statement
339
+ * @returns {Object} Expression statement
340
+ */
341
+ expressionStatement() {
342
+ const expr = this.expression();
343
+ return {
344
+ type: 'ExpressionStatement',
345
+ expression: expr,
346
+ };
347
+ }
348
+
349
+ /**
350
+ * Parses an expression
351
+ * @returns {Object} Parsed expression
352
+ */
353
+ expression() {
354
+ return this.assignment();
355
+ }
356
+
357
+ /**
358
+ * Parses a complete expression that can be followed by array access
359
+ * @returns {Object} Parsed expression
360
+ */
361
+ parseExpression() {
362
+ let expr = this.equality();
363
+
364
+ while (this.match('LEFT_BRACKET')) {
365
+ expr = this.finishArrayAccess(expr);
366
+ }
367
+
368
+ return expr;
369
+ }
370
+
371
+ /**
372
+ * Parses an assignment
373
+ * @returns {Object} Parsed assignment
374
+ */
375
+ assignment() {
376
+ let expr = this.logicalOr();
377
+
378
+ if (this.match('EQUAL')) {
379
+ const equals = this.previous();
380
+ const value = this.logicalOr();
381
+
382
+ if (expr.type === 'Variable') {
383
+ const name = expr.name;
384
+ return {
385
+ type: 'Assign',
386
+ name,
387
+ value,
388
+ };
389
+ }
390
+
391
+ if (expr.type === 'ArrayAccess') {
392
+ return {
393
+ type: 'ArrayAssign',
394
+ array: expr.array,
395
+ index: expr.index,
396
+ value,
397
+ };
398
+ }
399
+
400
+ if (expr.type === 'PropertyAccess') {
401
+ return {
402
+ type: 'PropertyAssign',
403
+ object: expr.object,
404
+ name: expr.name,
405
+ value,
406
+ };
407
+ }
408
+
409
+ throw new Error('Invalid assignment target');
410
+ }
411
+
412
+ // Handle compound assignment operators
413
+ if (
414
+ this.match(
415
+ 'PLUS_EQUAL',
416
+ 'MINUS_EQUAL',
417
+ 'STAR_EQUAL',
418
+ 'SLASH_EQUAL',
419
+ 'PERCENT_EQUAL'
420
+ )
421
+ ) {
422
+ const operator = this.previous();
423
+ const value = this.logicalOr();
424
+
425
+ if (expr.type === 'Variable') {
426
+ const name = expr.name;
427
+ return {
428
+ type: 'CompoundAssign',
429
+ name,
430
+ operator: operator.type,
431
+ value,
432
+ };
433
+ }
434
+
435
+ if (expr.type === 'ArrayAccess') {
436
+ return {
437
+ type: 'CompoundArrayAssign',
438
+ array: expr.array,
439
+ index: expr.index,
440
+ operator: operator.type,
441
+ value,
442
+ };
443
+ }
444
+
445
+ if (expr.type === 'PropertyAccess') {
446
+ return {
447
+ type: 'CompoundPropertyAssign',
448
+ object: expr.object,
449
+ name: expr.name,
450
+ operator: operator.type,
451
+ value,
452
+ };
453
+ }
454
+
455
+ throw new Error('Invalid compound assignment target');
456
+ }
457
+
458
+ return expr;
459
+ }
460
+
461
+ /**
462
+ * Parses an equality expression
463
+ * @returns {Object} Equality expression
464
+ */
465
+ equality() {
466
+ let expr = this.comparison();
467
+
468
+ while (this.match('EQUAL_EQUAL', 'BANG_EQUAL')) {
469
+ const operator = this.previous();
470
+ const right = this.comparison();
471
+ expr = {
472
+ type: 'Binary',
473
+ left: expr,
474
+ operator: operator.type,
475
+ right,
476
+ };
477
+ }
478
+
479
+ return expr;
480
+ }
481
+
482
+ /**
483
+ * Parses a logical AND expression
484
+ * @returns {Object} Logical AND expression
485
+ */
486
+ logicalAnd() {
487
+ let expr = this.equality();
488
+
489
+ while (this.match('AND')) {
490
+ const operator = this.previous();
491
+ const right = this.equality();
492
+ expr = {
493
+ type: 'Logical',
494
+ left: expr,
495
+ operator: operator.type,
496
+ right,
497
+ };
498
+ }
499
+
500
+ return expr;
501
+ }
502
+
503
+ /**
504
+ * Parses a logical OR expression
505
+ * @returns {Object} Logical OR expression
506
+ */
507
+ logicalOr() {
508
+ let expr = this.logicalAnd();
509
+
510
+ while (this.match('OR')) {
511
+ const operator = this.previous();
512
+ const right = this.logicalAnd();
513
+ expr = {
514
+ type: 'Logical',
515
+ left: expr,
516
+ operator: operator.type,
517
+ right,
518
+ };
519
+ }
520
+
521
+ return expr;
522
+ }
523
+
524
+ /**
525
+ * Parses a comparison expression
526
+ * @returns {Object} Comparison expression
527
+ */
528
+ comparison() {
529
+ let expr = this.term();
530
+
531
+ while (this.match('GREATER', 'GREATER_EQUAL', 'LESS', 'LESS_EQUAL')) {
532
+ const operator = this.previous();
533
+ const right = this.term();
534
+ expr = {
535
+ type: 'Binary',
536
+ left: expr,
537
+ operator: operator.type,
538
+ right,
539
+ };
540
+ }
541
+
542
+ return expr;
543
+ }
544
+
545
+ /**
546
+ * Parses a term expression
547
+ * @returns {Object} Term expression
548
+ */
549
+ term() {
550
+ let expr = this.factor();
551
+
552
+ while (this.match('MINUS', 'PLUS')) {
553
+ const operator = this.previous();
554
+ const right = this.factor();
555
+ expr = {
556
+ type: 'Binary',
557
+ left: expr,
558
+ operator: operator.type,
559
+ right,
560
+ };
561
+ }
562
+
563
+ return expr;
564
+ }
565
+
566
+ /**
567
+ * Parses a factor expression
568
+ * @returns {Object} Factor expression
569
+ */
570
+ factor() {
571
+ let expr = this.unary();
572
+
573
+ while (this.match('SLASH', 'STAR', 'PERCENT')) {
574
+ const operator = this.previous();
575
+ const right = this.unary();
576
+ expr = {
577
+ type: 'Binary',
578
+ left: expr,
579
+ operator: operator.type,
580
+ right,
581
+ };
582
+ }
583
+
584
+ return expr;
585
+ }
586
+
587
+ /**
588
+ * Parses a unary expression
589
+ * @returns {Object} Unary expression
590
+ */
591
+ unary() {
592
+ if (this.match('BANG', 'MINUS')) {
593
+ const operator = this.previous();
594
+ const right = this.unary();
595
+ return {
596
+ type: 'Unary',
597
+ operator: operator.type,
598
+ right,
599
+ };
600
+ }
601
+
602
+ return this.postfix();
603
+ }
604
+
605
+ /**
606
+ * Parses a postfix expression (increment/decrement)
607
+ * @returns {Object} Postfix expression
608
+ */
609
+ postfix() {
610
+ let expr = this.call();
611
+
612
+ while (this.match('PLUS_PLUS', 'MINUS_MINUS')) {
613
+ const operator = this.previous();
614
+ expr = {
615
+ type: 'Postfix',
616
+ operator: operator.type,
617
+ operand: expr,
618
+ };
619
+ }
620
+
621
+ return expr;
622
+ }
623
+
624
+ /**
625
+ * Parses a call expression (function calls and array access)
626
+ * @returns {Object} Call expression
627
+ */
628
+ call() {
629
+ let expr = this.primary();
630
+
631
+ while (true) {
632
+ if (this.match('LEFT_BRACKET')) {
633
+ expr = this.finishArrayAccess(expr);
634
+ } else if (this.match('DOT')) {
635
+ expr = this.finishPropertyAccess(expr);
636
+ } else {
637
+ break;
638
+ }
639
+ }
640
+
641
+ return expr;
642
+ }
643
+
644
+ /**
645
+ * Parses a primary expression
646
+ * @returns {Object} Primary expression
647
+ */
648
+ primary() {
649
+ if (this.match('FALSE')) return { type: 'Literal', value: false };
650
+ if (this.match('TRUE')) return { type: 'Literal', value: true };
651
+ if (this.match('NULL')) return { type: 'Literal', value: null };
652
+ if (this.match('UNDEFINED')) return { type: 'Literal', value: undefined };
653
+ if (this.match('NUMBER', 'STRING')) {
654
+ return {
655
+ type: 'Literal',
656
+ value: this.previous().literal,
657
+ };
658
+ }
659
+
660
+ if (this.match('IDENTIFIER')) {
661
+ const identifier = this.previous();
662
+ if (this.check('LEFT_PAREN')) {
663
+ this.advance(); // Consume the LEFT_PAREN
664
+ return this.finishCall(identifier);
665
+ }
666
+ return {
667
+ type: 'Variable',
668
+ name: identifier.lexeme,
669
+ };
670
+ }
671
+
672
+ if (this.match('AND')) {
673
+ const identifier = this.previous();
674
+ if (this.check('LEFT_PAREN')) {
675
+ this.advance(); // Consume the LEFT_PAREN
676
+ return this.finishCall(identifier);
677
+ }
678
+ return {
679
+ type: 'Variable',
680
+ name: identifier.lexeme,
681
+ };
682
+ }
683
+
684
+ if (this.match('LEFT_PAREN')) {
685
+ const expr = this.expression();
686
+ this.consume('RIGHT_PAREN', 'Expected ) after expression');
687
+ return expr;
688
+ }
689
+
690
+ if (this.match('LEFT_BRACKET')) {
691
+ return this.arrayLiteral();
692
+ }
693
+
694
+ if (this.match('LEFT_BRACE')) {
695
+ return this.objectLiteral();
696
+ }
697
+
698
+ if (this.match('FUNCION')) {
699
+ return this.anonymousFunction();
700
+ }
701
+
702
+ throw new Error('Expected expression');
703
+ }
704
+
705
+ /**
706
+ * Parses an anonymous function
707
+ * @returns {Object} Anonymous function expression
708
+ */
709
+ anonymousFunction() {
710
+ this.consume('LEFT_PAREN', 'Expected ( after funcion');
711
+
712
+ const parameters = [];
713
+ if (!this.check('RIGHT_PAREN')) {
714
+ do {
715
+ if (parameters.length >= 255) {
716
+ throw new Error('Cannot have more than 255 parameters');
717
+ }
718
+ let param;
719
+ if (this.match('IDENTIFIER')) {
720
+ param = this.previous();
721
+ } else if (this.match('AND')) {
722
+ param = this.previous();
723
+ } else {
724
+ throw new Error('Expected parameter name');
725
+ }
726
+ parameters.push(param.lexeme);
727
+ } while (this.match('COMMA'));
728
+ }
729
+
730
+ this.consume('RIGHT_PAREN', 'Expected ) after parameters');
731
+ this.consume('LEFT_BRACE', 'Expected { before function body');
732
+ const body = this.block();
733
+ this.consume('RIGHT_BRACE', 'Expected } after function body');
734
+
735
+ return {
736
+ type: 'AnonymousFunction',
737
+ parameters,
738
+ body,
739
+ };
740
+ }
741
+
742
+ /**
743
+ * Finishes parsing an array access
744
+ * @param {Object} array - The array being accessed
745
+ * @returns {Object} Array access expression
746
+ */
747
+ finishArrayAccess(array) {
748
+ const index = this.expression();
749
+ this.consume('RIGHT_BRACKET', 'Expected ] after array index');
750
+
751
+ return {
752
+ type: 'ArrayAccess',
753
+ array,
754
+ index,
755
+ };
756
+ }
757
+
758
+ /**
759
+ * Finishes parsing a property access
760
+ * @param {Object} object - The object being accessed
761
+ * @returns {Object} Property access expression
762
+ */
763
+ finishPropertyAccess(object) {
764
+ this.consume('IDENTIFIER', 'Expected property name after .');
765
+ const name = this.previous();
766
+
767
+ // Check if this is a method call (array or string)
768
+ if (
769
+ name.lexeme === 'longitud' ||
770
+ name.lexeme === 'primero' ||
771
+ name.lexeme === 'ultimo' ||
772
+ name.lexeme === 'agregar' ||
773
+ name.lexeme === 'remover' ||
774
+ name.lexeme === 'contiene' ||
775
+ name.lexeme === 'recorrer' ||
776
+ name.lexeme === 'mayusculas' ||
777
+ name.lexeme === 'minusculas'
778
+ ) {
779
+ // Check if there are parentheses (method call syntax)
780
+ if (this.match('LEFT_PAREN')) {
781
+ // Consume the opening parenthesis
782
+ // Check if there are arguments
783
+ if (!this.check('RIGHT_PAREN')) {
784
+ // Handle methods that accept arguments (like agregar, contiene, recorrer)
785
+ if (
786
+ name.lexeme === 'agregar' ||
787
+ name.lexeme === 'contiene' ||
788
+ name.lexeme === 'recorrer'
789
+ ) {
790
+ const args = [];
791
+ do {
792
+ args.push(this.expression());
793
+ } while (this.match('COMMA'));
794
+ this.consume('RIGHT_PAREN', 'Expected ) after method call');
795
+
796
+ return {
797
+ type: 'MethodCall',
798
+ object,
799
+ method: name.lexeme,
800
+ arguments: args,
801
+ };
802
+ } else {
803
+ throw new Error(
804
+ `Method ${name.lexeme}() does not accept arguments`
805
+ );
806
+ }
807
+ }
808
+ // Consume the closing parenthesis
809
+ this.consume('RIGHT_PAREN', 'Expected ) after method call');
810
+ }
811
+
812
+ return {
813
+ type: 'MethodCall',
814
+ object,
815
+ method: name.lexeme,
816
+ };
817
+ }
818
+
819
+ return {
820
+ type: 'PropertyAccess',
821
+ object,
822
+ name: name.lexeme,
823
+ };
824
+ }
825
+
826
+ /**
827
+ * Finishes parsing a function call
828
+ * @param {Object} callee - The function being called
829
+ * @returns {Object} Function call expression
830
+ */
831
+ finishCall(callee) {
832
+ const args = [];
833
+
834
+ if (!this.check('RIGHT_PAREN')) {
835
+ do {
836
+ if (args.length >= 255) {
837
+ throw new Error('Cannot have more than 255 arguments');
838
+ }
839
+ args.push(this.expression());
840
+ } while (this.match('COMMA'));
841
+ }
842
+
843
+ const paren = this.consume('RIGHT_PAREN', 'Expected ) after arguments');
844
+
845
+ return {
846
+ type: 'Call',
847
+ callee: {
848
+ type: 'Variable',
849
+ name: callee.lexeme,
850
+ },
851
+ arguments: args,
852
+ };
853
+ }
854
+
855
+ /**
856
+ * Parses an array literal
857
+ * @returns {Object} Array literal
858
+ */
859
+ arrayLiteral() {
860
+ const elements = [];
861
+
862
+ if (!this.check('RIGHT_BRACKET')) {
863
+ do {
864
+ elements.push(this.expression());
865
+ } while (this.match('COMMA'));
866
+ }
867
+
868
+ this.consume('RIGHT_BRACKET', 'Expected ] after array elements');
869
+
870
+ return {
871
+ type: 'ArrayLiteral',
872
+ elements,
873
+ };
874
+ }
875
+
876
+ /**
877
+ * Parses an object literal
878
+ * @returns {Object} Object literal
879
+ */
880
+ objectLiteral() {
881
+ const properties = [];
882
+
883
+ if (!this.check('RIGHT_BRACE')) {
884
+ do {
885
+ // Parse property name (identifier or string)
886
+ let name;
887
+ if (this.match('IDENTIFIER')) {
888
+ name = this.previous().lexeme;
889
+ } else if (this.match('STRING')) {
890
+ name = this.previous().literal;
891
+ } else {
892
+ throw new Error('Expected property name');
893
+ }
894
+
895
+ this.consume('COLON', 'Expected : after property name');
896
+ const value = this.expression();
897
+
898
+ properties.push({
899
+ type: 'Property',
900
+ name,
901
+ value,
902
+ });
903
+ } while (this.match('COMMA'));
904
+ }
905
+
906
+ this.consume('RIGHT_BRACE', 'Expected } after object properties');
907
+
908
+ return {
909
+ type: 'ObjectLiteral',
910
+ properties,
911
+ };
912
+ }
913
+
914
+ /**
915
+ * Checks if the current token matches any of the given types
916
+ * @param {...string} types - Token types to check
917
+ * @returns {boolean} True if it matches
918
+ */
919
+ match(...types) {
920
+ for (const type of types) {
921
+ if (this.check(type)) {
922
+ this.advance();
923
+ return true;
924
+ }
925
+ }
926
+ return false;
927
+ }
928
+
929
+ /**
930
+ * Checks if the current token is of the given type
931
+ * @param {string} type - Token type to check
932
+ * @returns {boolean} True if it is of the type
933
+ */
934
+ check(type) {
935
+ if (this.isAtEnd()) return false;
936
+ return this.peek().type === type;
937
+ }
938
+
939
+ /**
940
+ * Advances to the next token
941
+ * @returns {Object} Previous token
942
+ */
943
+ advance() {
944
+ if (!this.isAtEnd()) this.current++;
945
+ return this.previous();
946
+ }
947
+
948
+ /**
949
+ * Checks if we reached the end
950
+ * @returns {boolean} True if we reached the end
951
+ */
952
+ isAtEnd() {
953
+ return this.peek().type === 'EOF';
954
+ }
955
+
956
+ /**
957
+ * Returns the current token
958
+ * @returns {Object} Current token
959
+ */
960
+ peek() {
961
+ return this.tokens[this.current];
962
+ }
963
+
964
+ /**
965
+ * Returns the previous token
966
+ * @returns {Object} Previous token
967
+ */
968
+ previous() {
969
+ return this.tokens[this.current - 1];
970
+ }
971
+
972
+ /**
973
+ * Consumes a token of the expected type
974
+ * @param {string} type - Expected token type
975
+ * @param {string} message - Error message if it doesn't match
976
+ * @returns {Object} Consumed token
977
+ */
978
+ consume(type, message) {
979
+ if (this.check(type)) return this.advance();
980
+
981
+ throw new Error(`${message} at line ${this.peek().line}`);
982
+ }
983
+
984
+ /**
985
+ * Synchronizes the parser after an error
986
+ */
987
+ synchronize() {
988
+ this.advance();
989
+
990
+ while (!this.isAtEnd()) {
991
+ if (this.previous().type === 'EOF') return;
992
+
993
+ switch (this.peek().type) {
994
+ case 'VARIABLE':
995
+ case 'FUNCION':
996
+ case 'MOSTRAR':
997
+ case 'LEER':
998
+ case 'SI':
999
+ case 'MIENTRAS':
1000
+ case 'PARA':
1001
+ case 'RETORNAR':
1002
+ case 'ROMPER':
1003
+ case 'CONTINUAR':
1004
+ case 'INTENTAR':
1005
+ case 'CAPTURAR':
1006
+ return;
1007
+ }
1008
+
1009
+ this.advance();
1010
+ }
1011
+ }
1012
+
1013
+ /**
1014
+ * Parses a try-catch statement
1015
+ * @returns {Object} Try-catch statement
1016
+ */
1017
+ tryStatement() {
1018
+ // Parse the try block
1019
+ this.consume('LEFT_BRACE', 'Expected { after intentar');
1020
+ const tryBlock = this.block();
1021
+ this.consume('RIGHT_BRACE', 'Expected } after intentar block');
1022
+
1023
+ // Look for catch block
1024
+ if (this.match('CAPTURAR')) {
1025
+ // Parse catch parameter (error variable name)
1026
+ this.consume('LEFT_PAREN', 'Expected ( after capturar');
1027
+ const errorVariable = this.consume(
1028
+ 'IDENTIFIER',
1029
+ 'Expected error variable name'
1030
+ );
1031
+ this.consume('RIGHT_PAREN', 'Expected ) after error variable');
1032
+
1033
+ // Parse catch block
1034
+ this.consume('LEFT_BRACE', 'Expected { after capturar');
1035
+ const catchBlock = this.block();
1036
+ this.consume('RIGHT_BRACE', 'Expected } after capturar block');
1037
+
1038
+ return {
1039
+ type: 'TryCatch',
1040
+ tryBlock,
1041
+ catchBlock,
1042
+ errorVariable: errorVariable.lexeme,
1043
+ };
1044
+ } else {
1045
+ throw new Error('Expected capturar after intentar block');
1046
+ }
1047
+ }
1048
+ }
1049
+
1050
+ module.exports = Parser;