hispano-lang 1.0.8 → 2.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/README.md +637 -237
- package/dist/evaluator.js +1395 -259
- package/dist/parser.js +646 -201
- package/dist/tokenizer.js +199 -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,173 @@ 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
|
-
if (this.check(
|
|
856
|
+
if (this.check("LEFT_PAREN")) {
|
|
663
857
|
this.advance(); // Consume the LEFT_PAREN
|
|
664
858
|
return this.finishCall(identifier);
|
|
665
859
|
}
|
|
666
860
|
return {
|
|
667
|
-
type:
|
|
861
|
+
type: "Variable",
|
|
668
862
|
name: identifier.lexeme,
|
|
669
863
|
};
|
|
670
864
|
}
|
|
671
865
|
|
|
672
|
-
if (this.match(
|
|
866
|
+
if (this.match("AND")) {
|
|
673
867
|
const identifier = this.previous();
|
|
674
|
-
if (this.check(
|
|
868
|
+
if (this.check("LEFT_PAREN")) {
|
|
675
869
|
this.advance(); // Consume the LEFT_PAREN
|
|
676
870
|
return this.finishCall(identifier);
|
|
677
871
|
}
|
|
678
872
|
return {
|
|
679
|
-
type:
|
|
873
|
+
type: "Variable",
|
|
680
874
|
name: identifier.lexeme,
|
|
681
875
|
};
|
|
682
876
|
}
|
|
683
877
|
|
|
684
|
-
if (this.match(
|
|
878
|
+
if (this.match("LEFT_PAREN")) {
|
|
685
879
|
const expr = this.expression();
|
|
686
|
-
this.consume(
|
|
880
|
+
this.consume("RIGHT_PAREN", "Expected ) after expression");
|
|
687
881
|
return expr;
|
|
688
882
|
}
|
|
689
883
|
|
|
690
|
-
if (this.match(
|
|
884
|
+
if (this.match("LEFT_BRACKET")) {
|
|
691
885
|
return this.arrayLiteral();
|
|
692
886
|
}
|
|
693
887
|
|
|
694
|
-
if (this.match(
|
|
888
|
+
if (this.match("LEFT_BRACE")) {
|
|
695
889
|
return this.objectLiteral();
|
|
696
890
|
}
|
|
697
891
|
|
|
698
|
-
if (this.match(
|
|
892
|
+
if (this.match("FUNCION")) {
|
|
699
893
|
return this.anonymousFunction();
|
|
700
894
|
}
|
|
701
895
|
|
|
702
|
-
|
|
896
|
+
if (this.match("NUEVO")) {
|
|
897
|
+
return this.newExpression();
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
if (this.match("ESTE")) {
|
|
901
|
+
return this.thisExpression();
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
if (this.match("SUPER")) {
|
|
905
|
+
return this.superExpression();
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
throw new Error("Se esperaba una expresión");
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
/**
|
|
912
|
+
* Parses a new expression for class instantiation
|
|
913
|
+
* @returns {Object} New expression
|
|
914
|
+
*/
|
|
915
|
+
newExpression() {
|
|
916
|
+
const className = this.consume(
|
|
917
|
+
"IDENTIFIER",
|
|
918
|
+
"Se esperaba el nombre de la clase después de 'nuevo'",
|
|
919
|
+
);
|
|
920
|
+
this.consume("LEFT_PAREN", "Se esperaba ( después del nombre de la clase");
|
|
921
|
+
|
|
922
|
+
const args = [];
|
|
923
|
+
if (!this.check("RIGHT_PAREN")) {
|
|
924
|
+
do {
|
|
925
|
+
args.push(this.expression());
|
|
926
|
+
} while (this.match("COMMA"));
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
this.consume("RIGHT_PAREN", "Se esperaba ) después de los argumentos");
|
|
930
|
+
|
|
931
|
+
return {
|
|
932
|
+
type: "NewExpression",
|
|
933
|
+
className: className.lexeme,
|
|
934
|
+
arguments: args,
|
|
935
|
+
};
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
/**
|
|
939
|
+
* Parses 'este' (this) expression
|
|
940
|
+
* @returns {Object} This expression
|
|
941
|
+
*/
|
|
942
|
+
thisExpression() {
|
|
943
|
+
// Check if accessing a property
|
|
944
|
+
if (this.match("DOT")) {
|
|
945
|
+
const property = this.consume(
|
|
946
|
+
"IDENTIFIER",
|
|
947
|
+
"Se esperaba el nombre de la propiedad después de 'este.'",
|
|
948
|
+
);
|
|
949
|
+
return {
|
|
950
|
+
type: "ThisPropertyAccess",
|
|
951
|
+
property: property.lexeme,
|
|
952
|
+
};
|
|
953
|
+
}
|
|
954
|
+
return {
|
|
955
|
+
type: "This",
|
|
956
|
+
};
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
/**
|
|
960
|
+
* Parses 'super' expression for parent class calls
|
|
961
|
+
* @returns {Object} Super expression
|
|
962
|
+
*/
|
|
963
|
+
superExpression() {
|
|
964
|
+
this.consume("LEFT_PAREN", "Se esperaba ( después de 'super'");
|
|
965
|
+
|
|
966
|
+
const args = [];
|
|
967
|
+
if (!this.check("RIGHT_PAREN")) {
|
|
968
|
+
do {
|
|
969
|
+
args.push(this.expression());
|
|
970
|
+
} while (this.match("COMMA"));
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
this.consume(
|
|
974
|
+
"RIGHT_PAREN",
|
|
975
|
+
"Se esperaba ) después de los argumentos de super",
|
|
976
|
+
);
|
|
977
|
+
|
|
978
|
+
return {
|
|
979
|
+
type: "SuperCall",
|
|
980
|
+
arguments: args,
|
|
981
|
+
};
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
/**
|
|
985
|
+
* Parses a template string expression
|
|
986
|
+
* @returns {Object} Template string expression with parsed expressions
|
|
987
|
+
*/
|
|
988
|
+
templateStringExpression() {
|
|
989
|
+
const token = this.previous();
|
|
990
|
+
const { parts, expressions } = token.literal;
|
|
991
|
+
const Tokenizer = require("./tokenizer.js");
|
|
992
|
+
|
|
993
|
+
// Parse each expression string into an AST
|
|
994
|
+
const parsedExpressions = expressions.map((exprSource) => {
|
|
995
|
+
const tokenizer = new Tokenizer();
|
|
996
|
+
const exprTokens = tokenizer.tokenize(exprSource);
|
|
997
|
+
const exprParser = new Parser(exprTokens);
|
|
998
|
+
return exprParser.expression();
|
|
999
|
+
});
|
|
1000
|
+
|
|
1001
|
+
return {
|
|
1002
|
+
type: "TemplateString",
|
|
1003
|
+
parts,
|
|
1004
|
+
expressions: parsedExpressions,
|
|
1005
|
+
};
|
|
703
1006
|
}
|
|
704
1007
|
|
|
705
1008
|
/**
|
|
@@ -707,33 +1010,33 @@ class Parser {
|
|
|
707
1010
|
* @returns {Object} Anonymous function expression
|
|
708
1011
|
*/
|
|
709
1012
|
anonymousFunction() {
|
|
710
|
-
this.consume(
|
|
1013
|
+
this.consume("LEFT_PAREN", "Expected ( after funcion");
|
|
711
1014
|
|
|
712
1015
|
const parameters = [];
|
|
713
|
-
if (!this.check(
|
|
1016
|
+
if (!this.check("RIGHT_PAREN")) {
|
|
714
1017
|
do {
|
|
715
1018
|
if (parameters.length >= 255) {
|
|
716
|
-
throw new Error(
|
|
1019
|
+
throw new Error("No se pueden tener más de 255 parámetros");
|
|
717
1020
|
}
|
|
718
1021
|
let param;
|
|
719
|
-
if (this.match(
|
|
1022
|
+
if (this.match("IDENTIFIER")) {
|
|
720
1023
|
param = this.previous();
|
|
721
|
-
} else if (this.match(
|
|
1024
|
+
} else if (this.match("AND")) {
|
|
722
1025
|
param = this.previous();
|
|
723
1026
|
} else {
|
|
724
|
-
throw new Error(
|
|
1027
|
+
throw new Error("Se esperaba un nombre de parámetro");
|
|
725
1028
|
}
|
|
726
1029
|
parameters.push(param.lexeme);
|
|
727
|
-
} while (this.match(
|
|
1030
|
+
} while (this.match("COMMA"));
|
|
728
1031
|
}
|
|
729
1032
|
|
|
730
|
-
this.consume(
|
|
731
|
-
this.consume(
|
|
1033
|
+
this.consume("RIGHT_PAREN", "Expected ) after parameters");
|
|
1034
|
+
this.consume("LEFT_BRACE", "Expected { before function body");
|
|
732
1035
|
const body = this.block();
|
|
733
|
-
this.consume(
|
|
1036
|
+
this.consume("RIGHT_BRACE", "Expected } after function body");
|
|
734
1037
|
|
|
735
1038
|
return {
|
|
736
|
-
type:
|
|
1039
|
+
type: "AnonymousFunction",
|
|
737
1040
|
parameters,
|
|
738
1041
|
body,
|
|
739
1042
|
};
|
|
@@ -746,10 +1049,10 @@ class Parser {
|
|
|
746
1049
|
*/
|
|
747
1050
|
finishArrayAccess(array) {
|
|
748
1051
|
const index = this.expression();
|
|
749
|
-
this.consume(
|
|
1052
|
+
this.consume("RIGHT_BRACKET", "Expected ] after array index");
|
|
750
1053
|
|
|
751
1054
|
return {
|
|
752
|
-
type:
|
|
1055
|
+
type: "ArrayAccess",
|
|
753
1056
|
array,
|
|
754
1057
|
index,
|
|
755
1058
|
};
|
|
@@ -761,63 +1064,101 @@ class Parser {
|
|
|
761
1064
|
* @returns {Object} Property access expression
|
|
762
1065
|
*/
|
|
763
1066
|
finishPropertyAccess(object) {
|
|
764
|
-
this.consume(
|
|
1067
|
+
this.consume("IDENTIFIER", "Expected property name after .");
|
|
765
1068
|
const name = this.previous();
|
|
766
1069
|
|
|
767
|
-
// Check if this is a method call (array or
|
|
1070
|
+
// Check if this is a method call (array, string, or number)
|
|
768
1071
|
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 ===
|
|
1072
|
+
name.lexeme === "longitud" ||
|
|
1073
|
+
name.lexeme === "primero" ||
|
|
1074
|
+
name.lexeme === "ultimo" ||
|
|
1075
|
+
name.lexeme === "agregar" ||
|
|
1076
|
+
name.lexeme === "remover" ||
|
|
1077
|
+
name.lexeme === "contiene" ||
|
|
1078
|
+
name.lexeme === "recorrer" ||
|
|
1079
|
+
name.lexeme === "mayusculas" ||
|
|
1080
|
+
name.lexeme === "minusculas" ||
|
|
1081
|
+
name.lexeme === "dividir" ||
|
|
1082
|
+
name.lexeme === "reemplazar" ||
|
|
1083
|
+
name.lexeme === "recortar" ||
|
|
1084
|
+
name.lexeme === "incluye" ||
|
|
1085
|
+
name.lexeme === "empiezaCon" ||
|
|
1086
|
+
name.lexeme === "terminaCon" ||
|
|
1087
|
+
name.lexeme === "caracter" ||
|
|
1088
|
+
name.lexeme === "subcadena" ||
|
|
1089
|
+
name.lexeme === "invertir" ||
|
|
1090
|
+
name.lexeme === "filtrar" ||
|
|
1091
|
+
name.lexeme === "mapear" ||
|
|
1092
|
+
name.lexeme === "reducir" ||
|
|
1093
|
+
name.lexeme === "ordenar" ||
|
|
1094
|
+
name.lexeme === "buscar" ||
|
|
1095
|
+
name.lexeme === "algunos" ||
|
|
1096
|
+
name.lexeme === "todos" ||
|
|
1097
|
+
name.lexeme === "unir" ||
|
|
1098
|
+
name.lexeme === "cortar" ||
|
|
1099
|
+
name.lexeme === "insertar" ||
|
|
1100
|
+
name.lexeme === "esPar" ||
|
|
1101
|
+
name.lexeme === "esImpar" ||
|
|
1102
|
+
name.lexeme === "esPositivo" ||
|
|
1103
|
+
name.lexeme === "esNegativo" ||
|
|
1104
|
+
name.lexeme === "aTexto"
|
|
778
1105
|
) {
|
|
779
1106
|
// Check if there are parentheses (method call syntax)
|
|
780
|
-
if (this.match(
|
|
1107
|
+
if (this.match("LEFT_PAREN")) {
|
|
781
1108
|
// Consume the opening parenthesis
|
|
782
1109
|
// Check if there are arguments
|
|
783
|
-
if (!this.check(
|
|
784
|
-
// Handle methods that accept arguments
|
|
1110
|
+
if (!this.check("RIGHT_PAREN")) {
|
|
1111
|
+
// Handle methods that accept arguments
|
|
785
1112
|
if (
|
|
786
|
-
name.lexeme ===
|
|
787
|
-
name.lexeme ===
|
|
788
|
-
name.lexeme ===
|
|
1113
|
+
name.lexeme === "agregar" ||
|
|
1114
|
+
name.lexeme === "contiene" ||
|
|
1115
|
+
name.lexeme === "recorrer" ||
|
|
1116
|
+
name.lexeme === "dividir" ||
|
|
1117
|
+
name.lexeme === "reemplazar" ||
|
|
1118
|
+
name.lexeme === "incluye" ||
|
|
1119
|
+
name.lexeme === "empiezaCon" ||
|
|
1120
|
+
name.lexeme === "terminaCon" ||
|
|
1121
|
+
name.lexeme === "caracter" ||
|
|
1122
|
+
name.lexeme === "subcadena" ||
|
|
1123
|
+
name.lexeme === "filtrar" ||
|
|
1124
|
+
name.lexeme === "mapear" ||
|
|
1125
|
+
name.lexeme === "reducir" ||
|
|
1126
|
+
name.lexeme === "buscar" ||
|
|
1127
|
+
name.lexeme === "algunos" ||
|
|
1128
|
+
name.lexeme === "todos" ||
|
|
1129
|
+
name.lexeme === "unir" ||
|
|
1130
|
+
name.lexeme === "cortar" ||
|
|
1131
|
+
name.lexeme === "insertar"
|
|
789
1132
|
) {
|
|
790
1133
|
const args = [];
|
|
791
1134
|
do {
|
|
792
1135
|
args.push(this.expression());
|
|
793
|
-
} while (this.match(
|
|
794
|
-
this.consume(
|
|
1136
|
+
} while (this.match("COMMA"));
|
|
1137
|
+
this.consume("RIGHT_PAREN", "Expected ) after method call");
|
|
795
1138
|
|
|
796
1139
|
return {
|
|
797
|
-
type:
|
|
1140
|
+
type: "MethodCall",
|
|
798
1141
|
object,
|
|
799
1142
|
method: name.lexeme,
|
|
800
1143
|
arguments: args,
|
|
801
1144
|
};
|
|
802
1145
|
} else {
|
|
803
|
-
throw new Error(
|
|
804
|
-
`El método ${name.lexeme}() no acepta argumentos`
|
|
805
|
-
);
|
|
1146
|
+
throw new Error(`El método ${name.lexeme}() no acepta argumentos`);
|
|
806
1147
|
}
|
|
807
1148
|
}
|
|
808
1149
|
// Consume the closing parenthesis
|
|
809
|
-
this.consume(
|
|
1150
|
+
this.consume("RIGHT_PAREN", "Expected ) after method call");
|
|
810
1151
|
}
|
|
811
1152
|
|
|
812
1153
|
return {
|
|
813
|
-
type:
|
|
1154
|
+
type: "MethodCall",
|
|
814
1155
|
object,
|
|
815
1156
|
method: name.lexeme,
|
|
816
1157
|
};
|
|
817
1158
|
}
|
|
818
1159
|
|
|
819
1160
|
return {
|
|
820
|
-
type:
|
|
1161
|
+
type: "PropertyAccess",
|
|
821
1162
|
object,
|
|
822
1163
|
name: name.lexeme,
|
|
823
1164
|
};
|
|
@@ -831,21 +1172,21 @@ class Parser {
|
|
|
831
1172
|
finishCall(callee) {
|
|
832
1173
|
const args = [];
|
|
833
1174
|
|
|
834
|
-
if (!this.check(
|
|
1175
|
+
if (!this.check("RIGHT_PAREN")) {
|
|
835
1176
|
do {
|
|
836
1177
|
if (args.length >= 255) {
|
|
837
|
-
throw new Error(
|
|
1178
|
+
throw new Error("Cannot have more than 255 arguments");
|
|
838
1179
|
}
|
|
839
1180
|
args.push(this.expression());
|
|
840
|
-
} while (this.match(
|
|
1181
|
+
} while (this.match("COMMA"));
|
|
841
1182
|
}
|
|
842
1183
|
|
|
843
|
-
const paren = this.consume(
|
|
1184
|
+
const paren = this.consume("RIGHT_PAREN", "Expected ) after arguments");
|
|
844
1185
|
|
|
845
1186
|
return {
|
|
846
|
-
type:
|
|
1187
|
+
type: "Call",
|
|
847
1188
|
callee: {
|
|
848
|
-
type:
|
|
1189
|
+
type: "Variable",
|
|
849
1190
|
name: callee.lexeme,
|
|
850
1191
|
},
|
|
851
1192
|
arguments: args,
|
|
@@ -859,16 +1200,16 @@ class Parser {
|
|
|
859
1200
|
arrayLiteral() {
|
|
860
1201
|
const elements = [];
|
|
861
1202
|
|
|
862
|
-
if (!this.check(
|
|
1203
|
+
if (!this.check("RIGHT_BRACKET")) {
|
|
863
1204
|
do {
|
|
864
1205
|
elements.push(this.expression());
|
|
865
|
-
} while (this.match(
|
|
1206
|
+
} while (this.match("COMMA"));
|
|
866
1207
|
}
|
|
867
1208
|
|
|
868
|
-
this.consume(
|
|
1209
|
+
this.consume("RIGHT_BRACKET", "Expected ] after array elements");
|
|
869
1210
|
|
|
870
1211
|
return {
|
|
871
|
-
type:
|
|
1212
|
+
type: "ArrayLiteral",
|
|
872
1213
|
elements,
|
|
873
1214
|
};
|
|
874
1215
|
}
|
|
@@ -880,33 +1221,33 @@ class Parser {
|
|
|
880
1221
|
objectLiteral() {
|
|
881
1222
|
const properties = [];
|
|
882
1223
|
|
|
883
|
-
if (!this.check(
|
|
1224
|
+
if (!this.check("RIGHT_BRACE")) {
|
|
884
1225
|
do {
|
|
885
1226
|
// Parse property name (identifier or string)
|
|
886
1227
|
let name;
|
|
887
|
-
if (this.match(
|
|
1228
|
+
if (this.match("IDENTIFIER")) {
|
|
888
1229
|
name = this.previous().lexeme;
|
|
889
|
-
} else if (this.match(
|
|
1230
|
+
} else if (this.match("STRING")) {
|
|
890
1231
|
name = this.previous().literal;
|
|
891
1232
|
} else {
|
|
892
|
-
throw new Error(
|
|
1233
|
+
throw new Error("Se esperaba un nombre de propiedad");
|
|
893
1234
|
}
|
|
894
1235
|
|
|
895
|
-
this.consume(
|
|
1236
|
+
this.consume("COLON", "Expected : after property name");
|
|
896
1237
|
const value = this.expression();
|
|
897
1238
|
|
|
898
1239
|
properties.push({
|
|
899
|
-
type:
|
|
1240
|
+
type: "Property",
|
|
900
1241
|
name,
|
|
901
1242
|
value,
|
|
902
1243
|
});
|
|
903
|
-
} while (this.match(
|
|
1244
|
+
} while (this.match("COMMA"));
|
|
904
1245
|
}
|
|
905
1246
|
|
|
906
|
-
this.consume(
|
|
1247
|
+
this.consume("RIGHT_BRACE", "Expected } after object properties");
|
|
907
1248
|
|
|
908
1249
|
return {
|
|
909
|
-
type:
|
|
1250
|
+
type: "ObjectLiteral",
|
|
910
1251
|
properties,
|
|
911
1252
|
};
|
|
912
1253
|
}
|
|
@@ -950,7 +1291,7 @@ class Parser {
|
|
|
950
1291
|
* @returns {boolean} True if we reached the end
|
|
951
1292
|
*/
|
|
952
1293
|
isAtEnd() {
|
|
953
|
-
return this.peek().type ===
|
|
1294
|
+
return this.peek().type === "EOF";
|
|
954
1295
|
}
|
|
955
1296
|
|
|
956
1297
|
/**
|
|
@@ -988,21 +1329,23 @@ class Parser {
|
|
|
988
1329
|
this.advance();
|
|
989
1330
|
|
|
990
1331
|
while (!this.isAtEnd()) {
|
|
991
|
-
if (this.previous().type ===
|
|
1332
|
+
if (this.previous().type === "EOF") return;
|
|
992
1333
|
|
|
993
1334
|
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
|
|
1335
|
+
case "VARIABLE":
|
|
1336
|
+
case "CONSTANTE":
|
|
1337
|
+
case "FUNCION":
|
|
1338
|
+
case "CLASE":
|
|
1339
|
+
case "MOSTRAR":
|
|
1340
|
+
case "LEER":
|
|
1341
|
+
case "SI":
|
|
1342
|
+
case "MIENTRAS":
|
|
1343
|
+
case "PARA":
|
|
1344
|
+
case "RETORNAR":
|
|
1345
|
+
case "ROMPER":
|
|
1346
|
+
case "CONTINUAR":
|
|
1347
|
+
case "INTENTAR":
|
|
1348
|
+
case "CAPTURAR":
|
|
1006
1349
|
return;
|
|
1007
1350
|
}
|
|
1008
1351
|
|
|
@@ -1016,34 +1359,136 @@ class Parser {
|
|
|
1016
1359
|
*/
|
|
1017
1360
|
tryStatement() {
|
|
1018
1361
|
// Parse the try block
|
|
1019
|
-
this.consume(
|
|
1362
|
+
this.consume("LEFT_BRACE", "Expected { after intentar");
|
|
1020
1363
|
const tryBlock = this.block();
|
|
1021
|
-
this.consume(
|
|
1364
|
+
this.consume("RIGHT_BRACE", "Expected } after intentar block");
|
|
1022
1365
|
|
|
1023
1366
|
// Look for catch block
|
|
1024
|
-
if (this.match(
|
|
1367
|
+
if (this.match("CAPTURAR")) {
|
|
1025
1368
|
// Parse catch parameter (error variable name)
|
|
1026
|
-
this.consume(
|
|
1369
|
+
this.consume("LEFT_PAREN", "Expected ( after capturar");
|
|
1027
1370
|
const errorVariable = this.consume(
|
|
1028
|
-
|
|
1029
|
-
|
|
1371
|
+
"IDENTIFIER",
|
|
1372
|
+
"Expected error variable name",
|
|
1030
1373
|
);
|
|
1031
|
-
this.consume(
|
|
1374
|
+
this.consume("RIGHT_PAREN", "Expected ) after error variable");
|
|
1032
1375
|
|
|
1033
1376
|
// Parse catch block
|
|
1034
|
-
this.consume(
|
|
1377
|
+
this.consume("LEFT_BRACE", "Expected { after capturar");
|
|
1035
1378
|
const catchBlock = this.block();
|
|
1036
|
-
this.consume(
|
|
1379
|
+
this.consume("RIGHT_BRACE", "Expected } after capturar block");
|
|
1037
1380
|
|
|
1038
1381
|
return {
|
|
1039
|
-
type:
|
|
1382
|
+
type: "TryCatch",
|
|
1040
1383
|
tryBlock,
|
|
1041
1384
|
catchBlock,
|
|
1042
1385
|
errorVariable: errorVariable.lexeme,
|
|
1043
1386
|
};
|
|
1044
1387
|
} else {
|
|
1045
|
-
throw new Error(
|
|
1388
|
+
throw new Error("Se esperaba capturar después del bloque intentar");
|
|
1389
|
+
}
|
|
1390
|
+
}
|
|
1391
|
+
|
|
1392
|
+
/**
|
|
1393
|
+
* Parses an elegir (switch) statement
|
|
1394
|
+
* @returns {Object} Elegir statement
|
|
1395
|
+
*/
|
|
1396
|
+
elegirStatement() {
|
|
1397
|
+
const discriminant = this.expression();
|
|
1398
|
+
this.consume("LEFT_BRACE", "Expected { after elegir expression");
|
|
1399
|
+
|
|
1400
|
+
const cases = [];
|
|
1401
|
+
let defaultCase = null;
|
|
1402
|
+
|
|
1403
|
+
while (!this.check("RIGHT_BRACE") && !this.isAtEnd()) {
|
|
1404
|
+
if (this.match("CASO")) {
|
|
1405
|
+
const testValue = this.expression();
|
|
1406
|
+
this.consume("COLON", "Expected : after caso value");
|
|
1407
|
+
|
|
1408
|
+
let consequent;
|
|
1409
|
+
if (this.check("LEFT_BRACE")) {
|
|
1410
|
+
this.advance();
|
|
1411
|
+
consequent = this.block();
|
|
1412
|
+
this.consume("RIGHT_BRACE", "Expected } after caso block");
|
|
1413
|
+
} else {
|
|
1414
|
+
consequent = [this.declaration()];
|
|
1415
|
+
}
|
|
1416
|
+
|
|
1417
|
+
cases.push({
|
|
1418
|
+
test: testValue,
|
|
1419
|
+
consequent,
|
|
1420
|
+
});
|
|
1421
|
+
} else if (this.match("PORDEFECTO")) {
|
|
1422
|
+
this.consume("COLON", "Expected : after pordefecto");
|
|
1423
|
+
|
|
1424
|
+
let consequent;
|
|
1425
|
+
if (this.check("LEFT_BRACE")) {
|
|
1426
|
+
this.advance();
|
|
1427
|
+
consequent = this.block();
|
|
1428
|
+
this.consume("RIGHT_BRACE", "Expected } after pordefecto block");
|
|
1429
|
+
} else {
|
|
1430
|
+
consequent = [this.declaration()];
|
|
1431
|
+
}
|
|
1432
|
+
|
|
1433
|
+
defaultCase = {
|
|
1434
|
+
consequent,
|
|
1435
|
+
};
|
|
1436
|
+
} else {
|
|
1437
|
+
throw new Error("Se esperaba caso o pordefecto dentro de elegir");
|
|
1438
|
+
}
|
|
1046
1439
|
}
|
|
1440
|
+
|
|
1441
|
+
this.consume("RIGHT_BRACE", "Expected } after elegir block");
|
|
1442
|
+
|
|
1443
|
+
return {
|
|
1444
|
+
type: "ElegirStatement",
|
|
1445
|
+
discriminant,
|
|
1446
|
+
cases,
|
|
1447
|
+
defaultCase,
|
|
1448
|
+
};
|
|
1449
|
+
}
|
|
1450
|
+
|
|
1451
|
+
/**
|
|
1452
|
+
* Parses a hacer/mientras (do-while) statement
|
|
1453
|
+
* @returns {Object} HacerMientras statement
|
|
1454
|
+
*/
|
|
1455
|
+
hacerMientrasStatement() {
|
|
1456
|
+
this.consume("LEFT_BRACE", "Expected { after hacer");
|
|
1457
|
+
const body = this.block();
|
|
1458
|
+
this.consume("RIGHT_BRACE", "Expected } after hacer block");
|
|
1459
|
+
|
|
1460
|
+
this.consume("MIENTRAS", "Expected mientras after hacer block");
|
|
1461
|
+
const condition = this.expression();
|
|
1462
|
+
|
|
1463
|
+
return {
|
|
1464
|
+
type: "HacerMientrasStatement",
|
|
1465
|
+
body,
|
|
1466
|
+
condition,
|
|
1467
|
+
};
|
|
1468
|
+
}
|
|
1469
|
+
|
|
1470
|
+
/**
|
|
1471
|
+
* Parses a para cada (for-each) statement
|
|
1472
|
+
* @returns {Object} ForEach statement
|
|
1473
|
+
*/
|
|
1474
|
+
forEachStatement() {
|
|
1475
|
+
const iteratorName = this.consume(
|
|
1476
|
+
"IDENTIFIER",
|
|
1477
|
+
"Expected iterator variable name after cada",
|
|
1478
|
+
);
|
|
1479
|
+
this.consume("EN", "Expected en after iterator variable");
|
|
1480
|
+
const iterable = this.expression();
|
|
1481
|
+
|
|
1482
|
+
this.consume("LEFT_BRACE", "Expected { after iterable");
|
|
1483
|
+
const body = this.block();
|
|
1484
|
+
this.consume("RIGHT_BRACE", "Expected } after para cada block");
|
|
1485
|
+
|
|
1486
|
+
return {
|
|
1487
|
+
type: "ForEachStatement",
|
|
1488
|
+
iterator: iteratorName.lexeme,
|
|
1489
|
+
iterable,
|
|
1490
|
+
body,
|
|
1491
|
+
};
|
|
1047
1492
|
}
|
|
1048
1493
|
}
|
|
1049
1494
|
|