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