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/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 'VariableDeclaration':
38
+ case "VariableDeclaration":
38
39
  return this.executeVariableDeclaration(statement);
39
- case 'FunctionDeclaration':
40
+ case "ConstantDeclaration":
41
+ return this.executeConstantDeclaration(statement);
42
+ case "FunctionDeclaration":
40
43
  return this.executeFunctionDeclaration(statement);
41
- case 'MostrarStatement':
44
+ case "ClassDeclaration":
45
+ return this.executeClassDeclaration(statement);
46
+ case "MostrarStatement":
42
47
  return this.executeMostrarStatement(statement);
43
- case 'LeerStatement':
48
+ case "LeerStatement":
44
49
  return this.executeLeerStatement(statement);
45
- case 'IfStatement':
50
+ case "IfStatement":
46
51
  return this.executeIfStatement(statement);
47
- case 'WhileStatement':
52
+ case "WhileStatement":
48
53
  return this.executeWhileStatement(statement);
49
- case 'ForStatement':
54
+ case "ForStatement":
50
55
  return this.executeForStatement(statement);
51
- case 'ReturnStatement':
56
+ case "ReturnStatement":
52
57
  return this.executeReturnStatement(statement);
53
- case 'BreakStatement':
58
+ case "BreakStatement":
54
59
  return this.executeBreakStatement(statement);
55
- case 'ContinueStatement':
60
+ case "ContinueStatement":
56
61
  return this.executeContinueStatement(statement);
57
- case 'TryCatch':
62
+ case "TryCatch":
58
63
  return this.executeTryCatch(statement);
59
- case 'ExpressionStatement':
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 'Block':
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: 'Function',
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('readline-sync');
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('fs');
126
- input = fs.readFileSync(0, 'utf8').trim();
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('readline');
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('(Entrada no disponible en este entorno)');
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 'Literal':
346
+ case "Literal":
290
347
  return expression.value;
291
348
 
292
- case 'Variable':
349
+ case "TemplateString":
350
+ return this.evaluateTemplateString(expression);
351
+
352
+ case "Variable":
293
353
  return this.environment.get(expression.name);
294
354
 
295
- case 'AnonymousFunction':
355
+ case "AnonymousFunction":
296
356
  return {
297
- type: 'Function',
357
+ type: "Function",
298
358
  name: null,
299
359
  parameters: expression.parameters,
300
360
  body: expression.body,
301
361
  };
302
362
 
303
- case 'Assign':
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 'ArrayLiteral':
377
+ case "ArrayLiteral":
309
378
  return this.evaluateArrayLiteral(expression);
310
379
 
311
- case 'ArrayAccess':
380
+ case "ArrayAccess":
312
381
  return this.evaluateArrayAccess(expression);
313
382
 
314
- case 'ArrayAssign':
383
+ case "ArrayAssign":
315
384
  return this.evaluateArrayAssign(expression);
316
385
 
317
- case 'ObjectLiteral':
386
+ case "ObjectLiteral":
318
387
  return this.evaluateObjectLiteral(expression);
319
388
 
320
- case 'PropertyAccess':
389
+ case "PropertyAccess":
321
390
  return this.evaluatePropertyAccess(expression);
322
391
 
323
- case 'PropertyAssign':
392
+ case "PropertyAssign":
324
393
  return this.evaluatePropertyAssign(expression);
325
394
 
326
- case 'CompoundAssign':
395
+ case "CompoundAssign":
327
396
  return this.evaluateCompoundAssign(expression);
328
397
 
329
- case 'CompoundArrayAssign':
398
+ case "CompoundArrayAssign":
330
399
  return this.evaluateCompoundArrayAssign(expression);
331
400
 
332
- case 'CompoundPropertyAssign':
401
+ case "CompoundPropertyAssign":
333
402
  return this.evaluateCompoundPropertyAssign(expression);
334
403
 
335
- case 'Logical':
404
+ case "Logical":
336
405
  return this.evaluateLogicalExpression(expression);
337
406
 
338
- case 'Postfix':
407
+ case "Postfix":
339
408
  return this.evaluatePostfixExpression(expression);
340
409
 
341
- case 'Call':
410
+ case "Call":
342
411
  return this.evaluateCallExpression(expression);
343
412
 
344
- case 'MethodCall':
413
+ case "MethodCall":
345
414
  return this.evaluateMethodCall(expression);
346
415
 
347
- case 'Unary':
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 'Binary':
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 'MINUS':
465
+ case "MINUS":
374
466
  this.checkNumberOperand(operator, right);
375
467
  return -right;
376
468
 
377
- case 'BANG':
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 'MINUS':
486
+ case "MINUS":
395
487
  this.checkNumberOperands(operator, left, right);
396
488
  return left - right;
397
489
 
398
- case 'PLUS':
399
- if (typeof left === 'number' && typeof right === 'number') {
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 'SLASH':
499
+ case "SLASH":
408
500
  this.checkNumberOperands(operator, left, right);
409
501
  if (right === 0) {
410
- throw new Error('División por cero');
502
+ throw new Error("División por cero");
411
503
  }
412
504
  return left / right;
413
505
 
414
- case 'STAR':
506
+ case "STAR":
415
507
  this.checkNumberOperands(operator, left, right);
416
508
  return left * right;
417
509
 
418
- case 'PERCENT':
510
+ case "PERCENT":
419
511
  this.checkNumberOperands(operator, left, right);
420
512
  if (right === 0) {
421
- throw new Error('Módulo por cero');
513
+ throw new Error("Módulo por cero");
422
514
  }
423
515
  return left % right;
424
516
 
425
- case 'GREATER':
426
- if (typeof left === 'string' && typeof right === 'string') {
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 'GREATER_EQUAL':
433
- if (typeof left === 'string' && typeof right === 'string') {
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 'LESS':
440
- if (typeof left === 'string' && typeof right === 'string') {
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 'LESS_EQUAL':
447
- if (typeof left === 'string' && typeof right === 'string') {
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 'EQUAL_EQUAL':
545
+ case "EQUAL_EQUAL":
454
546
  return this.isEqual(left, right);
455
547
 
456
- case 'BANG_EQUAL':
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 === 'boolean') return value;
472
- if (typeof value === 'number') return value !== 0;
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 === 'number') return;
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 === 'number' && typeof right === 'number') return;
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 === 'Variable' &&
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 !== 'Function') {
533
- throw new Error('Solo se pueden llamar funciones');
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 string)
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 or string method based on the object type
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 === 'string') {
686
+ } else if (typeof object === "string") {
579
687
  // Check if the method is valid for strings
580
688
  if (
581
- expression.method === 'agregar' ||
582
- expression.method === 'remover' ||
583
- expression.method === 'contiene' ||
584
- expression.method === 'recorrer'
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(object, expression.method);
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 o cadenas, se recibió ${typeof object}`
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 'longitud':
805
+ case "longitud":
608
806
  return array.length;
609
807
 
610
- case 'primero':
808
+ case "primero":
611
809
  if (array.length === 0) {
612
- throw new Error('No se puede obtener el primer elemento de un arreglo vacío');
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 'ultimo':
816
+ case "ultimo":
617
817
  if (array.length === 0) {
618
- throw new Error('No se puede obtener el último elemento de un arreglo vacío');
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 'agregar':
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 'remover':
832
+ case "remover":
631
833
  // Remove and return the last element
632
834
  if (array.length === 0) {
633
- throw new Error('No se puede eliminar un elemento de un arreglo vacío');
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 'contiene':
841
+ case "contiene":
638
842
  // Check if array contains the specified element
639
843
  if (args.length !== 1) {
640
- throw new Error('El método contiene() requiere exactamente un argumento');
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 'recorrer':
851
+ case "recorrer":
646
852
  // Iterate through array and call function for each element
647
853
  if (args.length !== 1) {
648
- throw new Error('El método recorrer() requiere exactamente un argumento');
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 !== 'Function') {
652
- throw new Error('El método recorrer() requiere una función como argumento');
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('elemento', array[i]);
664
- callbackEnv.define('indice', i);
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('indice', i);
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
- 'La función en recorrer() puede tener máximo 2 parámetros'
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.executeBlock(callback.body);
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 'longitud':
1180
+ case "longitud":
704
1181
  return string.length;
705
1182
 
706
- case 'mayusculas':
1183
+ case "mayusculas":
707
1184
  return string.toUpperCase();
708
1185
 
709
- case 'minusculas':
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
- 'raiz',
725
- 'potencia',
726
- 'seno',
727
- 'coseno',
728
- 'tangente',
729
- 'logaritmo',
730
- 'valorAbsoluto',
731
- 'redondear',
732
- 'techo',
733
- 'piso',
734
- 'aleatorio',
735
- 'maximo',
736
- 'minimo',
737
- 'suma',
738
- 'promedio',
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 'raiz':
1559
+ case "raiz":
752
1560
  if (args.length !== 1) {
753
- throw new Error('raiz() requiere exactamente 1 argumento');
1561
+ throw new Error("raiz() requiere exactamente 1 argumento");
754
1562
  }
755
- if (typeof args[0] !== 'number') {
756
- throw new Error('raiz() requiere un argumento numérico');
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('No se puede calcular la raíz cuadrada de un número negativo');
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 'potencia':
1573
+ case "potencia":
764
1574
  if (args.length !== 2) {
765
- throw new Error('potencia() requiere exactamente 2 argumentos');
1575
+ throw new Error("potencia() requiere exactamente 2 argumentos");
766
1576
  }
767
- if (typeof args[0] !== 'number' || typeof args[1] !== 'number') {
768
- throw new Error('potencia() requiere argumentos numéricos');
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 'seno':
1582
+ case "seno":
773
1583
  if (args.length !== 1) {
774
- throw new Error('seno() requiere exactamente 1 argumento');
1584
+ throw new Error("seno() requiere exactamente 1 argumento");
775
1585
  }
776
- if (typeof args[0] !== 'number') {
777
- throw new Error('seno() requiere un argumento numérico');
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 'coseno':
1591
+ case "coseno":
782
1592
  if (args.length !== 1) {
783
- throw new Error('coseno() requiere exactamente 1 argumento');
1593
+ throw new Error("coseno() requiere exactamente 1 argumento");
784
1594
  }
785
- if (typeof args[0] !== 'number') {
786
- throw new Error('coseno() requiere un argumento numérico');
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 'tangente':
1600
+ case "tangente":
791
1601
  if (args.length !== 1) {
792
- throw new Error('tangente() requiere exactamente 1 argumento');
1602
+ throw new Error("tangente() requiere exactamente 1 argumento");
793
1603
  }
794
- if (typeof args[0] !== 'number') {
795
- throw new Error('tangente() requiere un argumento numérico');
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 'logaritmo':
1609
+ case "logaritmo":
800
1610
  if (args.length !== 1) {
801
- throw new Error('logaritmo() requiere exactamente 1 argumento');
1611
+ throw new Error("logaritmo() requiere exactamente 1 argumento");
802
1612
  }
803
- if (typeof args[0] !== 'number') {
804
- throw new Error('logaritmo() requiere un argumento numérico');
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('No se puede calcular el logaritmo de un número no positivo');
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 'valorAbsoluto':
1623
+ case "valorAbsoluto":
812
1624
  if (args.length !== 1) {
813
- throw new Error('valorAbsoluto() requiere exactamente 1 argumento');
1625
+ throw new Error("valorAbsoluto() requiere exactamente 1 argumento");
814
1626
  }
815
- if (typeof args[0] !== 'number') {
816
- throw new Error('valorAbsoluto() requiere un argumento numérico');
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 'redondear':
1632
+ case "redondear":
821
1633
  if (args.length !== 1) {
822
- throw new Error('redondear() requiere exactamente 1 argumento');
1634
+ throw new Error("redondear() requiere exactamente 1 argumento");
823
1635
  }
824
- if (typeof args[0] !== 'number') {
825
- throw new Error('redondear() requiere un argumento numérico');
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 'techo':
1641
+ case "techo":
830
1642
  if (args.length !== 1) {
831
- throw new Error('techo() requiere exactamente 1 argumento');
1643
+ throw new Error("techo() requiere exactamente 1 argumento");
832
1644
  }
833
- if (typeof args[0] !== 'number') {
834
- throw new Error('techo() requiere un argumento numérico');
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 'piso':
1650
+ case "piso":
839
1651
  if (args.length !== 1) {
840
- throw new Error('piso() requiere exactamente 1 argumento');
1652
+ throw new Error("piso() requiere exactamente 1 argumento");
841
1653
  }
842
- if (typeof args[0] !== 'number') {
843
- throw new Error('piso() requiere un argumento numérico');
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 'aleatorio':
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] !== 'number') {
852
- throw new Error('aleatorio() requiere un argumento numérico');
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] !== 'number' || typeof args[1] !== 'number') {
857
- throw new Error('aleatorio() requiere argumentos numéricos');
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('aleatorio() acepta 0, 1, o 2 argumentos');
1673
+ throw new Error("aleatorio() acepta 0, 1, o 2 argumentos");
862
1674
  }
863
1675
 
864
- case 'maximo':
1676
+ case "maximo":
865
1677
  if (args.length < 1) {
866
- throw new Error('maximo() requiere al menos 1 argumento');
1678
+ throw new Error("maximo() requiere al menos 1 argumento");
867
1679
  }
868
1680
  for (const arg of args) {
869
- if (typeof arg !== 'number') {
870
- throw new Error('maximo() requiere argumentos numéricos');
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 'minimo':
1687
+ case "minimo":
876
1688
  if (args.length < 1) {
877
- throw new Error('minimo() requiere al menos 1 argumento');
1689
+ throw new Error("minimo() requiere al menos 1 argumento");
878
1690
  }
879
1691
  for (const arg of args) {
880
- if (typeof arg !== 'number') {
881
- throw new Error('minimo() requiere argumentos numéricos');
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 'suma':
1698
+ case "suma":
887
1699
  if (args.length < 1) {
888
- throw new Error('suma() requiere al menos 1 argumento');
1700
+ throw new Error("suma() requiere al menos 1 argumento");
889
1701
  }
890
1702
  for (const arg of args) {
891
- if (typeof arg !== 'number') {
892
- throw new Error('suma() requiere argumentos numéricos');
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 'promedio':
1709
+ case "promedio":
898
1710
  if (args.length < 1) {
899
- throw new Error('promedio() requiere al menos 1 argumento');
1711
+ throw new Error("promedio() requiere al menos 1 argumento");
900
1712
  }
901
1713
  for (const arg of args) {
902
- if (typeof arg !== 'number') {
903
- throw new Error('promedio() requiere argumentos numéricos');
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('Solo se pueden acceder elementos de arreglos');
1892
+ throw new Error("Solo se pueden acceder elementos de arreglos");
937
1893
  }
938
1894
 
939
- if (typeof index !== 'number') {
940
- throw new Error('El índice del arreglo debe ser un número');
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('Índice del arreglo fuera de rango');
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('Solo se pueden asignar elementos de arreglos');
1917
+ throw new Error("Solo se pueden asignar elementos de arreglos");
962
1918
  }
963
1919
 
964
- if (typeof index !== 'number') {
965
- throw new Error('El índice del arreglo debe ser un número');
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('Índice del arreglo fuera de rango');
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 !== 'object' || object === null) {
1001
- throw new Error('Solo se pueden acceder propiedades de objetos');
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 !== 'object' || object === null) {
1017
- throw new Error('Solo se pueden asignar propiedades de objetos');
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 === 'OR') {
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 === 'AND') {
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('Solo se pueden asignar elementos de arreglos');
2077
+ throw new Error("Solo se pueden asignar elementos de arreglos");
1081
2078
  }
1082
- if (typeof index !== 'number') {
1083
- throw new Error('El índice del arreglo debe ser un número');
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('Índice del arreglo fuera de rango');
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 !== 'object' || object === null) {
1110
- throw new Error('Solo se pueden asignar propiedades de objetos');
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 'PLUS_EQUAL':
1134
- if (typeof left === 'number' && typeof right === 'number') {
2130
+ case "PLUS_EQUAL":
2131
+ if (typeof left === "number" && typeof right === "number") {
1135
2132
  return left + right;
1136
2133
  }
1137
- if (typeof left === 'string' || typeof right === 'string') {
2134
+ if (typeof left === "string" || typeof right === "string") {
1138
2135
  return String(left) + String(right);
1139
2136
  }
1140
- throw new Error('No se pueden sumar valores no numéricos');
2137
+ throw new Error("No se pueden sumar valores no numéricos");
1141
2138
 
1142
- case 'MINUS_EQUAL':
1143
- if (typeof left !== 'number' || typeof right !== 'number') {
1144
- throw new Error('Solo se pueden restar números');
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 'STAR_EQUAL':
1149
- if (typeof left !== 'number' || typeof right !== 'number') {
1150
- throw new Error('Solo se pueden multiplicar números');
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 'SLASH_EQUAL':
1155
- if (typeof left !== 'number' || typeof right !== 'number') {
1156
- throw new Error('Solo se pueden dividir números');
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('División por cero');
2156
+ throw new Error("División por cero");
1160
2157
  }
1161
2158
  return left / right;
1162
2159
 
1163
- case 'PERCENT_EQUAL':
1164
- if (typeof left !== 'number' || typeof right !== 'number') {
1165
- throw new Error('Solo se puede hacer módulo con números');
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('Módulo por cero');
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 === 'PLUS_PLUS') {
1186
- if (typeof operand !== 'number') {
1187
- throw new Error('Solo se pueden incrementar números');
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 === 'Variable') {
2189
+ if (expression.operand.type === "Variable") {
1193
2190
  this.environment.assign(expression.operand.name, newValue);
1194
- } else if (expression.operand.type === 'PropertyAccess') {
2191
+ } else if (expression.operand.type === "PropertyAccess") {
1195
2192
  const object = this.evaluateExpression(expression.operand.object);
1196
- if (typeof object !== 'object' || object === null) {
1197
- throw new Error('Solo se pueden incrementar propiedades de objetos');
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 === 'ArrayAccess') {
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('Solo se pueden incrementar elementos de arreglos');
2201
+ throw new Error("Solo se pueden incrementar elementos de arreglos");
1205
2202
  }
1206
- if (typeof index !== 'number') {
1207
- throw new Error('El índice del arreglo debe ser un número');
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('Índice del arreglo fuera de rango');
2207
+ throw new Error("Índice del arreglo fuera de rango");
1211
2208
  }
1212
2209
  array[index] = newValue;
1213
2210
  } else {
1214
- throw new Error('Objetivo de incremento inválido');
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 === 'MINUS_MINUS') {
1221
- if (typeof operand !== 'number') {
1222
- throw new Error('Solo se pueden decrementar números');
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 === 'Variable') {
2224
+ if (expression.operand.type === "Variable") {
1228
2225
  this.environment.assign(expression.operand.name, newValue);
1229
- } else if (expression.operand.type === 'PropertyAccess') {
2226
+ } else if (expression.operand.type === "PropertyAccess") {
1230
2227
  const object = this.evaluateExpression(expression.operand.object);
1231
- if (typeof object !== 'object' || object === null) {
1232
- throw new Error('Solo se pueden decrementar propiedades de objetos');
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 === 'ArrayAccess') {
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('Solo se pueden decrementar elementos de arreglos');
2236
+ throw new Error("Solo se pueden decrementar elementos de arreglos");
1240
2237
  }
1241
- if (typeof index !== 'number') {
1242
- throw new Error('El índice del arreglo debe ser un número');
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('Índice del arreglo fuera de rango');
2242
+ throw new Error("Índice del arreglo fuera de rango");
1246
2243
  }
1247
2244
  array[index] = newValue;
1248
2245
  } else {
1249
- throw new Error('Objetivo de decremento inválido');
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 'null';
1265
- if (value === undefined) return 'undefined';
1266
- if (typeof value === 'string') return 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 '[' + value.map(v => this.stringify(v)).join(', ') + ']';
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 = 'break';
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 = 'continue';
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 = 'try-catch';
2515
+ this.type = "try-catch";
1387
2516
  this.message = message;
1388
2517
  }
1389
2518
  }