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.
- package/.gitattributes +24 -0
- package/LICENSE +21 -0
- package/README.md +71 -39
- package/adapters/browserAdapter.js +86 -0
- package/adapters/nodeAdapter.js +101 -0
- package/bin/cli.js +80 -0
- package/bin/commands/convert.js +27 -0
- package/bin/commands/doctor.js +139 -0
- package/bin/commands/eval.js +39 -0
- package/bin/commands/fmt.js +109 -0
- package/bin/commands/help.js +72 -0
- package/bin/commands/lint.js +117 -0
- package/bin/commands/repl.js +24 -0
- package/bin/commands/run.js +64 -0
- package/bin/commands/test.js +126 -0
- package/bin/utils/colors.js +38 -0
- package/bin/utils/formatError.js +47 -0
- package/bin/utils/fs.js +57 -0
- package/bin/utils/version.js +8 -0
- package/build.js +18 -0
- package/bun.lock +74 -0
- package/index.js +48 -77
- package/index.web.js +364 -0
- package/interpreter/BuiltinFunction.js +32 -0
- package/interpreter/ErrorHandler.js +120 -0
- package/interpreter/ExpressionEvaluator.js +106 -0
- package/interpreter/Interpreter.js +172 -0
- package/interpreter/MimoError.js +112 -0
- package/interpreter/ModuleLoader.js +236 -0
- package/interpreter/StatementExecutor.js +107 -0
- package/interpreter/Utils.js +82 -0
- package/interpreter/Values.js +87 -0
- package/interpreter/coreBuiltins.js +490 -0
- package/interpreter/environment.js +99 -0
- package/interpreter/evaluators/binaryExpressionEvaluator.js +111 -0
- package/interpreter/evaluators/collectionEvaluator.js +151 -0
- package/interpreter/evaluators/functionCallEvaluator.js +76 -0
- package/interpreter/evaluators/literalEvaluator.js +27 -0
- package/interpreter/evaluators/moduleAccessEvaluator.js +25 -0
- package/interpreter/evaluators/templateLiteralEvaluator.js +20 -0
- package/interpreter/executors/BaseExecutor.js +37 -0
- package/interpreter/executors/ControlFlowExecutor.js +206 -0
- package/interpreter/executors/FunctionExecutor.js +126 -0
- package/interpreter/executors/PatternMatchExecutor.js +93 -0
- package/interpreter/executors/VariableExecutor.js +144 -0
- package/interpreter/index.js +8 -0
- package/interpreter/stdlib/array/accessFunctions.js +61 -0
- package/interpreter/stdlib/array/arrayUtils.js +36 -0
- package/interpreter/stdlib/array/higherOrderFunctions.js +285 -0
- package/interpreter/stdlib/array/searchFunctions.js +77 -0
- package/interpreter/stdlib/array/setFunctions.js +49 -0
- package/interpreter/stdlib/array/transformationFunctions.js +68 -0
- package/interpreter/stdlib/array.js +85 -0
- package/interpreter/stdlib/assert.js +143 -0
- package/interpreter/stdlib/datetime.js +170 -0
- package/interpreter/stdlib/env.js +54 -0
- package/interpreter/stdlib/fs.js +161 -0
- package/interpreter/stdlib/http.js +92 -0
- package/interpreter/stdlib/json.js +70 -0
- package/interpreter/stdlib/math.js +309 -0
- package/interpreter/stdlib/object.js +142 -0
- package/interpreter/stdlib/path.js +69 -0
- package/interpreter/stdlib/regex.js +134 -0
- package/interpreter/stdlib/string.js +260 -0
- package/interpreter/suggestions.js +46 -0
- package/lexer/Lexer.js +245 -0
- package/lexer/TokenTypes.js +131 -0
- package/lexer/createToken.js +11 -0
- package/lexer/tokenizers/commentTokenizer.js +45 -0
- package/lexer/tokenizers/literalTokenizer.js +163 -0
- package/lexer/tokenizers/symbolTokenizer.js +69 -0
- package/lexer/tokenizers/whitespaceTokenizer.js +36 -0
- package/package.json +29 -13
- package/parser/ASTNodes.js +448 -0
- package/parser/Parser.js +188 -0
- package/parser/expressions/atomicExpressions.js +165 -0
- package/parser/expressions/conditionalExpressions.js +0 -0
- package/parser/expressions/operatorExpressions.js +79 -0
- package/parser/expressions/primaryExpressions.js +77 -0
- package/parser/parseStatement.js +184 -0
- package/parser/parserExpressions.js +115 -0
- package/parser/parserUtils.js +19 -0
- package/parser/statements/controlFlowParsers.js +106 -0
- package/parser/statements/functionParsers.js +314 -0
- package/parser/statements/moduleParsers.js +57 -0
- package/parser/statements/patternMatchParsers.js +124 -0
- package/parser/statements/variableParsers.js +155 -0
- package/repl.js +325 -0
- package/test.js +47 -0
- package/tools/PrettyPrinter.js +3 -0
- package/tools/convert/Args.js +46 -0
- package/tools/convert/Registry.js +91 -0
- package/tools/convert/Transpiler.js +78 -0
- package/tools/convert/plugins/README.md +66 -0
- package/tools/convert/plugins/alya/index.js +10 -0
- package/tools/convert/plugins/alya/to_alya.js +289 -0
- package/tools/convert/plugins/alya/visitors/expressions.js +257 -0
- package/tools/convert/plugins/alya/visitors/statements.js +403 -0
- package/tools/convert/plugins/base_converter.js +228 -0
- package/tools/convert/plugins/javascript/index.js +10 -0
- package/tools/convert/plugins/javascript/mimo_runtime.js +265 -0
- package/tools/convert/plugins/javascript/to_js.js +155 -0
- package/tools/convert/plugins/javascript/visitors/expressions.js +197 -0
- package/tools/convert/plugins/javascript/visitors/patterns.js +102 -0
- package/tools/convert/plugins/javascript/visitors/statements.js +236 -0
- package/tools/convert/plugins/python/index.js +10 -0
- package/tools/convert/plugins/python/mimo_runtime.py +811 -0
- package/tools/convert/plugins/python/to_py.js +329 -0
- package/tools/convert/plugins/python/visitors/expressions.js +272 -0
- package/tools/convert/plugins/python/visitors/patterns.js +100 -0
- package/tools/convert/plugins/python/visitors/statements.js +257 -0
- package/tools/convert.js +102 -0
- package/tools/format/CommentAttacher.js +190 -0
- package/tools/format/CommentLexer.js +152 -0
- package/tools/format/Printer.js +849 -0
- package/tools/format/config.js +107 -0
- package/tools/formatter.js +169 -0
- package/tools/lint/Linter.js +391 -0
- package/tools/lint/config.js +114 -0
- package/tools/lint/rules/consistent-return.js +62 -0
- package/tools/lint/rules/max-depth.js +56 -0
- package/tools/lint/rules/no-empty-function.js +45 -0
- package/tools/lint/rules/no-magic-numbers.js +46 -0
- package/tools/lint/rules/no-shadow.js +113 -0
- package/tools/lint/rules/no-unused-vars.js +26 -0
- package/tools/lint/rules/prefer-const.js +19 -0
- package/tools/linter.js +261 -0
- package/tools/replFormatter.js +93 -0
- package/tools/stamp-version.js +32 -0
- package/web/index.js +9 -0
- package/bun.lockb +0 -0
- package/cli.js +0 -84
- package/compiler/execute/interpreter.js +0 -68
- package/compiler/execute/interpreters/binary.js +0 -12
- package/compiler/execute/interpreters/call.js +0 -10
- package/compiler/execute/interpreters/if.js +0 -10
- package/compiler/execute/interpreters/try-catch.js +0 -10
- package/compiler/execute/interpreters/while.js +0 -8
- package/compiler/execute/utils/createfunction.js +0 -11
- package/compiler/execute/utils/evaluate.js +0 -20
- package/compiler/execute/utils/operate.js +0 -23
- package/compiler/lexer/processToken.js +0 -40
- package/compiler/lexer/tokenTypes.js +0 -4
- package/compiler/lexer/tokenizer.js +0 -74
- package/compiler/parser/expression/comparison.js +0 -18
- package/compiler/parser/expression/identifier.js +0 -29
- package/compiler/parser/expression/number.js +0 -10
- package/compiler/parser/expression/operator.js +0 -21
- package/compiler/parser/expression/punctuation.js +0 -31
- package/compiler/parser/expression/string.js +0 -6
- package/compiler/parser/parseExpression.js +0 -27
- package/compiler/parser/parseStatement.js +0 -34
- package/compiler/parser/parser.js +0 -45
- package/compiler/parser/statement/call.js +0 -26
- package/compiler/parser/statement/function.js +0 -29
- package/compiler/parser/statement/if.js +0 -34
- package/compiler/parser/statement/return.js +0 -10
- package/compiler/parser/statement/set.js +0 -11
- package/compiler/parser/statement/show.js +0 -10
- package/compiler/parser/statement/try-catch.js +0 -25
- package/compiler/parser/statement/while.js +0 -22
- package/converter/go/convert.js +0 -110
- package/converter/js/convert.js +0 -107
- package/jsconfig.json +0 -27
- 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
|
+
}
|