mimo-lang 1.1.1 → 2.0.6

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.
Files changed (165) hide show
  1. package/.gitattributes +24 -0
  2. package/LICENSE +21 -0
  3. package/README.md +71 -39
  4. package/adapters/browserAdapter.js +86 -0
  5. package/adapters/nodeAdapter.js +101 -0
  6. package/bin/cli.js +80 -0
  7. package/bin/commands/convert.js +27 -0
  8. package/bin/commands/doctor.js +139 -0
  9. package/bin/commands/eval.js +39 -0
  10. package/bin/commands/fmt.js +109 -0
  11. package/bin/commands/help.js +72 -0
  12. package/bin/commands/lint.js +117 -0
  13. package/bin/commands/repl.js +24 -0
  14. package/bin/commands/run.js +64 -0
  15. package/bin/commands/test.js +126 -0
  16. package/bin/utils/colors.js +38 -0
  17. package/bin/utils/formatError.js +47 -0
  18. package/bin/utils/fs.js +57 -0
  19. package/bin/utils/version.js +8 -0
  20. package/build.js +18 -0
  21. package/bun.lock +74 -0
  22. package/index.js +48 -77
  23. package/index.web.js +364 -0
  24. package/interpreter/BuiltinFunction.js +32 -0
  25. package/interpreter/ErrorHandler.js +120 -0
  26. package/interpreter/ExpressionEvaluator.js +106 -0
  27. package/interpreter/Interpreter.js +172 -0
  28. package/interpreter/MimoError.js +112 -0
  29. package/interpreter/ModuleLoader.js +236 -0
  30. package/interpreter/StatementExecutor.js +107 -0
  31. package/interpreter/Utils.js +82 -0
  32. package/interpreter/Values.js +87 -0
  33. package/interpreter/coreBuiltins.js +490 -0
  34. package/interpreter/environment.js +99 -0
  35. package/interpreter/evaluators/binaryExpressionEvaluator.js +111 -0
  36. package/interpreter/evaluators/collectionEvaluator.js +151 -0
  37. package/interpreter/evaluators/functionCallEvaluator.js +76 -0
  38. package/interpreter/evaluators/literalEvaluator.js +27 -0
  39. package/interpreter/evaluators/moduleAccessEvaluator.js +25 -0
  40. package/interpreter/evaluators/templateLiteralEvaluator.js +20 -0
  41. package/interpreter/executors/BaseExecutor.js +37 -0
  42. package/interpreter/executors/ControlFlowExecutor.js +206 -0
  43. package/interpreter/executors/FunctionExecutor.js +126 -0
  44. package/interpreter/executors/PatternMatchExecutor.js +93 -0
  45. package/interpreter/executors/VariableExecutor.js +144 -0
  46. package/interpreter/index.js +8 -0
  47. package/interpreter/stdlib/array/accessFunctions.js +61 -0
  48. package/interpreter/stdlib/array/arrayUtils.js +36 -0
  49. package/interpreter/stdlib/array/higherOrderFunctions.js +285 -0
  50. package/interpreter/stdlib/array/searchFunctions.js +77 -0
  51. package/interpreter/stdlib/array/setFunctions.js +49 -0
  52. package/interpreter/stdlib/array/transformationFunctions.js +68 -0
  53. package/interpreter/stdlib/array.js +85 -0
  54. package/interpreter/stdlib/assert.js +143 -0
  55. package/interpreter/stdlib/datetime.js +170 -0
  56. package/interpreter/stdlib/env.js +54 -0
  57. package/interpreter/stdlib/fs.js +161 -0
  58. package/interpreter/stdlib/http.js +92 -0
  59. package/interpreter/stdlib/json.js +70 -0
  60. package/interpreter/stdlib/math.js +309 -0
  61. package/interpreter/stdlib/object.js +142 -0
  62. package/interpreter/stdlib/path.js +69 -0
  63. package/interpreter/stdlib/regex.js +134 -0
  64. package/interpreter/stdlib/string.js +260 -0
  65. package/interpreter/suggestions.js +46 -0
  66. package/lexer/Lexer.js +245 -0
  67. package/lexer/TokenTypes.js +131 -0
  68. package/lexer/createToken.js +11 -0
  69. package/lexer/tokenizers/commentTokenizer.js +45 -0
  70. package/lexer/tokenizers/literalTokenizer.js +163 -0
  71. package/lexer/tokenizers/symbolTokenizer.js +69 -0
  72. package/lexer/tokenizers/whitespaceTokenizer.js +36 -0
  73. package/package.json +29 -13
  74. package/parser/ASTNodes.js +448 -0
  75. package/parser/Parser.js +188 -0
  76. package/parser/expressions/atomicExpressions.js +165 -0
  77. package/parser/expressions/conditionalExpressions.js +0 -0
  78. package/parser/expressions/operatorExpressions.js +79 -0
  79. package/parser/expressions/primaryExpressions.js +77 -0
  80. package/parser/parseStatement.js +184 -0
  81. package/parser/parserExpressions.js +115 -0
  82. package/parser/parserUtils.js +19 -0
  83. package/parser/statements/controlFlowParsers.js +106 -0
  84. package/parser/statements/functionParsers.js +314 -0
  85. package/parser/statements/moduleParsers.js +57 -0
  86. package/parser/statements/patternMatchParsers.js +124 -0
  87. package/parser/statements/variableParsers.js +155 -0
  88. package/repl.js +325 -0
  89. package/test.js +47 -0
  90. package/tools/PrettyPrinter.js +3 -0
  91. package/tools/convert/Args.js +46 -0
  92. package/tools/convert/Registry.js +91 -0
  93. package/tools/convert/Transpiler.js +78 -0
  94. package/tools/convert/plugins/README.md +66 -0
  95. package/tools/convert/plugins/alya/index.js +10 -0
  96. package/tools/convert/plugins/alya/to_alya.js +289 -0
  97. package/tools/convert/plugins/alya/visitors/expressions.js +257 -0
  98. package/tools/convert/plugins/alya/visitors/statements.js +403 -0
  99. package/tools/convert/plugins/base_converter.js +228 -0
  100. package/tools/convert/plugins/javascript/index.js +10 -0
  101. package/tools/convert/plugins/javascript/mimo_runtime.js +265 -0
  102. package/tools/convert/plugins/javascript/to_js.js +155 -0
  103. package/tools/convert/plugins/javascript/visitors/expressions.js +197 -0
  104. package/tools/convert/plugins/javascript/visitors/patterns.js +102 -0
  105. package/tools/convert/plugins/javascript/visitors/statements.js +236 -0
  106. package/tools/convert/plugins/python/index.js +10 -0
  107. package/tools/convert/plugins/python/mimo_runtime.py +811 -0
  108. package/tools/convert/plugins/python/to_py.js +329 -0
  109. package/tools/convert/plugins/python/visitors/expressions.js +272 -0
  110. package/tools/convert/plugins/python/visitors/patterns.js +100 -0
  111. package/tools/convert/plugins/python/visitors/statements.js +257 -0
  112. package/tools/convert.js +102 -0
  113. package/tools/format/CommentAttacher.js +190 -0
  114. package/tools/format/CommentLexer.js +152 -0
  115. package/tools/format/Printer.js +849 -0
  116. package/tools/format/config.js +107 -0
  117. package/tools/formatter.js +169 -0
  118. package/tools/lint/Linter.js +391 -0
  119. package/tools/lint/config.js +114 -0
  120. package/tools/lint/rules/consistent-return.js +62 -0
  121. package/tools/lint/rules/max-depth.js +56 -0
  122. package/tools/lint/rules/no-empty-function.js +45 -0
  123. package/tools/lint/rules/no-magic-numbers.js +46 -0
  124. package/tools/lint/rules/no-shadow.js +113 -0
  125. package/tools/lint/rules/no-unused-vars.js +26 -0
  126. package/tools/lint/rules/prefer-const.js +19 -0
  127. package/tools/linter.js +261 -0
  128. package/tools/replFormatter.js +93 -0
  129. package/tools/stamp-version.js +32 -0
  130. package/web/index.js +9 -0
  131. package/bun.lockb +0 -0
  132. package/cli.js +0 -84
  133. package/compiler/execute/interpreter.js +0 -68
  134. package/compiler/execute/interpreters/binary.js +0 -12
  135. package/compiler/execute/interpreters/call.js +0 -10
  136. package/compiler/execute/interpreters/if.js +0 -10
  137. package/compiler/execute/interpreters/try-catch.js +0 -10
  138. package/compiler/execute/interpreters/while.js +0 -8
  139. package/compiler/execute/utils/createfunction.js +0 -11
  140. package/compiler/execute/utils/evaluate.js +0 -20
  141. package/compiler/execute/utils/operate.js +0 -23
  142. package/compiler/lexer/processToken.js +0 -40
  143. package/compiler/lexer/tokenTypes.js +0 -4
  144. package/compiler/lexer/tokenizer.js +0 -74
  145. package/compiler/parser/expression/comparison.js +0 -18
  146. package/compiler/parser/expression/identifier.js +0 -29
  147. package/compiler/parser/expression/number.js +0 -10
  148. package/compiler/parser/expression/operator.js +0 -21
  149. package/compiler/parser/expression/punctuation.js +0 -31
  150. package/compiler/parser/expression/string.js +0 -6
  151. package/compiler/parser/parseExpression.js +0 -27
  152. package/compiler/parser/parseStatement.js +0 -34
  153. package/compiler/parser/parser.js +0 -45
  154. package/compiler/parser/statement/call.js +0 -26
  155. package/compiler/parser/statement/function.js +0 -29
  156. package/compiler/parser/statement/if.js +0 -34
  157. package/compiler/parser/statement/return.js +0 -10
  158. package/compiler/parser/statement/set.js +0 -11
  159. package/compiler/parser/statement/show.js +0 -10
  160. package/compiler/parser/statement/try-catch.js +0 -25
  161. package/compiler/parser/statement/while.js +0 -22
  162. package/converter/go/convert.js +0 -110
  163. package/converter/js/convert.js +0 -107
  164. package/jsconfig.json +0 -27
  165. package/vite.config.js +0 -17
@@ -0,0 +1,151 @@
1
+ // In interpreter/evaluators/collectionEvaluator.js
2
+ import { suggestNearestName } from "../suggestions.js";
3
+
4
+ export function evaluateArrayLiteral(interpreter, node) {
5
+ const result = [];
6
+ for (const element of node.elements) {
7
+ if (element.type === "SpreadElement") {
8
+ const spreadValue = interpreter.visitNode(element.argument);
9
+ if (!Array.isArray(spreadValue)) {
10
+ throw interpreter.errorHandler.createRuntimeError(
11
+ `Cannot spread non-array value of type '${typeof spreadValue}'.`,
12
+ element,
13
+ "TYPE001",
14
+ 'Spread operator "..." can only be used with arrays.'
15
+ );
16
+ }
17
+ result.push(...spreadValue);
18
+ } else {
19
+ result.push(interpreter.visitNode(element));
20
+ }
21
+ }
22
+ return result;
23
+ }
24
+
25
+ export function evaluateArrayAccess(interpreter, node) {
26
+ const object = interpreter.visitNode(node.object);
27
+ const index = interpreter.visitNode(node.index); // This is the key or the index
28
+
29
+ // Case 1: The target is an Array
30
+ if (Array.isArray(object)) {
31
+ if (typeof index !== 'number' || !Number.isInteger(index)) {
32
+ throw interpreter.errorHandler.createRuntimeError(
33
+ `Array index must be an integer. Got type '${typeof index}'.`,
34
+ node.index, 'TYPE001', "Provide an integer for array indexing."
35
+ );
36
+ }
37
+ return (index < 0 || index >= object.length) ? null : object[index];
38
+ }
39
+
40
+ // Case 2: The target is an Object
41
+ if (typeof object === 'object' && object !== null) {
42
+ const key = String(index);
43
+ return Object.prototype.hasOwnProperty.call(object, key) ? object[key] : null;
44
+ }
45
+
46
+ // Case 3: The target is a String
47
+ if (typeof object === 'string') {
48
+ if (typeof index !== 'number' || !Number.isInteger(index)) {
49
+ throw interpreter.errorHandler.createRuntimeError(
50
+ `String index must be an integer. Got type '${typeof index}'.`,
51
+ node.index, 'TYPE001', "Provide an integer for string character access."
52
+ );
53
+ }
54
+ return (index < 0 || index >= object.length) ? null : object.charAt(index);
55
+ }
56
+
57
+ // If none of the above, it's an error
58
+ throw interpreter.errorHandler.createRuntimeError(
59
+ `Cannot access property on value of type '${typeof object}'. Only arrays, objects, and strings can be indexed.`,
60
+ node.object, 'TYPE002', "Ensure you are using bracket notation on a valid collection type."
61
+ );
62
+ }
63
+
64
+ export function evaluateObjectLiteral(interpreter, node) {
65
+ const obj = {};
66
+ for (const prop of node.properties) {
67
+ if (prop && prop.type === "SpreadElement") {
68
+ const spreadValue = interpreter.visitNode(prop.argument);
69
+ if (typeof spreadValue !== 'object' || spreadValue === null || Array.isArray(spreadValue)) {
70
+ throw interpreter.errorHandler.createRuntimeError(
71
+ `Cannot spread non-object value of type '${typeof spreadValue}'.`,
72
+ prop.argument,
73
+ "TYPE001",
74
+ 'Object spread operator "..." can only be used with objects.'
75
+ );
76
+ }
77
+ Object.assign(obj, spreadValue);
78
+ } else {
79
+ const value = interpreter.visitNode(prop.value);
80
+ obj[prop.key] = value;
81
+ }
82
+ }
83
+ return obj;
84
+ }
85
+
86
+ export function evaluatePropertyAccess(interpreter, node) {
87
+ const object = interpreter.visitNode(node.object);
88
+
89
+ if (object === null || object === undefined) {
90
+ throw interpreter.errorHandler.createRuntimeError(
91
+ `Cannot access property '${node.property}' of 'null'.`,
92
+ node.object, // AST node for the object being accessed
93
+ "REF001",
94
+ "Ensure the object is not null or undefined before accessing its properties. Consider using safe navigation (?. )."
95
+ );
96
+ }
97
+
98
+ // Allow property access on strings (e.g., `my_string.length`)
99
+ if (typeof object !== "object" && typeof object !== "string") {
100
+ throw interpreter.errorHandler.createRuntimeError(
101
+ `Cannot access property '${node.property
102
+ }' of non-object value of type '${typeof object}'.`,
103
+ node.object,
104
+ "TYPE002",
105
+ "Properties can only be accessed on objects or strings."
106
+ );
107
+ }
108
+
109
+ if (!(node.property in object)) {
110
+ const candidates = typeof object === "object" && object !== null
111
+ ? Object.keys(object)
112
+ : Object.getOwnPropertyNames(object);
113
+ const nearest = suggestNearestName(node.property, candidates);
114
+ const suggestion = nearest
115
+ ? `Did you mean '${nearest}'?`
116
+ : "Check for typos or ensure the property exists.";
117
+ throw interpreter.errorHandler.createRuntimeError(
118
+ `Property '${node.property}' does not exist on ${Array.isArray(object) ? "array" : typeof object}.`,
119
+ node,
120
+ "PROP001",
121
+ suggestion
122
+ );
123
+ }
124
+
125
+ return object[node.property];
126
+ }
127
+
128
+ export function evaluateSafePropertyAccess(interpreter, node) {
129
+ const object = interpreter.visitNode(node.object);
130
+
131
+ if (object === null || object === undefined) {
132
+ return null;
133
+ }
134
+
135
+ // If property doesn't exist, return null instead of letting evaluatePropertyAccess throw
136
+ if (!(node.property in object)) {
137
+ return null;
138
+ }
139
+
140
+ return evaluatePropertyAccess(interpreter, { ...node, type: 'PropertyAccess' });
141
+ }
142
+
143
+ export function evaluateSafeArrayAccess(interpreter, node) {
144
+ const object = interpreter.visitNode(node.object);
145
+
146
+ if (object === null || object === undefined) {
147
+ return null;
148
+ }
149
+
150
+ return evaluateArrayAccess(interpreter, { ...node, type: 'ArrayAccess' });
151
+ }
@@ -0,0 +1,76 @@
1
+ // interpreter/evaluators/functionCallEvaluator.js
2
+
3
+ import { FunctionValue } from '../Values.js';
4
+ import { BuiltinFunction } from '../BuiltinFunction.js';
5
+ import { ErrorHandler } from '../ErrorHandler.js';
6
+
7
+ export function evaluateAnonymousFunction(interpreter, node) {
8
+ // Create a function value with the current environment as closure
9
+ return new FunctionValue(node, interpreter.currentEnv);
10
+ }
11
+
12
+ export function evaluateCallExpression(interpreter, node) {
13
+ // Evaluate the callee (could be identifier or module access)
14
+ let func;
15
+ let functionName;
16
+
17
+ if (typeof node.callee === 'string') {
18
+ // Legacy support for string callee
19
+ func = interpreter.currentEnv.lookup(node.callee);
20
+ functionName = node.callee;
21
+ } else {
22
+ // New AST node callee (Identifier, ModuleAccess, or expression)
23
+ func = interpreter.visitNode(node.callee);
24
+ if (node.callee.type === 'ModuleAccess') {
25
+ functionName = `${node.callee.module}.${node.callee.property}`;
26
+ } else if (node.callee.type === 'Identifier') {
27
+ functionName = node.callee.name;
28
+ } else {
29
+ functionName = '<anonymous function>';
30
+ }
31
+ }
32
+
33
+ if (
34
+ !(func instanceof FunctionValue) &&
35
+ !(func instanceof BuiltinFunction)
36
+ ) {
37
+ throw interpreter.errorHandler.createRuntimeError(
38
+ `'${functionName}' is not a callable function.`,
39
+ node,
40
+ 'TYPE002',
41
+ 'Ensure you are calling a function or method.'
42
+ );
43
+ }
44
+
45
+ const args = [];
46
+ for (const arg of node.arguments) {
47
+ if (arg.type === 'SpreadElement') {
48
+ const spreadValue = interpreter.visitNode(arg.argument);
49
+ if (!Array.isArray(spreadValue)) {
50
+ throw interpreter.errorHandler.createRuntimeError(
51
+ `Cannot spread non-array value in function call.`,
52
+ arg.argument,
53
+ 'TYPE001'
54
+ );
55
+ }
56
+ args.push(...spreadValue);
57
+ } else {
58
+ args.push(interpreter.visitNode(arg));
59
+ }
60
+ }
61
+ const result = func.call(interpreter, args, node); // Pass the CallExpression node
62
+
63
+ return result;
64
+ }
65
+
66
+ export function evaluateSafeCallExpression(interpreter, node) {
67
+ const calleeValue = interpreter.visitNode(node.callee);
68
+
69
+ if (calleeValue === null || calleeValue === undefined) {
70
+ return null;
71
+ }
72
+
73
+ // Since we already evaluated the callee, we could refine this to avoid double evaluation.
74
+ // However, to keep it simple and consistent with other safe navigation implementations:
75
+ return evaluateCallExpression(interpreter, { ...node, type: 'CallExpression' });
76
+ }
@@ -0,0 +1,27 @@
1
+ // interpreter/evaluators/literalEvaluator.js
2
+ import { suggestNearestName } from "../suggestions.js";
3
+
4
+ export function evaluateIdentifier(interpreter, node) {
5
+ try {
6
+ return interpreter.currentEnv.lookup(node.name);
7
+ } catch (error) {
8
+ if (error?.message?.startsWith("Undefined variable:")) {
9
+ const candidates = interpreter.currentEnv.getVisibleNames();
10
+ const nearest = suggestNearestName(node.name, candidates);
11
+ const suggestion = nearest
12
+ ? `Did you mean '${nearest}'?`
13
+ : "Declare the variable before using it.";
14
+ throw interpreter.errorHandler.createRuntimeError(
15
+ `Undefined variable '${node.name}'.`,
16
+ node,
17
+ "REF001",
18
+ suggestion
19
+ );
20
+ }
21
+ throw error;
22
+ }
23
+ }
24
+
25
+ export function evaluateLiteral(interpreter, node) {
26
+ return node.value;
27
+ }
@@ -0,0 +1,25 @@
1
+
2
+ export function evaluateModuleAccess(interpreter, node) {
3
+ // A module alias is now just a regular variable in the environment.
4
+ // The `import` statement defines it as a constant object.
5
+ const moduleObject = interpreter.currentEnv.lookup(node.module);
6
+
7
+ if (typeof moduleObject !== 'object' || moduleObject === null) {
8
+ throw interpreter.errorHandler.createRuntimeError(
9
+ `Cannot access property on non-module/non-object '${node.module}'.`,
10
+ node.module, 'TYPE002'
11
+ );
12
+ }
13
+
14
+ // Check if property exists in the module's exports object.
15
+ if (!Object.prototype.hasOwnProperty.call(moduleObject, node.property)) {
16
+ const availableProps = Object.keys(moduleObject).sort();
17
+ throw interpreter.errorHandler.createRuntimeError(
18
+ `Property '${node.property}' not found in module '${node.module}'.`,
19
+ node, 'MOD002',
20
+ `Available properties in '${node.module}': ${availableProps.join(', ')}`
21
+ );
22
+ }
23
+
24
+ return moduleObject[node.property];
25
+ }
@@ -0,0 +1,20 @@
1
+ // interpreter/evaluators/templateLiteralEvaluator.js
2
+
3
+ import { stringify } from '../Utils.js';
4
+
5
+ export function evaluateTemplateLiteral(interpreter, node) {
6
+ let resultString = "";
7
+
8
+ for (const part of node.parts) {
9
+ if (part.type === 'Literal') {
10
+ // This is a string fragment from the template
11
+ resultString += part.value;
12
+ } else {
13
+ // This is an interpolated expression - evaluate it and stringify
14
+ const value = interpreter.visitNode(part);
15
+ resultString += stringify(value);
16
+ }
17
+ }
18
+
19
+ return resultString;
20
+ }
@@ -0,0 +1,37 @@
1
+ import { ErrorHandler } from "../ErrorHandler.js";
2
+ import { ReturnValue } from "../Values.js";
3
+
4
+ export class BaseExecutor {
5
+ constructor(interpreter) {
6
+ this.interpreter = interpreter;
7
+ }
8
+
9
+ // Helper function to find the nearest function scope or global scope
10
+ findFunctionScope(env) {
11
+ let currentEnv = env;
12
+ while (currentEnv && !currentEnv.isGlobalScope) {
13
+ if (currentEnv.vars.size > 0) {
14
+ return currentEnv;
15
+ }
16
+ currentEnv = currentEnv.parent;
17
+ }
18
+ return currentEnv || this.interpreter.globalEnv;
19
+ }
20
+
21
+ // Helper: treat any environment that is the closure for a function as a function scope
22
+ isFunctionScope(env) {
23
+ return env.isFunctionContext; // Now correctly identifies function scopes
24
+ }
25
+
26
+ executeProgram(node) {
27
+ this.interpreter.hoistFunctionDeclarations(node.body);
28
+ let result = null;
29
+ for (const statement of node.body) {
30
+ result = this.interpreter.visitNode(statement);
31
+ if (result instanceof ReturnValue) {
32
+ return result.value;
33
+ }
34
+ }
35
+ return result;
36
+ }
37
+ }
@@ -0,0 +1,206 @@
1
+ import { ErrorHandler } from "../ErrorHandler.js";
2
+ import { Environment } from "../environment.js";
3
+ import { isTruthy, stringify } from "../Utils.js";
4
+ import { BreakException, ContinueException, ReturnValue } from "../Values.js";
5
+ import { BaseExecutor } from "./BaseExecutor.js";
6
+ import { MimoError } from "../MimoError.js";
7
+
8
+ export class ControlFlowExecutor extends BaseExecutor {
9
+ executeIfStatement(node) {
10
+ const condition = this.interpreter.visitNode(node.condition);
11
+
12
+ if (isTruthy(condition)) {
13
+ // Create new block scope for consequent
14
+ const blockEnv = new Environment(this.interpreter.currentEnv);
15
+ return this.interpreter.executeBlock(node.consequent, blockEnv);
16
+ }
17
+
18
+ if (node.alternate) {
19
+ if (node.alternate.type === "IfStatement") {
20
+ return this.interpreter.visitNode(node.alternate);
21
+ } else {
22
+ const blockEnv = new Environment(this.interpreter.currentEnv);
23
+ return this.interpreter.executeBlock(node.alternate, blockEnv);
24
+ }
25
+ }
26
+
27
+ return null;
28
+ }
29
+
30
+ executeGuardStatement(node) {
31
+ const condition = this.interpreter.visitNode(node.condition);
32
+
33
+ // If the condition is met, do nothing (continue execution sequentially)
34
+ if (isTruthy(condition)) {
35
+ return null;
36
+ }
37
+
38
+ // Execute the alternate (else) block
39
+ const blockEnv = new Environment(this.interpreter.currentEnv);
40
+ const result = this.interpreter.executeBlock(node.alternate, blockEnv);
41
+
42
+ // Check if the else block terminated execution correctly
43
+ // It should either return a ReturnValue, or throw a BreakException/ContinueException/MimoError.
44
+ // If it reaches this point without doing so, it means the guard block didn't properly exit or the result wasn't a return.
45
+ if (!(result instanceof ReturnValue)) {
46
+ throw this.interpreter.errorHandler.createRuntimeError(
47
+ `A guard statement's 'else' block must terminate execution (e.g., via return, throw, break, or continue).`,
48
+ node.alternate[node.alternate.length - 1] || node, // point to last statement or guard itself
49
+ "CTRL001",
50
+ "Add a return, throw, break, or continue statement to the end of the guard's else block."
51
+ );
52
+ }
53
+
54
+ return result; // returning the ReturnValue effectively terminates the current block
55
+ }
56
+
57
+ executeWhileStatement(node) {
58
+ let result = null;
59
+ while (isTruthy(this.interpreter.visitNode(node.condition))) {
60
+ try {
61
+ // Create new block scope for each iteration
62
+ const blockEnv = new Environment(this.interpreter.currentEnv);
63
+ result = this.interpreter.executeBlock(node.body, blockEnv);
64
+ if (result instanceof ReturnValue) {
65
+ return result;
66
+ }
67
+ } catch (exception) {
68
+ if (exception instanceof BreakException) {
69
+ if (exception.label && exception.label !== node.label) throw exception;
70
+ break;
71
+ }
72
+ if (exception instanceof ContinueException) {
73
+ if (exception.label && exception.label !== node.label) throw exception;
74
+ continue;
75
+ }
76
+ throw exception;
77
+ }
78
+ }
79
+ return result;
80
+ }
81
+
82
+ executeForStatement(node) {
83
+ const iterable = this.interpreter.visitNode(node.iterable);
84
+ if (!Array.isArray(iterable)) {
85
+ throw this.interpreter.errorHandler.createRuntimeError(
86
+ `For loop requires an iterable (array). Got '${typeof iterable}'.`,
87
+ node.iterable,
88
+ "TYPE001",
89
+ 'Ensure the expression after "in" is an array.',
90
+ this.interpreter.callStack
91
+ );
92
+ }
93
+
94
+ const previousEnv = this.interpreter.currentEnv;
95
+ this.interpreter.currentEnv = new Environment(previousEnv);
96
+
97
+ let result = null;
98
+ try {
99
+ for (const item of iterable) {
100
+ try {
101
+ // Create a new block scope for each iteration
102
+ const iterationEnv = new Environment(this.interpreter.currentEnv);
103
+ iterationEnv.define(node.variable.name, item);
104
+ result = this.interpreter.executeBlock(node.body, iterationEnv);
105
+ if (result instanceof ReturnValue) {
106
+ return result;
107
+ }
108
+ } catch (exception) {
109
+ if (exception instanceof BreakException) {
110
+ if (exception.label && exception.label !== node.label) throw exception;
111
+ break;
112
+ }
113
+ if (exception instanceof ContinueException) {
114
+ if (exception.label && exception.label !== node.label) throw exception;
115
+ continue;
116
+ }
117
+ throw exception;
118
+ }
119
+ }
120
+ } finally {
121
+ this.interpreter.currentEnv = previousEnv;
122
+ }
123
+ return result;
124
+ }
125
+
126
+ executeLoopStatement(node) {
127
+ let result = null;
128
+ while (true) {
129
+ try {
130
+ // Create new block scope for each iteration
131
+ const blockEnv = new Environment(this.interpreter.currentEnv);
132
+ result = this.interpreter.executeBlock(node.body, blockEnv);
133
+ if (result instanceof ReturnValue) {
134
+ return result;
135
+ }
136
+ } catch (exception) {
137
+ if (exception instanceof BreakException) {
138
+ if (exception.label && exception.label !== node.label) throw exception;
139
+ break;
140
+ }
141
+ if (exception instanceof ContinueException) {
142
+ if (exception.label && exception.label !== node.label) throw exception;
143
+ continue;
144
+ }
145
+ throw exception;
146
+ }
147
+ }
148
+ return result;
149
+ }
150
+
151
+ executeBreakStatement(node) {
152
+ throw new BreakException(node.label);
153
+ }
154
+
155
+ executeContinueStatement(node) {
156
+ throw new ContinueException(node.label);
157
+ }
158
+
159
+ executeTryStatement(node) {
160
+ try {
161
+ // Create new block scope for try block
162
+ const tryEnv = new Environment(this.interpreter.currentEnv);
163
+ return this.interpreter.executeBlock(node.tryBlock, tryEnv);
164
+ } catch (error) {
165
+ // Don't catch control flow exceptions or return values
166
+ if (
167
+ error instanceof BreakException ||
168
+ error instanceof ContinueException ||
169
+ error instanceof ReturnValue
170
+ ) {
171
+ throw error;
172
+ }
173
+
174
+ if (node.catchVar && node.catchBlock) {
175
+ // Create new block scope for catch block
176
+ const catchEnv = new Environment(this.interpreter.currentEnv);
177
+ // The caught error can be a MimoError or a raw JS Error
178
+ // We pass the MimoError object or a simplified string representation
179
+ // All errors caught by the try block (from Mimo code) should already be MimoErrors.
180
+ // If a raw JS error somehow propagates (which should be rare with Interpreter.interpret's wrapping),
181
+ // wrap it here too for consistency in the catch block.
182
+ const errorValue =
183
+ error instanceof MimoError
184
+ ? error
185
+ : this.interpreter.errorHandler.createRuntimeError(
186
+ `Unhandled JavaScript error: ${error.message}`,
187
+ node, // Use the try statement node for location if a raw JS error occurs
188
+ "INT005",
189
+ "An unexpected error occurred during execution. This indicates a bug in the Mimo interpreter, or an unhandled case. Please report this error."
190
+ );
191
+ catchEnv.define(node.catchVar.name, errorValue);
192
+ return this.interpreter.executeBlock(node.catchBlock, catchEnv);
193
+ }
194
+ // If no catch block, re-throw the error
195
+ throw error;
196
+ }
197
+ }
198
+
199
+ executeLabeledStatement(node) {
200
+ if (node.statement) {
201
+ node.statement.label = node.label;
202
+ return this.interpreter.statementExecutor.executeStatement(node.statement);
203
+ }
204
+ return null;
205
+ }
206
+ }
@@ -0,0 +1,126 @@
1
+ import { BuiltinFunction } from "../BuiltinFunction.js";
2
+ import { stringify } from "../Utils.js";
3
+ import { FunctionValue, ReturnValue } from "../Values.js";
4
+ import { BaseExecutor } from "./BaseExecutor.js";
5
+
6
+ export class FunctionExecutor extends BaseExecutor {
7
+ executeFunctionDeclaration(node) {
8
+ let func = new FunctionValue(node, this.interpreter.currentEnv);
9
+
10
+ // Apply decorators in reverse order (bottom-up)
11
+ if (node.decorators && node.decorators.length > 0) {
12
+ for (let i = node.decorators.length - 1; i >= 0; i--) {
13
+ const decoratorNode = node.decorators[i];
14
+ const decorator = this.interpreter.visitNode(decoratorNode.name);
15
+
16
+ // Arity/Type check for decorator should ideally be here or inside FunctionValue.call
17
+ // For simplicity, we just call it.
18
+
19
+ if (decoratorNode.arguments) {
20
+ // Factory decorator: @retry(3)
21
+ const factoryArgs = decoratorNode.arguments.map(arg => this.interpreter.visitNode(arg));
22
+ const actualDecorator = decorator.call(this.interpreter, factoryArgs, decoratorNode);
23
+
24
+ if (typeof actualDecorator?.call !== 'function') {
25
+ throw this.interpreter.errorHandler.createRuntimeError(
26
+ `Decorator factory '${decoratorNode.name.name}' must return a callable function.`,
27
+ decoratorNode, 'DEC001'
28
+ );
29
+ }
30
+ func = actualDecorator.call(this.interpreter, [func], decoratorNode);
31
+ } else {
32
+ // Simple decorator: @log
33
+ func = decorator.call(this.interpreter, [func], decoratorNode);
34
+ }
35
+ }
36
+ }
37
+
38
+ this.interpreter.currentEnv.define(node.name, func);
39
+ return null;
40
+ }
41
+
42
+ executeCallStatement(node) {
43
+ // Evaluate the callee (could be identifier or module access)
44
+ let func;
45
+ let functionName;
46
+
47
+ if (typeof node.callee === "string") {
48
+ // Legacy support for string callee
49
+ func = this.interpreter.currentEnv.lookup(node.callee);
50
+ functionName = node.callee;
51
+ } else {
52
+ // New AST node callee (Identifier, ModuleAccess, or expression)
53
+ func = this.interpreter.visitNode(node.callee);
54
+ if (node.callee.type === "ModuleAccess") {
55
+ functionName = `${node.callee.module}.${node.callee.property}`;
56
+ } else if (node.callee.type === "Identifier") {
57
+ functionName = node.callee.name;
58
+ } else {
59
+ functionName = "<anonymous function>";
60
+ }
61
+ }
62
+
63
+ if (
64
+ !(func instanceof FunctionValue) &&
65
+ !(func instanceof BuiltinFunction)
66
+ ) {
67
+ throw this.interpreter.errorHandler.createRuntimeError(
68
+ `'${functionName}' is not a callable function.`,
69
+ node,
70
+ "TYPE002",
71
+ "Ensure you are calling a function or method."
72
+ );
73
+ }
74
+
75
+ const args = [];
76
+ for (const arg of node.arguments) {
77
+ if (arg.type === 'SpreadElement') {
78
+ const spreadValue = this.interpreter.visitNode(arg.argument);
79
+ if (!Array.isArray(spreadValue)) {
80
+ throw this.interpreter.errorHandler.createRuntimeError(
81
+ `Cannot spread non-array value in function call.`,
82
+ arg.argument,
83
+ 'TYPE001'
84
+ );
85
+ }
86
+ args.push(...spreadValue);
87
+ } else {
88
+ args.push(this.interpreter.visitNode(arg));
89
+ }
90
+ }
91
+ const result = func.call(this.interpreter, args, node); // Directly call, pass node for location
92
+
93
+ if (node.destination) {
94
+ this.interpreter.currentEnv.define(node.destination.name, result);
95
+ }
96
+ return result;
97
+ }
98
+
99
+ executeReturnStatement(node) {
100
+ const value = node.argument
101
+ ? this.interpreter.visitNode(node.argument)
102
+ : null;
103
+ return new ReturnValue(value);
104
+ }
105
+
106
+ executeShowStatement(node) {
107
+ const value = this.interpreter.visitNode(node.expression);
108
+ const rendered = stringify(value);
109
+ if (this.interpreter.adapter && typeof this.interpreter.adapter.log === "function") {
110
+ this.interpreter.adapter.log(rendered);
111
+ } else {
112
+ console.log(rendered);
113
+ }
114
+ return value;
115
+ }
116
+
117
+ executeThrowStatement(node) {
118
+ const value = this.interpreter.visitNode(node.argument);
119
+ throw this.interpreter.errorHandler.createRuntimeError(
120
+ stringify(value),
121
+ node,
122
+ "USER001", // A code for user-thrown errors
123
+ 'This error was thrown by the "throw" statement.'
124
+ );
125
+ }
126
+ }