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.
Files changed (166) hide show
  1. package/.gitattributes +24 -0
  2. package/LICENSE +21 -0
  3. package/README.md +91 -6
  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 +49 -39
  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 -11
  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 -1
  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 -63
  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 -35
  153. package/compiler/parser/parser.js +0 -16
  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/i.js +0 -30
  165. package/jsconfig.json +0 -27
  166. 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
+ }