mimo-lang 1.1.1 → 2.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (165) hide show
  1. package/.gitattributes +24 -0
  2. package/LICENSE +21 -0
  3. package/README.md +71 -39
  4. package/adapters/browserAdapter.js +86 -0
  5. package/adapters/nodeAdapter.js +101 -0
  6. package/bin/cli.js +80 -0
  7. package/bin/commands/convert.js +27 -0
  8. package/bin/commands/doctor.js +139 -0
  9. package/bin/commands/eval.js +39 -0
  10. package/bin/commands/fmt.js +109 -0
  11. package/bin/commands/help.js +72 -0
  12. package/bin/commands/lint.js +117 -0
  13. package/bin/commands/repl.js +24 -0
  14. package/bin/commands/run.js +64 -0
  15. package/bin/commands/test.js +126 -0
  16. package/bin/utils/colors.js +38 -0
  17. package/bin/utils/formatError.js +47 -0
  18. package/bin/utils/fs.js +57 -0
  19. package/bin/utils/version.js +8 -0
  20. package/build.js +18 -0
  21. package/bun.lock +74 -0
  22. package/index.js +48 -77
  23. package/index.web.js +364 -0
  24. package/interpreter/BuiltinFunction.js +32 -0
  25. package/interpreter/ErrorHandler.js +120 -0
  26. package/interpreter/ExpressionEvaluator.js +106 -0
  27. package/interpreter/Interpreter.js +172 -0
  28. package/interpreter/MimoError.js +112 -0
  29. package/interpreter/ModuleLoader.js +236 -0
  30. package/interpreter/StatementExecutor.js +107 -0
  31. package/interpreter/Utils.js +82 -0
  32. package/interpreter/Values.js +87 -0
  33. package/interpreter/coreBuiltins.js +490 -0
  34. package/interpreter/environment.js +99 -0
  35. package/interpreter/evaluators/binaryExpressionEvaluator.js +111 -0
  36. package/interpreter/evaluators/collectionEvaluator.js +151 -0
  37. package/interpreter/evaluators/functionCallEvaluator.js +76 -0
  38. package/interpreter/evaluators/literalEvaluator.js +27 -0
  39. package/interpreter/evaluators/moduleAccessEvaluator.js +25 -0
  40. package/interpreter/evaluators/templateLiteralEvaluator.js +20 -0
  41. package/interpreter/executors/BaseExecutor.js +37 -0
  42. package/interpreter/executors/ControlFlowExecutor.js +206 -0
  43. package/interpreter/executors/FunctionExecutor.js +126 -0
  44. package/interpreter/executors/PatternMatchExecutor.js +93 -0
  45. package/interpreter/executors/VariableExecutor.js +144 -0
  46. package/interpreter/index.js +8 -0
  47. package/interpreter/stdlib/array/accessFunctions.js +61 -0
  48. package/interpreter/stdlib/array/arrayUtils.js +36 -0
  49. package/interpreter/stdlib/array/higherOrderFunctions.js +285 -0
  50. package/interpreter/stdlib/array/searchFunctions.js +77 -0
  51. package/interpreter/stdlib/array/setFunctions.js +49 -0
  52. package/interpreter/stdlib/array/transformationFunctions.js +68 -0
  53. package/interpreter/stdlib/array.js +85 -0
  54. package/interpreter/stdlib/assert.js +143 -0
  55. package/interpreter/stdlib/datetime.js +170 -0
  56. package/interpreter/stdlib/env.js +54 -0
  57. package/interpreter/stdlib/fs.js +161 -0
  58. package/interpreter/stdlib/http.js +92 -0
  59. package/interpreter/stdlib/json.js +70 -0
  60. package/interpreter/stdlib/math.js +309 -0
  61. package/interpreter/stdlib/object.js +142 -0
  62. package/interpreter/stdlib/path.js +69 -0
  63. package/interpreter/stdlib/regex.js +134 -0
  64. package/interpreter/stdlib/string.js +260 -0
  65. package/interpreter/suggestions.js +46 -0
  66. package/lexer/Lexer.js +245 -0
  67. package/lexer/TokenTypes.js +131 -0
  68. package/lexer/createToken.js +11 -0
  69. package/lexer/tokenizers/commentTokenizer.js +45 -0
  70. package/lexer/tokenizers/literalTokenizer.js +163 -0
  71. package/lexer/tokenizers/symbolTokenizer.js +69 -0
  72. package/lexer/tokenizers/whitespaceTokenizer.js +36 -0
  73. package/package.json +29 -13
  74. package/parser/ASTNodes.js +448 -0
  75. package/parser/Parser.js +188 -0
  76. package/parser/expressions/atomicExpressions.js +165 -0
  77. package/parser/expressions/conditionalExpressions.js +0 -0
  78. package/parser/expressions/operatorExpressions.js +79 -0
  79. package/parser/expressions/primaryExpressions.js +77 -0
  80. package/parser/parseStatement.js +184 -0
  81. package/parser/parserExpressions.js +115 -0
  82. package/parser/parserUtils.js +19 -0
  83. package/parser/statements/controlFlowParsers.js +106 -0
  84. package/parser/statements/functionParsers.js +314 -0
  85. package/parser/statements/moduleParsers.js +57 -0
  86. package/parser/statements/patternMatchParsers.js +124 -0
  87. package/parser/statements/variableParsers.js +155 -0
  88. package/repl.js +325 -0
  89. package/test.js +47 -0
  90. package/tools/PrettyPrinter.js +3 -0
  91. package/tools/convert/Args.js +46 -0
  92. package/tools/convert/Registry.js +91 -0
  93. package/tools/convert/Transpiler.js +78 -0
  94. package/tools/convert/plugins/README.md +66 -0
  95. package/tools/convert/plugins/alya/index.js +10 -0
  96. package/tools/convert/plugins/alya/to_alya.js +289 -0
  97. package/tools/convert/plugins/alya/visitors/expressions.js +257 -0
  98. package/tools/convert/plugins/alya/visitors/statements.js +403 -0
  99. package/tools/convert/plugins/base_converter.js +228 -0
  100. package/tools/convert/plugins/javascript/index.js +10 -0
  101. package/tools/convert/plugins/javascript/mimo_runtime.js +265 -0
  102. package/tools/convert/plugins/javascript/to_js.js +155 -0
  103. package/tools/convert/plugins/javascript/visitors/expressions.js +197 -0
  104. package/tools/convert/plugins/javascript/visitors/patterns.js +102 -0
  105. package/tools/convert/plugins/javascript/visitors/statements.js +236 -0
  106. package/tools/convert/plugins/python/index.js +10 -0
  107. package/tools/convert/plugins/python/mimo_runtime.py +811 -0
  108. package/tools/convert/plugins/python/to_py.js +329 -0
  109. package/tools/convert/plugins/python/visitors/expressions.js +272 -0
  110. package/tools/convert/plugins/python/visitors/patterns.js +100 -0
  111. package/tools/convert/plugins/python/visitors/statements.js +257 -0
  112. package/tools/convert.js +102 -0
  113. package/tools/format/CommentAttacher.js +190 -0
  114. package/tools/format/CommentLexer.js +152 -0
  115. package/tools/format/Printer.js +849 -0
  116. package/tools/format/config.js +107 -0
  117. package/tools/formatter.js +169 -0
  118. package/tools/lint/Linter.js +391 -0
  119. package/tools/lint/config.js +114 -0
  120. package/tools/lint/rules/consistent-return.js +62 -0
  121. package/tools/lint/rules/max-depth.js +56 -0
  122. package/tools/lint/rules/no-empty-function.js +45 -0
  123. package/tools/lint/rules/no-magic-numbers.js +46 -0
  124. package/tools/lint/rules/no-shadow.js +113 -0
  125. package/tools/lint/rules/no-unused-vars.js +26 -0
  126. package/tools/lint/rules/prefer-const.js +19 -0
  127. package/tools/linter.js +261 -0
  128. package/tools/replFormatter.js +93 -0
  129. package/tools/stamp-version.js +32 -0
  130. package/web/index.js +9 -0
  131. package/bun.lockb +0 -0
  132. package/cli.js +0 -84
  133. package/compiler/execute/interpreter.js +0 -68
  134. package/compiler/execute/interpreters/binary.js +0 -12
  135. package/compiler/execute/interpreters/call.js +0 -10
  136. package/compiler/execute/interpreters/if.js +0 -10
  137. package/compiler/execute/interpreters/try-catch.js +0 -10
  138. package/compiler/execute/interpreters/while.js +0 -8
  139. package/compiler/execute/utils/createfunction.js +0 -11
  140. package/compiler/execute/utils/evaluate.js +0 -20
  141. package/compiler/execute/utils/operate.js +0 -23
  142. package/compiler/lexer/processToken.js +0 -40
  143. package/compiler/lexer/tokenTypes.js +0 -4
  144. package/compiler/lexer/tokenizer.js +0 -74
  145. package/compiler/parser/expression/comparison.js +0 -18
  146. package/compiler/parser/expression/identifier.js +0 -29
  147. package/compiler/parser/expression/number.js +0 -10
  148. package/compiler/parser/expression/operator.js +0 -21
  149. package/compiler/parser/expression/punctuation.js +0 -31
  150. package/compiler/parser/expression/string.js +0 -6
  151. package/compiler/parser/parseExpression.js +0 -27
  152. package/compiler/parser/parseStatement.js +0 -34
  153. package/compiler/parser/parser.js +0 -45
  154. package/compiler/parser/statement/call.js +0 -26
  155. package/compiler/parser/statement/function.js +0 -29
  156. package/compiler/parser/statement/if.js +0 -34
  157. package/compiler/parser/statement/return.js +0 -10
  158. package/compiler/parser/statement/set.js +0 -11
  159. package/compiler/parser/statement/show.js +0 -10
  160. package/compiler/parser/statement/try-catch.js +0 -25
  161. package/compiler/parser/statement/while.js +0 -22
  162. package/converter/go/convert.js +0 -110
  163. package/converter/js/convert.js +0 -107
  164. package/jsconfig.json +0 -27
  165. package/vite.config.js +0 -17
@@ -0,0 +1,155 @@
1
+ // In: parser/statements/variableParsers.js
2
+
3
+ import { TokenType } from "../../lexer/TokenTypes.js";
4
+ import { ASTNode } from "../ASTNodes.js";
5
+ import { parseExpression } from "../parserExpressions.js";
6
+
7
+ // Helper function that parses a "left-hand-side" expression,
8
+ // which can be a simple identifier or a chain of property/bracket accesses.
9
+ function parseAssignable(parser) {
10
+ let expr = parser.parseIdentifier('SYN097', "Expected an identifier for the variable name or assignment target.");
11
+
12
+ while (true) {
13
+ const token = parser.peek();
14
+ if (token?.type === TokenType.Operator && token.value === '.') {
15
+ parser.consume(); // consume '.'
16
+ const property = parser.expect(TokenType.Identifier, undefined, 'SYN098', 'Expected property name after dot.');
17
+ expr = ASTNode.PropertyAccess(expr, property.value, property);
18
+ } else if (token?.type === TokenType.LBracket) {
19
+ parser.consume(); // consume '['
20
+ const index = parseExpression(parser);
21
+ parser.expect(TokenType.RBracket, undefined, 'SYN100', "Expected a closing ']' after index.");
22
+ expr = ASTNode.ArrayAccess(expr, index, token);
23
+ } else {
24
+ break; // No more accessors
25
+ }
26
+ }
27
+ return expr;
28
+ }
29
+
30
+
31
+ export function parseVariableOrAssignment(parser, isExported = false, exportToken) {
32
+ const kindToken = parser.expectKeyword(['set', 'let', 'const', 'global'], 'SYN092');
33
+ const astNodeLocationToken = exportToken || kindToken;
34
+
35
+ const nextToken = parser.peek();
36
+ if (nextToken && (nextToken.type === TokenType.LBracket || nextToken.type === TokenType.LBrace)) {
37
+ const pattern = parseDestructuringPattern(parser);
38
+ const value = parseExpression(parser);
39
+ return ASTNode.VariableDeclaration(pattern, value, kindToken.value, isExported, astNodeLocationToken);
40
+ }
41
+
42
+ // We need the raw token for its position, not just the AST node.
43
+ const identifierToken = parser.expect(TokenType.Identifier, undefined, 'SYN097', "Expected an identifier for the variable name.");
44
+
45
+ // -- WHITESPACE-SENSITIVE AMBIGUITY RESOLUTION --
46
+ const followingToken = parser.peek();
47
+ let isMemberAssignment = false;
48
+
49
+ if (followingToken) {
50
+ if (followingToken.type === TokenType.Operator && followingToken.value === '.') {
51
+ // `obj.` is unambiguously a member assignment.
52
+ isMemberAssignment = true;
53
+ } else if (followingToken.type === TokenType.LBracket) {
54
+ // `arr[` vs `arr [`
55
+ // Your brilliant idea: check if there's a space.
56
+ const hasNoSpace = (followingToken.start === identifierToken.start + identifierToken.length);
57
+ if (hasNoSpace) {
58
+ isMemberAssignment = true;
59
+ }
60
+ }
61
+ }
62
+
63
+ // --- DISPATCH TO THE CORRECT PARSING PATH ---
64
+
65
+ if (isMemberAssignment) {
66
+ // --- PATH 1: MEMBER ASSIGNMENT (e.g., set arr[0] 99) ---
67
+ if (kindToken.value !== 'set') {
68
+ parser.error(`The '${kindToken.value}' keyword is for declarations. Use 'set' for direct member assignment.`, kindToken, 'SYN102');
69
+ }
70
+
71
+ let lhs = ASTNode.Identifier(identifierToken.value, identifierToken);
72
+
73
+ // Loop to consume the full accessor chain (e.g., obj.prop[0].name)
74
+ while (true) {
75
+ const currentToken = parser.peek();
76
+ const hasNoSpace = currentToken && (currentToken.start === parser.peek(-1).start + parser.peek(-1).length);
77
+
78
+ if (currentToken?.type === TokenType.Operator && currentToken.value === '.') {
79
+ parser.consume(); // consume '.'
80
+ const property = parser.expect(TokenType.Identifier, undefined, 'SYN098', 'Expected property name after dot.');
81
+ lhs = ASTNode.PropertyAccess(lhs, property.value, property);
82
+ } else if (currentToken?.type === TokenType.LBracket && hasNoSpace) {
83
+ parser.consume(); // consume '['
84
+ const index = parseExpression(parser);
85
+ parser.expect(TokenType.RBracket, undefined, 'SYN100', "Expected a closing ']' after index.");
86
+ lhs = ASTNode.ArrayAccess(lhs, index, currentToken);
87
+ } else {
88
+ break; // No more accessors
89
+ }
90
+ }
91
+
92
+ const value = parseExpression(parser);
93
+
94
+ if (lhs.type === 'PropertyAccess') {
95
+ return ASTNode.PropertyAssignment(lhs.object, lhs.property, value, astNodeLocationToken);
96
+ } else if (lhs.type === 'ArrayAccess') {
97
+ return ASTNode.BracketAssignment(lhs.object, lhs.index, value, astNodeLocationToken);
98
+ } else {
99
+ parser.error("Invalid member assignment target.", lhs, 'SYN103');
100
+ }
101
+
102
+ } else {
103
+ // --- PATH 2: VARIABLE DECLARATION (e.g., let x, set arr [1,2,3]) ---
104
+ const value = parseExpression(parser);
105
+ return ASTNode.VariableDeclaration(identifierToken.value, value, kindToken.value, isExported, astNodeLocationToken);
106
+ }
107
+ }
108
+
109
+ // NEW function to parse an object pattern like {a, b}
110
+ function parseObjectPattern(parser) {
111
+ const startBrace = parser.expect(TokenType.LBrace, undefined, 'SYN115', 'Expected "{" to start object destructuring pattern.');
112
+ const properties = [];
113
+
114
+ if (parser.peek()?.type !== TokenType.RBrace) {
115
+ do {
116
+ // Each property in the pattern must be an identifier.
117
+ properties.push(parser.parseIdentifier('SYN116', 'Expected an identifier for property in object destructuring.'));
118
+ } while (parser.match(TokenType.Comma));
119
+ }
120
+
121
+ parser.expect(TokenType.RBrace, undefined, 'SYN117', 'Expected "}" to close object destructuring pattern.');
122
+ return ASTNode.ObjectPattern(properties, startBrace);
123
+ }
124
+
125
+ // (The destructuring parsers below remain unchanged and correct)
126
+ function parseDestructuringPattern(parser) {
127
+ const token = parser.peek();
128
+
129
+ // Dispatch based on the opening token.
130
+ if (token.type === TokenType.LBracket) {
131
+ // Array Pattern: [a, b]
132
+ const startBracket = parser.consume();
133
+ const variables = [];
134
+ if (parser.peek()?.type !== TokenType.RBracket) {
135
+ do {
136
+ variables.push(parser.parseIdentifier("SYN110", "Expected an identifier in destructuring pattern."));
137
+ } while (parser.match(TokenType.Comma));
138
+ }
139
+ parser.expect(TokenType.RBracket, undefined, "SYN111", 'Expected a closing "]" for array destructuring pattern.');
140
+ return ASTNode.ArrayPattern(variables, startBracket);
141
+ } else if (token.type === TokenType.LBrace) {
142
+ // Object Pattern: {name, age}
143
+ return parseObjectPattern(parser); // Call our new helper
144
+ }
145
+
146
+ parser.error('Expected an array or object pattern (e.g., [a, b] or {a, b}) for destructuring.', token, 'SYN112');
147
+ }
148
+
149
+ export function parseDestructuringStatement(parser) {
150
+ const destructureToken = parser.expectKeyword("destructure", "SYN113");
151
+ const pattern = parseDestructuringPattern(parser);
152
+ parser.expectKeyword("from", "SYN114", 'Expected "from" keyword after destructuring pattern.');
153
+ const expression = parseExpression(parser);
154
+ return ASTNode.DestructuringAssignment(pattern, expression, destructureToken);
155
+ }
package/repl.js ADDED
@@ -0,0 +1,325 @@
1
+ #!/usr/bin/env bun
2
+ import readline from "node:readline";
3
+ import { fileURLToPath } from 'url';
4
+ import { nodeAdapter as adapter } from "./adapters/nodeAdapter.js";
5
+ import { Interpreter } from "./interpreter/index.js";
6
+ import { Lexer } from "./lexer/Lexer.js";
7
+ import { Parser } from "./parser/Parser.js";
8
+ import { stringify, highlightMimoCode } from "./interpreter/Utils.js";
9
+ import { formatValue } from "./tools/replFormatter.js";
10
+ import fs from "node:fs";
11
+ import path from "node:path";
12
+ import { getMimoType } from "./interpreter/suggestions.js";
13
+
14
+ const HISTORY_FILE = path.join(process.cwd(), ".mimo_history");
15
+
16
+ /**
17
+ * Tracking nesting levels for dynamic multiline input.
18
+ */
19
+ class NestingTracker {
20
+ constructor() {
21
+ this.reset();
22
+ }
23
+
24
+ reset() {
25
+ this.nestedBlocks = 0; // for if, while, for, function, fn, try, match
26
+ this.openParens = 0;
27
+ this.openBrackets = 0;
28
+ this.openBraces = 0;
29
+ }
30
+
31
+ track(input) {
32
+ // Simple scanning for nesting indicators
33
+ // We use words for blocks, but avoid them in strings/comments
34
+ const tokens = input.split(/\s+/);
35
+
36
+ // Count keywords that increase depth
37
+ const blockStart = ["if", "while", "for", "function", "fn", "try", "match"];
38
+ const blockEnd = ["end"];
39
+
40
+ for (const token of tokens) {
41
+ if (blockStart.includes(token)) this.nestedBlocks++;
42
+ if (blockEnd.includes(token)) this.nestedBlocks--;
43
+ }
44
+
45
+ // Count individual chars for delimiters
46
+ for (const char of input) {
47
+ if (char === '(') this.openParens++;
48
+ if (char === ')') this.openParens--;
49
+ if (char === '[') this.openBrackets++;
50
+ if (char === ']') this.openBrackets--;
51
+ if (char === '{') this.openBraces++;
52
+ if (char === '}') this.openBraces--;
53
+ }
54
+ }
55
+
56
+ isBalanced() {
57
+ return this.nestedBlocks <= 0 &&
58
+ this.openParens <= 0 &&
59
+ this.openBrackets <= 0 &&
60
+ this.openBraces <= 0;
61
+ }
62
+ }
63
+
64
+ export function runRepl() {
65
+ const isInteractive = process.stdin.isTTY;
66
+
67
+ if (isInteractive) {
68
+ console.log("\x1b[1;35mWelcome to Mimo REPL!\x1b[0m");
69
+ console.log("Type \x1b[36m:help\x1b[0m for commands or \x1b[36mexit\x1b[0m to leave.");
70
+ }
71
+
72
+ const promptString = isInteractive ? "\x1b[36m(mimo)\x1b[0m " : "";
73
+ const multilinePrompt = isInteractive ? "\x1b[90m ... \x1b[0m " : "";
74
+ const replFilePath = "/repl";
75
+
76
+ // Load history if exists and interactive
77
+ let history = [];
78
+ if (isInteractive && fs.existsSync(HISTORY_FILE)) {
79
+ history = fs.readFileSync(HISTORY_FILE, "utf-8").split("\n").filter(Boolean);
80
+ }
81
+
82
+ const rl = readline.createInterface({
83
+ input: process.stdin,
84
+ output: process.stdout,
85
+ prompt: promptString,
86
+ historySize: 1000,
87
+ history: isInteractive ? history.reverse() : [],
88
+ });
89
+
90
+ let interpreter = new Interpreter(adapter);
91
+ const nesting = new NestingTracker();
92
+ const sessionHistory = [];
93
+
94
+ function processInput(input) {
95
+ // Save to history file if interactive
96
+ if (isInteractive) {
97
+ fs.appendFileSync(HISTORY_FILE, input.trim() + "\n");
98
+ }
99
+ sessionHistory.push(input);
100
+
101
+ interpreter.currentFile = replFilePath;
102
+ interpreter.errorHandler.addSourceFile(replFilePath, input);
103
+
104
+ try {
105
+ const lexer = new Lexer(input, replFilePath);
106
+ const tokens = [];
107
+ let token;
108
+ while ((token = lexer.nextToken()) !== null) {
109
+ tokens.push(token);
110
+ }
111
+
112
+ if (tokens.length === 0) return;
113
+
114
+ const parser = new Parser(tokens, replFilePath);
115
+ parser.setErrorHandler(interpreter.errorHandler);
116
+ const ast = parser.parse();
117
+
118
+ const result = interpreter.interpret(ast, replFilePath);
119
+ setLastResult(result);
120
+
121
+ const lastStatement = ast.body[ast.body.length - 1];
122
+ if (lastStatement && lastStatement.type !== "ShowStatement" && lastStatement.type !== "FunctionDeclaration") {
123
+ if (result !== undefined && result !== null) {
124
+ const prefix = isInteractive ? "\x1b[90m=>\x1b[0m " : "=> ";
125
+ console.log(`${prefix}${formatValue(result, isInteractive)}`);
126
+ }
127
+ }
128
+ } catch (err) {
129
+ interpreter.errorHandler.printError(err);
130
+ }
131
+ }
132
+
133
+ function parseToAst(source, filePath = replFilePath) {
134
+ const lexer = new Lexer(source, filePath);
135
+ const tokens = [];
136
+ let token;
137
+ while ((token = lexer.nextToken()) !== null) {
138
+ tokens.push(token);
139
+ }
140
+
141
+ const parser = new Parser(tokens, filePath);
142
+ parser.setErrorHandler(interpreter.errorHandler);
143
+ return parser.parse();
144
+ }
145
+
146
+ function setLastResult(value) {
147
+ if (value === undefined) return;
148
+ const existing = interpreter.globalEnv.getVariableInfo("_");
149
+ if (existing) {
150
+ existing.env.assign("_", value);
151
+ } else {
152
+ interpreter.globalEnv.define("_", value, "set");
153
+ }
154
+ }
155
+
156
+ function resetEnvironment() {
157
+ interpreter = new Interpreter(adapter);
158
+ }
159
+
160
+ function handleCommand(cmd) {
161
+ if (!isInteractive) return;
162
+ const trimmed = cmd.trim();
163
+ const parts = trimmed.split(/\s+/);
164
+ const action = parts[0].toLowerCase();
165
+ const payload = trimmed.slice(action.length).trim();
166
+
167
+ switch (action) {
168
+ case ".help":
169
+ case ":help":
170
+ console.log("\x1b[1mREPL Commands:\x1b[0m");
171
+ console.log(" \x1b[36m:help\x1b[0m Show this help");
172
+ console.log(" \x1b[36m:clear\x1b[0m Reset REPL environment");
173
+ console.log(" \x1b[36m:history\x1b[0m Show recently used commands");
174
+ console.log(" \x1b[36m:type x\x1b[0m Show Mimo type of variable x");
175
+ console.log(" \x1b[36m:load f\x1b[0m Execute a .mimo file into current REPL env");
176
+ console.log(" \x1b[36m:save f\x1b[0m Save REPL history to file");
177
+ console.log(" \x1b[36m:time expr\x1b[0m Measure expression/program execution time");
178
+ console.log(" \x1b[36m:ast expr\x1b[0m Parse input and print AST JSON");
179
+ console.log(" \x1b[36m:exit\x1b[0m Exit the REPL");
180
+ break;
181
+ case ".clear":
182
+ case ":clear":
183
+ resetEnvironment();
184
+ process.stdout.write("\u001b[2J\u001b[0;0H");
185
+ break;
186
+ case ".history":
187
+ case ":history":
188
+ rl.history
189
+ .slice(0, 20)
190
+ .reverse()
191
+ .forEach((entry, idx) => console.log(`${idx + 1}. ${entry}`));
192
+ break;
193
+ case ":type": {
194
+ const targetName = parts[1];
195
+ if (!targetName) {
196
+ console.log("Usage: :type <variable>");
197
+ break;
198
+ }
199
+ try {
200
+ const value = interpreter.currentEnv.lookup(targetName);
201
+ console.log(getMimoType(value));
202
+ } catch {
203
+ console.log(`Undefined variable: ${targetName}`);
204
+ }
205
+ break;
206
+ }
207
+ case ":load": {
208
+ if (!payload) {
209
+ console.log("Usage: :load <file>");
210
+ break;
211
+ }
212
+ const target = path.resolve(process.cwd(), payload);
213
+ if (!fs.existsSync(target)) {
214
+ console.log(`File not found: ${target}`);
215
+ break;
216
+ }
217
+ try {
218
+ const source = fs.readFileSync(target, "utf-8");
219
+ processInput(source);
220
+ } catch (err) {
221
+ console.log(`Failed to load '${target}': ${err.message}`);
222
+ }
223
+ break;
224
+ }
225
+ case ":save": {
226
+ if (!payload) {
227
+ console.log("Usage: :save <file>");
228
+ break;
229
+ }
230
+ const target = path.resolve(process.cwd(), payload);
231
+ try {
232
+ const sessionLines = sessionHistory
233
+ .slice()
234
+ .filter((entry) => entry && !entry.trim().startsWith(":") && !entry.trim().startsWith("."));
235
+ fs.writeFileSync(target, `${sessionLines.join("\n")}\n`, "utf-8");
236
+ console.log(`Saved ${sessionLines.length} entries to ${target}`);
237
+ } catch (err) {
238
+ console.log(`Failed to save '${target}': ${err.message}`);
239
+ }
240
+ break;
241
+ }
242
+ case ":time": {
243
+ if (!payload) {
244
+ console.log("Usage: :time <expr|statement>");
245
+ break;
246
+ }
247
+ const start = process.hrtime.bigint();
248
+ processInput(payload);
249
+ const end = process.hrtime.bigint();
250
+ const elapsedMs = Number(end - start) / 1_000_000;
251
+ console.log(`\x1b[90mExecuted in ${elapsedMs.toFixed(3)} ms\x1b[0m`);
252
+ break;
253
+ }
254
+ case ":ast": {
255
+ if (!payload) {
256
+ console.log("Usage: :ast <expr|statement>");
257
+ break;
258
+ }
259
+ try {
260
+ const ast = parseToAst(payload, "/repl_ast");
261
+ console.log(JSON.stringify(ast, null, 2));
262
+ } catch (err) {
263
+ interpreter.errorHandler.printError(err);
264
+ }
265
+ break;
266
+ }
267
+ case ".exit":
268
+ case ":exit":
269
+ rl.close();
270
+ break;
271
+ default:
272
+ console.log(`Unknown command: ${action}`);
273
+ }
274
+ }
275
+
276
+ let buffer = "";
277
+
278
+ rl.on("line", (line) => {
279
+ const trimmed = line.trim();
280
+
281
+ if (trimmed === "exit") {
282
+ rl.close();
283
+ return;
284
+ }
285
+
286
+ if ((trimmed.startsWith(".") || trimmed.startsWith(":")) && isInteractive) {
287
+ handleCommand(trimmed);
288
+ rl.prompt();
289
+ return;
290
+ }
291
+
292
+ buffer += (buffer ? "\n" : "") + line;
293
+ nesting.track(line);
294
+
295
+ if (nesting.isBalanced()) {
296
+ if (buffer.trim()) {
297
+ processInput(buffer);
298
+ }
299
+ buffer = "";
300
+ nesting.reset();
301
+ rl.setPrompt(promptString);
302
+ } else {
303
+ rl.setPrompt(multilinePrompt);
304
+ }
305
+
306
+ rl.prompt();
307
+ });
308
+
309
+ rl.on('close', () => {
310
+ if (isInteractive) {
311
+ console.log("\nBye!");
312
+ }
313
+ process.exit(0);
314
+ });
315
+
316
+ rl.prompt();
317
+ }
318
+
319
+
320
+
321
+
322
+ // This check ensures the REPL starts automatically only when `node repl.js` is run
323
+ if (process.argv[1] === fileURLToPath(import.meta.url)) {
324
+ runRepl();
325
+ }
package/test.js ADDED
@@ -0,0 +1,47 @@
1
+ #!/usr/bin/env node
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+ import { nodeAdapter as adapter } from './adapters/nodeAdapter.js';
5
+ import { Mimo } from './index.js';
6
+
7
+
8
+ async function runTest() {
9
+ const testName = process.argv[2] || 'all';
10
+ const filePath = findTestFile(testName);
11
+
12
+ if (!filePath) {
13
+ console.error(`Error: Test file "${testName}" not found.`);
14
+ return;
15
+ }
16
+
17
+ console.log(`\n=== Running Test: ${path.basename(filePath)} ===\n`);
18
+
19
+ const source = fs.readFileSync(filePath, 'utf-8');
20
+ const mimo = new Mimo(adapter);
21
+
22
+ try {
23
+ mimo.run(source, filePath);
24
+ console.log(`\n=== Test Passed: ${path.basename(filePath)} ===\n`);
25
+ } catch (err) {
26
+ console.error(err.message || err); // Show error even if it's a string
27
+ console.error(`\n=== Test FAILED: ${path.basename(filePath)} ===\n`);
28
+ process.exit(1);
29
+ }
30
+ }
31
+
32
+
33
+ function findTestFile(fileName) {
34
+ const directPath = path.resolve(process.cwd(), fileName);
35
+ if (fs.existsSync(directPath)) return directPath;
36
+
37
+ const withExt = directPath.endsWith('.mimo') ? '' : '.mimo';
38
+ if (fs.existsSync(`${directPath}${withExt}`)) return `${directPath}${withExt}`;
39
+
40
+ const testSourcePath = path.resolve(process.cwd(), 'test/source', fileName);
41
+ if (fs.existsSync(testSourcePath)) return testSourcePath;
42
+ if (fs.existsSync(`${testSourcePath}.mimo`)) return `${testSourcePath}.mimo`;
43
+
44
+ return null;
45
+ }
46
+
47
+ runTest();
@@ -0,0 +1,3 @@
1
+ // tools/PrettyPrinter.js
2
+ // Backwards-compatibility shim. The implementation now lives in tools/format/Printer.js.
3
+ export { PrettyPrinter } from './format/Printer.js';
@@ -0,0 +1,46 @@
1
+ import path from 'node:path';
2
+
3
+ export function parseArgs(args) {
4
+ const options = {};
5
+ if (args.length === 2 && !args[0].startsWith('--')) {
6
+ options.in = args[0];
7
+ options.out = args[1];
8
+ options.to = 'js';
9
+ return options;
10
+ }
11
+ for (let i = 0; i < args.length; i += 2) {
12
+ const flag = args[i];
13
+ const value = args[i + 1];
14
+ if (flag && flag.startsWith('--')) {
15
+ options[flag.substring(2)] = value;
16
+ }
17
+ }
18
+ return options;
19
+ }
20
+
21
+ /**
22
+ * Determine the target language and converter to use
23
+ */
24
+ export function determineTarget(options, registry) {
25
+ let targetLanguage = options.to;
26
+
27
+ if (!targetLanguage && options.out) {
28
+ targetLanguage = registry.detectLanguageFromExtension(options.out);
29
+ }
30
+
31
+ if (!targetLanguage) {
32
+ targetLanguage = 'js';
33
+ }
34
+
35
+ const converterInfo = registry.get(targetLanguage);
36
+ if (!converterInfo) {
37
+ const availableLanguages = registry.getLanguages().join(', ');
38
+ throw new Error(`Unsupported target language: ${targetLanguage}. Available: ${availableLanguages}`);
39
+ }
40
+
41
+ return {
42
+ language: targetLanguage,
43
+ converterInfo,
44
+ targetExtension: converterInfo.extension
45
+ };
46
+ }
@@ -0,0 +1,91 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+
5
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
6
+
7
+ export class ConverterRegistry {
8
+ constructor() {
9
+ this.converters = new Map();
10
+ }
11
+
12
+ /**
13
+ * Register a converter for a target language
14
+ */
15
+ register(language, extension, ConverterClass, runtimeFile = null) {
16
+ this.converters.set(language, {
17
+ extension,
18
+ ConverterClass,
19
+ runtimeFile
20
+ });
21
+ }
22
+
23
+ /**
24
+ * Get converter info for a language
25
+ */
26
+ get(language) {
27
+ return this.converters.get(language) || null;
28
+ }
29
+
30
+ /**
31
+ * Get all registered languages
32
+ */
33
+ getLanguages() {
34
+ return Array.from(this.converters.keys());
35
+ }
36
+
37
+ /**
38
+ * Detect target language from file extension
39
+ */
40
+ detectLanguageFromExtension(filePath) {
41
+ const ext = path.extname(filePath);
42
+ for (const [lang, info] of this.converters) {
43
+ if (info.extension === ext) {
44
+ return lang;
45
+ }
46
+ }
47
+ return null;
48
+ }
49
+
50
+ /**
51
+ * Dynamically scan the plugins directory and load all plugins
52
+ */
53
+ async discoverConverters() {
54
+ const pluginsDir = path.join(__dirname, 'plugins');
55
+ if (!fs.existsSync(pluginsDir)) return;
56
+
57
+ const entries = fs.readdirSync(pluginsDir, { withFileTypes: true });
58
+
59
+ for (const entry of entries) {
60
+ if (entry.isDirectory()) {
61
+ const langDir = path.join(pluginsDir, entry.name);
62
+ const indexPath = path.join(langDir, 'index.js');
63
+
64
+ if (fs.existsSync(indexPath)) {
65
+ try {
66
+ const modulePath = `file://${indexPath}`;
67
+ const { config, Converter } = await import(modulePath);
68
+
69
+ if (config && Converter) {
70
+ const { name, aliases, extension, runtimeFile } = config;
71
+ const regName = name || entry.name;
72
+ const runtimePath = runtimeFile ? path.join(entry.name, runtimeFile) : null;
73
+
74
+ this.register(regName, extension, Converter, runtimePath);
75
+
76
+ if (aliases && Array.isArray(aliases)) {
77
+ for (const alias of aliases) {
78
+ if (alias !== regName) {
79
+ this.register(alias, extension, Converter, runtimePath);
80
+ }
81
+ }
82
+ }
83
+ }
84
+ } catch (error) {
85
+ console.warn(`Warning: Failed to load converter in ${entry.name}: ${error.message}`);
86
+ }
87
+ }
88
+ }
89
+ }
90
+ }
91
+ }