mimo-lang 1.1.0 → 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 +91 -6
- 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 +49 -39
- 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 -11
- 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 -1
- 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 -63
- 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 -35
- package/compiler/parser/parser.js +0 -16
- 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/i.js +0 -30
- package/jsconfig.json +0 -27
- package/webpack.config.js +0 -9
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { MimoError } from './MimoError.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Handles and formats errors within the Mimo interpreter.
|
|
5
|
+
*/
|
|
6
|
+
export class ErrorHandler {
|
|
7
|
+
constructor(sourceCodeMap = {}, options = {}) {
|
|
8
|
+
this.sourceCodeMap = sourceCodeMap; // Map of filePath -> sourceCodeString
|
|
9
|
+
this.stackFramesProvider = options.stackFramesProvider || null;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Adds or updates source code for a given file path in the handler's map.
|
|
14
|
+
* Useful for REPL or module loading where source becomes available dynamically.
|
|
15
|
+
* @param {string} filePath
|
|
16
|
+
* @param {string} sourceCode
|
|
17
|
+
*/
|
|
18
|
+
addSourceFile(filePath, sourceCode) {
|
|
19
|
+
this.sourceCodeMap[filePath] = sourceCode;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Clears source code for a given file path from the handler's map.
|
|
24
|
+
* Useful for cleaning up memory after a module is processed or a REPL line is done.
|
|
25
|
+
* @param {string} filePath
|
|
26
|
+
*/
|
|
27
|
+
clearSourceFile(filePath) {
|
|
28
|
+
delete this.sourceCodeMap[filePath];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Gets a specific line from the source code map for error reporting.
|
|
33
|
+
* @param {string} filePath - The path to the source file.
|
|
34
|
+
* @param {number} lineNumber - The 1-based line number.
|
|
35
|
+
* @returns {string} The requested line of code, or an empty string if not found.
|
|
36
|
+
*/
|
|
37
|
+
getLine(filePath, lineNumber) {
|
|
38
|
+
const source = this.sourceCodeMap[filePath];
|
|
39
|
+
if (!source) return '';
|
|
40
|
+
const lines = source.split('\n');
|
|
41
|
+
// line numbers are 1-based
|
|
42
|
+
if (lineNumber > 0 && lineNumber <= lines.length) {
|
|
43
|
+
return lines[lineNumber - 1];
|
|
44
|
+
}
|
|
45
|
+
return '';
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Creates and formats a LexerError.
|
|
50
|
+
* @param {string} message - Error message.
|
|
51
|
+
* @param {Object} token - The token that caused the error.
|
|
52
|
+
* @param {string} [code='LEX000'] - Unique error code.
|
|
53
|
+
* @param {string} [suggestion=''] - Actionable suggestion.
|
|
54
|
+
* @returns {MimoError}
|
|
55
|
+
*/
|
|
56
|
+
createLexerError(message, token, code = 'LEX000', suggestion = '') {
|
|
57
|
+
const error = MimoError.lexerError(code, message, token, suggestion);
|
|
58
|
+
error.location.file = token.file || 'unknown'; // Ensure file is set from token
|
|
59
|
+
error.location.snippet = this.getLine(error.location.file, error.location.line);
|
|
60
|
+
return error;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Creates and formats a SyntaxError.
|
|
65
|
+
* @param {string} message - Error message.
|
|
66
|
+
* @param {Object} astNodeOrToken - The AST node or token that caused the error.
|
|
67
|
+
* @param {string} [code='SYN000'] - Unique error code.
|
|
68
|
+
* @param {string} [suggestion=''] - Actionable suggestion.
|
|
69
|
+
* @returns {MimoError}
|
|
70
|
+
*/
|
|
71
|
+
createSyntaxError(message, astNodeOrToken, code = 'SYN000', suggestion = '') {
|
|
72
|
+
const error = MimoError.syntaxError(code, message, astNodeOrToken, suggestion);
|
|
73
|
+
error.location.file = astNodeOrToken.file || 'unknown'; // Ensure file is set
|
|
74
|
+
error.location.snippet = this.getLine(error.location.file, error.location.line);
|
|
75
|
+
return error;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Creates and formats a RuntimeError.
|
|
80
|
+
* @param {string} message - Error message.
|
|
81
|
+
* @param {Object} astNode - The AST node being evaluated when the error occurred.
|
|
82
|
+
* @param {string} [code='RUN000'] - Unique error code.
|
|
83
|
+
* @param {string} [suggestion=''] - Actionable suggestion.
|
|
84
|
+
* @param {Array<Object>} [stackFrames=[]] - The Mimo call stack (from interpreter).
|
|
85
|
+
* @returns {MimoError}
|
|
86
|
+
*/
|
|
87
|
+
createRuntimeError(message, astNode, code = 'RUN000', suggestion = '', stackFrames = null) {
|
|
88
|
+
const framesFromProvider = (stackFrames === null && typeof this.stackFramesProvider === 'function')
|
|
89
|
+
? this.stackFramesProvider()
|
|
90
|
+
: (stackFrames || []);
|
|
91
|
+
|
|
92
|
+
const enrichedFrames = Array.isArray(framesFromProvider)
|
|
93
|
+
? framesFromProvider.map((frame) => ({
|
|
94
|
+
...frame,
|
|
95
|
+
snippet: this.getLine(frame.file || 'unknown', frame.line),
|
|
96
|
+
}))
|
|
97
|
+
: [];
|
|
98
|
+
|
|
99
|
+
const error = MimoError.runtimeError(code, message, astNode, suggestion, enrichedFrames);
|
|
100
|
+
// Ensure file is set for runtime errors too, even if astNode has it.
|
|
101
|
+
// It's crucial for context.
|
|
102
|
+
error.location.file = astNode?.file || 'unknown';
|
|
103
|
+
error.location.snippet = this.getLine(error.location.file, error.location.line);
|
|
104
|
+
return error;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Prints an error to the console.
|
|
109
|
+
* @param {Error|MimoError} error - The error object.
|
|
110
|
+
*/
|
|
111
|
+
printError(error) {
|
|
112
|
+
if (error instanceof MimoError) {
|
|
113
|
+
console.error(error.format(error.location.snippet));
|
|
114
|
+
} else {
|
|
115
|
+
// Fallback for unexpected non-Mimo errors
|
|
116
|
+
console.error(`An unexpected error occurred: ${error.message}`);
|
|
117
|
+
console.error(error.stack);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import {
|
|
2
|
+
evaluateBinaryExpression,
|
|
3
|
+
evaluateUnaryExpression,
|
|
4
|
+
} from "./evaluators/binaryExpressionEvaluator.js";
|
|
5
|
+
import {
|
|
6
|
+
evaluateIdentifier,
|
|
7
|
+
evaluateLiteral,
|
|
8
|
+
} from "./evaluators/literalEvaluator.js";
|
|
9
|
+
import {
|
|
10
|
+
evaluateArrayLiteral,
|
|
11
|
+
evaluateArrayAccess,
|
|
12
|
+
evaluateObjectLiteral,
|
|
13
|
+
evaluatePropertyAccess,
|
|
14
|
+
evaluateSafePropertyAccess,
|
|
15
|
+
evaluateSafeArrayAccess,
|
|
16
|
+
} from "./evaluators/collectionEvaluator.js";
|
|
17
|
+
import {
|
|
18
|
+
evaluateAnonymousFunction,
|
|
19
|
+
evaluateCallExpression,
|
|
20
|
+
evaluateSafeCallExpression,
|
|
21
|
+
} from "./evaluators/functionCallEvaluator.js";
|
|
22
|
+
import { evaluateTemplateLiteral } from "./evaluators/templateLiteralEvaluator.js";
|
|
23
|
+
import { evaluateModuleAccess } from "./evaluators/moduleAccessEvaluator.js";
|
|
24
|
+
|
|
25
|
+
export class ExpressionEvaluator {
|
|
26
|
+
constructor(interpreter) {
|
|
27
|
+
this.interpreter = interpreter;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
evaluateExpression(node) {
|
|
31
|
+
switch (node.type) {
|
|
32
|
+
case "BinaryExpression":
|
|
33
|
+
return evaluateBinaryExpression(this.interpreter, node);
|
|
34
|
+
case "UnaryExpression":
|
|
35
|
+
return evaluateUnaryExpression(this.interpreter, node);
|
|
36
|
+
case "Identifier":
|
|
37
|
+
return evaluateIdentifier(this.interpreter, node);
|
|
38
|
+
case "Literal":
|
|
39
|
+
return evaluateLiteral(this.interpreter, node);
|
|
40
|
+
case "ArrayLiteral":
|
|
41
|
+
return evaluateArrayLiteral(this.interpreter, node);
|
|
42
|
+
case "ObjectLiteral":
|
|
43
|
+
return evaluateObjectLiteral(this.interpreter, node);
|
|
44
|
+
case "PropertyAccess":
|
|
45
|
+
return evaluatePropertyAccess(this.interpreter, node);
|
|
46
|
+
case "SafePropertyAccess":
|
|
47
|
+
return evaluateSafePropertyAccess(this.interpreter, node);
|
|
48
|
+
case "SafeArrayAccess":
|
|
49
|
+
return evaluateSafeArrayAccess(this.interpreter, node);
|
|
50
|
+
case "ArrayAccess":
|
|
51
|
+
return evaluateArrayAccess(this.interpreter, node);
|
|
52
|
+
case "ModuleAccess":
|
|
53
|
+
return evaluateModuleAccess(this.interpreter, node);
|
|
54
|
+
case "AnonymousFunction":
|
|
55
|
+
return evaluateAnonymousFunction(this.interpreter, node);
|
|
56
|
+
case "TemplateLiteral":
|
|
57
|
+
return evaluateTemplateLiteral(this.interpreter, node);
|
|
58
|
+
case "CallExpression":
|
|
59
|
+
return evaluateCallExpression(this.interpreter, node);
|
|
60
|
+
case "SafeCallExpression":
|
|
61
|
+
return evaluateSafeCallExpression(this.interpreter, node);
|
|
62
|
+
case "InlineIfExpression":
|
|
63
|
+
return this.isTruthy(this.interpreter.visitNode(node.condition))
|
|
64
|
+
? this.interpreter.visitNode(node.consequent)
|
|
65
|
+
: this.interpreter.visitNode(node.alternate);
|
|
66
|
+
case "PipeExpression": {
|
|
67
|
+
// Evaluate the piped value (left side)
|
|
68
|
+
const pipedValue = this.interpreter.visitNode(node.left);
|
|
69
|
+
|
|
70
|
+
// Evaluate the callee — if it's an inline-if, we get the function from it
|
|
71
|
+
let func;
|
|
72
|
+
if (node.callee.type === "InlineIfExpression") {
|
|
73
|
+
func = this.isTruthy(this.interpreter.visitNode(node.callee.condition))
|
|
74
|
+
? this.interpreter.visitNode(node.callee.consequent)
|
|
75
|
+
: this.interpreter.visitNode(node.callee.alternate);
|
|
76
|
+
} else {
|
|
77
|
+
func = this.interpreter.visitNode(node.callee);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Evaluate any extra args, then prepend the piped value
|
|
81
|
+
const extraArgs = node.args.map(arg => this.interpreter.visitNode(arg));
|
|
82
|
+
const allArgs = [pipedValue, ...extraArgs];
|
|
83
|
+
|
|
84
|
+
// Call the function
|
|
85
|
+
return func.call(this.interpreter, allArgs, node);
|
|
86
|
+
}
|
|
87
|
+
default:
|
|
88
|
+
throw new Error(`Unknown expression type: ${node.type}`);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Keep isTruthy as a utility method in case it's used elsewhere
|
|
93
|
+
isTruthy(value) {
|
|
94
|
+
// JavaScript-like truthiness: false, 0, "", null, undefined are falsy
|
|
95
|
+
if (
|
|
96
|
+
value === false ||
|
|
97
|
+
value === 0 ||
|
|
98
|
+
value === "" ||
|
|
99
|
+
value === null ||
|
|
100
|
+
value === undefined
|
|
101
|
+
) {
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
return true;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file The main Mimo Interpreter class.
|
|
3
|
+
* This file contains the core Interpreter class that orchestrates the entire
|
|
4
|
+
* execution process, managing environments, call stacks, and dispatching
|
|
5
|
+
* to specialized executors and evaluators.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { initializeBuiltins } from "./coreBuiltins.js";
|
|
9
|
+
import { initializeArrayModule } from "./stdlib/array.js";
|
|
10
|
+
|
|
11
|
+
import { ErrorHandler } from "./ErrorHandler.js";
|
|
12
|
+
import { MimoError } from "./MimoError.js";
|
|
13
|
+
import { ExpressionEvaluator } from "./ExpressionEvaluator.js";
|
|
14
|
+
import { ModuleLoader } from "./ModuleLoader.js";
|
|
15
|
+
import { StatementExecutor } from "./StatementExecutor.js";
|
|
16
|
+
import { FunctionValue, ReturnValue } from "./Values.js";
|
|
17
|
+
import { Environment } from "./environment.js";
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* The core Mimo interpreter. It walks the AST produced by the parser and
|
|
21
|
+
* executes the program's logic.
|
|
22
|
+
*/
|
|
23
|
+
export class Interpreter {
|
|
24
|
+
/**
|
|
25
|
+
* Creates a new Interpreter instance.
|
|
26
|
+
* @param {object} adapter - The host adapter for interacting with the underlying system (e.g., file system, console).
|
|
27
|
+
*/
|
|
28
|
+
constructor(adapter) {
|
|
29
|
+
this.adapter = adapter;
|
|
30
|
+
this.callStack = [];
|
|
31
|
+
this.globalEnv = new Environment(null, true);
|
|
32
|
+
this.currentEnv = this.globalEnv;
|
|
33
|
+
this.expressionEvaluator = new ExpressionEvaluator(this);
|
|
34
|
+
this.statementExecutor = new StatementExecutor(this);
|
|
35
|
+
this.errorHandler = new ErrorHandler({}, {
|
|
36
|
+
stackFramesProvider: () => [...this.callStack],
|
|
37
|
+
});
|
|
38
|
+
this.moduleLoader = new ModuleLoader(this);
|
|
39
|
+
this.currentFile = "/repl";
|
|
40
|
+
|
|
41
|
+
initializeBuiltins(this.globalEnv);
|
|
42
|
+
initializeArrayModule(this.globalEnv);
|
|
43
|
+
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Interprets an AST and executes the Mimo program. This is the main entry point.
|
|
48
|
+
* @param {object} ast - The Abstract Syntax Tree (Program node) to interpret.
|
|
49
|
+
* @param {string} [filePath="main.mimo"] - The path of the file being executed, for error reporting.
|
|
50
|
+
* @returns {any} The result of the last executed statement.
|
|
51
|
+
* @throws {MimoError} If any lexing, parsing, or runtime errors occur.
|
|
52
|
+
*/
|
|
53
|
+
interpret(ast, filePath = "main.mimo") {
|
|
54
|
+
const previousCurrentFile = this.currentFile;
|
|
55
|
+
this.currentFile = filePath;
|
|
56
|
+
|
|
57
|
+
try {
|
|
58
|
+
this.pushCallStack('<root>', ast);
|
|
59
|
+
const result = this.visitNode(ast);
|
|
60
|
+
return result;
|
|
61
|
+
} catch (error) {
|
|
62
|
+
if (error instanceof MimoError) {
|
|
63
|
+
throw error;
|
|
64
|
+
} else {
|
|
65
|
+
const lastFrameNode = this.callStack.length > 0 ? this.callStack[this.callStack.length - 1].node : ast;
|
|
66
|
+
const mimoError = this.errorHandler.createRuntimeError(
|
|
67
|
+
`Internal JavaScript error: ${error.message}`,
|
|
68
|
+
lastFrameNode,
|
|
69
|
+
'INT001',
|
|
70
|
+
'An unexpected internal error occurred. This might be a bug in the Mimo interpreter.',
|
|
71
|
+
this.callStack
|
|
72
|
+
);
|
|
73
|
+
throw mimoError;
|
|
74
|
+
}
|
|
75
|
+
} finally {
|
|
76
|
+
this.popCallStack();
|
|
77
|
+
this.currentFile = previousCurrentFile;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Pushes a new frame onto the Mimo call stack for better error reporting.
|
|
83
|
+
* @param {string} functionName - The name of the function being called.
|
|
84
|
+
* @param {object} node - The AST node of the call site (for location info).
|
|
85
|
+
*/
|
|
86
|
+
pushCallStack(functionName, node) {
|
|
87
|
+
if (node && node.line !== undefined && node.column !== undefined && node.file !== undefined) {
|
|
88
|
+
this.callStack.push({
|
|
89
|
+
functionName: functionName || '<anonymous>',
|
|
90
|
+
file: node.file,
|
|
91
|
+
line: node.line,
|
|
92
|
+
column: node.column,
|
|
93
|
+
node: node
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Pops the top frame from the Mimo call stack.
|
|
100
|
+
*/
|
|
101
|
+
popCallStack() {
|
|
102
|
+
this.callStack.pop();
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* The main visitor dispatch method. It determines whether a node is an
|
|
107
|
+
* expression or a statement and delegates to the appropriate handler.
|
|
108
|
+
* @param {object} node - The AST node to visit.
|
|
109
|
+
* @returns {any} The result of the node's evaluation or execution.
|
|
110
|
+
*/
|
|
111
|
+
visitNode(node) {
|
|
112
|
+
if (this.isExpression(node)) {
|
|
113
|
+
return this.expressionEvaluator.evaluateExpression(node);
|
|
114
|
+
}
|
|
115
|
+
return this.statementExecutor.executeStatement(node);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Checks if a given AST node is an expression type.
|
|
120
|
+
* @param {object} node - The AST node to check.
|
|
121
|
+
* @returns {boolean} True if the node is an expression.
|
|
122
|
+
*/
|
|
123
|
+
isExpression(node) {
|
|
124
|
+
const expressionTypes = [
|
|
125
|
+
"BinaryExpression", "UnaryExpression", "Identifier", "Literal",
|
|
126
|
+
"ArrayLiteral", "ArrayAccess", "ObjectLiteral", "PropertyAccess",
|
|
127
|
+
"SafePropertyAccess", "SafeArrayAccess", "SafeCallExpression",
|
|
128
|
+
"ModuleAccess", "AnonymousFunction", "TemplateLiteral",
|
|
129
|
+
"CallExpression", "InlineIfExpression", "PipeExpression"
|
|
130
|
+
];
|
|
131
|
+
return expressionTypes.includes(node.type);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Executes a block of statements within a new or existing environment.
|
|
136
|
+
* @param {object[]} statements - An array of statement AST nodes.
|
|
137
|
+
* @param {Environment} [env=null] - The environment to execute the block in. If null, uses the current environment.
|
|
138
|
+
* @returns {any} The result of the last statement in the block, or a ReturnValue.
|
|
139
|
+
*/
|
|
140
|
+
executeBlock(statements, env = null) {
|
|
141
|
+
const previousEnv = this.currentEnv;
|
|
142
|
+
if (env) {
|
|
143
|
+
this.currentEnv = env;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
try {
|
|
147
|
+
this.hoistFunctionDeclarations(statements);
|
|
148
|
+
let result = null;
|
|
149
|
+
for (const statement of statements) {
|
|
150
|
+
result = this.visitNode(statement);
|
|
151
|
+
if (result instanceof ReturnValue) {
|
|
152
|
+
return result;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
return result;
|
|
156
|
+
} finally {
|
|
157
|
+
if (env) {
|
|
158
|
+
this.currentEnv = previousEnv;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
hoistFunctionDeclarations(statements) {
|
|
164
|
+
for (const statement of statements) {
|
|
165
|
+
if (statement?.type === "FunctionDeclaration") {
|
|
166
|
+
if (!this.currentEnv.hasInCurrentScope(statement.name)) {
|
|
167
|
+
this.currentEnv.define(statement.name, new FunctionValue(statement, this.currentEnv));
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Represents a Mimo language error.
|
|
3
|
+
* Includes detailed information for better debugging and user experience.
|
|
4
|
+
*/
|
|
5
|
+
export class MimoError extends Error {
|
|
6
|
+
/**
|
|
7
|
+
* @param {string} type - 'LexerError', 'SyntaxError', 'RuntimeError'
|
|
8
|
+
* @param {string} code - A unique error code (e.g., 'PARSE001', 'RUNTIME001', 'TYPE001')
|
|
9
|
+
* @param {string} message - A concise description of the error.
|
|
10
|
+
* @param {string} [suggestion=''] - An actionable suggestion for how to fix the error.
|
|
11
|
+
* @param {Object} [location={}] - Object containing line, column, file, and a code snippet.
|
|
12
|
+
* @param {Array<Object>} [stackFrames=[]] - An array of stack frames for runtime errors.
|
|
13
|
+
*/
|
|
14
|
+
constructor(type, code, message, suggestion = '', location = {}, stackFrames = []) {
|
|
15
|
+
super(message);
|
|
16
|
+
this.name = type;
|
|
17
|
+
this.type = type;
|
|
18
|
+
this.code = code;
|
|
19
|
+
this.suggestion = suggestion;
|
|
20
|
+
|
|
21
|
+
// Ensure all location properties are explicitly defined, defaulting if not provided.
|
|
22
|
+
this.location = {
|
|
23
|
+
file: location.file || 'unknown',
|
|
24
|
+
line: location.line,
|
|
25
|
+
column: location.column,
|
|
26
|
+
start: location.start, // <--- Add start
|
|
27
|
+
length: location.length, // <--- Add length
|
|
28
|
+
snippet: location.snippet, // <--- Add snippet
|
|
29
|
+
};
|
|
30
|
+
this.stackFrames = stackFrames;
|
|
31
|
+
|
|
32
|
+
// Standard JavaScript error properties
|
|
33
|
+
if (Error.captureStackTrace) {
|
|
34
|
+
Error.captureStackTrace(this, this.constructor);
|
|
35
|
+
} else {
|
|
36
|
+
this.stack = new Error().stack;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
static lexerError(code, message, token, suggestion) {
|
|
41
|
+
const location = token ? {
|
|
42
|
+
line: token.line,
|
|
43
|
+
column: token.column,
|
|
44
|
+
start: token.start, // <--- Add start
|
|
45
|
+
length: token.length, // <--- Add length
|
|
46
|
+
file: token.file
|
|
47
|
+
} : {};
|
|
48
|
+
return new MimoError('LexerError', code, message, suggestion, location);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
static syntaxError(code, message, astNodeOrToken, suggestion) {
|
|
52
|
+
const location = astNodeOrToken ? {
|
|
53
|
+
line: astNodeOrToken.line,
|
|
54
|
+
column: astNodeOrToken.column,
|
|
55
|
+
start: astNodeOrToken.start, // <--- Add start
|
|
56
|
+
length: astNodeOrToken.length, // <--- Add length
|
|
57
|
+
file: astNodeOrToken.file
|
|
58
|
+
} : {};
|
|
59
|
+
return new MimoError('SyntaxError', code, message, suggestion, location);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
static runtimeError(code, message, astNode, suggestion, stackFrames) {
|
|
63
|
+
const location = astNode ? {
|
|
64
|
+
line: astNode.line,
|
|
65
|
+
column: astNode.column,
|
|
66
|
+
start: astNode.start, // <--- Add start
|
|
67
|
+
length: astNode.length, // <--- Add length
|
|
68
|
+
file: astNode.file,
|
|
69
|
+
} : {};
|
|
70
|
+
return new MimoError('RuntimeError', code, message, suggestion, location, stackFrames);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
format(sourceCodeLine = '') {
|
|
74
|
+
let output = `[${this.type} ${this.code}]: ${this.message}\n`;
|
|
75
|
+
|
|
76
|
+
if (this.location.file) {
|
|
77
|
+
output += ` at ${this.location.file}:${this.location.line}:${this.location.column}\n`;
|
|
78
|
+
} else if (this.location.line !== undefined && this.location.column !== undefined) {
|
|
79
|
+
output += ` at Line ${this.location.line}, Col ${this.location.column}\n`;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (sourceCodeLine) {
|
|
83
|
+
output += `> ${sourceCodeLine.trim()}\n`;
|
|
84
|
+
if (this.location.column !== undefined && sourceCodeLine.trim().length > 0) {
|
|
85
|
+
const trimmedOffset = sourceCodeLine.length - sourceCodeLine.trimStart().length;
|
|
86
|
+
const pointerCol = Math.max(0, this.location.column - 1 - trimmedOffset);
|
|
87
|
+
output += ` ${' '.repeat(pointerCol)}^\n`;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (this.suggestion) {
|
|
92
|
+
output += `Suggestion: ${this.suggestion}\n`;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (this.stackFrames && this.stackFrames.length > 0) {
|
|
96
|
+
output += "Mimo Stack:\n";
|
|
97
|
+
this.stackFrames.forEach(frame => {
|
|
98
|
+
output += ` at ${frame.functionName} (${frame.file || 'unknown'}:${frame.line}:${frame.column})\n`;
|
|
99
|
+
if (frame.snippet) {
|
|
100
|
+
const snippet = String(frame.snippet);
|
|
101
|
+
output += ` > ${snippet.trim()}\n`;
|
|
102
|
+
if (frame.column !== undefined && snippet.trim().length > 0) {
|
|
103
|
+
const trimmedOffset = snippet.length - snippet.trimStart().length;
|
|
104
|
+
const pointerCol = Math.max(0, frame.column - 1 - trimmedOffset);
|
|
105
|
+
output += ` ${' '.repeat(pointerCol)}^\n`;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
return output;
|
|
111
|
+
}
|
|
112
|
+
}
|