hispano-lang 1.1.7 → 2.1.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/README.md +672 -237
- package/dist/evaluator.js +1390 -261
- package/dist/parser.js +758 -201
- package/dist/tokenizer.js +202 -121
- package/package.json +12 -3
package/dist/parser.js
CHANGED
|
@@ -20,7 +20,7 @@ class Parser {
|
|
|
20
20
|
statements.push(this.declaration());
|
|
21
21
|
|
|
22
22
|
// Consume semicolon if present between statements
|
|
23
|
-
if (this.match(
|
|
23
|
+
if (this.match("SEMICOLON")) {
|
|
24
24
|
// Semicolon consumed
|
|
25
25
|
}
|
|
26
26
|
}
|
|
@@ -34,47 +34,67 @@ class Parser {
|
|
|
34
34
|
*/
|
|
35
35
|
declaration() {
|
|
36
36
|
try {
|
|
37
|
-
if (this.match(
|
|
37
|
+
if (this.match("VARIABLE")) {
|
|
38
38
|
return this.variableDeclaration();
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
-
if (this.match(
|
|
41
|
+
if (this.match("CONSTANTE")) {
|
|
42
|
+
return this.constantDeclaration();
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (this.match("FUNCION")) {
|
|
42
46
|
return this.functionDeclaration();
|
|
43
47
|
}
|
|
44
48
|
|
|
45
|
-
if (this.match(
|
|
49
|
+
if (this.match("CLASE")) {
|
|
50
|
+
return this.classDeclaration();
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (this.match("MOSTRAR")) {
|
|
46
54
|
return this.mostrarStatement();
|
|
47
55
|
}
|
|
48
56
|
|
|
49
|
-
if (this.match(
|
|
57
|
+
if (this.match("LEER")) {
|
|
50
58
|
return this.leerStatement();
|
|
51
59
|
}
|
|
52
60
|
|
|
53
|
-
if (this.match(
|
|
61
|
+
if (this.match("SI")) {
|
|
54
62
|
return this.ifStatement();
|
|
55
63
|
}
|
|
56
64
|
|
|
57
|
-
if (this.match(
|
|
65
|
+
if (this.match("MIENTRAS")) {
|
|
58
66
|
return this.whileStatement();
|
|
59
67
|
}
|
|
60
68
|
|
|
61
|
-
if (this.match(
|
|
69
|
+
if (this.match("PARA")) {
|
|
70
|
+
// Check if it's "para cada" (for-each) or regular "para" (for)
|
|
71
|
+
if (this.match("CADA")) {
|
|
72
|
+
return this.forEachStatement();
|
|
73
|
+
}
|
|
62
74
|
return this.forStatement();
|
|
63
75
|
}
|
|
64
76
|
|
|
65
|
-
if (this.match(
|
|
77
|
+
if (this.match("ELEGIR")) {
|
|
78
|
+
return this.elegirStatement();
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (this.match("HACER")) {
|
|
82
|
+
return this.hacerMientrasStatement();
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (this.match("RETORNAR")) {
|
|
66
86
|
return this.returnStatement();
|
|
67
87
|
}
|
|
68
88
|
|
|
69
|
-
if (this.match(
|
|
89
|
+
if (this.match("ROMPER")) {
|
|
70
90
|
return this.breakStatement();
|
|
71
91
|
}
|
|
72
92
|
|
|
73
|
-
if (this.match(
|
|
93
|
+
if (this.match("CONTINUAR")) {
|
|
74
94
|
return this.continueStatement();
|
|
75
95
|
}
|
|
76
96
|
|
|
77
|
-
if (this.match(
|
|
97
|
+
if (this.match("INTENTAR")) {
|
|
78
98
|
return this.tryStatement();
|
|
79
99
|
}
|
|
80
100
|
|
|
@@ -91,21 +111,46 @@ class Parser {
|
|
|
91
111
|
*/
|
|
92
112
|
variableDeclaration() {
|
|
93
113
|
let name;
|
|
94
|
-
if (this.match(
|
|
114
|
+
if (this.match("IDENTIFIER")) {
|
|
95
115
|
name = this.previous();
|
|
96
|
-
} else if (this.match(
|
|
116
|
+
} else if (this.match("AND")) {
|
|
97
117
|
name = this.previous();
|
|
98
118
|
} else {
|
|
99
|
-
throw new Error(
|
|
119
|
+
throw new Error("Se esperaba un nombre de variable");
|
|
100
120
|
}
|
|
101
121
|
|
|
102
122
|
let initializer = null;
|
|
103
|
-
if (this.match(
|
|
123
|
+
if (this.match("EQUAL")) {
|
|
104
124
|
initializer = this.expression();
|
|
105
125
|
}
|
|
106
126
|
|
|
107
127
|
return {
|
|
108
|
-
type:
|
|
128
|
+
type: "VariableDeclaration",
|
|
129
|
+
name: name.lexeme,
|
|
130
|
+
initializer,
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Parses a constant declaration
|
|
136
|
+
* @returns {Object} Constant declaration
|
|
137
|
+
*/
|
|
138
|
+
constantDeclaration() {
|
|
139
|
+
let name;
|
|
140
|
+
if (this.match("IDENTIFIER")) {
|
|
141
|
+
name = this.previous();
|
|
142
|
+
} else {
|
|
143
|
+
throw new Error("Se esperaba un nombre de constante");
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (!this.match("EQUAL")) {
|
|
147
|
+
throw new Error("Las constantes deben ser inicializadas");
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const initializer = this.expression();
|
|
151
|
+
|
|
152
|
+
return {
|
|
153
|
+
type: "ConstantDeclaration",
|
|
109
154
|
name: name.lexeme,
|
|
110
155
|
initializer,
|
|
111
156
|
};
|
|
@@ -116,40 +161,143 @@ class Parser {
|
|
|
116
161
|
* @returns {Object} Function declaration
|
|
117
162
|
*/
|
|
118
163
|
functionDeclaration() {
|
|
119
|
-
const name = this.consume(
|
|
120
|
-
this.consume(
|
|
164
|
+
const name = this.consume("IDENTIFIER", "Expected function name");
|
|
165
|
+
this.consume("LEFT_PAREN", "Expected ( after function name");
|
|
121
166
|
|
|
122
167
|
const parameters = [];
|
|
123
|
-
if (!this.check(
|
|
168
|
+
if (!this.check("RIGHT_PAREN")) {
|
|
124
169
|
do {
|
|
125
170
|
if (parameters.length >= 255) {
|
|
126
|
-
throw new Error(
|
|
171
|
+
throw new Error("No se pueden tener más de 255 parámetros");
|
|
127
172
|
}
|
|
128
173
|
let param;
|
|
129
|
-
if (this.match(
|
|
174
|
+
if (this.match("IDENTIFIER")) {
|
|
130
175
|
param = this.previous();
|
|
131
|
-
} else if (this.match(
|
|
176
|
+
} else if (this.match("AND")) {
|
|
132
177
|
param = this.previous();
|
|
133
178
|
} else {
|
|
134
|
-
throw new Error(
|
|
179
|
+
throw new Error("Se esperaba un nombre de parámetro");
|
|
135
180
|
}
|
|
136
181
|
parameters.push(param.lexeme);
|
|
137
|
-
} while (this.match(
|
|
182
|
+
} while (this.match("COMMA"));
|
|
138
183
|
}
|
|
139
184
|
|
|
140
|
-
this.consume(
|
|
141
|
-
this.consume(
|
|
185
|
+
this.consume("RIGHT_PAREN", "Expected ) after parameters");
|
|
186
|
+
this.consume("LEFT_BRACE", "Expected { before function body");
|
|
142
187
|
const body = this.block();
|
|
143
|
-
this.consume(
|
|
188
|
+
this.consume("RIGHT_BRACE", "Expected } after function body");
|
|
144
189
|
|
|
145
190
|
return {
|
|
146
|
-
type:
|
|
191
|
+
type: "FunctionDeclaration",
|
|
147
192
|
name: name.lexeme,
|
|
148
193
|
parameters,
|
|
149
194
|
body,
|
|
150
195
|
};
|
|
151
196
|
}
|
|
152
197
|
|
|
198
|
+
/**
|
|
199
|
+
* Parses a class declaration
|
|
200
|
+
* @returns {Object} Class declaration
|
|
201
|
+
*/
|
|
202
|
+
classDeclaration() {
|
|
203
|
+
const name = this.consume("IDENTIFIER", "Se esperaba un nombre de clase");
|
|
204
|
+
|
|
205
|
+
// Check for inheritance
|
|
206
|
+
let superclass = null;
|
|
207
|
+
if (this.match("EXTIENDE")) {
|
|
208
|
+
this.consume("IDENTIFIER", "Se esperaba el nombre de la clase padre");
|
|
209
|
+
superclass = this.previous().lexeme;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
this.consume("LEFT_BRACE", "Se esperaba { después del nombre de clase");
|
|
213
|
+
|
|
214
|
+
// Parse class body (constructor and methods)
|
|
215
|
+
let constructor = null;
|
|
216
|
+
const methods = [];
|
|
217
|
+
|
|
218
|
+
while (!this.check("RIGHT_BRACE") && !this.isAtEnd()) {
|
|
219
|
+
if (this.match("CONSTRUCTOR")) {
|
|
220
|
+
// Parse constructor
|
|
221
|
+
this.consume("LEFT_PAREN", "Se esperaba ( después de constructor");
|
|
222
|
+
const parameters = [];
|
|
223
|
+
if (!this.check("RIGHT_PAREN")) {
|
|
224
|
+
do {
|
|
225
|
+
if (parameters.length >= 255) {
|
|
226
|
+
throw new Error("No se pueden tener más de 255 parámetros");
|
|
227
|
+
}
|
|
228
|
+
const param = this.consume(
|
|
229
|
+
"IDENTIFIER",
|
|
230
|
+
"Se esperaba nombre de parámetro",
|
|
231
|
+
);
|
|
232
|
+
parameters.push(param.lexeme);
|
|
233
|
+
} while (this.match("COMMA"));
|
|
234
|
+
}
|
|
235
|
+
this.consume("RIGHT_PAREN", "Se esperaba ) después de parámetros");
|
|
236
|
+
this.consume(
|
|
237
|
+
"LEFT_BRACE",
|
|
238
|
+
"Se esperaba { antes del cuerpo del constructor",
|
|
239
|
+
);
|
|
240
|
+
const body = this.block();
|
|
241
|
+
this.consume(
|
|
242
|
+
"RIGHT_BRACE",
|
|
243
|
+
"Se esperaba } después del cuerpo del constructor",
|
|
244
|
+
);
|
|
245
|
+
|
|
246
|
+
constructor = {
|
|
247
|
+
parameters,
|
|
248
|
+
body,
|
|
249
|
+
};
|
|
250
|
+
} else if (this.match("IDENTIFIER")) {
|
|
251
|
+
// Parse method
|
|
252
|
+
const methodName = this.previous().lexeme;
|
|
253
|
+
this.consume(
|
|
254
|
+
"LEFT_PAREN",
|
|
255
|
+
"Se esperaba ( después del nombre del método",
|
|
256
|
+
);
|
|
257
|
+
const parameters = [];
|
|
258
|
+
if (!this.check("RIGHT_PAREN")) {
|
|
259
|
+
do {
|
|
260
|
+
if (parameters.length >= 255) {
|
|
261
|
+
throw new Error("No se pueden tener más de 255 parámetros");
|
|
262
|
+
}
|
|
263
|
+
const param = this.consume(
|
|
264
|
+
"IDENTIFIER",
|
|
265
|
+
"Se esperaba nombre de parámetro",
|
|
266
|
+
);
|
|
267
|
+
parameters.push(param.lexeme);
|
|
268
|
+
} while (this.match("COMMA"));
|
|
269
|
+
}
|
|
270
|
+
this.consume("RIGHT_PAREN", "Se esperaba ) después de parámetros");
|
|
271
|
+
this.consume("LEFT_BRACE", "Se esperaba { antes del cuerpo del método");
|
|
272
|
+
const body = this.block();
|
|
273
|
+
this.consume(
|
|
274
|
+
"RIGHT_BRACE",
|
|
275
|
+
"Se esperaba } después del cuerpo del método",
|
|
276
|
+
);
|
|
277
|
+
|
|
278
|
+
methods.push({
|
|
279
|
+
name: methodName,
|
|
280
|
+
parameters,
|
|
281
|
+
body,
|
|
282
|
+
});
|
|
283
|
+
} else {
|
|
284
|
+
throw new Error(
|
|
285
|
+
"Se esperaba constructor o método en el cuerpo de la clase",
|
|
286
|
+
);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
this.consume("RIGHT_BRACE", "Se esperaba } después del cuerpo de la clase");
|
|
291
|
+
|
|
292
|
+
return {
|
|
293
|
+
type: "ClassDeclaration",
|
|
294
|
+
name: name.lexeme,
|
|
295
|
+
superclass,
|
|
296
|
+
constructor,
|
|
297
|
+
methods,
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
|
|
153
301
|
/**
|
|
154
302
|
* Parses a show statement
|
|
155
303
|
* @returns {Object} Show statement
|
|
@@ -158,7 +306,7 @@ class Parser {
|
|
|
158
306
|
const value = this.expression();
|
|
159
307
|
|
|
160
308
|
return {
|
|
161
|
-
type:
|
|
309
|
+
type: "MostrarStatement",
|
|
162
310
|
expression: value,
|
|
163
311
|
};
|
|
164
312
|
}
|
|
@@ -170,18 +318,18 @@ class Parser {
|
|
|
170
318
|
leerStatement() {
|
|
171
319
|
// Parse the variable name to store the input
|
|
172
320
|
const variableName = this.consume(
|
|
173
|
-
|
|
174
|
-
|
|
321
|
+
"IDENTIFIER",
|
|
322
|
+
"Expected variable name after leer",
|
|
175
323
|
);
|
|
176
324
|
|
|
177
325
|
// Optional prompt message
|
|
178
326
|
let prompt = null;
|
|
179
|
-
if (this.match(
|
|
327
|
+
if (this.match("STRING")) {
|
|
180
328
|
prompt = this.previous().literal;
|
|
181
329
|
}
|
|
182
330
|
|
|
183
331
|
return {
|
|
184
|
-
type:
|
|
332
|
+
type: "LeerStatement",
|
|
185
333
|
variable: variableName.lexeme,
|
|
186
334
|
prompt: prompt,
|
|
187
335
|
};
|
|
@@ -193,19 +341,19 @@ class Parser {
|
|
|
193
341
|
*/
|
|
194
342
|
ifStatement() {
|
|
195
343
|
const condition = this.expression();
|
|
196
|
-
this.consume(
|
|
344
|
+
this.consume("LEFT_BRACE", "Expected { after condition");
|
|
197
345
|
const thenBranch = this.block();
|
|
198
|
-
this.consume(
|
|
346
|
+
this.consume("RIGHT_BRACE", "Expected } after block");
|
|
199
347
|
let elseBranch = null;
|
|
200
348
|
|
|
201
|
-
if (this.match(
|
|
202
|
-
this.consume(
|
|
349
|
+
if (this.match("SINO")) {
|
|
350
|
+
this.consume("LEFT_BRACE", "Expected { after else");
|
|
203
351
|
elseBranch = this.block();
|
|
204
|
-
this.consume(
|
|
352
|
+
this.consume("RIGHT_BRACE", "Expected } after else block");
|
|
205
353
|
}
|
|
206
354
|
|
|
207
355
|
return {
|
|
208
|
-
type:
|
|
356
|
+
type: "IfStatement",
|
|
209
357
|
condition,
|
|
210
358
|
thenBranch,
|
|
211
359
|
elseBranch,
|
|
@@ -218,12 +366,12 @@ class Parser {
|
|
|
218
366
|
*/
|
|
219
367
|
whileStatement() {
|
|
220
368
|
const condition = this.expression();
|
|
221
|
-
this.consume(
|
|
369
|
+
this.consume("LEFT_BRACE", "Expected { after condition");
|
|
222
370
|
const body = this.block();
|
|
223
|
-
this.consume(
|
|
371
|
+
this.consume("RIGHT_BRACE", "Expected } after block");
|
|
224
372
|
|
|
225
373
|
return {
|
|
226
|
-
type:
|
|
374
|
+
type: "WhileStatement",
|
|
227
375
|
condition,
|
|
228
376
|
body,
|
|
229
377
|
};
|
|
@@ -234,18 +382,18 @@ class Parser {
|
|
|
234
382
|
* @returns {Object} For statement
|
|
235
383
|
*/
|
|
236
384
|
forStatement() {
|
|
237
|
-
this.consume(
|
|
385
|
+
this.consume("LEFT_PAREN", "Expected ( after para");
|
|
238
386
|
|
|
239
387
|
// Initializer (optional)
|
|
240
388
|
let initializer = null;
|
|
241
|
-
if (!this.check(
|
|
242
|
-
if (this.match(
|
|
389
|
+
if (!this.check("SEMICOLON")) {
|
|
390
|
+
if (this.match("VARIABLE")) {
|
|
243
391
|
initializer = this.variableDeclaration();
|
|
244
392
|
} else {
|
|
245
393
|
initializer = this.expressionStatement();
|
|
246
394
|
}
|
|
247
395
|
// Consume the semicolon after initializer
|
|
248
|
-
this.consume(
|
|
396
|
+
this.consume("SEMICOLON", "Expected ; after for initializer");
|
|
249
397
|
} else {
|
|
250
398
|
// Skip the first semicolon if no initializer
|
|
251
399
|
this.advance();
|
|
@@ -253,24 +401,24 @@ class Parser {
|
|
|
253
401
|
|
|
254
402
|
// Condition (optional)
|
|
255
403
|
let condition = null;
|
|
256
|
-
if (!this.check(
|
|
404
|
+
if (!this.check("SEMICOLON")) {
|
|
257
405
|
condition = this.expression();
|
|
258
406
|
}
|
|
259
|
-
this.consume(
|
|
407
|
+
this.consume("SEMICOLON", "Expected ; after condition");
|
|
260
408
|
|
|
261
409
|
// Increment (optional)
|
|
262
410
|
let increment = null;
|
|
263
|
-
if (!this.check(
|
|
411
|
+
if (!this.check("RIGHT_PAREN")) {
|
|
264
412
|
increment = this.expression();
|
|
265
413
|
}
|
|
266
|
-
this.consume(
|
|
414
|
+
this.consume("RIGHT_PAREN", "Expected ) after for clause");
|
|
267
415
|
|
|
268
|
-
this.consume(
|
|
416
|
+
this.consume("LEFT_BRACE", "Expected { after for clause");
|
|
269
417
|
const body = this.block();
|
|
270
|
-
this.consume(
|
|
418
|
+
this.consume("RIGHT_BRACE", "Expected } after block");
|
|
271
419
|
|
|
272
420
|
return {
|
|
273
|
-
type:
|
|
421
|
+
type: "ForStatement",
|
|
274
422
|
initializer,
|
|
275
423
|
condition,
|
|
276
424
|
increment,
|
|
@@ -285,12 +433,12 @@ class Parser {
|
|
|
285
433
|
returnStatement() {
|
|
286
434
|
const keyword = this.previous();
|
|
287
435
|
let value = null;
|
|
288
|
-
if (!this.check(
|
|
436
|
+
if (!this.check("RIGHT_BRACE")) {
|
|
289
437
|
value = this.expression();
|
|
290
438
|
}
|
|
291
439
|
|
|
292
440
|
return {
|
|
293
|
-
type:
|
|
441
|
+
type: "ReturnStatement",
|
|
294
442
|
keyword,
|
|
295
443
|
value,
|
|
296
444
|
};
|
|
@@ -303,7 +451,7 @@ class Parser {
|
|
|
303
451
|
breakStatement() {
|
|
304
452
|
const keyword = this.previous();
|
|
305
453
|
return {
|
|
306
|
-
type:
|
|
454
|
+
type: "BreakStatement",
|
|
307
455
|
keyword,
|
|
308
456
|
};
|
|
309
457
|
}
|
|
@@ -315,7 +463,7 @@ class Parser {
|
|
|
315
463
|
continueStatement() {
|
|
316
464
|
const keyword = this.previous();
|
|
317
465
|
return {
|
|
318
|
-
type:
|
|
466
|
+
type: "ContinueStatement",
|
|
319
467
|
keyword,
|
|
320
468
|
};
|
|
321
469
|
}
|
|
@@ -327,7 +475,7 @@ class Parser {
|
|
|
327
475
|
block() {
|
|
328
476
|
const statements = [];
|
|
329
477
|
|
|
330
|
-
while (!this.check(
|
|
478
|
+
while (!this.check("RIGHT_BRACE") && !this.isAtEnd()) {
|
|
331
479
|
statements.push(this.declaration());
|
|
332
480
|
}
|
|
333
481
|
|
|
@@ -341,7 +489,7 @@ class Parser {
|
|
|
341
489
|
expressionStatement() {
|
|
342
490
|
const expr = this.expression();
|
|
343
491
|
return {
|
|
344
|
-
type:
|
|
492
|
+
type: "ExpressionStatement",
|
|
345
493
|
expression: expr,
|
|
346
494
|
};
|
|
347
495
|
}
|
|
@@ -361,7 +509,7 @@ class Parser {
|
|
|
361
509
|
parseExpression() {
|
|
362
510
|
let expr = this.equality();
|
|
363
511
|
|
|
364
|
-
while (this.match(
|
|
512
|
+
while (this.match("LEFT_BRACKET")) {
|
|
365
513
|
expr = this.finishArrayAccess(expr);
|
|
366
514
|
}
|
|
367
515
|
|
|
@@ -373,68 +521,76 @@ class Parser {
|
|
|
373
521
|
* @returns {Object} Parsed assignment
|
|
374
522
|
*/
|
|
375
523
|
assignment() {
|
|
376
|
-
|
|
524
|
+
const expr = this.logicalOr();
|
|
377
525
|
|
|
378
|
-
if (this.match(
|
|
526
|
+
if (this.match("EQUAL")) {
|
|
379
527
|
const equals = this.previous();
|
|
380
528
|
const value = this.logicalOr();
|
|
381
529
|
|
|
382
|
-
if (expr.type ===
|
|
530
|
+
if (expr.type === "Variable") {
|
|
383
531
|
const name = expr.name;
|
|
384
532
|
return {
|
|
385
|
-
type:
|
|
533
|
+
type: "Assign",
|
|
386
534
|
name,
|
|
387
535
|
value,
|
|
388
536
|
};
|
|
389
537
|
}
|
|
390
538
|
|
|
391
|
-
if (expr.type ===
|
|
539
|
+
if (expr.type === "ArrayAccess") {
|
|
392
540
|
return {
|
|
393
|
-
type:
|
|
541
|
+
type: "ArrayAssign",
|
|
394
542
|
array: expr.array,
|
|
395
543
|
index: expr.index,
|
|
396
544
|
value,
|
|
397
545
|
};
|
|
398
546
|
}
|
|
399
547
|
|
|
400
|
-
if (expr.type ===
|
|
548
|
+
if (expr.type === "PropertyAccess") {
|
|
401
549
|
return {
|
|
402
|
-
type:
|
|
550
|
+
type: "PropertyAssign",
|
|
403
551
|
object: expr.object,
|
|
404
552
|
name: expr.name,
|
|
405
553
|
value,
|
|
406
554
|
};
|
|
407
555
|
}
|
|
408
556
|
|
|
409
|
-
|
|
557
|
+
if (expr.type === "ThisPropertyAccess") {
|
|
558
|
+
return {
|
|
559
|
+
type: "ThisPropertyAssign",
|
|
560
|
+
property: expr.property,
|
|
561
|
+
value,
|
|
562
|
+
};
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
throw new Error("Objetivo de asignación inválido");
|
|
410
566
|
}
|
|
411
567
|
|
|
412
568
|
// Handle compound assignment operators
|
|
413
569
|
if (
|
|
414
570
|
this.match(
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
571
|
+
"PLUS_EQUAL",
|
|
572
|
+
"MINUS_EQUAL",
|
|
573
|
+
"STAR_EQUAL",
|
|
574
|
+
"SLASH_EQUAL",
|
|
575
|
+
"PERCENT_EQUAL",
|
|
420
576
|
)
|
|
421
577
|
) {
|
|
422
578
|
const operator = this.previous();
|
|
423
579
|
const value = this.logicalOr();
|
|
424
580
|
|
|
425
|
-
if (expr.type ===
|
|
581
|
+
if (expr.type === "Variable") {
|
|
426
582
|
const name = expr.name;
|
|
427
583
|
return {
|
|
428
|
-
type:
|
|
584
|
+
type: "CompoundAssign",
|
|
429
585
|
name,
|
|
430
586
|
operator: operator.type,
|
|
431
587
|
value,
|
|
432
588
|
};
|
|
433
589
|
}
|
|
434
590
|
|
|
435
|
-
if (expr.type ===
|
|
591
|
+
if (expr.type === "ArrayAccess") {
|
|
436
592
|
return {
|
|
437
|
-
type:
|
|
593
|
+
type: "CompoundArrayAssign",
|
|
438
594
|
array: expr.array,
|
|
439
595
|
index: expr.index,
|
|
440
596
|
operator: operator.type,
|
|
@@ -442,9 +598,9 @@ class Parser {
|
|
|
442
598
|
};
|
|
443
599
|
}
|
|
444
600
|
|
|
445
|
-
if (expr.type ===
|
|
601
|
+
if (expr.type === "PropertyAccess") {
|
|
446
602
|
return {
|
|
447
|
-
type:
|
|
603
|
+
type: "CompoundPropertyAssign",
|
|
448
604
|
object: expr.object,
|
|
449
605
|
name: expr.name,
|
|
450
606
|
operator: operator.type,
|
|
@@ -452,7 +608,7 @@ class Parser {
|
|
|
452
608
|
};
|
|
453
609
|
}
|
|
454
610
|
|
|
455
|
-
throw new Error(
|
|
611
|
+
throw new Error("Objetivo de asignación compuesta inválido");
|
|
456
612
|
}
|
|
457
613
|
|
|
458
614
|
return expr;
|
|
@@ -465,11 +621,11 @@ class Parser {
|
|
|
465
621
|
equality() {
|
|
466
622
|
let expr = this.comparison();
|
|
467
623
|
|
|
468
|
-
while (this.match(
|
|
624
|
+
while (this.match("EQUAL_EQUAL", "BANG_EQUAL")) {
|
|
469
625
|
const operator = this.previous();
|
|
470
626
|
const right = this.comparison();
|
|
471
627
|
expr = {
|
|
472
|
-
type:
|
|
628
|
+
type: "Binary",
|
|
473
629
|
left: expr,
|
|
474
630
|
operator: operator.type,
|
|
475
631
|
right,
|
|
@@ -486,11 +642,11 @@ class Parser {
|
|
|
486
642
|
logicalAnd() {
|
|
487
643
|
let expr = this.equality();
|
|
488
644
|
|
|
489
|
-
while (this.match(
|
|
645
|
+
while (this.match("AND")) {
|
|
490
646
|
const operator = this.previous();
|
|
491
647
|
const right = this.equality();
|
|
492
648
|
expr = {
|
|
493
|
-
type:
|
|
649
|
+
type: "Logical",
|
|
494
650
|
left: expr,
|
|
495
651
|
operator: operator.type,
|
|
496
652
|
right,
|
|
@@ -507,11 +663,11 @@ class Parser {
|
|
|
507
663
|
logicalOr() {
|
|
508
664
|
let expr = this.logicalAnd();
|
|
509
665
|
|
|
510
|
-
while (this.match(
|
|
666
|
+
while (this.match("OR")) {
|
|
511
667
|
const operator = this.previous();
|
|
512
668
|
const right = this.logicalAnd();
|
|
513
669
|
expr = {
|
|
514
|
-
type:
|
|
670
|
+
type: "Logical",
|
|
515
671
|
left: expr,
|
|
516
672
|
operator: operator.type,
|
|
517
673
|
right,
|
|
@@ -528,11 +684,11 @@ class Parser {
|
|
|
528
684
|
comparison() {
|
|
529
685
|
let expr = this.term();
|
|
530
686
|
|
|
531
|
-
while (this.match(
|
|
687
|
+
while (this.match("GREATER", "GREATER_EQUAL", "LESS", "LESS_EQUAL")) {
|
|
532
688
|
const operator = this.previous();
|
|
533
689
|
const right = this.term();
|
|
534
690
|
expr = {
|
|
535
|
-
type:
|
|
691
|
+
type: "Binary",
|
|
536
692
|
left: expr,
|
|
537
693
|
operator: operator.type,
|
|
538
694
|
right,
|
|
@@ -549,11 +705,11 @@ class Parser {
|
|
|
549
705
|
term() {
|
|
550
706
|
let expr = this.factor();
|
|
551
707
|
|
|
552
|
-
while (this.match(
|
|
708
|
+
while (this.match("MINUS", "PLUS")) {
|
|
553
709
|
const operator = this.previous();
|
|
554
710
|
const right = this.factor();
|
|
555
711
|
expr = {
|
|
556
|
-
type:
|
|
712
|
+
type: "Binary",
|
|
557
713
|
left: expr,
|
|
558
714
|
operator: operator.type,
|
|
559
715
|
right,
|
|
@@ -570,11 +726,11 @@ class Parser {
|
|
|
570
726
|
factor() {
|
|
571
727
|
let expr = this.unary();
|
|
572
728
|
|
|
573
|
-
while (this.match(
|
|
729
|
+
while (this.match("SLASH", "STAR", "PERCENT")) {
|
|
574
730
|
const operator = this.previous();
|
|
575
731
|
const right = this.unary();
|
|
576
732
|
expr = {
|
|
577
|
-
type:
|
|
733
|
+
type: "Binary",
|
|
578
734
|
left: expr,
|
|
579
735
|
operator: operator.type,
|
|
580
736
|
right,
|
|
@@ -589,11 +745,11 @@ class Parser {
|
|
|
589
745
|
* @returns {Object} Unary expression
|
|
590
746
|
*/
|
|
591
747
|
unary() {
|
|
592
|
-
if (this.match(
|
|
748
|
+
if (this.match("BANG", "MINUS")) {
|
|
593
749
|
const operator = this.previous();
|
|
594
750
|
const right = this.unary();
|
|
595
751
|
return {
|
|
596
|
-
type:
|
|
752
|
+
type: "Unary",
|
|
597
753
|
operator: operator.type,
|
|
598
754
|
right,
|
|
599
755
|
};
|
|
@@ -609,10 +765,10 @@ class Parser {
|
|
|
609
765
|
postfix() {
|
|
610
766
|
let expr = this.call();
|
|
611
767
|
|
|
612
|
-
while (this.match(
|
|
768
|
+
while (this.match("PLUS_PLUS", "MINUS_MINUS")) {
|
|
613
769
|
const operator = this.previous();
|
|
614
770
|
expr = {
|
|
615
|
-
type:
|
|
771
|
+
type: "Postfix",
|
|
616
772
|
operator: operator.type,
|
|
617
773
|
operand: expr,
|
|
618
774
|
};
|
|
@@ -629,10 +785,44 @@ class Parser {
|
|
|
629
785
|
let expr = this.primary();
|
|
630
786
|
|
|
631
787
|
while (true) {
|
|
632
|
-
if (this.match(
|
|
788
|
+
if (this.match("LEFT_BRACKET")) {
|
|
633
789
|
expr = this.finishArrayAccess(expr);
|
|
634
|
-
} else if (this.match(
|
|
790
|
+
} else if (this.match("DOT")) {
|
|
635
791
|
expr = this.finishPropertyAccess(expr);
|
|
792
|
+
} else if (this.match("LEFT_PAREN")) {
|
|
793
|
+
// Handle method/function calls on PropertyAccess or ThisPropertyAccess
|
|
794
|
+
if (expr.type === "PropertyAccess") {
|
|
795
|
+
const args = [];
|
|
796
|
+
if (!this.check("RIGHT_PAREN")) {
|
|
797
|
+
do {
|
|
798
|
+
args.push(this.expression());
|
|
799
|
+
} while (this.match("COMMA"));
|
|
800
|
+
}
|
|
801
|
+
this.consume("RIGHT_PAREN", "Expected ) after method arguments");
|
|
802
|
+
expr = {
|
|
803
|
+
type: "MethodCall",
|
|
804
|
+
object: expr.object,
|
|
805
|
+
method: expr.name,
|
|
806
|
+
arguments: args,
|
|
807
|
+
};
|
|
808
|
+
} else if (expr.type === "ThisPropertyAccess") {
|
|
809
|
+
const args = [];
|
|
810
|
+
if (!this.check("RIGHT_PAREN")) {
|
|
811
|
+
do {
|
|
812
|
+
args.push(this.expression());
|
|
813
|
+
} while (this.match("COMMA"));
|
|
814
|
+
}
|
|
815
|
+
this.consume("RIGHT_PAREN", "Expected ) after method arguments");
|
|
816
|
+
expr = {
|
|
817
|
+
type: "ThisMethodCall",
|
|
818
|
+
method: expr.property,
|
|
819
|
+
arguments: args,
|
|
820
|
+
};
|
|
821
|
+
} else {
|
|
822
|
+
// Put back the LEFT_PAREN for other handlers
|
|
823
|
+
this.current--;
|
|
824
|
+
break;
|
|
825
|
+
}
|
|
636
826
|
} else {
|
|
637
827
|
break;
|
|
638
828
|
}
|
|
@@ -646,60 +836,182 @@ class Parser {
|
|
|
646
836
|
* @returns {Object} Primary expression
|
|
647
837
|
*/
|
|
648
838
|
primary() {
|
|
649
|
-
if (this.match(
|
|
650
|
-
if (this.match(
|
|
651
|
-
if (this.match(
|
|
652
|
-
if (this.match(
|
|
653
|
-
if (this.match(
|
|
839
|
+
if (this.match("FALSE")) return { type: "Literal", value: false };
|
|
840
|
+
if (this.match("TRUE")) return { type: "Literal", value: true };
|
|
841
|
+
if (this.match("NULL")) return { type: "Literal", value: null };
|
|
842
|
+
if (this.match("UNDEFINED")) return { type: "Literal", value: undefined };
|
|
843
|
+
if (this.match("NUMBER", "STRING")) {
|
|
654
844
|
return {
|
|
655
|
-
type:
|
|
845
|
+
type: "Literal",
|
|
656
846
|
value: this.previous().literal,
|
|
657
847
|
};
|
|
658
848
|
}
|
|
659
849
|
|
|
660
|
-
if (this.match(
|
|
850
|
+
if (this.match("TEMPLATE_STRING")) {
|
|
851
|
+
return this.templateStringExpression();
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
if (this.match("IDENTIFIER")) {
|
|
661
855
|
const identifier = this.previous();
|
|
662
|
-
|
|
856
|
+
// Check for single-param arrow function: x => ...
|
|
857
|
+
if (this.check("ARROW")) {
|
|
858
|
+
this.advance(); // Consume the ARROW
|
|
859
|
+
return this.arrowFunctionBody([identifier.lexeme]);
|
|
860
|
+
}
|
|
861
|
+
if (this.check("LEFT_PAREN")) {
|
|
663
862
|
this.advance(); // Consume the LEFT_PAREN
|
|
664
863
|
return this.finishCall(identifier);
|
|
665
864
|
}
|
|
666
865
|
return {
|
|
667
|
-
type:
|
|
866
|
+
type: "Variable",
|
|
668
867
|
name: identifier.lexeme,
|
|
669
868
|
};
|
|
670
869
|
}
|
|
671
870
|
|
|
672
|
-
if (this.match(
|
|
871
|
+
if (this.match("AND")) {
|
|
673
872
|
const identifier = this.previous();
|
|
674
|
-
if (this.check(
|
|
873
|
+
if (this.check("LEFT_PAREN")) {
|
|
675
874
|
this.advance(); // Consume the LEFT_PAREN
|
|
676
875
|
return this.finishCall(identifier);
|
|
677
876
|
}
|
|
678
877
|
return {
|
|
679
|
-
type:
|
|
878
|
+
type: "Variable",
|
|
680
879
|
name: identifier.lexeme,
|
|
681
880
|
};
|
|
682
881
|
}
|
|
683
882
|
|
|
684
|
-
if (this.match(
|
|
883
|
+
if (this.match("LEFT_PAREN")) {
|
|
884
|
+
// Check if this could be arrow function params
|
|
885
|
+
if (this.isArrowFunctionParams()) {
|
|
886
|
+
return this.arrowFunctionWithParams();
|
|
887
|
+
}
|
|
685
888
|
const expr = this.expression();
|
|
686
|
-
this.consume(
|
|
889
|
+
this.consume("RIGHT_PAREN", "Expected ) after expression");
|
|
687
890
|
return expr;
|
|
688
891
|
}
|
|
689
892
|
|
|
690
|
-
if (this.match(
|
|
893
|
+
if (this.match("LEFT_BRACKET")) {
|
|
691
894
|
return this.arrayLiteral();
|
|
692
895
|
}
|
|
693
896
|
|
|
694
|
-
if (this.match(
|
|
897
|
+
if (this.match("LEFT_BRACE")) {
|
|
695
898
|
return this.objectLiteral();
|
|
696
899
|
}
|
|
697
900
|
|
|
698
|
-
if (this.match(
|
|
901
|
+
if (this.match("FUNCION")) {
|
|
699
902
|
return this.anonymousFunction();
|
|
700
903
|
}
|
|
701
904
|
|
|
702
|
-
|
|
905
|
+
if (this.match("NUEVO")) {
|
|
906
|
+
return this.newExpression();
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
if (this.match("ESTE")) {
|
|
910
|
+
return this.thisExpression();
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
if (this.match("SUPER")) {
|
|
914
|
+
return this.superExpression();
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
throw new Error("Se esperaba una expresión");
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
/**
|
|
921
|
+
* Parses a new expression for class instantiation
|
|
922
|
+
* @returns {Object} New expression
|
|
923
|
+
*/
|
|
924
|
+
newExpression() {
|
|
925
|
+
const className = this.consume(
|
|
926
|
+
"IDENTIFIER",
|
|
927
|
+
"Se esperaba el nombre de la clase después de 'nuevo'",
|
|
928
|
+
);
|
|
929
|
+
this.consume("LEFT_PAREN", "Se esperaba ( después del nombre de la clase");
|
|
930
|
+
|
|
931
|
+
const args = [];
|
|
932
|
+
if (!this.check("RIGHT_PAREN")) {
|
|
933
|
+
do {
|
|
934
|
+
args.push(this.expression());
|
|
935
|
+
} while (this.match("COMMA"));
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
this.consume("RIGHT_PAREN", "Se esperaba ) después de los argumentos");
|
|
939
|
+
|
|
940
|
+
return {
|
|
941
|
+
type: "NewExpression",
|
|
942
|
+
className: className.lexeme,
|
|
943
|
+
arguments: args,
|
|
944
|
+
};
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
/**
|
|
948
|
+
* Parses 'este' (this) expression
|
|
949
|
+
* @returns {Object} This expression
|
|
950
|
+
*/
|
|
951
|
+
thisExpression() {
|
|
952
|
+
// Check if accessing a property
|
|
953
|
+
if (this.match("DOT")) {
|
|
954
|
+
const property = this.consume(
|
|
955
|
+
"IDENTIFIER",
|
|
956
|
+
"Se esperaba el nombre de la propiedad después de 'este.'",
|
|
957
|
+
);
|
|
958
|
+
return {
|
|
959
|
+
type: "ThisPropertyAccess",
|
|
960
|
+
property: property.lexeme,
|
|
961
|
+
};
|
|
962
|
+
}
|
|
963
|
+
return {
|
|
964
|
+
type: "This",
|
|
965
|
+
};
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
/**
|
|
969
|
+
* Parses 'super' expression for parent class calls
|
|
970
|
+
* @returns {Object} Super expression
|
|
971
|
+
*/
|
|
972
|
+
superExpression() {
|
|
973
|
+
this.consume("LEFT_PAREN", "Se esperaba ( después de 'super'");
|
|
974
|
+
|
|
975
|
+
const args = [];
|
|
976
|
+
if (!this.check("RIGHT_PAREN")) {
|
|
977
|
+
do {
|
|
978
|
+
args.push(this.expression());
|
|
979
|
+
} while (this.match("COMMA"));
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
this.consume(
|
|
983
|
+
"RIGHT_PAREN",
|
|
984
|
+
"Se esperaba ) después de los argumentos de super",
|
|
985
|
+
);
|
|
986
|
+
|
|
987
|
+
return {
|
|
988
|
+
type: "SuperCall",
|
|
989
|
+
arguments: args,
|
|
990
|
+
};
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
/**
|
|
994
|
+
* Parses a template string expression
|
|
995
|
+
* @returns {Object} Template string expression with parsed expressions
|
|
996
|
+
*/
|
|
997
|
+
templateStringExpression() {
|
|
998
|
+
const token = this.previous();
|
|
999
|
+
const { parts, expressions } = token.literal;
|
|
1000
|
+
const Tokenizer = require("./tokenizer.js");
|
|
1001
|
+
|
|
1002
|
+
// Parse each expression string into an AST
|
|
1003
|
+
const parsedExpressions = expressions.map((exprSource) => {
|
|
1004
|
+
const tokenizer = new Tokenizer();
|
|
1005
|
+
const exprTokens = tokenizer.tokenize(exprSource);
|
|
1006
|
+
const exprParser = new Parser(exprTokens);
|
|
1007
|
+
return exprParser.expression();
|
|
1008
|
+
});
|
|
1009
|
+
|
|
1010
|
+
return {
|
|
1011
|
+
type: "TemplateString",
|
|
1012
|
+
parts,
|
|
1013
|
+
expressions: parsedExpressions,
|
|
1014
|
+
};
|
|
703
1015
|
}
|
|
704
1016
|
|
|
705
1017
|
/**
|
|
@@ -707,38 +1019,141 @@ class Parser {
|
|
|
707
1019
|
* @returns {Object} Anonymous function expression
|
|
708
1020
|
*/
|
|
709
1021
|
anonymousFunction() {
|
|
710
|
-
this.consume(
|
|
1022
|
+
this.consume("LEFT_PAREN", "Expected ( after funcion");
|
|
711
1023
|
|
|
712
1024
|
const parameters = [];
|
|
713
|
-
if (!this.check(
|
|
1025
|
+
if (!this.check("RIGHT_PAREN")) {
|
|
714
1026
|
do {
|
|
715
1027
|
if (parameters.length >= 255) {
|
|
716
|
-
throw new Error(
|
|
1028
|
+
throw new Error("No se pueden tener más de 255 parámetros");
|
|
717
1029
|
}
|
|
718
1030
|
let param;
|
|
719
|
-
if (this.match(
|
|
1031
|
+
if (this.match("IDENTIFIER")) {
|
|
720
1032
|
param = this.previous();
|
|
721
|
-
} else if (this.match(
|
|
1033
|
+
} else if (this.match("AND")) {
|
|
722
1034
|
param = this.previous();
|
|
723
1035
|
} else {
|
|
724
|
-
throw new Error(
|
|
1036
|
+
throw new Error("Se esperaba un nombre de parámetro");
|
|
725
1037
|
}
|
|
726
1038
|
parameters.push(param.lexeme);
|
|
727
|
-
} while (this.match(
|
|
1039
|
+
} while (this.match("COMMA"));
|
|
728
1040
|
}
|
|
729
1041
|
|
|
730
|
-
this.consume(
|
|
731
|
-
this.consume(
|
|
1042
|
+
this.consume("RIGHT_PAREN", "Expected ) after parameters");
|
|
1043
|
+
this.consume("LEFT_BRACE", "Expected { before function body");
|
|
732
1044
|
const body = this.block();
|
|
733
|
-
this.consume(
|
|
1045
|
+
this.consume("RIGHT_BRACE", "Expected } after function body");
|
|
734
1046
|
|
|
735
1047
|
return {
|
|
736
|
-
type:
|
|
1048
|
+
type: "AnonymousFunction",
|
|
737
1049
|
parameters,
|
|
738
1050
|
body,
|
|
739
1051
|
};
|
|
740
1052
|
}
|
|
741
1053
|
|
|
1054
|
+
/**
|
|
1055
|
+
* Checks if current position looks like arrow function parameters
|
|
1056
|
+
* Looks ahead to find matching ) and checks for =>
|
|
1057
|
+
* @returns {boolean} True if this looks like arrow function params
|
|
1058
|
+
*/
|
|
1059
|
+
isArrowFunctionParams() {
|
|
1060
|
+
// Save current position
|
|
1061
|
+
const startPos = this.current;
|
|
1062
|
+
|
|
1063
|
+
// Empty params: () =>
|
|
1064
|
+
if (this.check("RIGHT_PAREN")) {
|
|
1065
|
+
// Check if next is =>
|
|
1066
|
+
if (
|
|
1067
|
+
this.tokens[this.current + 1] &&
|
|
1068
|
+
this.tokens[this.current + 1].type === "ARROW"
|
|
1069
|
+
) {
|
|
1070
|
+
return true;
|
|
1071
|
+
}
|
|
1072
|
+
return false;
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1075
|
+
// Try to match params pattern: identifier (comma identifier)*
|
|
1076
|
+
let parenDepth = 1;
|
|
1077
|
+
let pos = this.current;
|
|
1078
|
+
|
|
1079
|
+
while (pos < this.tokens.length && parenDepth > 0) {
|
|
1080
|
+
const token = this.tokens[pos];
|
|
1081
|
+
if (token.type === "LEFT_PAREN") {
|
|
1082
|
+
parenDepth++;
|
|
1083
|
+
} else if (token.type === "RIGHT_PAREN") {
|
|
1084
|
+
parenDepth--;
|
|
1085
|
+
}
|
|
1086
|
+
pos++;
|
|
1087
|
+
}
|
|
1088
|
+
|
|
1089
|
+
// pos is now after the matching )
|
|
1090
|
+
// Check if next token is =>
|
|
1091
|
+
if (pos < this.tokens.length && this.tokens[pos].type === "ARROW") {
|
|
1092
|
+
return true;
|
|
1093
|
+
}
|
|
1094
|
+
|
|
1095
|
+
return false;
|
|
1096
|
+
}
|
|
1097
|
+
|
|
1098
|
+
/**
|
|
1099
|
+
* Parses arrow function with parenthesized parameters
|
|
1100
|
+
* Called after LEFT_PAREN has been consumed
|
|
1101
|
+
* @returns {Object} Arrow function expression
|
|
1102
|
+
*/
|
|
1103
|
+
arrowFunctionWithParams() {
|
|
1104
|
+
const parameters = [];
|
|
1105
|
+
|
|
1106
|
+
// Parse parameters
|
|
1107
|
+
if (!this.check("RIGHT_PAREN")) {
|
|
1108
|
+
do {
|
|
1109
|
+
if (parameters.length >= 255) {
|
|
1110
|
+
throw new Error("No se pueden tener más de 255 parámetros");
|
|
1111
|
+
}
|
|
1112
|
+
const param = this.consume(
|
|
1113
|
+
"IDENTIFIER",
|
|
1114
|
+
"Se esperaba un nombre de parámetro",
|
|
1115
|
+
);
|
|
1116
|
+
parameters.push(param.lexeme);
|
|
1117
|
+
} while (this.match("COMMA"));
|
|
1118
|
+
}
|
|
1119
|
+
|
|
1120
|
+
this.consume("RIGHT_PAREN", "Se esperaba ) después de los parámetros");
|
|
1121
|
+
this.consume("ARROW", "Se esperaba => después de los parámetros");
|
|
1122
|
+
|
|
1123
|
+
return this.arrowFunctionBody(parameters);
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
/**
|
|
1127
|
+
* Parses arrow function body (expression or block)
|
|
1128
|
+
* @param {Array} parameters - Function parameters
|
|
1129
|
+
* @returns {Object} Arrow function expression
|
|
1130
|
+
*/
|
|
1131
|
+
arrowFunctionBody(parameters) {
|
|
1132
|
+
// Check if body is a block
|
|
1133
|
+
if (this.match("LEFT_BRACE")) {
|
|
1134
|
+
const body = this.block();
|
|
1135
|
+
this.consume(
|
|
1136
|
+
"RIGHT_BRACE",
|
|
1137
|
+
"Se esperaba } después del cuerpo de la función",
|
|
1138
|
+
);
|
|
1139
|
+
return {
|
|
1140
|
+
type: "ArrowFunction",
|
|
1141
|
+
parameters,
|
|
1142
|
+
body,
|
|
1143
|
+
isExpression: false,
|
|
1144
|
+
};
|
|
1145
|
+
}
|
|
1146
|
+
|
|
1147
|
+
// Body is a single expression (implicit return)
|
|
1148
|
+
const expression = this.expression();
|
|
1149
|
+
return {
|
|
1150
|
+
type: "ArrowFunction",
|
|
1151
|
+
parameters,
|
|
1152
|
+
body: expression,
|
|
1153
|
+
isExpression: true,
|
|
1154
|
+
};
|
|
1155
|
+
}
|
|
1156
|
+
|
|
742
1157
|
/**
|
|
743
1158
|
* Finishes parsing an array access
|
|
744
1159
|
* @param {Object} array - The array being accessed
|
|
@@ -746,10 +1161,10 @@ class Parser {
|
|
|
746
1161
|
*/
|
|
747
1162
|
finishArrayAccess(array) {
|
|
748
1163
|
const index = this.expression();
|
|
749
|
-
this.consume(
|
|
1164
|
+
this.consume("RIGHT_BRACKET", "Expected ] after array index");
|
|
750
1165
|
|
|
751
1166
|
return {
|
|
752
|
-
type:
|
|
1167
|
+
type: "ArrayAccess",
|
|
753
1168
|
array,
|
|
754
1169
|
index,
|
|
755
1170
|
};
|
|
@@ -761,63 +1176,101 @@ class Parser {
|
|
|
761
1176
|
* @returns {Object} Property access expression
|
|
762
1177
|
*/
|
|
763
1178
|
finishPropertyAccess(object) {
|
|
764
|
-
this.consume(
|
|
1179
|
+
this.consume("IDENTIFIER", "Expected property name after .");
|
|
765
1180
|
const name = this.previous();
|
|
766
1181
|
|
|
767
|
-
// Check if this is a method call (array or
|
|
1182
|
+
// Check if this is a method call (array, string, or number)
|
|
768
1183
|
if (
|
|
769
|
-
name.lexeme ===
|
|
770
|
-
name.lexeme ===
|
|
771
|
-
name.lexeme ===
|
|
772
|
-
name.lexeme ===
|
|
773
|
-
name.lexeme ===
|
|
774
|
-
name.lexeme ===
|
|
775
|
-
name.lexeme ===
|
|
776
|
-
name.lexeme ===
|
|
777
|
-
name.lexeme ===
|
|
1184
|
+
name.lexeme === "longitud" ||
|
|
1185
|
+
name.lexeme === "primero" ||
|
|
1186
|
+
name.lexeme === "ultimo" ||
|
|
1187
|
+
name.lexeme === "agregar" ||
|
|
1188
|
+
name.lexeme === "remover" ||
|
|
1189
|
+
name.lexeme === "contiene" ||
|
|
1190
|
+
name.lexeme === "recorrer" ||
|
|
1191
|
+
name.lexeme === "mayusculas" ||
|
|
1192
|
+
name.lexeme === "minusculas" ||
|
|
1193
|
+
name.lexeme === "dividir" ||
|
|
1194
|
+
name.lexeme === "reemplazar" ||
|
|
1195
|
+
name.lexeme === "recortar" ||
|
|
1196
|
+
name.lexeme === "incluye" ||
|
|
1197
|
+
name.lexeme === "empiezaCon" ||
|
|
1198
|
+
name.lexeme === "terminaCon" ||
|
|
1199
|
+
name.lexeme === "caracter" ||
|
|
1200
|
+
name.lexeme === "subcadena" ||
|
|
1201
|
+
name.lexeme === "invertir" ||
|
|
1202
|
+
name.lexeme === "filtrar" ||
|
|
1203
|
+
name.lexeme === "mapear" ||
|
|
1204
|
+
name.lexeme === "reducir" ||
|
|
1205
|
+
name.lexeme === "ordenar" ||
|
|
1206
|
+
name.lexeme === "buscar" ||
|
|
1207
|
+
name.lexeme === "algunos" ||
|
|
1208
|
+
name.lexeme === "todos" ||
|
|
1209
|
+
name.lexeme === "unir" ||
|
|
1210
|
+
name.lexeme === "cortar" ||
|
|
1211
|
+
name.lexeme === "insertar" ||
|
|
1212
|
+
name.lexeme === "esPar" ||
|
|
1213
|
+
name.lexeme === "esImpar" ||
|
|
1214
|
+
name.lexeme === "esPositivo" ||
|
|
1215
|
+
name.lexeme === "esNegativo" ||
|
|
1216
|
+
name.lexeme === "aTexto"
|
|
778
1217
|
) {
|
|
779
1218
|
// Check if there are parentheses (method call syntax)
|
|
780
|
-
if (this.match(
|
|
1219
|
+
if (this.match("LEFT_PAREN")) {
|
|
781
1220
|
// Consume the opening parenthesis
|
|
782
1221
|
// Check if there are arguments
|
|
783
|
-
if (!this.check(
|
|
784
|
-
// Handle methods that accept arguments
|
|
1222
|
+
if (!this.check("RIGHT_PAREN")) {
|
|
1223
|
+
// Handle methods that accept arguments
|
|
785
1224
|
if (
|
|
786
|
-
name.lexeme ===
|
|
787
|
-
name.lexeme ===
|
|
788
|
-
name.lexeme ===
|
|
1225
|
+
name.lexeme === "agregar" ||
|
|
1226
|
+
name.lexeme === "contiene" ||
|
|
1227
|
+
name.lexeme === "recorrer" ||
|
|
1228
|
+
name.lexeme === "dividir" ||
|
|
1229
|
+
name.lexeme === "reemplazar" ||
|
|
1230
|
+
name.lexeme === "incluye" ||
|
|
1231
|
+
name.lexeme === "empiezaCon" ||
|
|
1232
|
+
name.lexeme === "terminaCon" ||
|
|
1233
|
+
name.lexeme === "caracter" ||
|
|
1234
|
+
name.lexeme === "subcadena" ||
|
|
1235
|
+
name.lexeme === "filtrar" ||
|
|
1236
|
+
name.lexeme === "mapear" ||
|
|
1237
|
+
name.lexeme === "reducir" ||
|
|
1238
|
+
name.lexeme === "buscar" ||
|
|
1239
|
+
name.lexeme === "algunos" ||
|
|
1240
|
+
name.lexeme === "todos" ||
|
|
1241
|
+
name.lexeme === "unir" ||
|
|
1242
|
+
name.lexeme === "cortar" ||
|
|
1243
|
+
name.lexeme === "insertar"
|
|
789
1244
|
) {
|
|
790
1245
|
const args = [];
|
|
791
1246
|
do {
|
|
792
1247
|
args.push(this.expression());
|
|
793
|
-
} while (this.match(
|
|
794
|
-
this.consume(
|
|
1248
|
+
} while (this.match("COMMA"));
|
|
1249
|
+
this.consume("RIGHT_PAREN", "Expected ) after method call");
|
|
795
1250
|
|
|
796
1251
|
return {
|
|
797
|
-
type:
|
|
1252
|
+
type: "MethodCall",
|
|
798
1253
|
object,
|
|
799
1254
|
method: name.lexeme,
|
|
800
1255
|
arguments: args,
|
|
801
1256
|
};
|
|
802
1257
|
} else {
|
|
803
|
-
throw new Error(
|
|
804
|
-
`El método ${name.lexeme}() no acepta argumentos`
|
|
805
|
-
);
|
|
1258
|
+
throw new Error(`El método ${name.lexeme}() no acepta argumentos`);
|
|
806
1259
|
}
|
|
807
1260
|
}
|
|
808
1261
|
// Consume the closing parenthesis
|
|
809
|
-
this.consume(
|
|
1262
|
+
this.consume("RIGHT_PAREN", "Expected ) after method call");
|
|
810
1263
|
}
|
|
811
1264
|
|
|
812
1265
|
return {
|
|
813
|
-
type:
|
|
1266
|
+
type: "MethodCall",
|
|
814
1267
|
object,
|
|
815
1268
|
method: name.lexeme,
|
|
816
1269
|
};
|
|
817
1270
|
}
|
|
818
1271
|
|
|
819
1272
|
return {
|
|
820
|
-
type:
|
|
1273
|
+
type: "PropertyAccess",
|
|
821
1274
|
object,
|
|
822
1275
|
name: name.lexeme,
|
|
823
1276
|
};
|
|
@@ -831,21 +1284,21 @@ class Parser {
|
|
|
831
1284
|
finishCall(callee) {
|
|
832
1285
|
const args = [];
|
|
833
1286
|
|
|
834
|
-
if (!this.check(
|
|
1287
|
+
if (!this.check("RIGHT_PAREN")) {
|
|
835
1288
|
do {
|
|
836
1289
|
if (args.length >= 255) {
|
|
837
|
-
throw new Error(
|
|
1290
|
+
throw new Error("Cannot have more than 255 arguments");
|
|
838
1291
|
}
|
|
839
1292
|
args.push(this.expression());
|
|
840
|
-
} while (this.match(
|
|
1293
|
+
} while (this.match("COMMA"));
|
|
841
1294
|
}
|
|
842
1295
|
|
|
843
|
-
const paren = this.consume(
|
|
1296
|
+
const paren = this.consume("RIGHT_PAREN", "Expected ) after arguments");
|
|
844
1297
|
|
|
845
1298
|
return {
|
|
846
|
-
type:
|
|
1299
|
+
type: "Call",
|
|
847
1300
|
callee: {
|
|
848
|
-
type:
|
|
1301
|
+
type: "Variable",
|
|
849
1302
|
name: callee.lexeme,
|
|
850
1303
|
},
|
|
851
1304
|
arguments: args,
|
|
@@ -859,16 +1312,16 @@ class Parser {
|
|
|
859
1312
|
arrayLiteral() {
|
|
860
1313
|
const elements = [];
|
|
861
1314
|
|
|
862
|
-
if (!this.check(
|
|
1315
|
+
if (!this.check("RIGHT_BRACKET")) {
|
|
863
1316
|
do {
|
|
864
1317
|
elements.push(this.expression());
|
|
865
|
-
} while (this.match(
|
|
1318
|
+
} while (this.match("COMMA"));
|
|
866
1319
|
}
|
|
867
1320
|
|
|
868
|
-
this.consume(
|
|
1321
|
+
this.consume("RIGHT_BRACKET", "Expected ] after array elements");
|
|
869
1322
|
|
|
870
1323
|
return {
|
|
871
|
-
type:
|
|
1324
|
+
type: "ArrayLiteral",
|
|
872
1325
|
elements,
|
|
873
1326
|
};
|
|
874
1327
|
}
|
|
@@ -880,33 +1333,33 @@ class Parser {
|
|
|
880
1333
|
objectLiteral() {
|
|
881
1334
|
const properties = [];
|
|
882
1335
|
|
|
883
|
-
if (!this.check(
|
|
1336
|
+
if (!this.check("RIGHT_BRACE")) {
|
|
884
1337
|
do {
|
|
885
1338
|
// Parse property name (identifier or string)
|
|
886
1339
|
let name;
|
|
887
|
-
if (this.match(
|
|
1340
|
+
if (this.match("IDENTIFIER")) {
|
|
888
1341
|
name = this.previous().lexeme;
|
|
889
|
-
} else if (this.match(
|
|
1342
|
+
} else if (this.match("STRING")) {
|
|
890
1343
|
name = this.previous().literal;
|
|
891
1344
|
} else {
|
|
892
|
-
throw new Error(
|
|
1345
|
+
throw new Error("Se esperaba un nombre de propiedad");
|
|
893
1346
|
}
|
|
894
1347
|
|
|
895
|
-
this.consume(
|
|
1348
|
+
this.consume("COLON", "Expected : after property name");
|
|
896
1349
|
const value = this.expression();
|
|
897
1350
|
|
|
898
1351
|
properties.push({
|
|
899
|
-
type:
|
|
1352
|
+
type: "Property",
|
|
900
1353
|
name,
|
|
901
1354
|
value,
|
|
902
1355
|
});
|
|
903
|
-
} while (this.match(
|
|
1356
|
+
} while (this.match("COMMA"));
|
|
904
1357
|
}
|
|
905
1358
|
|
|
906
|
-
this.consume(
|
|
1359
|
+
this.consume("RIGHT_BRACE", "Expected } after object properties");
|
|
907
1360
|
|
|
908
1361
|
return {
|
|
909
|
-
type:
|
|
1362
|
+
type: "ObjectLiteral",
|
|
910
1363
|
properties,
|
|
911
1364
|
};
|
|
912
1365
|
}
|
|
@@ -950,7 +1403,7 @@ class Parser {
|
|
|
950
1403
|
* @returns {boolean} True if we reached the end
|
|
951
1404
|
*/
|
|
952
1405
|
isAtEnd() {
|
|
953
|
-
return this.peek().type ===
|
|
1406
|
+
return this.peek().type === "EOF";
|
|
954
1407
|
}
|
|
955
1408
|
|
|
956
1409
|
/**
|
|
@@ -988,21 +1441,23 @@ class Parser {
|
|
|
988
1441
|
this.advance();
|
|
989
1442
|
|
|
990
1443
|
while (!this.isAtEnd()) {
|
|
991
|
-
if (this.previous().type ===
|
|
1444
|
+
if (this.previous().type === "EOF") return;
|
|
992
1445
|
|
|
993
1446
|
switch (this.peek().type) {
|
|
994
|
-
case
|
|
995
|
-
case
|
|
996
|
-
case
|
|
997
|
-
case
|
|
998
|
-
case
|
|
999
|
-
case
|
|
1000
|
-
case
|
|
1001
|
-
case
|
|
1002
|
-
case
|
|
1003
|
-
case
|
|
1004
|
-
case
|
|
1005
|
-
case
|
|
1447
|
+
case "VARIABLE":
|
|
1448
|
+
case "CONSTANTE":
|
|
1449
|
+
case "FUNCION":
|
|
1450
|
+
case "CLASE":
|
|
1451
|
+
case "MOSTRAR":
|
|
1452
|
+
case "LEER":
|
|
1453
|
+
case "SI":
|
|
1454
|
+
case "MIENTRAS":
|
|
1455
|
+
case "PARA":
|
|
1456
|
+
case "RETORNAR":
|
|
1457
|
+
case "ROMPER":
|
|
1458
|
+
case "CONTINUAR":
|
|
1459
|
+
case "INTENTAR":
|
|
1460
|
+
case "CAPTURAR":
|
|
1006
1461
|
return;
|
|
1007
1462
|
}
|
|
1008
1463
|
|
|
@@ -1016,35 +1471,137 @@ class Parser {
|
|
|
1016
1471
|
*/
|
|
1017
1472
|
tryStatement() {
|
|
1018
1473
|
// Parse the try block
|
|
1019
|
-
this.consume(
|
|
1474
|
+
this.consume("LEFT_BRACE", "Expected { after intentar");
|
|
1020
1475
|
const tryBlock = this.block();
|
|
1021
|
-
this.consume(
|
|
1476
|
+
this.consume("RIGHT_BRACE", "Expected } after intentar block");
|
|
1022
1477
|
|
|
1023
1478
|
// Look for catch block
|
|
1024
|
-
if (this.match(
|
|
1479
|
+
if (this.match("CAPTURAR")) {
|
|
1025
1480
|
// Parse catch parameter (error variable name)
|
|
1026
|
-
this.consume(
|
|
1481
|
+
this.consume("LEFT_PAREN", "Expected ( after capturar");
|
|
1027
1482
|
const errorVariable = this.consume(
|
|
1028
|
-
|
|
1029
|
-
|
|
1483
|
+
"IDENTIFIER",
|
|
1484
|
+
"Expected error variable name",
|
|
1030
1485
|
);
|
|
1031
|
-
this.consume(
|
|
1486
|
+
this.consume("RIGHT_PAREN", "Expected ) after error variable");
|
|
1032
1487
|
|
|
1033
1488
|
// Parse catch block
|
|
1034
|
-
this.consume(
|
|
1489
|
+
this.consume("LEFT_BRACE", "Expected { after capturar");
|
|
1035
1490
|
const catchBlock = this.block();
|
|
1036
|
-
this.consume(
|
|
1491
|
+
this.consume("RIGHT_BRACE", "Expected } after capturar block");
|
|
1037
1492
|
|
|
1038
1493
|
return {
|
|
1039
|
-
type:
|
|
1494
|
+
type: "TryCatch",
|
|
1040
1495
|
tryBlock,
|
|
1041
1496
|
catchBlock,
|
|
1042
1497
|
errorVariable: errorVariable.lexeme,
|
|
1043
1498
|
};
|
|
1044
1499
|
} else {
|
|
1045
|
-
throw new Error(
|
|
1500
|
+
throw new Error("Se esperaba capturar después del bloque intentar");
|
|
1046
1501
|
}
|
|
1047
1502
|
}
|
|
1503
|
+
|
|
1504
|
+
/**
|
|
1505
|
+
* Parses an elegir (switch) statement
|
|
1506
|
+
* @returns {Object} Elegir statement
|
|
1507
|
+
*/
|
|
1508
|
+
elegirStatement() {
|
|
1509
|
+
const discriminant = this.expression();
|
|
1510
|
+
this.consume("LEFT_BRACE", "Expected { after elegir expression");
|
|
1511
|
+
|
|
1512
|
+
const cases = [];
|
|
1513
|
+
let defaultCase = null;
|
|
1514
|
+
|
|
1515
|
+
while (!this.check("RIGHT_BRACE") && !this.isAtEnd()) {
|
|
1516
|
+
if (this.match("CASO")) {
|
|
1517
|
+
const testValue = this.expression();
|
|
1518
|
+
this.consume("COLON", "Expected : after caso value");
|
|
1519
|
+
|
|
1520
|
+
let consequent;
|
|
1521
|
+
if (this.check("LEFT_BRACE")) {
|
|
1522
|
+
this.advance();
|
|
1523
|
+
consequent = this.block();
|
|
1524
|
+
this.consume("RIGHT_BRACE", "Expected } after caso block");
|
|
1525
|
+
} else {
|
|
1526
|
+
consequent = [this.declaration()];
|
|
1527
|
+
}
|
|
1528
|
+
|
|
1529
|
+
cases.push({
|
|
1530
|
+
test: testValue,
|
|
1531
|
+
consequent,
|
|
1532
|
+
});
|
|
1533
|
+
} else if (this.match("PORDEFECTO")) {
|
|
1534
|
+
this.consume("COLON", "Expected : after pordefecto");
|
|
1535
|
+
|
|
1536
|
+
let consequent;
|
|
1537
|
+
if (this.check("LEFT_BRACE")) {
|
|
1538
|
+
this.advance();
|
|
1539
|
+
consequent = this.block();
|
|
1540
|
+
this.consume("RIGHT_BRACE", "Expected } after pordefecto block");
|
|
1541
|
+
} else {
|
|
1542
|
+
consequent = [this.declaration()];
|
|
1543
|
+
}
|
|
1544
|
+
|
|
1545
|
+
defaultCase = {
|
|
1546
|
+
consequent,
|
|
1547
|
+
};
|
|
1548
|
+
} else {
|
|
1549
|
+
throw new Error("Se esperaba caso o pordefecto dentro de elegir");
|
|
1550
|
+
}
|
|
1551
|
+
}
|
|
1552
|
+
|
|
1553
|
+
this.consume("RIGHT_BRACE", "Expected } after elegir block");
|
|
1554
|
+
|
|
1555
|
+
return {
|
|
1556
|
+
type: "ElegirStatement",
|
|
1557
|
+
discriminant,
|
|
1558
|
+
cases,
|
|
1559
|
+
defaultCase,
|
|
1560
|
+
};
|
|
1561
|
+
}
|
|
1562
|
+
|
|
1563
|
+
/**
|
|
1564
|
+
* Parses a hacer/mientras (do-while) statement
|
|
1565
|
+
* @returns {Object} HacerMientras statement
|
|
1566
|
+
*/
|
|
1567
|
+
hacerMientrasStatement() {
|
|
1568
|
+
this.consume("LEFT_BRACE", "Expected { after hacer");
|
|
1569
|
+
const body = this.block();
|
|
1570
|
+
this.consume("RIGHT_BRACE", "Expected } after hacer block");
|
|
1571
|
+
|
|
1572
|
+
this.consume("MIENTRAS", "Expected mientras after hacer block");
|
|
1573
|
+
const condition = this.expression();
|
|
1574
|
+
|
|
1575
|
+
return {
|
|
1576
|
+
type: "HacerMientrasStatement",
|
|
1577
|
+
body,
|
|
1578
|
+
condition,
|
|
1579
|
+
};
|
|
1580
|
+
}
|
|
1581
|
+
|
|
1582
|
+
/**
|
|
1583
|
+
* Parses a para cada (for-each) statement
|
|
1584
|
+
* @returns {Object} ForEach statement
|
|
1585
|
+
*/
|
|
1586
|
+
forEachStatement() {
|
|
1587
|
+
const iteratorName = this.consume(
|
|
1588
|
+
"IDENTIFIER",
|
|
1589
|
+
"Expected iterator variable name after cada",
|
|
1590
|
+
);
|
|
1591
|
+
this.consume("EN", "Expected en after iterator variable");
|
|
1592
|
+
const iterable = this.expression();
|
|
1593
|
+
|
|
1594
|
+
this.consume("LEFT_BRACE", "Expected { after iterable");
|
|
1595
|
+
const body = this.block();
|
|
1596
|
+
this.consume("RIGHT_BRACE", "Expected } after para cada block");
|
|
1597
|
+
|
|
1598
|
+
return {
|
|
1599
|
+
type: "ForEachStatement",
|
|
1600
|
+
iterator: iteratorName.lexeme,
|
|
1601
|
+
iterable,
|
|
1602
|
+
body,
|
|
1603
|
+
};
|
|
1604
|
+
}
|
|
1048
1605
|
}
|
|
1049
1606
|
|
|
1050
1607
|
module.exports = Parser;
|