hispano-lang 1.0.8 → 2.0.0

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