hispano-lang 1.1.7 → 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/evaluator.js
CHANGED
|
@@ -7,6 +7,7 @@ class Evaluator {
|
|
|
7
7
|
constructor() {
|
|
8
8
|
this.environment = new Environment();
|
|
9
9
|
this.output = [];
|
|
10
|
+
this.currentInstance = null; // For tracking 'este' (this)
|
|
10
11
|
}
|
|
11
12
|
|
|
12
13
|
/**
|
|
@@ -34,31 +35,41 @@ class Evaluator {
|
|
|
34
35
|
*/
|
|
35
36
|
execute(statement) {
|
|
36
37
|
switch (statement.type) {
|
|
37
|
-
case
|
|
38
|
+
case "VariableDeclaration":
|
|
38
39
|
return this.executeVariableDeclaration(statement);
|
|
39
|
-
case
|
|
40
|
+
case "ConstantDeclaration":
|
|
41
|
+
return this.executeConstantDeclaration(statement);
|
|
42
|
+
case "FunctionDeclaration":
|
|
40
43
|
return this.executeFunctionDeclaration(statement);
|
|
41
|
-
case
|
|
44
|
+
case "ClassDeclaration":
|
|
45
|
+
return this.executeClassDeclaration(statement);
|
|
46
|
+
case "MostrarStatement":
|
|
42
47
|
return this.executeMostrarStatement(statement);
|
|
43
|
-
case
|
|
48
|
+
case "LeerStatement":
|
|
44
49
|
return this.executeLeerStatement(statement);
|
|
45
|
-
case
|
|
50
|
+
case "IfStatement":
|
|
46
51
|
return this.executeIfStatement(statement);
|
|
47
|
-
case
|
|
52
|
+
case "WhileStatement":
|
|
48
53
|
return this.executeWhileStatement(statement);
|
|
49
|
-
case
|
|
54
|
+
case "ForStatement":
|
|
50
55
|
return this.executeForStatement(statement);
|
|
51
|
-
case
|
|
56
|
+
case "ReturnStatement":
|
|
52
57
|
return this.executeReturnStatement(statement);
|
|
53
|
-
case
|
|
58
|
+
case "BreakStatement":
|
|
54
59
|
return this.executeBreakStatement(statement);
|
|
55
|
-
case
|
|
60
|
+
case "ContinueStatement":
|
|
56
61
|
return this.executeContinueStatement(statement);
|
|
57
|
-
case
|
|
62
|
+
case "TryCatch":
|
|
58
63
|
return this.executeTryCatch(statement);
|
|
59
|
-
case
|
|
64
|
+
case "ElegirStatement":
|
|
65
|
+
return this.executeElegirStatement(statement);
|
|
66
|
+
case "HacerMientrasStatement":
|
|
67
|
+
return this.executeHacerMientrasStatement(statement);
|
|
68
|
+
case "ForEachStatement":
|
|
69
|
+
return this.executeForEachStatement(statement);
|
|
70
|
+
case "ExpressionStatement":
|
|
60
71
|
return this.executeExpressionStatement(statement);
|
|
61
|
-
case
|
|
72
|
+
case "Block":
|
|
62
73
|
return this.executeBlock(statement);
|
|
63
74
|
}
|
|
64
75
|
}
|
|
@@ -76,13 +87,22 @@ class Evaluator {
|
|
|
76
87
|
this.environment.define(statement.name, value);
|
|
77
88
|
}
|
|
78
89
|
|
|
90
|
+
/**
|
|
91
|
+
* Executes a constant declaration
|
|
92
|
+
* @param {Object} statement - Constant declaration
|
|
93
|
+
*/
|
|
94
|
+
executeConstantDeclaration(statement) {
|
|
95
|
+
const value = this.evaluateExpression(statement.initializer);
|
|
96
|
+
this.environment.defineConstant(statement.name, value);
|
|
97
|
+
}
|
|
98
|
+
|
|
79
99
|
/**
|
|
80
100
|
* Executes a function declaration
|
|
81
101
|
* @param {Object} statement - Function declaration
|
|
82
102
|
*/
|
|
83
103
|
executeFunctionDeclaration(statement) {
|
|
84
104
|
const functionObj = {
|
|
85
|
-
type:
|
|
105
|
+
type: "Function",
|
|
86
106
|
name: statement.name,
|
|
87
107
|
parameters: statement.parameters,
|
|
88
108
|
body: statement.body,
|
|
@@ -91,6 +111,22 @@ class Evaluator {
|
|
|
91
111
|
this.environment.define(statement.name, functionObj);
|
|
92
112
|
}
|
|
93
113
|
|
|
114
|
+
/**
|
|
115
|
+
* Executes a class declaration
|
|
116
|
+
* @param {Object} statement - Class declaration
|
|
117
|
+
*/
|
|
118
|
+
executeClassDeclaration(statement) {
|
|
119
|
+
const classObj = {
|
|
120
|
+
type: "Class",
|
|
121
|
+
name: statement.name,
|
|
122
|
+
superclass: statement.superclass,
|
|
123
|
+
constructor: statement.constructor,
|
|
124
|
+
methods: statement.methods,
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
this.environment.define(statement.name, classObj);
|
|
128
|
+
}
|
|
129
|
+
|
|
94
130
|
/**
|
|
95
131
|
* Executes a show statement
|
|
96
132
|
* @param {Object} statement - Show statement
|
|
@@ -113,21 +149,21 @@ class Evaluator {
|
|
|
113
149
|
}
|
|
114
150
|
|
|
115
151
|
// Try different methods for input
|
|
116
|
-
let input =
|
|
152
|
+
let input = "";
|
|
117
153
|
|
|
118
154
|
try {
|
|
119
155
|
// Method 1: Try readline-sync
|
|
120
|
-
const readlineSync = require(
|
|
121
|
-
input = readlineSync.question(
|
|
156
|
+
const readlineSync = require("readline-sync");
|
|
157
|
+
input = readlineSync.question("");
|
|
122
158
|
} catch (error1) {
|
|
123
159
|
try {
|
|
124
160
|
// Method 2: Try fs.readFileSync with stdin
|
|
125
|
-
const fs = require(
|
|
126
|
-
input = fs.readFileSync(0,
|
|
161
|
+
const fs = require("fs");
|
|
162
|
+
input = fs.readFileSync(0, "utf8").trim();
|
|
127
163
|
} catch (error2) {
|
|
128
164
|
try {
|
|
129
165
|
// Method 3: Try process.stdin
|
|
130
|
-
const readline = require(
|
|
166
|
+
const readline = require("readline");
|
|
131
167
|
const rl = readline.createInterface({
|
|
132
168
|
input: process.stdin,
|
|
133
169
|
output: process.stdout,
|
|
@@ -135,18 +171,18 @@ class Evaluator {
|
|
|
135
171
|
|
|
136
172
|
// This is a simplified approach - in a real implementation
|
|
137
173
|
// you'd need to handle this asynchronously
|
|
138
|
-
input =
|
|
174
|
+
input = "";
|
|
139
175
|
} catch (error3) {
|
|
140
176
|
// Fallback: use empty string
|
|
141
|
-
input =
|
|
142
|
-
console.log(
|
|
177
|
+
input = "";
|
|
178
|
+
console.log("(Entrada no disponible en este entorno)");
|
|
143
179
|
}
|
|
144
180
|
}
|
|
145
181
|
}
|
|
146
182
|
|
|
147
183
|
// Try to parse as number first, then as string
|
|
148
184
|
let value = input;
|
|
149
|
-
if (!isNaN(input) && input.trim() !==
|
|
185
|
+
if (!isNaN(input) && input.trim() !== "") {
|
|
150
186
|
value = parseFloat(input);
|
|
151
187
|
}
|
|
152
188
|
|
|
@@ -286,75 +322,101 @@ class Evaluator {
|
|
|
286
322
|
*/
|
|
287
323
|
evaluateExpression(expression) {
|
|
288
324
|
switch (expression.type) {
|
|
289
|
-
case
|
|
325
|
+
case "Literal":
|
|
290
326
|
return expression.value;
|
|
291
327
|
|
|
292
|
-
case
|
|
328
|
+
case "TemplateString":
|
|
329
|
+
return this.evaluateTemplateString(expression);
|
|
330
|
+
|
|
331
|
+
case "Variable":
|
|
293
332
|
return this.environment.get(expression.name);
|
|
294
333
|
|
|
295
|
-
case
|
|
334
|
+
case "AnonymousFunction":
|
|
296
335
|
return {
|
|
297
|
-
type:
|
|
336
|
+
type: "Function",
|
|
298
337
|
name: null,
|
|
299
338
|
parameters: expression.parameters,
|
|
300
339
|
body: expression.body,
|
|
301
340
|
};
|
|
302
341
|
|
|
303
|
-
case
|
|
342
|
+
case "Assign":
|
|
304
343
|
const value = this.evaluateExpression(expression.value);
|
|
305
344
|
this.environment.assign(expression.name, value);
|
|
306
345
|
return value;
|
|
307
346
|
|
|
308
|
-
case
|
|
347
|
+
case "ArrayLiteral":
|
|
309
348
|
return this.evaluateArrayLiteral(expression);
|
|
310
349
|
|
|
311
|
-
case
|
|
350
|
+
case "ArrayAccess":
|
|
312
351
|
return this.evaluateArrayAccess(expression);
|
|
313
352
|
|
|
314
|
-
case
|
|
353
|
+
case "ArrayAssign":
|
|
315
354
|
return this.evaluateArrayAssign(expression);
|
|
316
355
|
|
|
317
|
-
case
|
|
356
|
+
case "ObjectLiteral":
|
|
318
357
|
return this.evaluateObjectLiteral(expression);
|
|
319
358
|
|
|
320
|
-
case
|
|
359
|
+
case "PropertyAccess":
|
|
321
360
|
return this.evaluatePropertyAccess(expression);
|
|
322
361
|
|
|
323
|
-
case
|
|
362
|
+
case "PropertyAssign":
|
|
324
363
|
return this.evaluatePropertyAssign(expression);
|
|
325
364
|
|
|
326
|
-
case
|
|
365
|
+
case "CompoundAssign":
|
|
327
366
|
return this.evaluateCompoundAssign(expression);
|
|
328
367
|
|
|
329
|
-
case
|
|
368
|
+
case "CompoundArrayAssign":
|
|
330
369
|
return this.evaluateCompoundArrayAssign(expression);
|
|
331
370
|
|
|
332
|
-
case
|
|
371
|
+
case "CompoundPropertyAssign":
|
|
333
372
|
return this.evaluateCompoundPropertyAssign(expression);
|
|
334
373
|
|
|
335
|
-
case
|
|
374
|
+
case "Logical":
|
|
336
375
|
return this.evaluateLogicalExpression(expression);
|
|
337
376
|
|
|
338
|
-
case
|
|
377
|
+
case "Postfix":
|
|
339
378
|
return this.evaluatePostfixExpression(expression);
|
|
340
379
|
|
|
341
|
-
case
|
|
380
|
+
case "Call":
|
|
342
381
|
return this.evaluateCallExpression(expression);
|
|
343
382
|
|
|
344
|
-
case
|
|
383
|
+
case "MethodCall":
|
|
345
384
|
return this.evaluateMethodCall(expression);
|
|
346
385
|
|
|
347
|
-
case
|
|
386
|
+
case "NewExpression":
|
|
387
|
+
return this.evaluateNewExpression(expression);
|
|
388
|
+
|
|
389
|
+
case "This":
|
|
390
|
+
if (this.currentInstance === null) {
|
|
391
|
+
throw new Error(
|
|
392
|
+
"'este' solo se puede usar dentro de un método de clase",
|
|
393
|
+
);
|
|
394
|
+
}
|
|
395
|
+
return this.currentInstance;
|
|
396
|
+
|
|
397
|
+
case "ThisPropertyAccess":
|
|
398
|
+
return this.evaluateThisPropertyAccess(expression);
|
|
399
|
+
|
|
400
|
+
case "ThisPropertyAssign":
|
|
401
|
+
return this.evaluateThisPropertyAssign(expression);
|
|
402
|
+
|
|
403
|
+
case "ThisMethodCall":
|
|
404
|
+
return this.evaluateThisMethodCall(expression);
|
|
405
|
+
|
|
406
|
+
case "SuperCall":
|
|
407
|
+
return this.evaluateSuperCall(expression);
|
|
408
|
+
|
|
409
|
+
case "Unary":
|
|
348
410
|
const right = this.evaluateExpression(expression.right);
|
|
349
411
|
return this.evaluateUnaryExpression(expression.operator, right);
|
|
350
412
|
|
|
351
|
-
case
|
|
413
|
+
case "Binary":
|
|
352
414
|
const left = this.evaluateExpression(expression.left);
|
|
353
415
|
const rightOperand = this.evaluateExpression(expression.right);
|
|
354
416
|
return this.evaluateBinaryExpression(
|
|
355
417
|
left,
|
|
356
418
|
expression.operator,
|
|
357
|
-
rightOperand
|
|
419
|
+
rightOperand,
|
|
358
420
|
);
|
|
359
421
|
|
|
360
422
|
default:
|
|
@@ -370,11 +432,11 @@ class Evaluator {
|
|
|
370
432
|
*/
|
|
371
433
|
evaluateUnaryExpression(operator, right) {
|
|
372
434
|
switch (operator) {
|
|
373
|
-
case
|
|
435
|
+
case "MINUS":
|
|
374
436
|
this.checkNumberOperand(operator, right);
|
|
375
437
|
return -right;
|
|
376
438
|
|
|
377
|
-
case
|
|
439
|
+
case "BANG":
|
|
378
440
|
return !this.isTruthy(right);
|
|
379
441
|
|
|
380
442
|
default:
|
|
@@ -391,12 +453,12 @@ class Evaluator {
|
|
|
391
453
|
*/
|
|
392
454
|
evaluateBinaryExpression(left, operator, right) {
|
|
393
455
|
switch (operator) {
|
|
394
|
-
case
|
|
456
|
+
case "MINUS":
|
|
395
457
|
this.checkNumberOperands(operator, left, right);
|
|
396
458
|
return left - right;
|
|
397
459
|
|
|
398
|
-
case
|
|
399
|
-
if (typeof left ===
|
|
460
|
+
case "PLUS":
|
|
461
|
+
if (typeof left === "number" && typeof right === "number") {
|
|
400
462
|
return left + right;
|
|
401
463
|
}
|
|
402
464
|
// Convert numbers to strings for concatenation
|
|
@@ -404,56 +466,56 @@ class Evaluator {
|
|
|
404
466
|
const rightStr = this.stringify(right);
|
|
405
467
|
return leftStr + rightStr;
|
|
406
468
|
|
|
407
|
-
case
|
|
469
|
+
case "SLASH":
|
|
408
470
|
this.checkNumberOperands(operator, left, right);
|
|
409
471
|
if (right === 0) {
|
|
410
|
-
throw new Error(
|
|
472
|
+
throw new Error("División por cero");
|
|
411
473
|
}
|
|
412
474
|
return left / right;
|
|
413
475
|
|
|
414
|
-
case
|
|
476
|
+
case "STAR":
|
|
415
477
|
this.checkNumberOperands(operator, left, right);
|
|
416
478
|
return left * right;
|
|
417
479
|
|
|
418
|
-
case
|
|
480
|
+
case "PERCENT":
|
|
419
481
|
this.checkNumberOperands(operator, left, right);
|
|
420
482
|
if (right === 0) {
|
|
421
|
-
throw new Error(
|
|
483
|
+
throw new Error("Módulo por cero");
|
|
422
484
|
}
|
|
423
485
|
return left % right;
|
|
424
486
|
|
|
425
|
-
case
|
|
426
|
-
if (typeof left ===
|
|
487
|
+
case "GREATER":
|
|
488
|
+
if (typeof left === "string" && typeof right === "string") {
|
|
427
489
|
return left > right;
|
|
428
490
|
}
|
|
429
491
|
this.checkNumberOperands(operator, left, right);
|
|
430
492
|
return left > right;
|
|
431
493
|
|
|
432
|
-
case
|
|
433
|
-
if (typeof left ===
|
|
494
|
+
case "GREATER_EQUAL":
|
|
495
|
+
if (typeof left === "string" && typeof right === "string") {
|
|
434
496
|
return left >= right;
|
|
435
497
|
}
|
|
436
498
|
this.checkNumberOperands(operator, left, right);
|
|
437
499
|
return left >= right;
|
|
438
500
|
|
|
439
|
-
case
|
|
440
|
-
if (typeof left ===
|
|
501
|
+
case "LESS":
|
|
502
|
+
if (typeof left === "string" && typeof right === "string") {
|
|
441
503
|
return left < right;
|
|
442
504
|
}
|
|
443
505
|
this.checkNumberOperands(operator, left, right);
|
|
444
506
|
return left < right;
|
|
445
507
|
|
|
446
|
-
case
|
|
447
|
-
if (typeof left ===
|
|
508
|
+
case "LESS_EQUAL":
|
|
509
|
+
if (typeof left === "string" && typeof right === "string") {
|
|
448
510
|
return left <= right;
|
|
449
511
|
}
|
|
450
512
|
this.checkNumberOperands(operator, left, right);
|
|
451
513
|
return left <= right;
|
|
452
514
|
|
|
453
|
-
case
|
|
515
|
+
case "EQUAL_EQUAL":
|
|
454
516
|
return this.isEqual(left, right);
|
|
455
517
|
|
|
456
|
-
case
|
|
518
|
+
case "BANG_EQUAL":
|
|
457
519
|
return !this.isEqual(left, right);
|
|
458
520
|
|
|
459
521
|
default:
|
|
@@ -468,8 +530,8 @@ class Evaluator {
|
|
|
468
530
|
*/
|
|
469
531
|
isTruthy(value) {
|
|
470
532
|
if (value === null) return false;
|
|
471
|
-
if (typeof value ===
|
|
472
|
-
if (typeof value ===
|
|
533
|
+
if (typeof value === "boolean") return value;
|
|
534
|
+
if (typeof value === "number") return value !== 0;
|
|
473
535
|
return true;
|
|
474
536
|
}
|
|
475
537
|
|
|
@@ -489,7 +551,7 @@ class Evaluator {
|
|
|
489
551
|
* @param {any} operand - Operand
|
|
490
552
|
*/
|
|
491
553
|
checkNumberOperand(operator, operand) {
|
|
492
|
-
if (typeof operand ===
|
|
554
|
+
if (typeof operand === "number") return;
|
|
493
555
|
throw new Error(`El operador ${operator} requiere un número`);
|
|
494
556
|
}
|
|
495
557
|
|
|
@@ -500,7 +562,7 @@ class Evaluator {
|
|
|
500
562
|
* @param {any} right - Right operand
|
|
501
563
|
*/
|
|
502
564
|
checkNumberOperands(operator, left, right) {
|
|
503
|
-
if (typeof left ===
|
|
565
|
+
if (typeof left === "number" && typeof right === "number") return;
|
|
504
566
|
throw new Error(`El operador ${operator} requiere dos números`);
|
|
505
567
|
}
|
|
506
568
|
|
|
@@ -512,7 +574,7 @@ class Evaluator {
|
|
|
512
574
|
evaluateCallExpression(expression) {
|
|
513
575
|
// Check if it's a built-in mathematical function BEFORE evaluating the callee
|
|
514
576
|
if (
|
|
515
|
-
expression.callee.type ===
|
|
577
|
+
expression.callee.type === "Variable" &&
|
|
516
578
|
this.isMathFunction(expression.callee.name)
|
|
517
579
|
) {
|
|
518
580
|
const args = [];
|
|
@@ -522,6 +584,18 @@ class Evaluator {
|
|
|
522
584
|
return this.evaluateMathFunction(expression.callee.name, args);
|
|
523
585
|
}
|
|
524
586
|
|
|
587
|
+
// Check if it's a built-in type conversion function
|
|
588
|
+
if (
|
|
589
|
+
expression.callee.type === "Variable" &&
|
|
590
|
+
this.isConversionFunction(expression.callee.name)
|
|
591
|
+
) {
|
|
592
|
+
const args = [];
|
|
593
|
+
for (const argument of expression.arguments) {
|
|
594
|
+
args.push(this.evaluateExpression(argument));
|
|
595
|
+
}
|
|
596
|
+
return this.evaluateConversionFunction(expression.callee.name, args);
|
|
597
|
+
}
|
|
598
|
+
|
|
525
599
|
const callee = this.evaluateExpression(expression.callee);
|
|
526
600
|
const args = [];
|
|
527
601
|
|
|
@@ -529,13 +603,13 @@ class Evaluator {
|
|
|
529
603
|
args.push(this.evaluateExpression(argument));
|
|
530
604
|
}
|
|
531
605
|
|
|
532
|
-
if (callee.type !==
|
|
533
|
-
throw new Error(
|
|
606
|
+
if (callee.type !== "Function") {
|
|
607
|
+
throw new Error("Solo se pueden llamar funciones");
|
|
534
608
|
}
|
|
535
609
|
|
|
536
610
|
if (args.length !== callee.parameters.length) {
|
|
537
611
|
throw new Error(
|
|
538
|
-
`Se esperaban ${callee.parameters.length} argumentos pero se recibieron ${args.length}
|
|
612
|
+
`Se esperaban ${callee.parameters.length} argumentos pero se recibieron ${args.length}`,
|
|
539
613
|
);
|
|
540
614
|
}
|
|
541
615
|
|
|
@@ -561,38 +635,128 @@ class Evaluator {
|
|
|
561
635
|
}
|
|
562
636
|
|
|
563
637
|
/**
|
|
564
|
-
* Evaluates a method call (array or
|
|
638
|
+
* Evaluates a method call (array, string, number, or instance)
|
|
565
639
|
* @param {Object} expression - Method call expression
|
|
566
640
|
* @returns {any} Method result
|
|
567
641
|
*/
|
|
568
642
|
evaluateMethodCall(expression) {
|
|
569
643
|
const object = this.evaluateExpression(expression.object);
|
|
570
644
|
|
|
571
|
-
// Determine if it's an array
|
|
645
|
+
// Determine if it's an array, string, number, or instance method
|
|
572
646
|
if (Array.isArray(object)) {
|
|
573
647
|
return this.evaluateArrayMethod(
|
|
574
648
|
object,
|
|
575
649
|
expression.method,
|
|
576
|
-
expression.arguments
|
|
650
|
+
expression.arguments,
|
|
577
651
|
);
|
|
578
|
-
} else if (typeof object ===
|
|
652
|
+
} else if (typeof object === "string") {
|
|
579
653
|
// Check if the method is valid for strings
|
|
580
654
|
if (
|
|
581
|
-
expression.method ===
|
|
582
|
-
expression.method ===
|
|
583
|
-
expression.method ===
|
|
584
|
-
expression.method ===
|
|
655
|
+
expression.method === "agregar" ||
|
|
656
|
+
expression.method === "remover" ||
|
|
657
|
+
expression.method === "recorrer" ||
|
|
658
|
+
expression.method === "primero" ||
|
|
659
|
+
expression.method === "ultimo"
|
|
585
660
|
) {
|
|
586
661
|
throw new Error(
|
|
587
|
-
`El método ${expression.method}() solo se puede llamar en arreglos
|
|
662
|
+
`El método ${expression.method}() solo se puede llamar en arreglos`,
|
|
588
663
|
);
|
|
589
664
|
}
|
|
590
|
-
return this.evaluateStringMethod(
|
|
665
|
+
return this.evaluateStringMethod(
|
|
666
|
+
object,
|
|
667
|
+
expression.method,
|
|
668
|
+
expression.arguments,
|
|
669
|
+
);
|
|
670
|
+
} else if (typeof object === "number") {
|
|
671
|
+
return this.evaluateNumberMethod(
|
|
672
|
+
object,
|
|
673
|
+
expression.method,
|
|
674
|
+
expression.arguments,
|
|
675
|
+
);
|
|
676
|
+
} else if (object && object.type === "Instance") {
|
|
677
|
+
return this.evaluateInstanceMethodCall(
|
|
678
|
+
object,
|
|
679
|
+
expression.method,
|
|
680
|
+
expression.arguments,
|
|
681
|
+
);
|
|
591
682
|
} else {
|
|
592
683
|
throw new Error(
|
|
593
|
-
`Solo se pueden llamar métodos en arreglos
|
|
684
|
+
`Solo se pueden llamar métodos en arreglos, cadenas, números o instancias`,
|
|
685
|
+
);
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
/**
|
|
690
|
+
* Evaluates an instance method call
|
|
691
|
+
* @param {Object} instance - The instance object
|
|
692
|
+
* @param {string} methodName - The method name
|
|
693
|
+
* @param {Array} args - Method arguments
|
|
694
|
+
* @returns {any} Method result
|
|
695
|
+
*/
|
|
696
|
+
evaluateInstanceMethodCall(instance, methodName, args) {
|
|
697
|
+
// Find the method in the class
|
|
698
|
+
let method = null;
|
|
699
|
+
const classObj = instance.classObj;
|
|
700
|
+
|
|
701
|
+
for (const m of classObj.methods) {
|
|
702
|
+
if (m.name === methodName) {
|
|
703
|
+
method = m;
|
|
704
|
+
break;
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
// If not found, check parent class
|
|
709
|
+
if (!method && instance.parentClass) {
|
|
710
|
+
for (const m of instance.parentClass.methods) {
|
|
711
|
+
if (m.name === methodName) {
|
|
712
|
+
method = m;
|
|
713
|
+
break;
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
if (!method) {
|
|
719
|
+
throw new Error(
|
|
720
|
+
`Método '${methodName}' no encontrado en la clase '${instance.className}'`,
|
|
594
721
|
);
|
|
595
722
|
}
|
|
723
|
+
|
|
724
|
+
// Evaluate arguments
|
|
725
|
+
const evaluatedArgs = [];
|
|
726
|
+
for (const arg of args) {
|
|
727
|
+
evaluatedArgs.push(this.evaluateExpression(arg));
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
// Check argument count
|
|
731
|
+
if (method.parameters.length !== evaluatedArgs.length) {
|
|
732
|
+
throw new Error(
|
|
733
|
+
`El método '${methodName}' espera ${method.parameters.length} argumentos pero recibió ${evaluatedArgs.length}`,
|
|
734
|
+
);
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
// Set up method environment
|
|
738
|
+
const methodEnv = new Environment(this.environment);
|
|
739
|
+
for (let i = 0; i < evaluatedArgs.length; i++) {
|
|
740
|
+
methodEnv.define(method.parameters[i], evaluatedArgs[i]);
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
const previousInstance = this.currentInstance;
|
|
744
|
+
const previousEnv = this.environment;
|
|
745
|
+
this.currentInstance = instance;
|
|
746
|
+
this.environment = methodEnv;
|
|
747
|
+
|
|
748
|
+
try {
|
|
749
|
+
this.executeBlock(method.body);
|
|
750
|
+
return null;
|
|
751
|
+
} catch (returnValue) {
|
|
752
|
+
if (returnValue instanceof ReturnException) {
|
|
753
|
+
return returnValue.value;
|
|
754
|
+
}
|
|
755
|
+
throw returnValue;
|
|
756
|
+
} finally {
|
|
757
|
+
this.environment = previousEnv;
|
|
758
|
+
this.currentInstance = previousInstance;
|
|
759
|
+
}
|
|
596
760
|
}
|
|
597
761
|
|
|
598
762
|
/**
|
|
@@ -604,22 +768,26 @@ class Evaluator {
|
|
|
604
768
|
*/
|
|
605
769
|
evaluateArrayMethod(array, method, args = []) {
|
|
606
770
|
switch (method) {
|
|
607
|
-
case
|
|
771
|
+
case "longitud":
|
|
608
772
|
return array.length;
|
|
609
773
|
|
|
610
|
-
case
|
|
774
|
+
case "primero":
|
|
611
775
|
if (array.length === 0) {
|
|
612
|
-
throw new Error(
|
|
776
|
+
throw new Error(
|
|
777
|
+
"No se puede obtener el primer elemento de un arreglo vacío",
|
|
778
|
+
);
|
|
613
779
|
}
|
|
614
780
|
return array[0];
|
|
615
781
|
|
|
616
|
-
case
|
|
782
|
+
case "ultimo":
|
|
617
783
|
if (array.length === 0) {
|
|
618
|
-
throw new Error(
|
|
784
|
+
throw new Error(
|
|
785
|
+
"No se puede obtener el último elemento de un arreglo vacío",
|
|
786
|
+
);
|
|
619
787
|
}
|
|
620
788
|
return array[array.length - 1];
|
|
621
789
|
|
|
622
|
-
case
|
|
790
|
+
case "agregar":
|
|
623
791
|
// Evaluate all arguments and add them to the array
|
|
624
792
|
for (const arg of args) {
|
|
625
793
|
const value = this.evaluateExpression(arg);
|
|
@@ -627,29 +795,37 @@ class Evaluator {
|
|
|
627
795
|
}
|
|
628
796
|
return array.length; // Return the new length
|
|
629
797
|
|
|
630
|
-
case
|
|
798
|
+
case "remover":
|
|
631
799
|
// Remove and return the last element
|
|
632
800
|
if (array.length === 0) {
|
|
633
|
-
throw new Error(
|
|
801
|
+
throw new Error(
|
|
802
|
+
"No se puede eliminar un elemento de un arreglo vacío",
|
|
803
|
+
);
|
|
634
804
|
}
|
|
635
805
|
return array.pop(); // Return the removed element
|
|
636
806
|
|
|
637
|
-
case
|
|
807
|
+
case "contiene":
|
|
638
808
|
// Check if array contains the specified element
|
|
639
809
|
if (args.length !== 1) {
|
|
640
|
-
throw new Error(
|
|
810
|
+
throw new Error(
|
|
811
|
+
"El método contiene() requiere exactamente un argumento",
|
|
812
|
+
);
|
|
641
813
|
}
|
|
642
814
|
const searchElement = this.evaluateExpression(args[0]);
|
|
643
815
|
return array.includes(searchElement);
|
|
644
816
|
|
|
645
|
-
case
|
|
817
|
+
case "recorrer":
|
|
646
818
|
// Iterate through array and call function for each element
|
|
647
819
|
if (args.length !== 1) {
|
|
648
|
-
throw new Error(
|
|
820
|
+
throw new Error(
|
|
821
|
+
"El método recorrer() requiere exactamente un argumento",
|
|
822
|
+
);
|
|
649
823
|
}
|
|
650
824
|
const callback = this.evaluateExpression(args[0]);
|
|
651
|
-
if (callback.type !==
|
|
652
|
-
throw new Error(
|
|
825
|
+
if (callback.type !== "Function") {
|
|
826
|
+
throw new Error(
|
|
827
|
+
"El método recorrer() requiere una función como argumento",
|
|
828
|
+
);
|
|
653
829
|
}
|
|
654
830
|
|
|
655
831
|
// Call the function for each element
|
|
@@ -660,19 +836,19 @@ class Evaluator {
|
|
|
660
836
|
// Handle function parameters
|
|
661
837
|
if (callback.parameters.length === 0) {
|
|
662
838
|
// No parameters - use automatic variables
|
|
663
|
-
callbackEnv.define(
|
|
664
|
-
callbackEnv.define(
|
|
839
|
+
callbackEnv.define("elemento", array[i]);
|
|
840
|
+
callbackEnv.define("indice", i);
|
|
665
841
|
} else if (callback.parameters.length === 1) {
|
|
666
842
|
// One parameter - element only
|
|
667
843
|
callbackEnv.define(callback.parameters[0], array[i]);
|
|
668
|
-
callbackEnv.define(
|
|
844
|
+
callbackEnv.define("indice", i);
|
|
669
845
|
} else if (callback.parameters.length === 2) {
|
|
670
846
|
// Two parameters - element and index
|
|
671
847
|
callbackEnv.define(callback.parameters[0], array[i]);
|
|
672
848
|
callbackEnv.define(callback.parameters[1], i);
|
|
673
849
|
} else {
|
|
674
850
|
throw new Error(
|
|
675
|
-
|
|
851
|
+
"La función en recorrer() puede tener máximo 2 parámetros",
|
|
676
852
|
);
|
|
677
853
|
}
|
|
678
854
|
|
|
@@ -687,6 +863,313 @@ class Evaluator {
|
|
|
687
863
|
}
|
|
688
864
|
return null; // forEach doesn't return anything
|
|
689
865
|
|
|
866
|
+
case "filtrar":
|
|
867
|
+
// Filter array elements based on a predicate function
|
|
868
|
+
if (args.length !== 1) {
|
|
869
|
+
throw new Error("filtrar() requiere exactamente 1 argumento");
|
|
870
|
+
}
|
|
871
|
+
const filterCallback = this.evaluateExpression(args[0]);
|
|
872
|
+
if (filterCallback.type !== "Function") {
|
|
873
|
+
throw new Error("filtrar() requiere una función como argumento");
|
|
874
|
+
}
|
|
875
|
+
const filteredArray = [];
|
|
876
|
+
for (let i = 0; i < array.length; i++) {
|
|
877
|
+
const filterEnv = new Environment(this.environment);
|
|
878
|
+
if (filterCallback.parameters.length >= 1) {
|
|
879
|
+
filterEnv.define(filterCallback.parameters[0], array[i]);
|
|
880
|
+
}
|
|
881
|
+
if (filterCallback.parameters.length >= 2) {
|
|
882
|
+
filterEnv.define(filterCallback.parameters[1], i);
|
|
883
|
+
}
|
|
884
|
+
const prevEnv = this.environment;
|
|
885
|
+
this.environment = filterEnv;
|
|
886
|
+
try {
|
|
887
|
+
this.executeBlock(filterCallback.body);
|
|
888
|
+
} catch (returnValue) {
|
|
889
|
+
if (returnValue instanceof ReturnException) {
|
|
890
|
+
if (this.isTruthy(returnValue.value)) {
|
|
891
|
+
filteredArray.push(array[i]);
|
|
892
|
+
}
|
|
893
|
+
} else {
|
|
894
|
+
throw returnValue;
|
|
895
|
+
}
|
|
896
|
+
} finally {
|
|
897
|
+
this.environment = prevEnv;
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
return filteredArray;
|
|
901
|
+
|
|
902
|
+
case "mapear":
|
|
903
|
+
// Transform each element using a function
|
|
904
|
+
if (args.length !== 1) {
|
|
905
|
+
throw new Error("mapear() requiere exactamente 1 argumento");
|
|
906
|
+
}
|
|
907
|
+
const mapCallback = this.evaluateExpression(args[0]);
|
|
908
|
+
if (mapCallback.type !== "Function") {
|
|
909
|
+
throw new Error("mapear() requiere una función como argumento");
|
|
910
|
+
}
|
|
911
|
+
const mappedArray = [];
|
|
912
|
+
for (let i = 0; i < array.length; i++) {
|
|
913
|
+
const mapEnv = new Environment(this.environment);
|
|
914
|
+
if (mapCallback.parameters.length >= 1) {
|
|
915
|
+
mapEnv.define(mapCallback.parameters[0], array[i]);
|
|
916
|
+
}
|
|
917
|
+
if (mapCallback.parameters.length >= 2) {
|
|
918
|
+
mapEnv.define(mapCallback.parameters[1], i);
|
|
919
|
+
}
|
|
920
|
+
const prevEnv = this.environment;
|
|
921
|
+
this.environment = mapEnv;
|
|
922
|
+
try {
|
|
923
|
+
this.executeBlock(mapCallback.body);
|
|
924
|
+
mappedArray.push(null); // If no return, push null
|
|
925
|
+
} catch (returnValue) {
|
|
926
|
+
if (returnValue instanceof ReturnException) {
|
|
927
|
+
mappedArray.push(returnValue.value);
|
|
928
|
+
} else {
|
|
929
|
+
throw returnValue;
|
|
930
|
+
}
|
|
931
|
+
} finally {
|
|
932
|
+
this.environment = prevEnv;
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
return mappedArray;
|
|
936
|
+
|
|
937
|
+
case "reducir":
|
|
938
|
+
// Reduce array to a single value
|
|
939
|
+
if (args.length < 1 || args.length > 2) {
|
|
940
|
+
throw new Error("reducir() requiere 1 o 2 argumentos");
|
|
941
|
+
}
|
|
942
|
+
const reduceCallback = this.evaluateExpression(args[0]);
|
|
943
|
+
if (reduceCallback.type !== "Function") {
|
|
944
|
+
throw new Error(
|
|
945
|
+
"reducir() requiere una función como primer argumento",
|
|
946
|
+
);
|
|
947
|
+
}
|
|
948
|
+
let accumulator =
|
|
949
|
+
args.length === 2 ? this.evaluateExpression(args[1]) : array[0];
|
|
950
|
+
const startIndex = args.length === 2 ? 0 : 1;
|
|
951
|
+
for (let i = startIndex; i < array.length; i++) {
|
|
952
|
+
const reduceEnv = new Environment(this.environment);
|
|
953
|
+
if (reduceCallback.parameters.length >= 1) {
|
|
954
|
+
reduceEnv.define(reduceCallback.parameters[0], accumulator);
|
|
955
|
+
}
|
|
956
|
+
if (reduceCallback.parameters.length >= 2) {
|
|
957
|
+
reduceEnv.define(reduceCallback.parameters[1], array[i]);
|
|
958
|
+
}
|
|
959
|
+
if (reduceCallback.parameters.length >= 3) {
|
|
960
|
+
reduceEnv.define(reduceCallback.parameters[2], i);
|
|
961
|
+
}
|
|
962
|
+
const prevEnv = this.environment;
|
|
963
|
+
this.environment = reduceEnv;
|
|
964
|
+
try {
|
|
965
|
+
this.executeBlock(reduceCallback.body);
|
|
966
|
+
} catch (returnValue) {
|
|
967
|
+
if (returnValue instanceof ReturnException) {
|
|
968
|
+
accumulator = returnValue.value;
|
|
969
|
+
} else {
|
|
970
|
+
throw returnValue;
|
|
971
|
+
}
|
|
972
|
+
} finally {
|
|
973
|
+
this.environment = prevEnv;
|
|
974
|
+
}
|
|
975
|
+
}
|
|
976
|
+
return accumulator;
|
|
977
|
+
|
|
978
|
+
case "ordenar":
|
|
979
|
+
// Sort array (optionally with comparison function)
|
|
980
|
+
if (args.length === 0) {
|
|
981
|
+
// Default sort (numeric or string)
|
|
982
|
+
return [...array].sort((a, b) => {
|
|
983
|
+
if (typeof a === "number" && typeof b === "number") {
|
|
984
|
+
return a - b;
|
|
985
|
+
}
|
|
986
|
+
return String(a).localeCompare(String(b));
|
|
987
|
+
});
|
|
988
|
+
} else if (args.length === 1) {
|
|
989
|
+
const sortCallback = this.evaluateExpression(args[0]);
|
|
990
|
+
if (sortCallback.type !== "Function") {
|
|
991
|
+
throw new Error("ordenar() requiere una función como argumento");
|
|
992
|
+
}
|
|
993
|
+
return [...array].sort((a, b) => {
|
|
994
|
+
const sortEnv = new Environment(this.environment);
|
|
995
|
+
if (sortCallback.parameters.length >= 1) {
|
|
996
|
+
sortEnv.define(sortCallback.parameters[0], a);
|
|
997
|
+
}
|
|
998
|
+
if (sortCallback.parameters.length >= 2) {
|
|
999
|
+
sortEnv.define(sortCallback.parameters[1], b);
|
|
1000
|
+
}
|
|
1001
|
+
const prevEnv = this.environment;
|
|
1002
|
+
this.environment = sortEnv;
|
|
1003
|
+
try {
|
|
1004
|
+
this.executeBlock(sortCallback.body);
|
|
1005
|
+
return 0;
|
|
1006
|
+
} catch (returnValue) {
|
|
1007
|
+
if (returnValue instanceof ReturnException) {
|
|
1008
|
+
return returnValue.value;
|
|
1009
|
+
}
|
|
1010
|
+
throw returnValue;
|
|
1011
|
+
} finally {
|
|
1012
|
+
this.environment = prevEnv;
|
|
1013
|
+
}
|
|
1014
|
+
});
|
|
1015
|
+
} else {
|
|
1016
|
+
throw new Error("ordenar() acepta 0 o 1 argumentos");
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
case "invertir":
|
|
1020
|
+
// Reverse array (returns new array)
|
|
1021
|
+
return [...array].reverse();
|
|
1022
|
+
|
|
1023
|
+
case "buscar":
|
|
1024
|
+
// Find first element matching predicate
|
|
1025
|
+
if (args.length !== 1) {
|
|
1026
|
+
throw new Error("buscar() requiere exactamente 1 argumento");
|
|
1027
|
+
}
|
|
1028
|
+
const findCallback = this.evaluateExpression(args[0]);
|
|
1029
|
+
if (findCallback.type !== "Function") {
|
|
1030
|
+
throw new Error("buscar() requiere una función como argumento");
|
|
1031
|
+
}
|
|
1032
|
+
for (let i = 0; i < array.length; i++) {
|
|
1033
|
+
const findEnv = new Environment(this.environment);
|
|
1034
|
+
if (findCallback.parameters.length >= 1) {
|
|
1035
|
+
findEnv.define(findCallback.parameters[0], array[i]);
|
|
1036
|
+
}
|
|
1037
|
+
if (findCallback.parameters.length >= 2) {
|
|
1038
|
+
findEnv.define(findCallback.parameters[1], i);
|
|
1039
|
+
}
|
|
1040
|
+
const prevEnv = this.environment;
|
|
1041
|
+
this.environment = findEnv;
|
|
1042
|
+
try {
|
|
1043
|
+
this.executeBlock(findCallback.body);
|
|
1044
|
+
} catch (returnValue) {
|
|
1045
|
+
if (returnValue instanceof ReturnException) {
|
|
1046
|
+
if (this.isTruthy(returnValue.value)) {
|
|
1047
|
+
this.environment = prevEnv;
|
|
1048
|
+
return array[i];
|
|
1049
|
+
}
|
|
1050
|
+
} else {
|
|
1051
|
+
throw returnValue;
|
|
1052
|
+
}
|
|
1053
|
+
} finally {
|
|
1054
|
+
this.environment = prevEnv;
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1057
|
+
return undefined;
|
|
1058
|
+
|
|
1059
|
+
case "algunos":
|
|
1060
|
+
// Check if some elements match predicate
|
|
1061
|
+
if (args.length !== 1) {
|
|
1062
|
+
throw new Error("algunos() requiere exactamente 1 argumento");
|
|
1063
|
+
}
|
|
1064
|
+
const someCallback = this.evaluateExpression(args[0]);
|
|
1065
|
+
if (someCallback.type !== "Function") {
|
|
1066
|
+
throw new Error("algunos() requiere una función como argumento");
|
|
1067
|
+
}
|
|
1068
|
+
for (let i = 0; i < array.length; i++) {
|
|
1069
|
+
const someEnv = new Environment(this.environment);
|
|
1070
|
+
if (someCallback.parameters.length >= 1) {
|
|
1071
|
+
someEnv.define(someCallback.parameters[0], array[i]);
|
|
1072
|
+
}
|
|
1073
|
+
if (someCallback.parameters.length >= 2) {
|
|
1074
|
+
someEnv.define(someCallback.parameters[1], i);
|
|
1075
|
+
}
|
|
1076
|
+
const prevEnv = this.environment;
|
|
1077
|
+
this.environment = someEnv;
|
|
1078
|
+
try {
|
|
1079
|
+
this.executeBlock(someCallback.body);
|
|
1080
|
+
} catch (returnValue) {
|
|
1081
|
+
if (returnValue instanceof ReturnException) {
|
|
1082
|
+
if (this.isTruthy(returnValue.value)) {
|
|
1083
|
+
this.environment = prevEnv;
|
|
1084
|
+
return true;
|
|
1085
|
+
}
|
|
1086
|
+
} else {
|
|
1087
|
+
throw returnValue;
|
|
1088
|
+
}
|
|
1089
|
+
} finally {
|
|
1090
|
+
this.environment = prevEnv;
|
|
1091
|
+
}
|
|
1092
|
+
}
|
|
1093
|
+
return false;
|
|
1094
|
+
|
|
1095
|
+
case "todos":
|
|
1096
|
+
// Check if all elements match predicate
|
|
1097
|
+
if (args.length !== 1) {
|
|
1098
|
+
throw new Error("todos() requiere exactamente 1 argumento");
|
|
1099
|
+
}
|
|
1100
|
+
const everyCallback = this.evaluateExpression(args[0]);
|
|
1101
|
+
if (everyCallback.type !== "Function") {
|
|
1102
|
+
throw new Error("todos() requiere una función como argumento");
|
|
1103
|
+
}
|
|
1104
|
+
for (let i = 0; i < array.length; i++) {
|
|
1105
|
+
const everyEnv = new Environment(this.environment);
|
|
1106
|
+
if (everyCallback.parameters.length >= 1) {
|
|
1107
|
+
everyEnv.define(everyCallback.parameters[0], array[i]);
|
|
1108
|
+
}
|
|
1109
|
+
if (everyCallback.parameters.length >= 2) {
|
|
1110
|
+
everyEnv.define(everyCallback.parameters[1], i);
|
|
1111
|
+
}
|
|
1112
|
+
const prevEnv = this.environment;
|
|
1113
|
+
this.environment = everyEnv;
|
|
1114
|
+
try {
|
|
1115
|
+
this.executeBlock(everyCallback.body);
|
|
1116
|
+
} catch (returnValue) {
|
|
1117
|
+
if (returnValue instanceof ReturnException) {
|
|
1118
|
+
if (!this.isTruthy(returnValue.value)) {
|
|
1119
|
+
this.environment = prevEnv;
|
|
1120
|
+
return false;
|
|
1121
|
+
}
|
|
1122
|
+
} else {
|
|
1123
|
+
throw returnValue;
|
|
1124
|
+
}
|
|
1125
|
+
} finally {
|
|
1126
|
+
this.environment = prevEnv;
|
|
1127
|
+
}
|
|
1128
|
+
}
|
|
1129
|
+
return true;
|
|
1130
|
+
|
|
1131
|
+
case "unir":
|
|
1132
|
+
// Join array elements into string
|
|
1133
|
+
if (args.length !== 1) {
|
|
1134
|
+
throw new Error("unir() requiere exactamente 1 argumento");
|
|
1135
|
+
}
|
|
1136
|
+
const separator = this.evaluateExpression(args[0]);
|
|
1137
|
+
if (typeof separator !== "string") {
|
|
1138
|
+
throw new Error("unir() requiere un argumento de tipo cadena");
|
|
1139
|
+
}
|
|
1140
|
+
return array.join(separator);
|
|
1141
|
+
|
|
1142
|
+
case "cortar":
|
|
1143
|
+
// Slice array (returns subarray)
|
|
1144
|
+
if (args.length < 1 || args.length > 2) {
|
|
1145
|
+
throw new Error("cortar() requiere 1 o 2 argumentos");
|
|
1146
|
+
}
|
|
1147
|
+
const sliceStart = this.evaluateExpression(args[0]);
|
|
1148
|
+
if (typeof sliceStart !== "number") {
|
|
1149
|
+
throw new Error("cortar() requiere argumentos numéricos");
|
|
1150
|
+
}
|
|
1151
|
+
if (args.length === 2) {
|
|
1152
|
+
const sliceEnd = this.evaluateExpression(args[1]);
|
|
1153
|
+
if (typeof sliceEnd !== "number") {
|
|
1154
|
+
throw new Error("cortar() requiere argumentos numéricos");
|
|
1155
|
+
}
|
|
1156
|
+
return array.slice(sliceStart, sliceEnd);
|
|
1157
|
+
}
|
|
1158
|
+
return array.slice(sliceStart);
|
|
1159
|
+
|
|
1160
|
+
case "insertar":
|
|
1161
|
+
// Insert element at position (modifies array)
|
|
1162
|
+
if (args.length !== 2) {
|
|
1163
|
+
throw new Error("insertar() requiere exactamente 2 argumentos");
|
|
1164
|
+
}
|
|
1165
|
+
const insertIndex = this.evaluateExpression(args[0]);
|
|
1166
|
+
const insertValue = this.evaluateExpression(args[1]);
|
|
1167
|
+
if (typeof insertIndex !== "number") {
|
|
1168
|
+
throw new Error("insertar() requiere un índice numérico");
|
|
1169
|
+
}
|
|
1170
|
+
array.splice(insertIndex, 0, insertValue);
|
|
1171
|
+
return array.length;
|
|
1172
|
+
|
|
690
1173
|
default:
|
|
691
1174
|
throw new Error(`Método de arreglo desconocido: ${method}`);
|
|
692
1175
|
}
|
|
@@ -696,24 +1179,356 @@ class Evaluator {
|
|
|
696
1179
|
* Evaluates a string method
|
|
697
1180
|
* @param {string} string - The string object
|
|
698
1181
|
* @param {string} method - The method name
|
|
1182
|
+
* @param {Array} args - Method arguments (optional)
|
|
699
1183
|
* @returns {any} Method result
|
|
700
1184
|
*/
|
|
701
|
-
evaluateStringMethod(string, method) {
|
|
1185
|
+
evaluateStringMethod(string, method, args = []) {
|
|
702
1186
|
switch (method) {
|
|
703
|
-
case
|
|
1187
|
+
case "longitud":
|
|
704
1188
|
return string.length;
|
|
705
1189
|
|
|
706
|
-
case
|
|
1190
|
+
case "mayusculas":
|
|
707
1191
|
return string.toUpperCase();
|
|
708
1192
|
|
|
709
|
-
case
|
|
1193
|
+
case "minusculas":
|
|
710
1194
|
return string.toLowerCase();
|
|
711
1195
|
|
|
1196
|
+
case "dividir":
|
|
1197
|
+
if (args.length !== 1) {
|
|
1198
|
+
throw new Error("dividir() requiere exactamente 1 argumento");
|
|
1199
|
+
}
|
|
1200
|
+
const separator = this.evaluateExpression(args[0]);
|
|
1201
|
+
if (typeof separator !== "string") {
|
|
1202
|
+
throw new Error("dividir() requiere un argumento de tipo cadena");
|
|
1203
|
+
}
|
|
1204
|
+
return string.split(separator);
|
|
1205
|
+
|
|
1206
|
+
case "reemplazar":
|
|
1207
|
+
if (args.length !== 2) {
|
|
1208
|
+
throw new Error("reemplazar() requiere exactamente 2 argumentos");
|
|
1209
|
+
}
|
|
1210
|
+
const search = this.evaluateExpression(args[0]);
|
|
1211
|
+
const replacement = this.evaluateExpression(args[1]);
|
|
1212
|
+
if (typeof search !== "string" || typeof replacement !== "string") {
|
|
1213
|
+
throw new Error("reemplazar() requiere argumentos de tipo cadena");
|
|
1214
|
+
}
|
|
1215
|
+
return string.split(search).join(replacement);
|
|
1216
|
+
|
|
1217
|
+
case "recortar":
|
|
1218
|
+
return string.trim();
|
|
1219
|
+
|
|
1220
|
+
case "incluye":
|
|
1221
|
+
if (args.length !== 1) {
|
|
1222
|
+
throw new Error("incluye() requiere exactamente 1 argumento");
|
|
1223
|
+
}
|
|
1224
|
+
const substring = this.evaluateExpression(args[0]);
|
|
1225
|
+
if (typeof substring !== "string") {
|
|
1226
|
+
throw new Error("incluye() requiere un argumento de tipo cadena");
|
|
1227
|
+
}
|
|
1228
|
+
return string.includes(substring);
|
|
1229
|
+
|
|
1230
|
+
case "empiezaCon":
|
|
1231
|
+
if (args.length !== 1) {
|
|
1232
|
+
throw new Error("empiezaCon() requiere exactamente 1 argumento");
|
|
1233
|
+
}
|
|
1234
|
+
const prefix = this.evaluateExpression(args[0]);
|
|
1235
|
+
if (typeof prefix !== "string") {
|
|
1236
|
+
throw new Error("empiezaCon() requiere un argumento de tipo cadena");
|
|
1237
|
+
}
|
|
1238
|
+
return string.startsWith(prefix);
|
|
1239
|
+
|
|
1240
|
+
case "terminaCon":
|
|
1241
|
+
if (args.length !== 1) {
|
|
1242
|
+
throw new Error("terminaCon() requiere exactamente 1 argumento");
|
|
1243
|
+
}
|
|
1244
|
+
const suffix = this.evaluateExpression(args[0]);
|
|
1245
|
+
if (typeof suffix !== "string") {
|
|
1246
|
+
throw new Error("terminaCon() requiere un argumento de tipo cadena");
|
|
1247
|
+
}
|
|
1248
|
+
return string.endsWith(suffix);
|
|
1249
|
+
|
|
1250
|
+
case "caracter":
|
|
1251
|
+
if (args.length !== 1) {
|
|
1252
|
+
throw new Error("caracter() requiere exactamente 1 argumento");
|
|
1253
|
+
}
|
|
1254
|
+
const charIndex = this.evaluateExpression(args[0]);
|
|
1255
|
+
if (typeof charIndex !== "number") {
|
|
1256
|
+
throw new Error("caracter() requiere un argumento numérico");
|
|
1257
|
+
}
|
|
1258
|
+
if (charIndex < 0 || charIndex >= string.length) {
|
|
1259
|
+
throw new Error("Índice fuera de rango");
|
|
1260
|
+
}
|
|
1261
|
+
return string.charAt(charIndex);
|
|
1262
|
+
|
|
1263
|
+
case "subcadena":
|
|
1264
|
+
if (args.length < 1 || args.length > 2) {
|
|
1265
|
+
throw new Error("subcadena() requiere 1 o 2 argumentos");
|
|
1266
|
+
}
|
|
1267
|
+
const start = this.evaluateExpression(args[0]);
|
|
1268
|
+
if (typeof start !== "number") {
|
|
1269
|
+
throw new Error("subcadena() requiere argumentos numéricos");
|
|
1270
|
+
}
|
|
1271
|
+
if (args.length === 2) {
|
|
1272
|
+
const end = this.evaluateExpression(args[1]);
|
|
1273
|
+
if (typeof end !== "number") {
|
|
1274
|
+
throw new Error("subcadena() requiere argumentos numéricos");
|
|
1275
|
+
}
|
|
1276
|
+
return string.substring(start, end);
|
|
1277
|
+
}
|
|
1278
|
+
return string.substring(start);
|
|
1279
|
+
|
|
1280
|
+
case "invertir":
|
|
1281
|
+
return string.split("").reverse().join("");
|
|
1282
|
+
|
|
1283
|
+
case "contiene":
|
|
1284
|
+
if (args.length !== 1) {
|
|
1285
|
+
throw new Error("contiene() requiere exactamente 1 argumento");
|
|
1286
|
+
}
|
|
1287
|
+
const searchStr = this.evaluateExpression(args[0]);
|
|
1288
|
+
if (typeof searchStr !== "string") {
|
|
1289
|
+
throw new Error("contiene() requiere un argumento de tipo cadena");
|
|
1290
|
+
}
|
|
1291
|
+
return string.includes(searchStr);
|
|
1292
|
+
|
|
712
1293
|
default:
|
|
713
1294
|
throw new Error(`Método de cadena desconocido: ${method}`);
|
|
714
1295
|
}
|
|
715
1296
|
}
|
|
716
1297
|
|
|
1298
|
+
/**
|
|
1299
|
+
* Evaluates a number method
|
|
1300
|
+
* @param {number} number - The number object
|
|
1301
|
+
* @param {string} method - The method name
|
|
1302
|
+
* @param {Array} args - Method arguments (optional)
|
|
1303
|
+
* @returns {any} Method result
|
|
1304
|
+
*/
|
|
1305
|
+
evaluateNumberMethod(number, method, args = []) {
|
|
1306
|
+
switch (method) {
|
|
1307
|
+
case "esPar":
|
|
1308
|
+
return number % 2 === 0;
|
|
1309
|
+
|
|
1310
|
+
case "esImpar":
|
|
1311
|
+
return number % 2 !== 0;
|
|
1312
|
+
|
|
1313
|
+
case "esPositivo":
|
|
1314
|
+
return number > 0;
|
|
1315
|
+
|
|
1316
|
+
case "esNegativo":
|
|
1317
|
+
return number < 0;
|
|
1318
|
+
|
|
1319
|
+
case "aTexto":
|
|
1320
|
+
return String(number);
|
|
1321
|
+
|
|
1322
|
+
default:
|
|
1323
|
+
throw new Error(`Método de número desconocido: ${method}`);
|
|
1324
|
+
}
|
|
1325
|
+
}
|
|
1326
|
+
|
|
1327
|
+
/**
|
|
1328
|
+
* Evaluates a new expression (class instantiation)
|
|
1329
|
+
* @param {Object} expression - New expression
|
|
1330
|
+
* @returns {Object} Instance object
|
|
1331
|
+
*/
|
|
1332
|
+
evaluateNewExpression(expression) {
|
|
1333
|
+
const className = expression.className;
|
|
1334
|
+
const classObj = this.environment.get(className);
|
|
1335
|
+
|
|
1336
|
+
if (!classObj || classObj.type !== "Class") {
|
|
1337
|
+
throw new Error(`'${className}' no es una clase`);
|
|
1338
|
+
}
|
|
1339
|
+
|
|
1340
|
+
// Create a new instance
|
|
1341
|
+
const instance = {
|
|
1342
|
+
type: "Instance",
|
|
1343
|
+
className: className,
|
|
1344
|
+
classObj: classObj,
|
|
1345
|
+
properties: {},
|
|
1346
|
+
};
|
|
1347
|
+
|
|
1348
|
+
// Evaluate constructor arguments
|
|
1349
|
+
const args = [];
|
|
1350
|
+
for (const arg of expression.arguments) {
|
|
1351
|
+
args.push(this.evaluateExpression(arg));
|
|
1352
|
+
}
|
|
1353
|
+
|
|
1354
|
+
// If class extends another, get parent class
|
|
1355
|
+
let parentClass = null;
|
|
1356
|
+
if (classObj.superclass) {
|
|
1357
|
+
parentClass = this.environment.get(classObj.superclass);
|
|
1358
|
+
if (!parentClass || parentClass.type !== "Class") {
|
|
1359
|
+
throw new Error(`Clase padre '${classObj.superclass}' no encontrada`);
|
|
1360
|
+
}
|
|
1361
|
+
instance.parentClass = parentClass;
|
|
1362
|
+
}
|
|
1363
|
+
|
|
1364
|
+
// Execute constructor if present
|
|
1365
|
+
if (classObj.constructor) {
|
|
1366
|
+
const previousInstance = this.currentInstance;
|
|
1367
|
+
this.currentInstance = instance;
|
|
1368
|
+
|
|
1369
|
+
const constructorEnv = new Environment(this.environment);
|
|
1370
|
+
|
|
1371
|
+
// Bind constructor parameters
|
|
1372
|
+
if (classObj.constructor.parameters.length !== args.length) {
|
|
1373
|
+
throw new Error(
|
|
1374
|
+
`El constructor de '${className}' espera ${classObj.constructor.parameters.length} argumentos pero recibió ${args.length}`,
|
|
1375
|
+
);
|
|
1376
|
+
}
|
|
1377
|
+
for (let i = 0; i < args.length; i++) {
|
|
1378
|
+
constructorEnv.define(classObj.constructor.parameters[i], args[i]);
|
|
1379
|
+
}
|
|
1380
|
+
|
|
1381
|
+
const previousEnv = this.environment;
|
|
1382
|
+
this.environment = constructorEnv;
|
|
1383
|
+
|
|
1384
|
+
try {
|
|
1385
|
+
this.executeBlock(classObj.constructor.body);
|
|
1386
|
+
} catch (returnValue) {
|
|
1387
|
+
if (!(returnValue instanceof ReturnException)) {
|
|
1388
|
+
throw returnValue;
|
|
1389
|
+
}
|
|
1390
|
+
} finally {
|
|
1391
|
+
this.environment = previousEnv;
|
|
1392
|
+
this.currentInstance = previousInstance;
|
|
1393
|
+
}
|
|
1394
|
+
}
|
|
1395
|
+
|
|
1396
|
+
return instance;
|
|
1397
|
+
}
|
|
1398
|
+
|
|
1399
|
+
/**
|
|
1400
|
+
* Evaluates this property access (este.propiedad)
|
|
1401
|
+
* @param {Object} expression - ThisPropertyAccess expression
|
|
1402
|
+
* @returns {any} Property value
|
|
1403
|
+
*/
|
|
1404
|
+
evaluateThisPropertyAccess(expression) {
|
|
1405
|
+
if (this.currentInstance === null) {
|
|
1406
|
+
throw new Error("'este' solo se puede usar dentro de un método de clase");
|
|
1407
|
+
}
|
|
1408
|
+
|
|
1409
|
+
const property = expression.property;
|
|
1410
|
+
|
|
1411
|
+
// Check if it's a property
|
|
1412
|
+
if (property in this.currentInstance.properties) {
|
|
1413
|
+
return this.currentInstance.properties[property];
|
|
1414
|
+
}
|
|
1415
|
+
|
|
1416
|
+
// Check if it's a method
|
|
1417
|
+
const classObj = this.currentInstance.classObj;
|
|
1418
|
+
for (const method of classObj.methods) {
|
|
1419
|
+
if (method.name === property) {
|
|
1420
|
+
// Return a bound method
|
|
1421
|
+
return {
|
|
1422
|
+
type: "BoundMethod",
|
|
1423
|
+
method: method,
|
|
1424
|
+
instance: this.currentInstance,
|
|
1425
|
+
};
|
|
1426
|
+
}
|
|
1427
|
+
}
|
|
1428
|
+
|
|
1429
|
+
// Check parent class methods
|
|
1430
|
+
if (this.currentInstance.parentClass) {
|
|
1431
|
+
for (const method of this.currentInstance.parentClass.methods) {
|
|
1432
|
+
if (method.name === property) {
|
|
1433
|
+
return {
|
|
1434
|
+
type: "BoundMethod",
|
|
1435
|
+
method: method,
|
|
1436
|
+
instance: this.currentInstance,
|
|
1437
|
+
};
|
|
1438
|
+
}
|
|
1439
|
+
}
|
|
1440
|
+
}
|
|
1441
|
+
|
|
1442
|
+
return undefined;
|
|
1443
|
+
}
|
|
1444
|
+
|
|
1445
|
+
/**
|
|
1446
|
+
* Evaluates this property assignment (este.propiedad = valor)
|
|
1447
|
+
* @param {Object} expression - ThisPropertyAssign expression
|
|
1448
|
+
* @returns {any} Assigned value
|
|
1449
|
+
*/
|
|
1450
|
+
evaluateThisPropertyAssign(expression) {
|
|
1451
|
+
if (this.currentInstance === null) {
|
|
1452
|
+
throw new Error("'este' solo se puede usar dentro de un método de clase");
|
|
1453
|
+
}
|
|
1454
|
+
|
|
1455
|
+
const value = this.evaluateExpression(expression.value);
|
|
1456
|
+
this.currentInstance.properties[expression.property] = value;
|
|
1457
|
+
return value;
|
|
1458
|
+
}
|
|
1459
|
+
|
|
1460
|
+
/**
|
|
1461
|
+
* Evaluates this method call (este.metodo())
|
|
1462
|
+
* @param {Object} expression - ThisMethodCall expression
|
|
1463
|
+
* @returns {any} Method result
|
|
1464
|
+
*/
|
|
1465
|
+
evaluateThisMethodCall(expression) {
|
|
1466
|
+
if (this.currentInstance === null) {
|
|
1467
|
+
throw new Error("'este' solo se puede usar dentro de un método de clase");
|
|
1468
|
+
}
|
|
1469
|
+
|
|
1470
|
+
return this.evaluateInstanceMethodCall(
|
|
1471
|
+
this.currentInstance,
|
|
1472
|
+
expression.method,
|
|
1473
|
+
expression.arguments,
|
|
1474
|
+
);
|
|
1475
|
+
}
|
|
1476
|
+
|
|
1477
|
+
/**
|
|
1478
|
+
* Evaluates super() call
|
|
1479
|
+
* @param {Object} expression - SuperCall expression
|
|
1480
|
+
* @returns {any} Result of parent constructor
|
|
1481
|
+
*/
|
|
1482
|
+
evaluateSuperCall(expression) {
|
|
1483
|
+
if (this.currentInstance === null) {
|
|
1484
|
+
throw new Error(
|
|
1485
|
+
"'super' solo se puede usar dentro de un método de clase",
|
|
1486
|
+
);
|
|
1487
|
+
}
|
|
1488
|
+
|
|
1489
|
+
const parentClass = this.currentInstance.parentClass;
|
|
1490
|
+
if (!parentClass) {
|
|
1491
|
+
throw new Error(
|
|
1492
|
+
"'super' solo se puede usar en clases que extienden otra clase",
|
|
1493
|
+
);
|
|
1494
|
+
}
|
|
1495
|
+
|
|
1496
|
+
// Evaluate arguments
|
|
1497
|
+
const args = [];
|
|
1498
|
+
for (const arg of expression.arguments) {
|
|
1499
|
+
args.push(this.evaluateExpression(arg));
|
|
1500
|
+
}
|
|
1501
|
+
|
|
1502
|
+
// Execute parent constructor
|
|
1503
|
+
if (parentClass.constructor) {
|
|
1504
|
+
const constructorEnv = new Environment(this.environment);
|
|
1505
|
+
|
|
1506
|
+
if (parentClass.constructor.parameters.length !== args.length) {
|
|
1507
|
+
throw new Error(
|
|
1508
|
+
`El constructor padre espera ${parentClass.constructor.parameters.length} argumentos pero recibió ${args.length}`,
|
|
1509
|
+
);
|
|
1510
|
+
}
|
|
1511
|
+
for (let i = 0; i < args.length; i++) {
|
|
1512
|
+
constructorEnv.define(parentClass.constructor.parameters[i], args[i]);
|
|
1513
|
+
}
|
|
1514
|
+
|
|
1515
|
+
const previousEnv = this.environment;
|
|
1516
|
+
this.environment = constructorEnv;
|
|
1517
|
+
|
|
1518
|
+
try {
|
|
1519
|
+
this.executeBlock(parentClass.constructor.body);
|
|
1520
|
+
} catch (returnValue) {
|
|
1521
|
+
if (!(returnValue instanceof ReturnException)) {
|
|
1522
|
+
throw returnValue;
|
|
1523
|
+
}
|
|
1524
|
+
} finally {
|
|
1525
|
+
this.environment = previousEnv;
|
|
1526
|
+
}
|
|
1527
|
+
}
|
|
1528
|
+
|
|
1529
|
+
return null;
|
|
1530
|
+
}
|
|
1531
|
+
|
|
717
1532
|
/**
|
|
718
1533
|
* Checks if a function name is a built-in mathematical function
|
|
719
1534
|
* @param {string} name - Function name
|
|
@@ -721,21 +1536,21 @@ class Evaluator {
|
|
|
721
1536
|
*/
|
|
722
1537
|
isMathFunction(name) {
|
|
723
1538
|
const mathFunctions = [
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
1539
|
+
"raiz",
|
|
1540
|
+
"potencia",
|
|
1541
|
+
"seno",
|
|
1542
|
+
"coseno",
|
|
1543
|
+
"tangente",
|
|
1544
|
+
"logaritmo",
|
|
1545
|
+
"valorAbsoluto",
|
|
1546
|
+
"redondear",
|
|
1547
|
+
"techo",
|
|
1548
|
+
"piso",
|
|
1549
|
+
"aleatorio",
|
|
1550
|
+
"maximo",
|
|
1551
|
+
"minimo",
|
|
1552
|
+
"suma",
|
|
1553
|
+
"promedio",
|
|
739
1554
|
];
|
|
740
1555
|
return mathFunctions.includes(name);
|
|
741
1556
|
}
|
|
@@ -748,159 +1563,163 @@ class Evaluator {
|
|
|
748
1563
|
*/
|
|
749
1564
|
evaluateMathFunction(name, args) {
|
|
750
1565
|
switch (name) {
|
|
751
|
-
case
|
|
1566
|
+
case "raiz":
|
|
752
1567
|
if (args.length !== 1) {
|
|
753
|
-
throw new Error(
|
|
1568
|
+
throw new Error("raiz() requiere exactamente 1 argumento");
|
|
754
1569
|
}
|
|
755
|
-
if (typeof args[0] !==
|
|
756
|
-
throw new Error(
|
|
1570
|
+
if (typeof args[0] !== "number") {
|
|
1571
|
+
throw new Error("raiz() requiere un argumento numérico");
|
|
757
1572
|
}
|
|
758
1573
|
if (args[0] < 0) {
|
|
759
|
-
throw new Error(
|
|
1574
|
+
throw new Error(
|
|
1575
|
+
"No se puede calcular la raíz cuadrada de un número negativo",
|
|
1576
|
+
);
|
|
760
1577
|
}
|
|
761
1578
|
return Math.sqrt(args[0]);
|
|
762
1579
|
|
|
763
|
-
case
|
|
1580
|
+
case "potencia":
|
|
764
1581
|
if (args.length !== 2) {
|
|
765
|
-
throw new Error(
|
|
1582
|
+
throw new Error("potencia() requiere exactamente 2 argumentos");
|
|
766
1583
|
}
|
|
767
|
-
if (typeof args[0] !==
|
|
768
|
-
throw new Error(
|
|
1584
|
+
if (typeof args[0] !== "number" || typeof args[1] !== "number") {
|
|
1585
|
+
throw new Error("potencia() requiere argumentos numéricos");
|
|
769
1586
|
}
|
|
770
1587
|
return Math.pow(args[0], args[1]);
|
|
771
1588
|
|
|
772
|
-
case
|
|
1589
|
+
case "seno":
|
|
773
1590
|
if (args.length !== 1) {
|
|
774
|
-
throw new Error(
|
|
1591
|
+
throw new Error("seno() requiere exactamente 1 argumento");
|
|
775
1592
|
}
|
|
776
|
-
if (typeof args[0] !==
|
|
777
|
-
throw new Error(
|
|
1593
|
+
if (typeof args[0] !== "number") {
|
|
1594
|
+
throw new Error("seno() requiere un argumento numérico");
|
|
778
1595
|
}
|
|
779
1596
|
return Math.sin(args[0]);
|
|
780
1597
|
|
|
781
|
-
case
|
|
1598
|
+
case "coseno":
|
|
782
1599
|
if (args.length !== 1) {
|
|
783
|
-
throw new Error(
|
|
1600
|
+
throw new Error("coseno() requiere exactamente 1 argumento");
|
|
784
1601
|
}
|
|
785
|
-
if (typeof args[0] !==
|
|
786
|
-
throw new Error(
|
|
1602
|
+
if (typeof args[0] !== "number") {
|
|
1603
|
+
throw new Error("coseno() requiere un argumento numérico");
|
|
787
1604
|
}
|
|
788
1605
|
return Math.cos(args[0]);
|
|
789
1606
|
|
|
790
|
-
case
|
|
1607
|
+
case "tangente":
|
|
791
1608
|
if (args.length !== 1) {
|
|
792
|
-
throw new Error(
|
|
1609
|
+
throw new Error("tangente() requiere exactamente 1 argumento");
|
|
793
1610
|
}
|
|
794
|
-
if (typeof args[0] !==
|
|
795
|
-
throw new Error(
|
|
1611
|
+
if (typeof args[0] !== "number") {
|
|
1612
|
+
throw new Error("tangente() requiere un argumento numérico");
|
|
796
1613
|
}
|
|
797
1614
|
return Math.tan(args[0]);
|
|
798
1615
|
|
|
799
|
-
case
|
|
1616
|
+
case "logaritmo":
|
|
800
1617
|
if (args.length !== 1) {
|
|
801
|
-
throw new Error(
|
|
1618
|
+
throw new Error("logaritmo() requiere exactamente 1 argumento");
|
|
802
1619
|
}
|
|
803
|
-
if (typeof args[0] !==
|
|
804
|
-
throw new Error(
|
|
1620
|
+
if (typeof args[0] !== "number") {
|
|
1621
|
+
throw new Error("logaritmo() requiere un argumento numérico");
|
|
805
1622
|
}
|
|
806
1623
|
if (args[0] <= 0) {
|
|
807
|
-
throw new Error(
|
|
1624
|
+
throw new Error(
|
|
1625
|
+
"No se puede calcular el logaritmo de un número no positivo",
|
|
1626
|
+
);
|
|
808
1627
|
}
|
|
809
1628
|
return Math.log(args[0]);
|
|
810
1629
|
|
|
811
|
-
case
|
|
1630
|
+
case "valorAbsoluto":
|
|
812
1631
|
if (args.length !== 1) {
|
|
813
|
-
throw new Error(
|
|
1632
|
+
throw new Error("valorAbsoluto() requiere exactamente 1 argumento");
|
|
814
1633
|
}
|
|
815
|
-
if (typeof args[0] !==
|
|
816
|
-
throw new Error(
|
|
1634
|
+
if (typeof args[0] !== "number") {
|
|
1635
|
+
throw new Error("valorAbsoluto() requiere un argumento numérico");
|
|
817
1636
|
}
|
|
818
1637
|
return Math.abs(args[0]);
|
|
819
1638
|
|
|
820
|
-
case
|
|
1639
|
+
case "redondear":
|
|
821
1640
|
if (args.length !== 1) {
|
|
822
|
-
throw new Error(
|
|
1641
|
+
throw new Error("redondear() requiere exactamente 1 argumento");
|
|
823
1642
|
}
|
|
824
|
-
if (typeof args[0] !==
|
|
825
|
-
throw new Error(
|
|
1643
|
+
if (typeof args[0] !== "number") {
|
|
1644
|
+
throw new Error("redondear() requiere un argumento numérico");
|
|
826
1645
|
}
|
|
827
1646
|
return Math.round(args[0]);
|
|
828
1647
|
|
|
829
|
-
case
|
|
1648
|
+
case "techo":
|
|
830
1649
|
if (args.length !== 1) {
|
|
831
|
-
throw new Error(
|
|
1650
|
+
throw new Error("techo() requiere exactamente 1 argumento");
|
|
832
1651
|
}
|
|
833
|
-
if (typeof args[0] !==
|
|
834
|
-
throw new Error(
|
|
1652
|
+
if (typeof args[0] !== "number") {
|
|
1653
|
+
throw new Error("techo() requiere un argumento numérico");
|
|
835
1654
|
}
|
|
836
1655
|
return Math.ceil(args[0]);
|
|
837
1656
|
|
|
838
|
-
case
|
|
1657
|
+
case "piso":
|
|
839
1658
|
if (args.length !== 1) {
|
|
840
|
-
throw new Error(
|
|
1659
|
+
throw new Error("piso() requiere exactamente 1 argumento");
|
|
841
1660
|
}
|
|
842
|
-
if (typeof args[0] !==
|
|
843
|
-
throw new Error(
|
|
1661
|
+
if (typeof args[0] !== "number") {
|
|
1662
|
+
throw new Error("piso() requiere un argumento numérico");
|
|
844
1663
|
}
|
|
845
1664
|
return Math.floor(args[0]);
|
|
846
1665
|
|
|
847
|
-
case
|
|
1666
|
+
case "aleatorio":
|
|
848
1667
|
if (args.length === 0) {
|
|
849
1668
|
return Math.random();
|
|
850
1669
|
} else if (args.length === 1) {
|
|
851
|
-
if (typeof args[0] !==
|
|
852
|
-
throw new Error(
|
|
1670
|
+
if (typeof args[0] !== "number") {
|
|
1671
|
+
throw new Error("aleatorio() requiere un argumento numérico");
|
|
853
1672
|
}
|
|
854
1673
|
return Math.random() * args[0];
|
|
855
1674
|
} else if (args.length === 2) {
|
|
856
|
-
if (typeof args[0] !==
|
|
857
|
-
throw new Error(
|
|
1675
|
+
if (typeof args[0] !== "number" || typeof args[1] !== "number") {
|
|
1676
|
+
throw new Error("aleatorio() requiere argumentos numéricos");
|
|
858
1677
|
}
|
|
859
1678
|
return Math.random() * (args[1] - args[0]) + args[0];
|
|
860
1679
|
} else {
|
|
861
|
-
throw new Error(
|
|
1680
|
+
throw new Error("aleatorio() acepta 0, 1, o 2 argumentos");
|
|
862
1681
|
}
|
|
863
1682
|
|
|
864
|
-
case
|
|
1683
|
+
case "maximo":
|
|
865
1684
|
if (args.length < 1) {
|
|
866
|
-
throw new Error(
|
|
1685
|
+
throw new Error("maximo() requiere al menos 1 argumento");
|
|
867
1686
|
}
|
|
868
1687
|
for (const arg of args) {
|
|
869
|
-
if (typeof arg !==
|
|
870
|
-
throw new Error(
|
|
1688
|
+
if (typeof arg !== "number") {
|
|
1689
|
+
throw new Error("maximo() requiere argumentos numéricos");
|
|
871
1690
|
}
|
|
872
1691
|
}
|
|
873
1692
|
return Math.max(...args);
|
|
874
1693
|
|
|
875
|
-
case
|
|
1694
|
+
case "minimo":
|
|
876
1695
|
if (args.length < 1) {
|
|
877
|
-
throw new Error(
|
|
1696
|
+
throw new Error("minimo() requiere al menos 1 argumento");
|
|
878
1697
|
}
|
|
879
1698
|
for (const arg of args) {
|
|
880
|
-
if (typeof arg !==
|
|
881
|
-
throw new Error(
|
|
1699
|
+
if (typeof arg !== "number") {
|
|
1700
|
+
throw new Error("minimo() requiere argumentos numéricos");
|
|
882
1701
|
}
|
|
883
1702
|
}
|
|
884
1703
|
return Math.min(...args);
|
|
885
1704
|
|
|
886
|
-
case
|
|
1705
|
+
case "suma":
|
|
887
1706
|
if (args.length < 1) {
|
|
888
|
-
throw new Error(
|
|
1707
|
+
throw new Error("suma() requiere al menos 1 argumento");
|
|
889
1708
|
}
|
|
890
1709
|
for (const arg of args) {
|
|
891
|
-
if (typeof arg !==
|
|
892
|
-
throw new Error(
|
|
1710
|
+
if (typeof arg !== "number") {
|
|
1711
|
+
throw new Error("suma() requiere argumentos numéricos");
|
|
893
1712
|
}
|
|
894
1713
|
}
|
|
895
1714
|
return args.reduce((sum, arg) => sum + arg, 0);
|
|
896
1715
|
|
|
897
|
-
case
|
|
1716
|
+
case "promedio":
|
|
898
1717
|
if (args.length < 1) {
|
|
899
|
-
throw new Error(
|
|
1718
|
+
throw new Error("promedio() requiere al menos 1 argumento");
|
|
900
1719
|
}
|
|
901
1720
|
for (const arg of args) {
|
|
902
|
-
if (typeof arg !==
|
|
903
|
-
throw new Error(
|
|
1721
|
+
if (typeof arg !== "number") {
|
|
1722
|
+
throw new Error("promedio() requiere argumentos numéricos");
|
|
904
1723
|
}
|
|
905
1724
|
}
|
|
906
1725
|
return args.reduce((sum, arg) => sum + arg, 0) / args.length;
|
|
@@ -910,6 +1729,150 @@ class Evaluator {
|
|
|
910
1729
|
}
|
|
911
1730
|
}
|
|
912
1731
|
|
|
1732
|
+
/**
|
|
1733
|
+
* Checks if a function name is a built-in type conversion function
|
|
1734
|
+
* @param {string} name - Function name
|
|
1735
|
+
* @returns {boolean} True if it's a conversion function
|
|
1736
|
+
*/
|
|
1737
|
+
isConversionFunction(name) {
|
|
1738
|
+
const conversionFunctions = [
|
|
1739
|
+
"entero",
|
|
1740
|
+
"decimal",
|
|
1741
|
+
"texto",
|
|
1742
|
+
"booleano",
|
|
1743
|
+
"tipo",
|
|
1744
|
+
];
|
|
1745
|
+
return conversionFunctions.includes(name);
|
|
1746
|
+
}
|
|
1747
|
+
|
|
1748
|
+
/**
|
|
1749
|
+
* Evaluates a type conversion function
|
|
1750
|
+
* @param {string} name - Function name
|
|
1751
|
+
* @param {Array} args - Function arguments
|
|
1752
|
+
* @returns {any} Converted value
|
|
1753
|
+
*/
|
|
1754
|
+
evaluateConversionFunction(name, args) {
|
|
1755
|
+
if (args.length !== 1) {
|
|
1756
|
+
throw new Error(`${name}() requiere exactamente 1 argumento`);
|
|
1757
|
+
}
|
|
1758
|
+
|
|
1759
|
+
const value = args[0];
|
|
1760
|
+
|
|
1761
|
+
switch (name) {
|
|
1762
|
+
case "entero":
|
|
1763
|
+
// Convert to integer
|
|
1764
|
+
if (typeof value === "number") {
|
|
1765
|
+
return Math.trunc(value);
|
|
1766
|
+
}
|
|
1767
|
+
if (typeof value === "string") {
|
|
1768
|
+
const parsed = parseInt(value, 10);
|
|
1769
|
+
return isNaN(parsed) ? NaN : parsed;
|
|
1770
|
+
}
|
|
1771
|
+
if (typeof value === "boolean") {
|
|
1772
|
+
return value ? 1 : 0;
|
|
1773
|
+
}
|
|
1774
|
+
if (value === null || value === undefined) {
|
|
1775
|
+
return 0;
|
|
1776
|
+
}
|
|
1777
|
+
throw new Error("No se puede convertir a entero");
|
|
1778
|
+
|
|
1779
|
+
case "decimal":
|
|
1780
|
+
// Convert to float
|
|
1781
|
+
if (typeof value === "number") {
|
|
1782
|
+
return value;
|
|
1783
|
+
}
|
|
1784
|
+
if (typeof value === "string") {
|
|
1785
|
+
const parsed = parseFloat(value);
|
|
1786
|
+
return isNaN(parsed) ? NaN : parsed;
|
|
1787
|
+
}
|
|
1788
|
+
if (typeof value === "boolean") {
|
|
1789
|
+
return value ? 1 : 0;
|
|
1790
|
+
}
|
|
1791
|
+
if (value === null || value === undefined) {
|
|
1792
|
+
return 0;
|
|
1793
|
+
}
|
|
1794
|
+
throw new Error("No se puede convertir a decimal");
|
|
1795
|
+
|
|
1796
|
+
case "texto":
|
|
1797
|
+
// Convert to string (Spanish representation)
|
|
1798
|
+
if (value === null) return "nulo";
|
|
1799
|
+
if (value === undefined) return "indefinido";
|
|
1800
|
+
if (typeof value === "boolean") return value ? "verdadero" : "falso";
|
|
1801
|
+
if (typeof value === "string") return value;
|
|
1802
|
+
if (Array.isArray(value)) {
|
|
1803
|
+
return `[${value.map((v) => this.stringifyForTexto(v)).join(", ")}]`;
|
|
1804
|
+
}
|
|
1805
|
+
if (typeof value === "object") {
|
|
1806
|
+
return "[objeto]";
|
|
1807
|
+
}
|
|
1808
|
+
return String(value);
|
|
1809
|
+
|
|
1810
|
+
case "booleano":
|
|
1811
|
+
// Convert to boolean
|
|
1812
|
+
if (value === null || value === undefined) return false;
|
|
1813
|
+
if (typeof value === "boolean") return value;
|
|
1814
|
+
if (typeof value === "number") return value !== 0;
|
|
1815
|
+
if (typeof value === "string") return value.length > 0;
|
|
1816
|
+
if (Array.isArray(value)) return value.length > 0;
|
|
1817
|
+
return true;
|
|
1818
|
+
|
|
1819
|
+
case "tipo":
|
|
1820
|
+
// Return type as Spanish string
|
|
1821
|
+
if (value === null) return "nulo";
|
|
1822
|
+
if (value === undefined) return "indefinido";
|
|
1823
|
+
if (typeof value === "number") return "numero";
|
|
1824
|
+
if (typeof value === "string") return "texto";
|
|
1825
|
+
if (typeof value === "boolean") return "booleano";
|
|
1826
|
+
if (Array.isArray(value)) return "arreglo";
|
|
1827
|
+
if (typeof value === "object" && value.type === "Function")
|
|
1828
|
+
return "funcion";
|
|
1829
|
+
if (typeof value === "object" && value.type === "Instance")
|
|
1830
|
+
return value.className;
|
|
1831
|
+
if (typeof value === "object" && value.type === "Class") return "clase";
|
|
1832
|
+
if (typeof value === "object") return "objeto";
|
|
1833
|
+
return "desconocido";
|
|
1834
|
+
|
|
1835
|
+
default:
|
|
1836
|
+
throw new Error(`Función de conversión desconocida: ${name}`);
|
|
1837
|
+
}
|
|
1838
|
+
}
|
|
1839
|
+
|
|
1840
|
+
/**
|
|
1841
|
+
* Helper to stringify values for texto() function
|
|
1842
|
+
* @param {any} value - Value to stringify
|
|
1843
|
+
* @returns {string} String representation
|
|
1844
|
+
*/
|
|
1845
|
+
stringifyForTexto(value) {
|
|
1846
|
+
if (value === null) return "nulo";
|
|
1847
|
+
if (value === undefined) return "indefinido";
|
|
1848
|
+
if (typeof value === "boolean") return value ? "verdadero" : "falso";
|
|
1849
|
+
if (typeof value === "string") return value;
|
|
1850
|
+
if (Array.isArray(value)) {
|
|
1851
|
+
return `[${value.map((v) => this.stringifyForTexto(v)).join(", ")}]`;
|
|
1852
|
+
}
|
|
1853
|
+
return String(value);
|
|
1854
|
+
}
|
|
1855
|
+
|
|
1856
|
+
/**
|
|
1857
|
+
* Evaluates a template string with interpolation
|
|
1858
|
+
* @param {Object} expression - Template string expression
|
|
1859
|
+
* @returns {string} Interpolated string result
|
|
1860
|
+
*/
|
|
1861
|
+
evaluateTemplateString(expression) {
|
|
1862
|
+
const { parts, expressions } = expression;
|
|
1863
|
+
let result = "";
|
|
1864
|
+
|
|
1865
|
+
for (let i = 0; i < parts.length; i++) {
|
|
1866
|
+
result += parts[i];
|
|
1867
|
+
if (i < expressions.length) {
|
|
1868
|
+
const value = this.evaluateExpression(expressions[i]);
|
|
1869
|
+
result += this.stringifySpanish(value);
|
|
1870
|
+
}
|
|
1871
|
+
}
|
|
1872
|
+
|
|
1873
|
+
return result;
|
|
1874
|
+
}
|
|
1875
|
+
|
|
913
1876
|
/**
|
|
914
1877
|
* Evaluates an array literal
|
|
915
1878
|
* @param {Object} expression - Array literal expression
|
|
@@ -933,15 +1896,15 @@ class Evaluator {
|
|
|
933
1896
|
const index = this.evaluateExpression(expression.index);
|
|
934
1897
|
|
|
935
1898
|
if (!Array.isArray(array)) {
|
|
936
|
-
throw new Error(
|
|
1899
|
+
throw new Error("Solo se pueden acceder elementos de arreglos");
|
|
937
1900
|
}
|
|
938
1901
|
|
|
939
|
-
if (typeof index !==
|
|
940
|
-
throw new Error(
|
|
1902
|
+
if (typeof index !== "number") {
|
|
1903
|
+
throw new Error("El índice del arreglo debe ser un número");
|
|
941
1904
|
}
|
|
942
1905
|
|
|
943
1906
|
if (index < 0 || index >= array.length) {
|
|
944
|
-
throw new Error(
|
|
1907
|
+
throw new Error("Índice del arreglo fuera de rango");
|
|
945
1908
|
}
|
|
946
1909
|
|
|
947
1910
|
return array[index];
|
|
@@ -958,15 +1921,15 @@ class Evaluator {
|
|
|
958
1921
|
const value = this.evaluateExpression(expression.value);
|
|
959
1922
|
|
|
960
1923
|
if (!Array.isArray(array)) {
|
|
961
|
-
throw new Error(
|
|
1924
|
+
throw new Error("Solo se pueden asignar elementos de arreglos");
|
|
962
1925
|
}
|
|
963
1926
|
|
|
964
|
-
if (typeof index !==
|
|
965
|
-
throw new Error(
|
|
1927
|
+
if (typeof index !== "number") {
|
|
1928
|
+
throw new Error("El índice del arreglo debe ser un número");
|
|
966
1929
|
}
|
|
967
1930
|
|
|
968
1931
|
if (index < 0 || index >= array.length) {
|
|
969
|
-
throw new Error(
|
|
1932
|
+
throw new Error("Índice del arreglo fuera de rango");
|
|
970
1933
|
}
|
|
971
1934
|
|
|
972
1935
|
array[index] = value;
|
|
@@ -997,8 +1960,43 @@ class Evaluator {
|
|
|
997
1960
|
evaluatePropertyAccess(expression) {
|
|
998
1961
|
const object = this.evaluateExpression(expression.object);
|
|
999
1962
|
|
|
1000
|
-
if (typeof object !==
|
|
1001
|
-
throw new Error(
|
|
1963
|
+
if (typeof object !== "object" || object === null) {
|
|
1964
|
+
throw new Error("Solo se pueden acceder propiedades de objetos");
|
|
1965
|
+
}
|
|
1966
|
+
|
|
1967
|
+
// Handle Instance objects
|
|
1968
|
+
if (object.type === "Instance") {
|
|
1969
|
+
// Check if it's a property
|
|
1970
|
+
if (expression.name in object.properties) {
|
|
1971
|
+
return object.properties[expression.name];
|
|
1972
|
+
}
|
|
1973
|
+
|
|
1974
|
+
// Check if it's a method (will be called later via MethodCall)
|
|
1975
|
+
const classObj = object.classObj;
|
|
1976
|
+
for (const method of classObj.methods) {
|
|
1977
|
+
if (method.name === expression.name) {
|
|
1978
|
+
return {
|
|
1979
|
+
type: "BoundMethod",
|
|
1980
|
+
method: method,
|
|
1981
|
+
instance: object,
|
|
1982
|
+
};
|
|
1983
|
+
}
|
|
1984
|
+
}
|
|
1985
|
+
|
|
1986
|
+
// Check parent class methods
|
|
1987
|
+
if (object.parentClass) {
|
|
1988
|
+
for (const method of object.parentClass.methods) {
|
|
1989
|
+
if (method.name === expression.name) {
|
|
1990
|
+
return {
|
|
1991
|
+
type: "BoundMethod",
|
|
1992
|
+
method: method,
|
|
1993
|
+
instance: object,
|
|
1994
|
+
};
|
|
1995
|
+
}
|
|
1996
|
+
}
|
|
1997
|
+
}
|
|
1998
|
+
|
|
1999
|
+
return undefined;
|
|
1002
2000
|
}
|
|
1003
2001
|
|
|
1004
2002
|
return object[expression.name];
|
|
@@ -1013,8 +2011,14 @@ class Evaluator {
|
|
|
1013
2011
|
const object = this.evaluateExpression(expression.object);
|
|
1014
2012
|
const value = this.evaluateExpression(expression.value);
|
|
1015
2013
|
|
|
1016
|
-
if (typeof object !==
|
|
1017
|
-
throw new Error(
|
|
2014
|
+
if (typeof object !== "object" || object === null) {
|
|
2015
|
+
throw new Error("Solo se pueden asignar propiedades de objetos");
|
|
2016
|
+
}
|
|
2017
|
+
|
|
2018
|
+
// Handle Instance objects
|
|
2019
|
+
if (object.type === "Instance") {
|
|
2020
|
+
object.properties[expression.name] = value;
|
|
2021
|
+
return value;
|
|
1018
2022
|
}
|
|
1019
2023
|
|
|
1020
2024
|
object[expression.name] = value;
|
|
@@ -1029,7 +2033,7 @@ class Evaluator {
|
|
|
1029
2033
|
evaluateLogicalExpression(expression) {
|
|
1030
2034
|
const left = this.evaluateExpression(expression.left);
|
|
1031
2035
|
|
|
1032
|
-
if (expression.operator ===
|
|
2036
|
+
if (expression.operator === "OR") {
|
|
1033
2037
|
// Short-circuit evaluation for OR
|
|
1034
2038
|
if (this.isTruthy(left)) {
|
|
1035
2039
|
return left;
|
|
@@ -1037,7 +2041,7 @@ class Evaluator {
|
|
|
1037
2041
|
return this.evaluateExpression(expression.right);
|
|
1038
2042
|
}
|
|
1039
2043
|
|
|
1040
|
-
if (expression.operator ===
|
|
2044
|
+
if (expression.operator === "AND") {
|
|
1041
2045
|
// Short-circuit evaluation for AND
|
|
1042
2046
|
if (!this.isTruthy(left)) {
|
|
1043
2047
|
return left;
|
|
@@ -1059,7 +2063,7 @@ class Evaluator {
|
|
|
1059
2063
|
const newValue = this.performCompoundOperation(
|
|
1060
2064
|
currentValue,
|
|
1061
2065
|
expression.operator,
|
|
1062
|
-
rightValue
|
|
2066
|
+
rightValue,
|
|
1063
2067
|
);
|
|
1064
2068
|
|
|
1065
2069
|
this.environment.assign(expression.name, newValue);
|
|
@@ -1077,20 +2081,20 @@ class Evaluator {
|
|
|
1077
2081
|
const rightValue = this.evaluateExpression(expression.value);
|
|
1078
2082
|
|
|
1079
2083
|
if (!Array.isArray(array)) {
|
|
1080
|
-
throw new Error(
|
|
2084
|
+
throw new Error("Solo se pueden asignar elementos de arreglos");
|
|
1081
2085
|
}
|
|
1082
|
-
if (typeof index !==
|
|
1083
|
-
throw new Error(
|
|
2086
|
+
if (typeof index !== "number") {
|
|
2087
|
+
throw new Error("El índice del arreglo debe ser un número");
|
|
1084
2088
|
}
|
|
1085
2089
|
if (index < 0 || index >= array.length) {
|
|
1086
|
-
throw new Error(
|
|
2090
|
+
throw new Error("Índice del arreglo fuera de rango");
|
|
1087
2091
|
}
|
|
1088
2092
|
|
|
1089
2093
|
const currentValue = array[index];
|
|
1090
2094
|
const newValue = this.performCompoundOperation(
|
|
1091
2095
|
currentValue,
|
|
1092
2096
|
expression.operator,
|
|
1093
|
-
rightValue
|
|
2097
|
+
rightValue,
|
|
1094
2098
|
);
|
|
1095
2099
|
|
|
1096
2100
|
array[index] = newValue;
|
|
@@ -1106,15 +2110,15 @@ class Evaluator {
|
|
|
1106
2110
|
const object = this.evaluateExpression(expression.object);
|
|
1107
2111
|
const rightValue = this.evaluateExpression(expression.value);
|
|
1108
2112
|
|
|
1109
|
-
if (typeof object !==
|
|
1110
|
-
throw new Error(
|
|
2113
|
+
if (typeof object !== "object" || object === null) {
|
|
2114
|
+
throw new Error("Solo se pueden asignar propiedades de objetos");
|
|
1111
2115
|
}
|
|
1112
2116
|
|
|
1113
2117
|
const currentValue = object[expression.name];
|
|
1114
2118
|
const newValue = this.performCompoundOperation(
|
|
1115
2119
|
currentValue,
|
|
1116
2120
|
expression.operator,
|
|
1117
|
-
rightValue
|
|
2121
|
+
rightValue,
|
|
1118
2122
|
);
|
|
1119
2123
|
|
|
1120
2124
|
object[expression.name] = newValue;
|
|
@@ -1130,42 +2134,42 @@ class Evaluator {
|
|
|
1130
2134
|
*/
|
|
1131
2135
|
performCompoundOperation(left, operator, right) {
|
|
1132
2136
|
switch (operator) {
|
|
1133
|
-
case
|
|
1134
|
-
if (typeof left ===
|
|
2137
|
+
case "PLUS_EQUAL":
|
|
2138
|
+
if (typeof left === "number" && typeof right === "number") {
|
|
1135
2139
|
return left + right;
|
|
1136
2140
|
}
|
|
1137
|
-
if (typeof left ===
|
|
2141
|
+
if (typeof left === "string" || typeof right === "string") {
|
|
1138
2142
|
return String(left) + String(right);
|
|
1139
2143
|
}
|
|
1140
|
-
throw new Error(
|
|
2144
|
+
throw new Error("No se pueden sumar valores no numéricos");
|
|
1141
2145
|
|
|
1142
|
-
case
|
|
1143
|
-
if (typeof left !==
|
|
1144
|
-
throw new Error(
|
|
2146
|
+
case "MINUS_EQUAL":
|
|
2147
|
+
if (typeof left !== "number" || typeof right !== "number") {
|
|
2148
|
+
throw new Error("Solo se pueden restar números");
|
|
1145
2149
|
}
|
|
1146
2150
|
return left - right;
|
|
1147
2151
|
|
|
1148
|
-
case
|
|
1149
|
-
if (typeof left !==
|
|
1150
|
-
throw new Error(
|
|
2152
|
+
case "STAR_EQUAL":
|
|
2153
|
+
if (typeof left !== "number" || typeof right !== "number") {
|
|
2154
|
+
throw new Error("Solo se pueden multiplicar números");
|
|
1151
2155
|
}
|
|
1152
2156
|
return left * right;
|
|
1153
2157
|
|
|
1154
|
-
case
|
|
1155
|
-
if (typeof left !==
|
|
1156
|
-
throw new Error(
|
|
2158
|
+
case "SLASH_EQUAL":
|
|
2159
|
+
if (typeof left !== "number" || typeof right !== "number") {
|
|
2160
|
+
throw new Error("Solo se pueden dividir números");
|
|
1157
2161
|
}
|
|
1158
2162
|
if (right === 0) {
|
|
1159
|
-
throw new Error(
|
|
2163
|
+
throw new Error("División por cero");
|
|
1160
2164
|
}
|
|
1161
2165
|
return left / right;
|
|
1162
2166
|
|
|
1163
|
-
case
|
|
1164
|
-
if (typeof left !==
|
|
1165
|
-
throw new Error(
|
|
2167
|
+
case "PERCENT_EQUAL":
|
|
2168
|
+
if (typeof left !== "number" || typeof right !== "number") {
|
|
2169
|
+
throw new Error("Solo se puede hacer módulo con números");
|
|
1166
2170
|
}
|
|
1167
2171
|
if (right === 0) {
|
|
1168
|
-
throw new Error(
|
|
2172
|
+
throw new Error("Módulo por cero");
|
|
1169
2173
|
}
|
|
1170
2174
|
return left % right;
|
|
1171
2175
|
|
|
@@ -1182,71 +2186,71 @@ class Evaluator {
|
|
|
1182
2186
|
evaluatePostfixExpression(expression) {
|
|
1183
2187
|
const operand = this.evaluateExpression(expression.operand);
|
|
1184
2188
|
|
|
1185
|
-
if (expression.operator ===
|
|
1186
|
-
if (typeof operand !==
|
|
1187
|
-
throw new Error(
|
|
2189
|
+
if (expression.operator === "PLUS_PLUS") {
|
|
2190
|
+
if (typeof operand !== "number") {
|
|
2191
|
+
throw new Error("Solo se pueden incrementar números");
|
|
1188
2192
|
}
|
|
1189
2193
|
const newValue = operand + 1;
|
|
1190
2194
|
|
|
1191
2195
|
// Update the variable if it's a variable reference
|
|
1192
|
-
if (expression.operand.type ===
|
|
2196
|
+
if (expression.operand.type === "Variable") {
|
|
1193
2197
|
this.environment.assign(expression.operand.name, newValue);
|
|
1194
|
-
} else if (expression.operand.type ===
|
|
2198
|
+
} else if (expression.operand.type === "PropertyAccess") {
|
|
1195
2199
|
const object = this.evaluateExpression(expression.operand.object);
|
|
1196
|
-
if (typeof object !==
|
|
1197
|
-
throw new Error(
|
|
2200
|
+
if (typeof object !== "object" || object === null) {
|
|
2201
|
+
throw new Error("Solo se pueden incrementar propiedades de objetos");
|
|
1198
2202
|
}
|
|
1199
2203
|
object[expression.operand.name] = newValue;
|
|
1200
|
-
} else if (expression.operand.type ===
|
|
2204
|
+
} else if (expression.operand.type === "ArrayAccess") {
|
|
1201
2205
|
const array = this.evaluateExpression(expression.operand.array);
|
|
1202
2206
|
const index = this.evaluateExpression(expression.operand.index);
|
|
1203
2207
|
if (!Array.isArray(array)) {
|
|
1204
|
-
throw new Error(
|
|
2208
|
+
throw new Error("Solo se pueden incrementar elementos de arreglos");
|
|
1205
2209
|
}
|
|
1206
|
-
if (typeof index !==
|
|
1207
|
-
throw new Error(
|
|
2210
|
+
if (typeof index !== "number") {
|
|
2211
|
+
throw new Error("El índice del arreglo debe ser un número");
|
|
1208
2212
|
}
|
|
1209
2213
|
if (index < 0 || index >= array.length) {
|
|
1210
|
-
throw new Error(
|
|
2214
|
+
throw new Error("Índice del arreglo fuera de rango");
|
|
1211
2215
|
}
|
|
1212
2216
|
array[index] = newValue;
|
|
1213
2217
|
} else {
|
|
1214
|
-
throw new Error(
|
|
2218
|
+
throw new Error("Objetivo de incremento inválido");
|
|
1215
2219
|
}
|
|
1216
2220
|
|
|
1217
2221
|
return operand; // Return the original value (postfix behavior)
|
|
1218
2222
|
}
|
|
1219
2223
|
|
|
1220
|
-
if (expression.operator ===
|
|
1221
|
-
if (typeof operand !==
|
|
1222
|
-
throw new Error(
|
|
2224
|
+
if (expression.operator === "MINUS_MINUS") {
|
|
2225
|
+
if (typeof operand !== "number") {
|
|
2226
|
+
throw new Error("Solo se pueden decrementar números");
|
|
1223
2227
|
}
|
|
1224
2228
|
const newValue = operand - 1;
|
|
1225
2229
|
|
|
1226
2230
|
// Update the variable if it's a variable reference
|
|
1227
|
-
if (expression.operand.type ===
|
|
2231
|
+
if (expression.operand.type === "Variable") {
|
|
1228
2232
|
this.environment.assign(expression.operand.name, newValue);
|
|
1229
|
-
} else if (expression.operand.type ===
|
|
2233
|
+
} else if (expression.operand.type === "PropertyAccess") {
|
|
1230
2234
|
const object = this.evaluateExpression(expression.operand.object);
|
|
1231
|
-
if (typeof object !==
|
|
1232
|
-
throw new Error(
|
|
2235
|
+
if (typeof object !== "object" || object === null) {
|
|
2236
|
+
throw new Error("Solo se pueden decrementar propiedades de objetos");
|
|
1233
2237
|
}
|
|
1234
2238
|
object[expression.operand.name] = newValue;
|
|
1235
|
-
} else if (expression.operand.type ===
|
|
2239
|
+
} else if (expression.operand.type === "ArrayAccess") {
|
|
1236
2240
|
const array = this.evaluateExpression(expression.operand.array);
|
|
1237
2241
|
const index = this.evaluateExpression(expression.operand.index);
|
|
1238
2242
|
if (!Array.isArray(array)) {
|
|
1239
|
-
throw new Error(
|
|
2243
|
+
throw new Error("Solo se pueden decrementar elementos de arreglos");
|
|
1240
2244
|
}
|
|
1241
|
-
if (typeof index !==
|
|
1242
|
-
throw new Error(
|
|
2245
|
+
if (typeof index !== "number") {
|
|
2246
|
+
throw new Error("El índice del arreglo debe ser un número");
|
|
1243
2247
|
}
|
|
1244
2248
|
if (index < 0 || index >= array.length) {
|
|
1245
|
-
throw new Error(
|
|
2249
|
+
throw new Error("Índice del arreglo fuera de rango");
|
|
1246
2250
|
}
|
|
1247
2251
|
array[index] = newValue;
|
|
1248
2252
|
} else {
|
|
1249
|
-
throw new Error(
|
|
2253
|
+
throw new Error("Objetivo de decremento inválido");
|
|
1250
2254
|
}
|
|
1251
2255
|
|
|
1252
2256
|
return operand; // Return the original value (postfix behavior)
|
|
@@ -1261,11 +2265,28 @@ class Evaluator {
|
|
|
1261
2265
|
* @returns {string} String representation
|
|
1262
2266
|
*/
|
|
1263
2267
|
stringify(value) {
|
|
1264
|
-
if (value === null) return
|
|
1265
|
-
if (value === undefined) return
|
|
1266
|
-
if (typeof value ===
|
|
2268
|
+
if (value === null) return "null";
|
|
2269
|
+
if (value === undefined) return "undefined";
|
|
2270
|
+
if (typeof value === "string") return value;
|
|
1267
2271
|
if (Array.isArray(value)) {
|
|
1268
|
-
return
|
|
2272
|
+
return `[${value.map((v) => this.stringify(v)).join(", ")}]`;
|
|
2273
|
+
}
|
|
2274
|
+
return value.toString();
|
|
2275
|
+
}
|
|
2276
|
+
|
|
2277
|
+
/**
|
|
2278
|
+
* Converts a value to its Spanish string representation for template strings
|
|
2279
|
+
* @param {any} value - Value to convert
|
|
2280
|
+
* @returns {string} Spanish string representation
|
|
2281
|
+
*/
|
|
2282
|
+
stringifySpanish(value) {
|
|
2283
|
+
if (value === null) return "nulo";
|
|
2284
|
+
if (value === undefined) return "indefinido";
|
|
2285
|
+
if (value === true) return "verdadero";
|
|
2286
|
+
if (value === false) return "falso";
|
|
2287
|
+
if (typeof value === "string") return value;
|
|
2288
|
+
if (Array.isArray(value)) {
|
|
2289
|
+
return `[${value.map((v) => this.stringifySpanish(v)).join(", ")}]`;
|
|
1269
2290
|
}
|
|
1270
2291
|
return value.toString();
|
|
1271
2292
|
}
|
|
@@ -1294,6 +2315,91 @@ class Evaluator {
|
|
|
1294
2315
|
this.executeBlock(statement.catchBlock);
|
|
1295
2316
|
}
|
|
1296
2317
|
}
|
|
2318
|
+
|
|
2319
|
+
/**
|
|
2320
|
+
* Executes an elegir (switch) statement
|
|
2321
|
+
* @param {Object} statement - Elegir statement to execute
|
|
2322
|
+
*/
|
|
2323
|
+
executeElegirStatement(statement) {
|
|
2324
|
+
const discriminantValue = this.evaluateExpression(statement.discriminant);
|
|
2325
|
+
|
|
2326
|
+
// Try to find a matching case
|
|
2327
|
+
for (const caseClause of statement.cases) {
|
|
2328
|
+
const testValue = this.evaluateExpression(caseClause.test);
|
|
2329
|
+
if (discriminantValue === testValue) {
|
|
2330
|
+
// Execute the matching case
|
|
2331
|
+
for (const stmt of caseClause.consequent) {
|
|
2332
|
+
this.execute(stmt);
|
|
2333
|
+
}
|
|
2334
|
+
return; // Exit after executing the matching case (no fall-through)
|
|
2335
|
+
}
|
|
2336
|
+
}
|
|
2337
|
+
|
|
2338
|
+
// If no case matched, execute default if present
|
|
2339
|
+
if (statement.defaultCase) {
|
|
2340
|
+
for (const stmt of statement.defaultCase.consequent) {
|
|
2341
|
+
this.execute(stmt);
|
|
2342
|
+
}
|
|
2343
|
+
}
|
|
2344
|
+
}
|
|
2345
|
+
|
|
2346
|
+
/**
|
|
2347
|
+
* Executes a hacer/mientras (do-while) statement
|
|
2348
|
+
* @param {Object} statement - HacerMientras statement to execute
|
|
2349
|
+
*/
|
|
2350
|
+
executeHacerMientrasStatement(statement) {
|
|
2351
|
+
do {
|
|
2352
|
+
try {
|
|
2353
|
+
this.executeBlock(statement.body);
|
|
2354
|
+
} catch (error) {
|
|
2355
|
+
if (error instanceof BreakException) {
|
|
2356
|
+
break;
|
|
2357
|
+
}
|
|
2358
|
+
if (error instanceof ContinueException) {
|
|
2359
|
+
continue;
|
|
2360
|
+
}
|
|
2361
|
+
throw error;
|
|
2362
|
+
}
|
|
2363
|
+
} while (this.isTruthy(this.evaluateExpression(statement.condition)));
|
|
2364
|
+
}
|
|
2365
|
+
|
|
2366
|
+
/**
|
|
2367
|
+
* Executes a para cada (for-each) statement
|
|
2368
|
+
* @param {Object} statement - ForEach statement to execute
|
|
2369
|
+
*/
|
|
2370
|
+
executeForEachStatement(statement) {
|
|
2371
|
+
const iterable = this.evaluateExpression(statement.iterable);
|
|
2372
|
+
|
|
2373
|
+
if (!Array.isArray(iterable)) {
|
|
2374
|
+
throw new Error("para cada solo puede iterar sobre arreglos");
|
|
2375
|
+
}
|
|
2376
|
+
|
|
2377
|
+
for (const element of iterable) {
|
|
2378
|
+
// Create a new environment for each iteration
|
|
2379
|
+
const loopEnv = new Environment(this.environment);
|
|
2380
|
+
loopEnv.define(statement.iterator, element);
|
|
2381
|
+
|
|
2382
|
+
const previousEnv = this.environment;
|
|
2383
|
+
this.environment = loopEnv;
|
|
2384
|
+
|
|
2385
|
+
try {
|
|
2386
|
+
this.executeBlock(statement.body);
|
|
2387
|
+
} catch (error) {
|
|
2388
|
+
if (error instanceof BreakException) {
|
|
2389
|
+
this.environment = previousEnv;
|
|
2390
|
+
break;
|
|
2391
|
+
}
|
|
2392
|
+
if (error instanceof ContinueException) {
|
|
2393
|
+
this.environment = previousEnv;
|
|
2394
|
+
continue;
|
|
2395
|
+
}
|
|
2396
|
+
this.environment = previousEnv;
|
|
2397
|
+
throw error;
|
|
2398
|
+
}
|
|
2399
|
+
|
|
2400
|
+
this.environment = previousEnv;
|
|
2401
|
+
}
|
|
2402
|
+
}
|
|
1297
2403
|
}
|
|
1298
2404
|
|
|
1299
2405
|
/**
|
|
@@ -1302,6 +2408,7 @@ class Evaluator {
|
|
|
1302
2408
|
class Environment {
|
|
1303
2409
|
constructor(enclosing = null) {
|
|
1304
2410
|
this.values = {};
|
|
2411
|
+
this.constants = new Set();
|
|
1305
2412
|
this.enclosing = enclosing;
|
|
1306
2413
|
}
|
|
1307
2414
|
|
|
@@ -1314,12 +2421,41 @@ class Environment {
|
|
|
1314
2421
|
this.values[name] = value;
|
|
1315
2422
|
}
|
|
1316
2423
|
|
|
2424
|
+
/**
|
|
2425
|
+
* Defines a constant in the environment
|
|
2426
|
+
* @param {string} name - Constant name
|
|
2427
|
+
* @param {any} value - Constant value
|
|
2428
|
+
*/
|
|
2429
|
+
defineConstant(name, value) {
|
|
2430
|
+
this.values[name] = value;
|
|
2431
|
+
this.constants.add(name);
|
|
2432
|
+
}
|
|
2433
|
+
|
|
2434
|
+
/**
|
|
2435
|
+
* Checks if a name is a constant
|
|
2436
|
+
* @param {string} name - Name to check
|
|
2437
|
+
* @returns {boolean} True if it's a constant
|
|
2438
|
+
*/
|
|
2439
|
+
isConstant(name) {
|
|
2440
|
+
if (this.constants.has(name)) {
|
|
2441
|
+
return true;
|
|
2442
|
+
}
|
|
2443
|
+
if (this.enclosing !== null) {
|
|
2444
|
+
return this.enclosing.isConstant(name);
|
|
2445
|
+
}
|
|
2446
|
+
return false;
|
|
2447
|
+
}
|
|
2448
|
+
|
|
1317
2449
|
/**
|
|
1318
2450
|
* Assigns a value to an existing variable
|
|
1319
2451
|
* @param {string} name - Variable name
|
|
1320
2452
|
* @param {any} value - Value to assign
|
|
1321
2453
|
*/
|
|
1322
2454
|
assign(name, value) {
|
|
2455
|
+
if (this.isConstant(name)) {
|
|
2456
|
+
throw new Error(`No se puede reasignar la constante: ${name}`);
|
|
2457
|
+
}
|
|
2458
|
+
|
|
1323
2459
|
if (name in this.values) {
|
|
1324
2460
|
this.values[name] = value;
|
|
1325
2461
|
return;
|
|
@@ -1365,7 +2501,7 @@ class ReturnException {
|
|
|
1365
2501
|
*/
|
|
1366
2502
|
class BreakException {
|
|
1367
2503
|
constructor() {
|
|
1368
|
-
this.type =
|
|
2504
|
+
this.type = "break";
|
|
1369
2505
|
}
|
|
1370
2506
|
}
|
|
1371
2507
|
|
|
@@ -1374,7 +2510,7 @@ class BreakException {
|
|
|
1374
2510
|
*/
|
|
1375
2511
|
class ContinueException {
|
|
1376
2512
|
constructor() {
|
|
1377
|
-
this.type =
|
|
2513
|
+
this.type = "continue";
|
|
1378
2514
|
}
|
|
1379
2515
|
}
|
|
1380
2516
|
|
|
@@ -1383,7 +2519,7 @@ class ContinueException {
|
|
|
1383
2519
|
*/
|
|
1384
2520
|
class TryCatchException {
|
|
1385
2521
|
constructor(message) {
|
|
1386
|
-
this.type =
|
|
2522
|
+
this.type = "try-catch";
|
|
1387
2523
|
this.message = message;
|
|
1388
2524
|
}
|
|
1389
2525
|
}
|