hispano-lang 1.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.
@@ -0,0 +1,1391 @@
1
+ /**
2
+ * Evaluator for HispanoLang
3
+ * Executes AST statements
4
+ */
5
+
6
+ class Evaluator {
7
+ constructor() {
8
+ this.environment = new Environment();
9
+ this.output = [];
10
+ }
11
+
12
+ /**
13
+ * Evaluates a list of statements
14
+ * @param {Array} statements - List of AST statements
15
+ * @returns {Array} Execution result
16
+ */
17
+ evaluate(statements) {
18
+ this.output = [];
19
+
20
+ try {
21
+ for (const statement of statements) {
22
+ this.execute(statement);
23
+ }
24
+ } catch (error) {
25
+ throw new Error(`Execution error: ${error.message}`);
26
+ }
27
+
28
+ return this.output;
29
+ }
30
+
31
+ /**
32
+ * Executes a statement
33
+ * @param {Object} statement - Statement to execute
34
+ */
35
+ execute(statement) {
36
+ switch (statement.type) {
37
+ case 'VariableDeclaration':
38
+ return this.executeVariableDeclaration(statement);
39
+ case 'FunctionDeclaration':
40
+ return this.executeFunctionDeclaration(statement);
41
+ case 'MostrarStatement':
42
+ return this.executeMostrarStatement(statement);
43
+ case 'LeerStatement':
44
+ return this.executeLeerStatement(statement);
45
+ case 'IfStatement':
46
+ return this.executeIfStatement(statement);
47
+ case 'WhileStatement':
48
+ return this.executeWhileStatement(statement);
49
+ case 'ForStatement':
50
+ return this.executeForStatement(statement);
51
+ case 'ReturnStatement':
52
+ return this.executeReturnStatement(statement);
53
+ case 'BreakStatement':
54
+ return this.executeBreakStatement(statement);
55
+ case 'ContinueStatement':
56
+ return this.executeContinueStatement(statement);
57
+ case 'TryCatch':
58
+ return this.executeTryCatch(statement);
59
+ case 'ExpressionStatement':
60
+ return this.executeExpressionStatement(statement);
61
+ case 'Block':
62
+ return this.executeBlock(statement);
63
+ }
64
+ }
65
+
66
+ /**
67
+ * Executes a variable declaration
68
+ * @param {Object} statement - Variable declaration
69
+ */
70
+ executeVariableDeclaration(statement) {
71
+ let value = null;
72
+ if (statement.initializer !== null) {
73
+ value = this.evaluateExpression(statement.initializer);
74
+ }
75
+
76
+ this.environment.define(statement.name, value);
77
+ }
78
+
79
+ /**
80
+ * Executes a function declaration
81
+ * @param {Object} statement - Function declaration
82
+ */
83
+ executeFunctionDeclaration(statement) {
84
+ const functionObj = {
85
+ type: 'Function',
86
+ name: statement.name,
87
+ parameters: statement.parameters,
88
+ body: statement.body,
89
+ };
90
+
91
+ this.environment.define(statement.name, functionObj);
92
+ }
93
+
94
+ /**
95
+ * Executes a show statement
96
+ * @param {Object} statement - Show statement
97
+ */
98
+ executeMostrarStatement(statement) {
99
+ const value = this.evaluateExpression(statement.expression);
100
+ const output = this.stringify(value);
101
+ this.output.push(output);
102
+ console.log(output);
103
+ }
104
+
105
+ /**
106
+ * Executes a read statement
107
+ * @param {Object} statement - Read statement
108
+ */
109
+ executeLeerStatement(statement) {
110
+ // Show prompt if provided
111
+ if (statement.prompt) {
112
+ console.log(statement.prompt);
113
+ }
114
+
115
+ // Try different methods for input
116
+ let input = '';
117
+
118
+ try {
119
+ // Method 1: Try readline-sync
120
+ const readlineSync = require('readline-sync');
121
+ input = readlineSync.question('');
122
+ } catch (error1) {
123
+ try {
124
+ // Method 2: Try fs.readFileSync with stdin
125
+ const fs = require('fs');
126
+ input = fs.readFileSync(0, 'utf8').trim();
127
+ } catch (error2) {
128
+ try {
129
+ // Method 3: Try process.stdin
130
+ const readline = require('readline');
131
+ const rl = readline.createInterface({
132
+ input: process.stdin,
133
+ output: process.stdout,
134
+ });
135
+
136
+ // This is a simplified approach - in a real implementation
137
+ // you'd need to handle this asynchronously
138
+ input = '';
139
+ } catch (error3) {
140
+ // Fallback: use empty string
141
+ input = '';
142
+ console.log('(Entrada no disponible en este entorno)');
143
+ }
144
+ }
145
+ }
146
+
147
+ // Try to parse as number first, then as string
148
+ let value = input;
149
+ if (!isNaN(input) && input.trim() !== '') {
150
+ value = parseFloat(input);
151
+ }
152
+
153
+ // Store the value in the environment
154
+ this.environment.define(statement.variable, value);
155
+ }
156
+
157
+ /**
158
+ * Executes an if statement
159
+ * @param {Object} statement - If statement
160
+ */
161
+ executeIfStatement(statement) {
162
+ if (this.isTruthy(this.evaluateExpression(statement.condition))) {
163
+ this.executeBlock(statement.thenBranch);
164
+ } else if (statement.elseBranch !== null) {
165
+ this.executeBlock(statement.elseBranch);
166
+ }
167
+ }
168
+
169
+ /**
170
+ * Executes a while statement
171
+ * @param {Object} statement - While statement
172
+ */
173
+ executeWhileStatement(statement) {
174
+ while (this.isTruthy(this.evaluateExpression(statement.condition))) {
175
+ try {
176
+ this.executeBlock(statement.body);
177
+ } catch (breakException) {
178
+ if (breakException instanceof BreakException) {
179
+ break;
180
+ }
181
+ if (breakException instanceof ContinueException) {
182
+ continue;
183
+ }
184
+ throw breakException;
185
+ }
186
+ }
187
+ }
188
+
189
+ /**
190
+ * Executes a for statement
191
+ * @param {Object} statement - For statement
192
+ */
193
+ executeForStatement(statement) {
194
+ // Execute initializer
195
+ if (statement.initializer !== null) {
196
+ this.execute(statement.initializer);
197
+ }
198
+
199
+ // Execute loop
200
+ // If no condition is provided, don't execute the loop (like JavaScript)
201
+ if (statement.condition !== null) {
202
+ while (this.isTruthy(this.evaluateExpression(statement.condition))) {
203
+ try {
204
+ this.executeBlock(statement.body);
205
+ } catch (breakException) {
206
+ if (breakException instanceof BreakException) {
207
+ break;
208
+ }
209
+ if (breakException instanceof ContinueException) {
210
+ // Execute increment before continuing
211
+ if (statement.increment !== null) {
212
+ this.evaluateExpression(statement.increment);
213
+ }
214
+ continue;
215
+ }
216
+ throw breakException;
217
+ }
218
+
219
+ // Execute increment
220
+ if (statement.increment !== null) {
221
+ this.evaluateExpression(statement.increment);
222
+ }
223
+ }
224
+ }
225
+ }
226
+
227
+ /**
228
+ * Executes a return statement
229
+ * @param {Object} statement - Return statement
230
+ */
231
+ executeReturnStatement(statement) {
232
+ let value = null;
233
+ if (statement.value !== null) {
234
+ value = this.evaluateExpression(statement.value);
235
+ }
236
+
237
+ throw new ReturnException(value);
238
+ }
239
+
240
+ /**
241
+ * Executes a break statement
242
+ * @param {Object} statement - Break statement
243
+ */
244
+ executeBreakStatement(statement) {
245
+ throw new BreakException();
246
+ }
247
+
248
+ /**
249
+ * Executes a continue statement
250
+ * @param {Object} statement - Continue statement
251
+ */
252
+ executeContinueStatement(statement) {
253
+ throw new ContinueException();
254
+ }
255
+
256
+ /**
257
+ * Executes a block of statements
258
+ * @param {Array} statements - List of statements in the block
259
+ */
260
+ executeBlock(statements) {
261
+ const previous = this.environment;
262
+
263
+ try {
264
+ this.environment = new Environment(previous);
265
+
266
+ for (const statement of statements) {
267
+ this.execute(statement);
268
+ }
269
+ } finally {
270
+ this.environment = previous;
271
+ }
272
+ }
273
+
274
+ /**
275
+ * Executes an expression statement
276
+ * @param {Object} statement - Expression statement
277
+ */
278
+ executeExpressionStatement(statement) {
279
+ return this.evaluateExpression(statement.expression);
280
+ }
281
+
282
+ /**
283
+ * Evaluates an expression
284
+ * @param {Object} expression - Expression to evaluate
285
+ * @returns {any} Expression result
286
+ */
287
+ evaluateExpression(expression) {
288
+ switch (expression.type) {
289
+ case 'Literal':
290
+ return expression.value;
291
+
292
+ case 'Variable':
293
+ return this.environment.get(expression.name);
294
+
295
+ case 'AnonymousFunction':
296
+ return {
297
+ type: 'Function',
298
+ name: null,
299
+ parameters: expression.parameters,
300
+ body: expression.body,
301
+ };
302
+
303
+ case 'Assign':
304
+ const value = this.evaluateExpression(expression.value);
305
+ this.environment.assign(expression.name, value);
306
+ return value;
307
+
308
+ case 'ArrayLiteral':
309
+ return this.evaluateArrayLiteral(expression);
310
+
311
+ case 'ArrayAccess':
312
+ return this.evaluateArrayAccess(expression);
313
+
314
+ case 'ArrayAssign':
315
+ return this.evaluateArrayAssign(expression);
316
+
317
+ case 'ObjectLiteral':
318
+ return this.evaluateObjectLiteral(expression);
319
+
320
+ case 'PropertyAccess':
321
+ return this.evaluatePropertyAccess(expression);
322
+
323
+ case 'PropertyAssign':
324
+ return this.evaluatePropertyAssign(expression);
325
+
326
+ case 'CompoundAssign':
327
+ return this.evaluateCompoundAssign(expression);
328
+
329
+ case 'CompoundArrayAssign':
330
+ return this.evaluateCompoundArrayAssign(expression);
331
+
332
+ case 'CompoundPropertyAssign':
333
+ return this.evaluateCompoundPropertyAssign(expression);
334
+
335
+ case 'Logical':
336
+ return this.evaluateLogicalExpression(expression);
337
+
338
+ case 'Postfix':
339
+ return this.evaluatePostfixExpression(expression);
340
+
341
+ case 'Call':
342
+ return this.evaluateCallExpression(expression);
343
+
344
+ case 'MethodCall':
345
+ return this.evaluateMethodCall(expression);
346
+
347
+ case 'Unary':
348
+ const right = this.evaluateExpression(expression.right);
349
+ return this.evaluateUnaryExpression(expression.operator, right);
350
+
351
+ case 'Binary':
352
+ const left = this.evaluateExpression(expression.left);
353
+ const rightOperand = this.evaluateExpression(expression.right);
354
+ return this.evaluateBinaryExpression(
355
+ left,
356
+ expression.operator,
357
+ rightOperand
358
+ );
359
+
360
+ default:
361
+ throw new Error(`Unrecognized expression type: ${expression.type}`);
362
+ }
363
+ }
364
+
365
+ /**
366
+ * Evaluates a unary expression
367
+ * @param {string} operator - Unary operator
368
+ * @param {any} right - Right operand
369
+ * @returns {any} Expression result
370
+ */
371
+ evaluateUnaryExpression(operator, right) {
372
+ switch (operator) {
373
+ case 'MINUS':
374
+ this.checkNumberOperand(operator, right);
375
+ return -right;
376
+
377
+ case 'BANG':
378
+ return !this.isTruthy(right);
379
+
380
+ default:
381
+ throw new Error(`Unrecognized unary operator: ${operator}`);
382
+ }
383
+ }
384
+
385
+ /**
386
+ * Evaluates a binary expression
387
+ * @param {any} left - Left operand
388
+ * @param {string} operator - Binary operator
389
+ * @param {any} right - Right operand
390
+ * @returns {any} Expression result
391
+ */
392
+ evaluateBinaryExpression(left, operator, right) {
393
+ switch (operator) {
394
+ case 'MINUS':
395
+ this.checkNumberOperands(operator, left, right);
396
+ return left - right;
397
+
398
+ case 'PLUS':
399
+ if (typeof left === 'number' && typeof right === 'number') {
400
+ return left + right;
401
+ }
402
+ // Convert numbers to strings for concatenation
403
+ const leftStr = this.stringify(left);
404
+ const rightStr = this.stringify(right);
405
+ return leftStr + rightStr;
406
+
407
+ case 'SLASH':
408
+ this.checkNumberOperands(operator, left, right);
409
+ if (right === 0) {
410
+ throw new Error('Division by zero');
411
+ }
412
+ return left / right;
413
+
414
+ case 'STAR':
415
+ this.checkNumberOperands(operator, left, right);
416
+ return left * right;
417
+
418
+ case 'PERCENT':
419
+ this.checkNumberOperands(operator, left, right);
420
+ if (right === 0) {
421
+ throw new Error('Modulo by zero');
422
+ }
423
+ return left % right;
424
+
425
+ case 'GREATER':
426
+ if (typeof left === 'string' && typeof right === 'string') {
427
+ return left > right;
428
+ }
429
+ this.checkNumberOperands(operator, left, right);
430
+ return left > right;
431
+
432
+ case 'GREATER_EQUAL':
433
+ if (typeof left === 'string' && typeof right === 'string') {
434
+ return left >= right;
435
+ }
436
+ this.checkNumberOperands(operator, left, right);
437
+ return left >= right;
438
+
439
+ case 'LESS':
440
+ if (typeof left === 'string' && typeof right === 'string') {
441
+ return left < right;
442
+ }
443
+ this.checkNumberOperands(operator, left, right);
444
+ return left < right;
445
+
446
+ case 'LESS_EQUAL':
447
+ if (typeof left === 'string' && typeof right === 'string') {
448
+ return left <= right;
449
+ }
450
+ this.checkNumberOperands(operator, left, right);
451
+ return left <= right;
452
+
453
+ case 'EQUAL_EQUAL':
454
+ return this.isEqual(left, right);
455
+
456
+ case 'BANG_EQUAL':
457
+ return !this.isEqual(left, right);
458
+
459
+ default:
460
+ throw new Error(`Unrecognized binary operator: ${operator}`);
461
+ }
462
+ }
463
+
464
+ /**
465
+ * Checks if a value is truthy
466
+ * @param {any} value - Value to check
467
+ * @returns {boolean} True if it is truthy
468
+ */
469
+ isTruthy(value) {
470
+ if (value === null) return false;
471
+ if (typeof value === 'boolean') return value;
472
+ if (typeof value === 'number') return value !== 0;
473
+ return true;
474
+ }
475
+
476
+ /**
477
+ * Checks if two values are equal
478
+ * @param {any} left - Left value
479
+ * @param {any} right - Right value
480
+ * @returns {boolean} True if they are equal
481
+ */
482
+ isEqual(left, right) {
483
+ return left === right;
484
+ }
485
+
486
+ /**
487
+ * Checks that an operand is a number
488
+ * @param {string} operator - Operator
489
+ * @param {any} operand - Operand
490
+ */
491
+ checkNumberOperand(operator, operand) {
492
+ if (typeof operand === 'number') return;
493
+ throw new Error(`Operator ${operator} requires a number`);
494
+ }
495
+
496
+ /**
497
+ * Checks that two operands are numbers
498
+ * @param {string} operator - Operator
499
+ * @param {any} left - Left operand
500
+ * @param {any} right - Right operand
501
+ */
502
+ checkNumberOperands(operator, left, right) {
503
+ if (typeof left === 'number' && typeof right === 'number') return;
504
+ throw new Error(`Operator ${operator} requires two numbers`);
505
+ }
506
+
507
+ /**
508
+ * Evaluates a function call
509
+ * @param {Object} expression - Call expression
510
+ * @returns {any} Function result
511
+ */
512
+ evaluateCallExpression(expression) {
513
+ // Check if it's a built-in mathematical function BEFORE evaluating the callee
514
+ if (
515
+ expression.callee.type === 'Variable' &&
516
+ this.isMathFunction(expression.callee.name)
517
+ ) {
518
+ const args = [];
519
+ for (const argument of expression.arguments) {
520
+ args.push(this.evaluateExpression(argument));
521
+ }
522
+ return this.evaluateMathFunction(expression.callee.name, args);
523
+ }
524
+
525
+ const callee = this.evaluateExpression(expression.callee);
526
+ const args = [];
527
+
528
+ for (const argument of expression.arguments) {
529
+ args.push(this.evaluateExpression(argument));
530
+ }
531
+
532
+ if (callee.type !== 'Function') {
533
+ throw new Error('Can only call functions');
534
+ }
535
+
536
+ if (args.length !== callee.parameters.length) {
537
+ throw new Error(
538
+ `Expected ${callee.parameters.length} arguments but got ${args.length}`
539
+ );
540
+ }
541
+
542
+ const environment = new Environment(this.environment);
543
+
544
+ for (let i = 0; i < args.length; i++) {
545
+ environment.define(callee.parameters[i], args[i]);
546
+ }
547
+
548
+ const previous = this.environment;
549
+ try {
550
+ this.environment = environment;
551
+ this.executeBlock(callee.body);
552
+ return null;
553
+ } catch (returnValue) {
554
+ if (returnValue instanceof ReturnException) {
555
+ return returnValue.value;
556
+ }
557
+ throw returnValue;
558
+ } finally {
559
+ this.environment = previous;
560
+ }
561
+ }
562
+
563
+ /**
564
+ * Evaluates a method call (array or string)
565
+ * @param {Object} expression - Method call expression
566
+ * @returns {any} Method result
567
+ */
568
+ evaluateMethodCall(expression) {
569
+ const object = this.evaluateExpression(expression.object);
570
+
571
+ // Determine if it's an array or string method based on the object type
572
+ if (Array.isArray(object)) {
573
+ return this.evaluateArrayMethod(
574
+ object,
575
+ expression.method,
576
+ expression.arguments
577
+ );
578
+ } else if (typeof object === 'string') {
579
+ // Check if the method is valid for strings
580
+ if (
581
+ expression.method === 'agregar' ||
582
+ expression.method === 'remover' ||
583
+ expression.method === 'contiene' ||
584
+ expression.method === 'recorrer'
585
+ ) {
586
+ throw new Error(
587
+ `Method ${expression.method}() can only be called on arrays`
588
+ );
589
+ }
590
+ return this.evaluateStringMethod(object, expression.method);
591
+ } else {
592
+ throw new Error(
593
+ `Can only call methods on arrays or strings, got ${typeof object}`
594
+ );
595
+ }
596
+ }
597
+
598
+ /**
599
+ * Evaluates an array method
600
+ * @param {Array} array - The array object
601
+ * @param {string} method - The method name
602
+ * @param {Array} args - Method arguments (optional)
603
+ * @returns {any} Method result
604
+ */
605
+ evaluateArrayMethod(array, method, args = []) {
606
+ switch (method) {
607
+ case 'longitud':
608
+ return array.length;
609
+
610
+ case 'primero':
611
+ if (array.length === 0) {
612
+ throw new Error('Cannot get first element of empty array');
613
+ }
614
+ return array[0];
615
+
616
+ case 'ultimo':
617
+ if (array.length === 0) {
618
+ throw new Error('Cannot get last element of empty array');
619
+ }
620
+ return array[array.length - 1];
621
+
622
+ case 'agregar':
623
+ // Evaluate all arguments and add them to the array
624
+ for (const arg of args) {
625
+ const value = this.evaluateExpression(arg);
626
+ array.push(value);
627
+ }
628
+ return array.length; // Return the new length
629
+
630
+ case 'remover':
631
+ // Remove and return the last element
632
+ if (array.length === 0) {
633
+ throw new Error('Cannot remove element from empty array');
634
+ }
635
+ return array.pop(); // Return the removed element
636
+
637
+ case 'contiene':
638
+ // Check if array contains the specified element
639
+ if (args.length !== 1) {
640
+ throw new Error('Method contiene() requires exactly one argument');
641
+ }
642
+ const searchElement = this.evaluateExpression(args[0]);
643
+ return array.includes(searchElement);
644
+
645
+ case 'recorrer':
646
+ // Iterate through array and call function for each element
647
+ if (args.length !== 1) {
648
+ throw new Error('Method recorrer() requires exactly one argument');
649
+ }
650
+ const callback = this.evaluateExpression(args[0]);
651
+ if (callback.type !== 'Function') {
652
+ throw new Error('Method recorrer() requires a function as argument');
653
+ }
654
+
655
+ // Call the function for each element
656
+ for (let i = 0; i < array.length; i++) {
657
+ // Create a new environment for the callback function
658
+ const callbackEnv = new Environment(this.environment);
659
+
660
+ // Handle function parameters
661
+ if (callback.parameters.length === 0) {
662
+ // No parameters - use automatic variables
663
+ callbackEnv.define('elemento', array[i]);
664
+ callbackEnv.define('indice', i);
665
+ } else if (callback.parameters.length === 1) {
666
+ // One parameter - element only
667
+ callbackEnv.define(callback.parameters[0], array[i]);
668
+ callbackEnv.define('indice', i);
669
+ } else if (callback.parameters.length === 2) {
670
+ // Two parameters - element and index
671
+ callbackEnv.define(callback.parameters[0], array[i]);
672
+ callbackEnv.define(callback.parameters[1], i);
673
+ } else {
674
+ throw new Error(
675
+ 'Function in recorrer() can have at most 2 parameters'
676
+ );
677
+ }
678
+
679
+ // Execute the callback function
680
+ const previousEnv = this.environment;
681
+ this.environment = callbackEnv;
682
+ try {
683
+ this.executeBlock(callback.body);
684
+ } finally {
685
+ this.environment = previousEnv;
686
+ }
687
+ }
688
+ return null; // forEach doesn't return anything
689
+
690
+ default:
691
+ throw new Error(`Unknown array method: ${method}`);
692
+ }
693
+ }
694
+
695
+ /**
696
+ * Evaluates a string method
697
+ * @param {string} string - The string object
698
+ * @param {string} method - The method name
699
+ * @returns {any} Method result
700
+ */
701
+ evaluateStringMethod(string, method) {
702
+ switch (method) {
703
+ case 'longitud':
704
+ return string.length;
705
+
706
+ case 'mayusculas':
707
+ return string.toUpperCase();
708
+
709
+ case 'minusculas':
710
+ return string.toLowerCase();
711
+
712
+ default:
713
+ throw new Error(`Unknown string method: ${method}`);
714
+ }
715
+ }
716
+
717
+ /**
718
+ * Checks if a function name is a built-in mathematical function
719
+ * @param {string} name - Function name
720
+ * @returns {boolean} True if it's a math function
721
+ */
722
+ isMathFunction(name) {
723
+ 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',
739
+ ];
740
+ return mathFunctions.includes(name);
741
+ }
742
+
743
+ /**
744
+ * Evaluates a mathematical function
745
+ * @param {string} name - Function name
746
+ * @param {Array} args - Function arguments
747
+ * @returns {number} Function result
748
+ */
749
+ evaluateMathFunction(name, args) {
750
+ switch (name) {
751
+ case 'raiz':
752
+ if (args.length !== 1) {
753
+ throw new Error('raiz() requires exactly 1 argument');
754
+ }
755
+ if (typeof args[0] !== 'number') {
756
+ throw new Error('raiz() requires a number argument');
757
+ }
758
+ if (args[0] < 0) {
759
+ throw new Error('Cannot take square root of negative number');
760
+ }
761
+ return Math.sqrt(args[0]);
762
+
763
+ case 'potencia':
764
+ if (args.length !== 2) {
765
+ throw new Error('potencia() requires exactly 2 arguments');
766
+ }
767
+ if (typeof args[0] !== 'number' || typeof args[1] !== 'number') {
768
+ throw new Error('potencia() requires number arguments');
769
+ }
770
+ return Math.pow(args[0], args[1]);
771
+
772
+ case 'seno':
773
+ if (args.length !== 1) {
774
+ throw new Error('seno() requires exactly 1 argument');
775
+ }
776
+ if (typeof args[0] !== 'number') {
777
+ throw new Error('seno() requires a number argument');
778
+ }
779
+ return Math.sin(args[0]);
780
+
781
+ case 'coseno':
782
+ if (args.length !== 1) {
783
+ throw new Error('coseno() requires exactly 1 argument');
784
+ }
785
+ if (typeof args[0] !== 'number') {
786
+ throw new Error('coseno() requires a number argument');
787
+ }
788
+ return Math.cos(args[0]);
789
+
790
+ case 'tangente':
791
+ if (args.length !== 1) {
792
+ throw new Error('tangente() requires exactly 1 argument');
793
+ }
794
+ if (typeof args[0] !== 'number') {
795
+ throw new Error('tangente() requires a number argument');
796
+ }
797
+ return Math.tan(args[0]);
798
+
799
+ case 'logaritmo':
800
+ if (args.length !== 1) {
801
+ throw new Error('logaritmo() requires exactly 1 argument');
802
+ }
803
+ if (typeof args[0] !== 'number') {
804
+ throw new Error('logaritmo() requires a number argument');
805
+ }
806
+ if (args[0] <= 0) {
807
+ throw new Error('Cannot take logarithm of non-positive number');
808
+ }
809
+ return Math.log(args[0]);
810
+
811
+ case 'valorAbsoluto':
812
+ if (args.length !== 1) {
813
+ throw new Error('valorAbsoluto() requires exactly 1 argument');
814
+ }
815
+ if (typeof args[0] !== 'number') {
816
+ throw new Error('valorAbsoluto() requires a number argument');
817
+ }
818
+ return Math.abs(args[0]);
819
+
820
+ case 'redondear':
821
+ if (args.length !== 1) {
822
+ throw new Error('redondear() requires exactly 1 argument');
823
+ }
824
+ if (typeof args[0] !== 'number') {
825
+ throw new Error('redondear() requires a number argument');
826
+ }
827
+ return Math.round(args[0]);
828
+
829
+ case 'techo':
830
+ if (args.length !== 1) {
831
+ throw new Error('techo() requires exactly 1 argument');
832
+ }
833
+ if (typeof args[0] !== 'number') {
834
+ throw new Error('techo() requires a number argument');
835
+ }
836
+ return Math.ceil(args[0]);
837
+
838
+ case 'piso':
839
+ if (args.length !== 1) {
840
+ throw new Error('piso() requires exactly 1 argument');
841
+ }
842
+ if (typeof args[0] !== 'number') {
843
+ throw new Error('piso() requires a number argument');
844
+ }
845
+ return Math.floor(args[0]);
846
+
847
+ case 'aleatorio':
848
+ if (args.length === 0) {
849
+ return Math.random();
850
+ } else if (args.length === 1) {
851
+ if (typeof args[0] !== 'number') {
852
+ throw new Error('aleatorio() requires a number argument');
853
+ }
854
+ return Math.random() * args[0];
855
+ } else if (args.length === 2) {
856
+ if (typeof args[0] !== 'number' || typeof args[1] !== 'number') {
857
+ throw new Error('aleatorio() requires number arguments');
858
+ }
859
+ return Math.random() * (args[1] - args[0]) + args[0];
860
+ } else {
861
+ throw new Error('aleatorio() accepts 0, 1, or 2 arguments');
862
+ }
863
+
864
+ case 'maximo':
865
+ if (args.length < 1) {
866
+ throw new Error('maximo() requires at least 1 argument');
867
+ }
868
+ for (const arg of args) {
869
+ if (typeof arg !== 'number') {
870
+ throw new Error('maximo() requires number arguments');
871
+ }
872
+ }
873
+ return Math.max(...args);
874
+
875
+ case 'minimo':
876
+ if (args.length < 1) {
877
+ throw new Error('minimo() requires at least 1 argument');
878
+ }
879
+ for (const arg of args) {
880
+ if (typeof arg !== 'number') {
881
+ throw new Error('minimo() requires number arguments');
882
+ }
883
+ }
884
+ return Math.min(...args);
885
+
886
+ case 'suma':
887
+ if (args.length < 1) {
888
+ throw new Error('suma() requires at least 1 argument');
889
+ }
890
+ for (const arg of args) {
891
+ if (typeof arg !== 'number') {
892
+ throw new Error('suma() requires number arguments');
893
+ }
894
+ }
895
+ return args.reduce((sum, arg) => sum + arg, 0);
896
+
897
+ case 'promedio':
898
+ if (args.length < 1) {
899
+ throw new Error('promedio() requires at least 1 argument');
900
+ }
901
+ for (const arg of args) {
902
+ if (typeof arg !== 'number') {
903
+ throw new Error('promedio() requires number arguments');
904
+ }
905
+ }
906
+ return args.reduce((sum, arg) => sum + arg, 0) / args.length;
907
+
908
+ default:
909
+ throw new Error(`Unknown mathematical function: ${name}`);
910
+ }
911
+ }
912
+
913
+ /**
914
+ * Evaluates an array literal
915
+ * @param {Object} expression - Array literal expression
916
+ * @returns {Array} Array value
917
+ */
918
+ evaluateArrayLiteral(expression) {
919
+ const elements = [];
920
+ for (const element of expression.elements) {
921
+ elements.push(this.evaluateExpression(element));
922
+ }
923
+ return elements;
924
+ }
925
+
926
+ /**
927
+ * Evaluates array access
928
+ * @param {Object} expression - Array access expression
929
+ * @returns {any} Array element value
930
+ */
931
+ evaluateArrayAccess(expression) {
932
+ const array = this.evaluateExpression(expression.array);
933
+ const index = this.evaluateExpression(expression.index);
934
+
935
+ if (!Array.isArray(array)) {
936
+ throw new Error('Can only access elements of arrays');
937
+ }
938
+
939
+ if (typeof index !== 'number') {
940
+ throw new Error('Array index must be a number');
941
+ }
942
+
943
+ if (index < 0 || index >= array.length) {
944
+ throw new Error('Array index out of bounds');
945
+ }
946
+
947
+ return array[index];
948
+ }
949
+
950
+ /**
951
+ * Evaluates array assignment
952
+ * @param {Object} expression - Array assignment expression
953
+ * @returns {any} Assigned value
954
+ */
955
+ evaluateArrayAssign(expression) {
956
+ const array = this.evaluateExpression(expression.array);
957
+ const index = this.evaluateExpression(expression.index);
958
+ const value = this.evaluateExpression(expression.value);
959
+
960
+ if (!Array.isArray(array)) {
961
+ throw new Error('Can only assign to elements of arrays');
962
+ }
963
+
964
+ if (typeof index !== 'number') {
965
+ throw new Error('Array index must be a number');
966
+ }
967
+
968
+ if (index < 0 || index >= array.length) {
969
+ throw new Error('Array index out of bounds');
970
+ }
971
+
972
+ array[index] = value;
973
+ return value;
974
+ }
975
+
976
+ /**
977
+ * Evaluates object literal
978
+ * @param {Object} expression - Object literal expression
979
+ * @returns {Object} Object value
980
+ */
981
+ evaluateObjectLiteral(expression) {
982
+ const object = {};
983
+
984
+ for (const property of expression.properties) {
985
+ const value = this.evaluateExpression(property.value);
986
+ object[property.name] = value;
987
+ }
988
+
989
+ return object;
990
+ }
991
+
992
+ /**
993
+ * Evaluates property access
994
+ * @param {Object} expression - Property access expression
995
+ * @returns {any} Property value
996
+ */
997
+ evaluatePropertyAccess(expression) {
998
+ const object = this.evaluateExpression(expression.object);
999
+
1000
+ if (typeof object !== 'object' || object === null) {
1001
+ throw new Error('Can only access properties of objects');
1002
+ }
1003
+
1004
+ return object[expression.name];
1005
+ }
1006
+
1007
+ /**
1008
+ * Evaluates property assignment
1009
+ * @param {Object} expression - Property assignment expression
1010
+ * @returns {any} Assigned value
1011
+ */
1012
+ evaluatePropertyAssign(expression) {
1013
+ const object = this.evaluateExpression(expression.object);
1014
+ const value = this.evaluateExpression(expression.value);
1015
+
1016
+ if (typeof object !== 'object' || object === null) {
1017
+ throw new Error('Can only assign to properties of objects');
1018
+ }
1019
+
1020
+ object[expression.name] = value;
1021
+ return value;
1022
+ }
1023
+
1024
+ /**
1025
+ * Evaluates logical expression
1026
+ * @param {Object} expression - Logical expression
1027
+ * @returns {any} Logical result
1028
+ */
1029
+ evaluateLogicalExpression(expression) {
1030
+ const left = this.evaluateExpression(expression.left);
1031
+
1032
+ if (expression.operator === 'OR') {
1033
+ // Short-circuit evaluation for OR
1034
+ if (this.isTruthy(left)) {
1035
+ return left;
1036
+ }
1037
+ return this.evaluateExpression(expression.right);
1038
+ }
1039
+
1040
+ if (expression.operator === 'AND') {
1041
+ // Short-circuit evaluation for AND
1042
+ if (!this.isTruthy(left)) {
1043
+ return left;
1044
+ }
1045
+ return this.evaluateExpression(expression.right);
1046
+ }
1047
+
1048
+ throw new Error(`Unknown logical operator: ${expression.operator}`);
1049
+ }
1050
+
1051
+ /**
1052
+ * Evaluates compound assignment
1053
+ * @param {Object} expression - Compound assignment expression
1054
+ * @returns {any} Assignment result
1055
+ */
1056
+ evaluateCompoundAssign(expression) {
1057
+ const currentValue = this.environment.get(expression.name);
1058
+ const rightValue = this.evaluateExpression(expression.value);
1059
+ const newValue = this.performCompoundOperation(
1060
+ currentValue,
1061
+ expression.operator,
1062
+ rightValue
1063
+ );
1064
+
1065
+ this.environment.assign(expression.name, newValue);
1066
+ return newValue;
1067
+ }
1068
+
1069
+ /**
1070
+ * Evaluates compound array assignment
1071
+ * @param {Object} expression - Compound array assignment expression
1072
+ * @returns {any} Assignment result
1073
+ */
1074
+ evaluateCompoundArrayAssign(expression) {
1075
+ const array = this.evaluateExpression(expression.array);
1076
+ const index = this.evaluateExpression(expression.index);
1077
+ const rightValue = this.evaluateExpression(expression.value);
1078
+
1079
+ if (!Array.isArray(array)) {
1080
+ throw new Error('Can only assign to elements of arrays');
1081
+ }
1082
+ if (typeof index !== 'number') {
1083
+ throw new Error('Array index must be a number');
1084
+ }
1085
+ if (index < 0 || index >= array.length) {
1086
+ throw new Error('Array index out of bounds');
1087
+ }
1088
+
1089
+ const currentValue = array[index];
1090
+ const newValue = this.performCompoundOperation(
1091
+ currentValue,
1092
+ expression.operator,
1093
+ rightValue
1094
+ );
1095
+
1096
+ array[index] = newValue;
1097
+ return newValue;
1098
+ }
1099
+
1100
+ /**
1101
+ * Evaluates compound property assignment
1102
+ * @param {Object} expression - Compound property assignment expression
1103
+ * @returns {any} Assignment result
1104
+ */
1105
+ evaluateCompoundPropertyAssign(expression) {
1106
+ const object = this.evaluateExpression(expression.object);
1107
+ const rightValue = this.evaluateExpression(expression.value);
1108
+
1109
+ if (typeof object !== 'object' || object === null) {
1110
+ throw new Error('Can only assign to properties of objects');
1111
+ }
1112
+
1113
+ const currentValue = object[expression.name];
1114
+ const newValue = this.performCompoundOperation(
1115
+ currentValue,
1116
+ expression.operator,
1117
+ rightValue
1118
+ );
1119
+
1120
+ object[expression.name] = newValue;
1121
+ return newValue;
1122
+ }
1123
+
1124
+ /**
1125
+ * Performs compound operation
1126
+ * @param {any} left - Left operand
1127
+ * @param {string} operator - Compound operator
1128
+ * @param {any} right - Right operand
1129
+ * @returns {any} Operation result
1130
+ */
1131
+ performCompoundOperation(left, operator, right) {
1132
+ switch (operator) {
1133
+ case 'PLUS_EQUAL':
1134
+ if (typeof left === 'number' && typeof right === 'number') {
1135
+ return left + right;
1136
+ }
1137
+ if (typeof left === 'string' || typeof right === 'string') {
1138
+ return String(left) + String(right);
1139
+ }
1140
+ throw new Error('Cannot add non-numeric values');
1141
+
1142
+ case 'MINUS_EQUAL':
1143
+ if (typeof left !== 'number' || typeof right !== 'number') {
1144
+ throw new Error('Can only subtract numbers');
1145
+ }
1146
+ return left - right;
1147
+
1148
+ case 'STAR_EQUAL':
1149
+ if (typeof left !== 'number' || typeof right !== 'number') {
1150
+ throw new Error('Can only multiply numbers');
1151
+ }
1152
+ return left * right;
1153
+
1154
+ case 'SLASH_EQUAL':
1155
+ if (typeof left !== 'number' || typeof right !== 'number') {
1156
+ throw new Error('Can only divide numbers');
1157
+ }
1158
+ if (right === 0) {
1159
+ throw new Error('Division by zero');
1160
+ }
1161
+ return left / right;
1162
+
1163
+ case 'PERCENT_EQUAL':
1164
+ if (typeof left !== 'number' || typeof right !== 'number') {
1165
+ throw new Error('Can only modulo numbers');
1166
+ }
1167
+ if (right === 0) {
1168
+ throw new Error('Modulo by zero');
1169
+ }
1170
+ return left % right;
1171
+
1172
+ default:
1173
+ throw new Error(`Unknown compound operator: ${operator}`);
1174
+ }
1175
+ }
1176
+
1177
+ /**
1178
+ * Evaluates postfix expression (increment/decrement)
1179
+ * @param {Object} expression - Postfix expression
1180
+ * @returns {any} Postfix result
1181
+ */
1182
+ evaluatePostfixExpression(expression) {
1183
+ const operand = this.evaluateExpression(expression.operand);
1184
+
1185
+ if (expression.operator === 'PLUS_PLUS') {
1186
+ if (typeof operand !== 'number') {
1187
+ throw new Error('Can only increment numbers');
1188
+ }
1189
+ const newValue = operand + 1;
1190
+
1191
+ // Update the variable if it's a variable reference
1192
+ if (expression.operand.type === 'Variable') {
1193
+ this.environment.assign(expression.operand.name, newValue);
1194
+ } else if (expression.operand.type === 'PropertyAccess') {
1195
+ const object = this.evaluateExpression(expression.operand.object);
1196
+ if (typeof object !== 'object' || object === null) {
1197
+ throw new Error('Can only increment properties of objects');
1198
+ }
1199
+ object[expression.operand.name] = newValue;
1200
+ } else if (expression.operand.type === 'ArrayAccess') {
1201
+ const array = this.evaluateExpression(expression.operand.array);
1202
+ const index = this.evaluateExpression(expression.operand.index);
1203
+ if (!Array.isArray(array)) {
1204
+ throw new Error('Can only increment elements of arrays');
1205
+ }
1206
+ if (typeof index !== 'number') {
1207
+ throw new Error('Array index must be a number');
1208
+ }
1209
+ if (index < 0 || index >= array.length) {
1210
+ throw new Error('Array index out of bounds');
1211
+ }
1212
+ array[index] = newValue;
1213
+ } else {
1214
+ throw new Error('Invalid increment target');
1215
+ }
1216
+
1217
+ return operand; // Return the original value (postfix behavior)
1218
+ }
1219
+
1220
+ if (expression.operator === 'MINUS_MINUS') {
1221
+ if (typeof operand !== 'number') {
1222
+ throw new Error('Can only decrement numbers');
1223
+ }
1224
+ const newValue = operand - 1;
1225
+
1226
+ // Update the variable if it's a variable reference
1227
+ if (expression.operand.type === 'Variable') {
1228
+ this.environment.assign(expression.operand.name, newValue);
1229
+ } else if (expression.operand.type === 'PropertyAccess') {
1230
+ const object = this.evaluateExpression(expression.operand.object);
1231
+ if (typeof object !== 'object' || object === null) {
1232
+ throw new Error('Can only decrement properties of objects');
1233
+ }
1234
+ object[expression.operand.name] = newValue;
1235
+ } else if (expression.operand.type === 'ArrayAccess') {
1236
+ const array = this.evaluateExpression(expression.operand.array);
1237
+ const index = this.evaluateExpression(expression.operand.index);
1238
+ if (!Array.isArray(array)) {
1239
+ throw new Error('Can only decrement elements of arrays');
1240
+ }
1241
+ if (typeof index !== 'number') {
1242
+ throw new Error('Array index must be a number');
1243
+ }
1244
+ if (index < 0 || index >= array.length) {
1245
+ throw new Error('Array index out of bounds');
1246
+ }
1247
+ array[index] = newValue;
1248
+ } else {
1249
+ throw new Error('Invalid decrement target');
1250
+ }
1251
+
1252
+ return operand; // Return the original value (postfix behavior)
1253
+ }
1254
+
1255
+ throw new Error(`Unknown postfix operator: ${expression.operator}`);
1256
+ }
1257
+
1258
+ /**
1259
+ * Converts a value to string for display
1260
+ * @param {any} value - Value to convert
1261
+ * @returns {string} String representation
1262
+ */
1263
+ stringify(value) {
1264
+ if (value === null) return 'null';
1265
+ if (value === undefined) return 'undefined';
1266
+ if (typeof value === 'string') return value;
1267
+ if (Array.isArray(value)) {
1268
+ return '[' + value.map(v => this.stringify(v)).join(', ') + ']';
1269
+ }
1270
+ return value.toString();
1271
+ }
1272
+
1273
+ /**
1274
+ * Executes a try-catch statement
1275
+ * @param {Object} statement - Try-catch statement to execute
1276
+ */
1277
+ executeTryCatch(statement) {
1278
+ try {
1279
+ // Execute the try block
1280
+ this.executeBlock(statement.tryBlock);
1281
+ } catch (error) {
1282
+ // Check if it's a control flow exception (break/continue)
1283
+ if (
1284
+ error instanceof BreakException ||
1285
+ error instanceof ContinueException
1286
+ ) {
1287
+ throw error; // Re-throw control flow exceptions
1288
+ }
1289
+
1290
+ // Store the error in the environment for the catch block
1291
+ this.environment.define(statement.errorVariable, error.message);
1292
+
1293
+ // Execute the catch block
1294
+ this.executeBlock(statement.catchBlock);
1295
+ }
1296
+ }
1297
+ }
1298
+
1299
+ /**
1300
+ * Class to handle variable environment
1301
+ */
1302
+ class Environment {
1303
+ constructor(enclosing = null) {
1304
+ this.values = {};
1305
+ this.enclosing = enclosing;
1306
+ }
1307
+
1308
+ /**
1309
+ * Defines a variable in the environment
1310
+ * @param {string} name - Variable name
1311
+ * @param {any} value - Variable value
1312
+ */
1313
+ define(name, value) {
1314
+ this.values[name] = value;
1315
+ }
1316
+
1317
+ /**
1318
+ * Assigns a value to an existing variable
1319
+ * @param {string} name - Variable name
1320
+ * @param {any} value - Value to assign
1321
+ */
1322
+ assign(name, value) {
1323
+ if (name in this.values) {
1324
+ this.values[name] = value;
1325
+ return;
1326
+ }
1327
+
1328
+ if (this.enclosing !== null) {
1329
+ this.enclosing.assign(name, value);
1330
+ return;
1331
+ }
1332
+
1333
+ throw new Error(`Undefined variable: ${name}`);
1334
+ }
1335
+
1336
+ /**
1337
+ * Gets the value of a variable
1338
+ * @param {string} name - Variable name
1339
+ * @returns {any} Variable value
1340
+ */
1341
+ get(name) {
1342
+ if (name in this.values) {
1343
+ return this.values[name];
1344
+ }
1345
+
1346
+ if (this.enclosing !== null) {
1347
+ return this.enclosing.get(name);
1348
+ }
1349
+
1350
+ throw new Error(`Undefined variable: ${name}`);
1351
+ }
1352
+ }
1353
+
1354
+ /**
1355
+ * Exception class for return statements
1356
+ */
1357
+ class ReturnException {
1358
+ constructor(value) {
1359
+ this.value = value;
1360
+ }
1361
+ }
1362
+
1363
+ /**
1364
+ * Exception class for break statements
1365
+ */
1366
+ class BreakException {
1367
+ constructor() {
1368
+ this.type = 'break';
1369
+ }
1370
+ }
1371
+
1372
+ /**
1373
+ * Exception class for continue statements
1374
+ */
1375
+ class ContinueException {
1376
+ constructor() {
1377
+ this.type = 'continue';
1378
+ }
1379
+ }
1380
+
1381
+ /**
1382
+ * Exception class for try-catch errors
1383
+ */
1384
+ class TryCatchException {
1385
+ constructor(message) {
1386
+ this.type = 'try-catch';
1387
+ this.message = message;
1388
+ }
1389
+ }
1390
+
1391
+ module.exports = Evaluator;