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,329 @@
1
+ /**
2
+ * Mimo → Python transpiler.
3
+ *
4
+ * Converts a Mimo AST to Python 3 source code.
5
+ * Extends BaseConverter for shared infrastructure; visitor methods are
6
+ * mixed in from focused sub-modules.
7
+ */
8
+ import { BaseConverter } from '../base_converter.js';
9
+ import { statementVisitors } from './visitors/statements.js';
10
+ import { expressionVisitors } from './visitors/expressions.js';
11
+ import { patternVisitors } from './visitors/patterns.js';
12
+
13
+ export class MimoToPyConverter extends BaseConverter {
14
+ constructor() {
15
+ super();
16
+ this.moduleAliases = new Map();
17
+ this._matchCounter = 0;
18
+ this._lambdaCounter = 0;
19
+ this._pendingImports = []; // stdlib imports gathered in first pass
20
+ this._hoistedFunctions = []; // hoisted inner defs for multi-stmt anon functions
21
+ this._deferredDecorators = []; // decorator applications emitted after all function defs
22
+ this._moduleVars = new Set(); // top-level variable names (for `global` declarations)
23
+ this._emittingHoistedFunctions = false; // true during program-level function hoisting pass
24
+ this._enclosingFunctionVars = []; // stack of variable sets for enclosing function scopes
25
+ }
26
+
27
+ // -------------------------------------------------------------------------
28
+ // Entry point
29
+ // -------------------------------------------------------------------------
30
+
31
+ convert(ast) {
32
+ // First pass: gather stdlib module imports so we can emit them at the top.
33
+ this._collectStdlibImports(ast);
34
+
35
+ // Second pass: collect all module-level variable names for `global` declarations.
36
+ this._collectModuleVars(ast);
37
+
38
+ // Build header
39
+ this.output = `from mimo_runtime import mimo\n`;
40
+ for (const imp of this._pendingImports) {
41
+ this.output += `${imp}\n`;
42
+ }
43
+ this.output += '\n';
44
+
45
+ this.visitNode(ast);
46
+
47
+ // Python idiom: guard the top-level execution
48
+ this.output += '\nif __name__ == "__main__":\n pass\n';
49
+
50
+ return this.output;
51
+ }
52
+
53
+ // -------------------------------------------------------------------------
54
+ // AST analysis helpers (pre-passes)
55
+ // -------------------------------------------------------------------------
56
+
57
+ /** Collect external (non-stdlib) module imports for top-level import stmts. */
58
+ _collectStdlibImports(node) {
59
+ if (!node || typeof node !== 'object') return;
60
+ if (node.type === 'ImportStatement' && !this.isStdlibModule(node.path)) {
61
+ const modName = node.path.endsWith('.mimo')
62
+ ? node.path.slice(0, -5)
63
+ : node.path;
64
+ const imp = `import ${modName} as ${node.alias}`;
65
+ if (!this._pendingImports.includes(imp)) {
66
+ this._pendingImports.push(imp);
67
+ }
68
+ }
69
+ for (const key of Object.keys(node)) {
70
+ const child = node[key];
71
+ if (Array.isArray(child)) {
72
+ child.forEach((c) => this._collectStdlibImports(c));
73
+ } else if (child && typeof child === 'object' && child.type) {
74
+ this._collectStdlibImports(child);
75
+ }
76
+ }
77
+ }
78
+
79
+ /**
80
+ * Collect module-level variable names from the top-level program body.
81
+ * These are used to emit `global` declarations inside functions that
82
+ * assign to them (Mimo uses dynamic scoping for `set`).
83
+ */
84
+ _collectModuleVars(ast) {
85
+ if (!ast || ast.type !== 'Program') return;
86
+ for (const stmt of ast.body) {
87
+ if (stmt.type === 'VariableDeclaration') {
88
+ this._moduleVars.add(stmt.identifier);
89
+ } else if (stmt.type === 'AssignmentStatement' && stmt.target?.type === 'Identifier') {
90
+ this._moduleVars.add(stmt.target.name);
91
+ } else if (stmt.type === 'FunctionDeclaration') {
92
+ this._moduleVars.add(stmt.name);
93
+ }
94
+ }
95
+ }
96
+
97
+ /**
98
+ * Collect all variable names that are assigned (set) within a block of statements.
99
+ * Does not recurse into nested function declarations.
100
+ */
101
+ _collectAssignedVars(stmts, result = new Set()) {
102
+ for (const stmt of stmts || []) {
103
+ if (!stmt) continue;
104
+ if (stmt.type === 'AssignmentStatement' && stmt.target?.type === 'Identifier') {
105
+ result.add(stmt.target.name);
106
+ } else if (stmt.type === 'VariableDeclaration') {
107
+ result.add(stmt.identifier);
108
+ } else if (stmt.type === 'IfStatement') {
109
+ this._collectAssignedVars(stmt.consequent, result);
110
+ if (Array.isArray(stmt.alternate)) this._collectAssignedVars(stmt.alternate, result);
111
+ else if (stmt.alternate) this._collectAssignedVars([stmt.alternate], result);
112
+ } else if (stmt.type === 'WhileStatement' || stmt.type === 'ForStatement' || stmt.type === 'LoopStatement') {
113
+ this._collectAssignedVars(stmt.body, result);
114
+ } else if (stmt.type === 'TryStatement') {
115
+ this._collectAssignedVars(stmt.tryBlock, result);
116
+ this._collectAssignedVars(stmt.catchBlock, result);
117
+ } else if (stmt.type === 'GuardStatement') {
118
+ this._collectAssignedVars(stmt.alternate || stmt.elseBlock, result);
119
+ }
120
+ // Do NOT recurse into nested FunctionDeclaration — they have their own scope
121
+ }
122
+ return result;
123
+ }
124
+
125
+ // -------------------------------------------------------------------------
126
+ // Overrides
127
+ // -------------------------------------------------------------------------
128
+
129
+ onUndefinedVisitor(node) {
130
+ console.warn(`[Python Converter] No visitor for AST node type: ${node.type}`);
131
+ }
132
+
133
+ /** Python blocks must not be empty — emit `pass` if empty. */
134
+ visitBlock(statements) {
135
+ if (!statements || statements.length === 0) {
136
+ this.indent();
137
+ this.writeLine('pass');
138
+ this.dedent();
139
+ return;
140
+ }
141
+ this.indent();
142
+ let prev = null;
143
+ statements.forEach((stmt) => {
144
+ this.emitLineGap(stmt, prev);
145
+ // Snapshot output length before visiting the statement.
146
+ // Any multi-statement anonymous functions encountered during visitNode(stmt)
147
+ // push into _hoistedFunctions. After the visit we splice their definitions
148
+ // BEFORE the statement that referenced them.
149
+ const outputBefore = this.output.length;
150
+ const hoistedBefore = this._hoistedFunctions.length;
151
+
152
+ this.visitNode(stmt);
153
+
154
+ const newlyHoisted = this._hoistedFunctions.splice(hoistedBefore);
155
+ if (newlyHoisted.length > 0) {
156
+ const stmtCode = this.output.slice(outputBefore);
157
+ this.output = this.output.slice(0, outputBefore);
158
+ for (const hfn of newlyHoisted) {
159
+ this._emitHoistedFunction(hfn);
160
+ }
161
+ this.output += stmtCode;
162
+ }
163
+ prev = stmt;
164
+ });
165
+ this.dedent();
166
+ }
167
+
168
+ emitSpread(node) {
169
+ this.write('*');
170
+ this.visitNode(node.argument);
171
+ }
172
+
173
+ // -------------------------------------------------------------------------
174
+ // Hoisting infrastructure
175
+ // -------------------------------------------------------------------------
176
+
177
+ /**
178
+ * Emit a hoisted anonymous function as a named `def`.
179
+ * Handles nonlocal declarations for closure variables and recursively
180
+ * emits any pre-hoisted sub-functions inside the body.
181
+ */
182
+ _emitHoistedFunction(hfn) {
183
+ this.writeLine(`def ${hfn.name}(${hfn.params}):`);
184
+
185
+ // Determine which vars are local to this hoisted function
186
+ const paramNames = new Set(
187
+ (hfn.params ? hfn.params.split(',').map(p => p.trim().replace(/=.*$/, '').replace(/^\*/, '')) : [])
188
+ .filter(Boolean)
189
+ );
190
+ const assignedInBody = this._collectAssignedVars(hfn.body);
191
+ const funcVars = new Set([...paramNames, ...assignedInBody]);
192
+
193
+ // Nonlocal: vars assigned in this body that live in an enclosing function scope
194
+ const nonlocalVars = [...assignedInBody].filter(
195
+ v => !paramNames.has(v) &&
196
+ !this._moduleVars.has(v) &&
197
+ this._enclosingFunctionVars.some(scope => scope.has(v))
198
+ );
199
+
200
+ // Push our vars so inner functions can detect their own nonlocals
201
+ this._enclosingFunctionVars.push(funcVars);
202
+
203
+ this.indent();
204
+ if (nonlocalVars.length > 0) {
205
+ this.writeLine(`nonlocal ${nonlocalVars.join(', ')}`);
206
+ }
207
+ // Emit any pre-hoisted sub-functions inside this def's body first
208
+ if (hfn.preHoisted && hfn.preHoisted.length > 0) {
209
+ for (const sub of hfn.preHoisted) {
210
+ this._emitHoistedFunction(sub);
211
+ }
212
+ }
213
+ // Emit body statements with hoisting support for inner anonymous functions
214
+ if (!hfn.body || hfn.body.length === 0) {
215
+ this.writeLine('pass');
216
+ } else {
217
+ for (const stmt of hfn.body) {
218
+ const outputBefore = this.output.length;
219
+ const hoistedBefore = this._hoistedFunctions.length;
220
+
221
+ this.visitNode(stmt);
222
+
223
+ const newlyHoisted = this._hoistedFunctions.splice(hoistedBefore);
224
+ if (newlyHoisted.length > 0) {
225
+ const stmtCode = this.output.slice(outputBefore);
226
+ this.output = this.output.slice(0, outputBefore);
227
+ for (const innerHfn of newlyHoisted) {
228
+ this._emitHoistedFunction(innerHfn);
229
+ }
230
+ this.output += stmtCode;
231
+ }
232
+ }
233
+ }
234
+ this.dedent();
235
+
236
+ this._enclosingFunctionVars.pop();
237
+ this.writeLine();
238
+ }
239
+
240
+ // -------------------------------------------------------------------------
241
+ // Callee helper — routes built-ins through mimo.xxx
242
+ // -------------------------------------------------------------------------
243
+
244
+ visitCallee(calleeNode) {
245
+ switch (calleeNode.type) {
246
+ case 'Identifier':
247
+ if (this.isCoreBuiltin(calleeNode.name)) {
248
+ this.write(`mimo.${calleeNode.name}`);
249
+ } else {
250
+ this.visitNode(calleeNode);
251
+ }
252
+ break;
253
+ default:
254
+ this.visitNode(calleeNode);
255
+ break;
256
+ }
257
+ }
258
+
259
+ // -------------------------------------------------------------------------
260
+ // Program
261
+ // -------------------------------------------------------------------------
262
+
263
+ visitProgram(node) {
264
+ // Mimo hoists all FunctionDeclarations before executing other statements.
265
+ // To match this, emit all function definitions first (without decorators),
266
+ // then apply all decorators, then run the rest of the statements.
267
+ const functions = node.body.filter(s => s.type === 'FunctionDeclaration');
268
+ const others = node.body.filter(s => s.type !== 'FunctionDeclaration');
269
+
270
+ // Pass 1: emit all function defs (decorator wrappers collected into _deferredDecorators)
271
+ this._emittingHoistedFunctions = true;
272
+ functions.forEach((stmt) => this.visitNode(stmt));
273
+ this._emittingHoistedFunctions = false;
274
+
275
+ // Pass 2: apply collected decorator wrappers
276
+ if (this._deferredDecorators.length > 0) {
277
+ for (const line of this._deferredDecorators) {
278
+ this.writeLine(line);
279
+ }
280
+ this._deferredDecorators = [];
281
+ this.writeLine();
282
+ }
283
+
284
+ // Pass 3: emit non-function statements (with hoisting support for inline anonymous functions)
285
+ let prev = null;
286
+ others.forEach((stmt) => {
287
+ this.emitLineGap(stmt, prev);
288
+ const outputBefore = this.output.length;
289
+ const hoistedBefore = this._hoistedFunctions.length;
290
+
291
+ this.visitNode(stmt);
292
+
293
+ const newlyHoisted = this._hoistedFunctions.splice(hoistedBefore);
294
+ if (newlyHoisted.length > 0) {
295
+ const stmtCode = this.output.slice(outputBefore);
296
+ this.output = this.output.slice(0, outputBefore);
297
+ for (const hfn of newlyHoisted) {
298
+ this._emitHoistedFunction(hfn);
299
+ }
300
+ this.output += stmtCode;
301
+ }
302
+ prev = stmt;
303
+ });
304
+ }
305
+
306
+ // -------------------------------------------------------------------------
307
+ // Private helpers
308
+ // -------------------------------------------------------------------------
309
+
310
+ /**
311
+ * Render an expression node to a string without writing to output.
312
+ * Used for default parameter values and decorator args.
313
+ */
314
+ _exprToString(node) {
315
+ if (!node) return 'None';
316
+ const savedOutput = this.output;
317
+ const savedIndent = this.currentIndent;
318
+ this.output = '';
319
+ this.currentIndent = '';
320
+ this.visitNode(node);
321
+ const result = this.output;
322
+ this.output = savedOutput;
323
+ this.currentIndent = savedIndent;
324
+ return result;
325
+ }
326
+ }
327
+
328
+ // Mix in visitor methods from sub-modules
329
+ Object.assign(MimoToPyConverter.prototype, statementVisitors, expressionVisitors, patternVisitors);
@@ -0,0 +1,272 @@
1
+ /**
2
+ * Expression visitors for the Mimo → Python converter.
3
+ * Mixed into MimoToPyConverter via Object.assign.
4
+ */
5
+ export const expressionVisitors = {
6
+ visitBinaryExpression(node) {
7
+ const opMap = {
8
+ '=': '==',
9
+ '!=': '!=',
10
+ '===': '==',
11
+ '!==': '!=',
12
+ and: 'and',
13
+ or: 'or',
14
+ };
15
+
16
+ // Mimo's + operator concatenates if either operand is a string.
17
+ // Python's + doesn't, so route through mimo.add() for type safety.
18
+ if (node.operator === '+') {
19
+ this.write('mimo.add(');
20
+ this.visitNode(node.left);
21
+ this.write(', ');
22
+ this.visitNode(node.right);
23
+ this.write(')');
24
+ return;
25
+ }
26
+
27
+ const pyOp = opMap[node.operator] || node.operator;
28
+ this.write('(');
29
+ this.visitNode(node.left);
30
+ this.write(` ${pyOp} `);
31
+ this.visitNode(node.right);
32
+ this.write(')');
33
+ },
34
+
35
+ visitUnaryExpression(node) {
36
+ const opMap = { '!': 'not ', not: 'not ' };
37
+ const pyOp = opMap[node.operator] || node.operator;
38
+ this.write(`(${pyOp}`);
39
+ this.visitNode(node.argument);
40
+ this.write(')');
41
+ },
42
+
43
+ visitInlineIfExpression(node) {
44
+ // if cond then a else b → (a if cond else b)
45
+ this.write('(');
46
+ this.visitNode(node.consequent);
47
+ this.write(' if ');
48
+ this.visitNode(node.condition);
49
+ this.write(' else ');
50
+ this.visitNode(node.alternate);
51
+ this.write(')');
52
+ },
53
+
54
+ visitPipeExpression(node) {
55
+ // value |> callee(extraArgs) → callee(value, extraArgs)
56
+ // value |> if cond then f else g → (f if cond else g)(value)
57
+ const callee = node.callee;
58
+ const extraArgs = node.args || [];
59
+
60
+ if (!callee) {
61
+ this.visitNode(node.left);
62
+ return;
63
+ }
64
+
65
+ if (callee.type === 'InlineIfExpression') {
66
+ this.write('(');
67
+ this.visitNode(callee.consequent);
68
+ this.write(' if ');
69
+ this.visitNode(callee.condition);
70
+ this.write(' else ');
71
+ this.visitNode(callee.alternate);
72
+ this.write(')(');
73
+ this.visitNode(node.left);
74
+ this.write(')');
75
+ } else {
76
+ this.visitNode(callee);
77
+ this.write('(');
78
+ this.visitNode(node.left);
79
+ if (extraArgs.length > 0) {
80
+ this.write(', ');
81
+ this.emitArgs(extraArgs);
82
+ }
83
+ this.write(')');
84
+ }
85
+ },
86
+
87
+ visitIdentifier(node) {
88
+ this.write(node.name);
89
+ },
90
+
91
+ visitLiteral(node) {
92
+ if (node.value === null) {
93
+ this.write('None');
94
+ } else if (node.value === true) {
95
+ this.write('True');
96
+ } else if (node.value === false) {
97
+ this.write('False');
98
+ } else {
99
+ this.write(JSON.stringify(node.value));
100
+ }
101
+ },
102
+
103
+ visitArrayLiteral(node) {
104
+ this.write('[');
105
+ node.elements.forEach((el, i) => {
106
+ if (el.type === 'SpreadElement') {
107
+ this.emitSpread(el);
108
+ } else {
109
+ this.visitNode(el);
110
+ }
111
+ if (i < node.elements.length - 1) this.write(', ');
112
+ });
113
+ this.write(']');
114
+ },
115
+
116
+ visitObjectLiteral(node) {
117
+ this.write('{');
118
+ node.properties.forEach((prop, i) => {
119
+ if (prop.type === 'SpreadElement') {
120
+ // Python dict unpacking: **expr
121
+ this.write('**');
122
+ this.visitNode(prop.argument);
123
+ } else {
124
+ this.write(`"${prop.key}": `);
125
+ this.visitNode(prop.value);
126
+ }
127
+ if (i < node.properties.length - 1) this.write(', ');
128
+ });
129
+ this.write('}');
130
+ },
131
+
132
+ visitTemplateLiteral(node) {
133
+ this.write('f"');
134
+ node.parts.forEach((part) => {
135
+ if (part.type === 'Literal') {
136
+ const escaped = String(part.value)
137
+ .replace(/\\/g, '\\\\')
138
+ .replace(/"/g, '\\"')
139
+ .replace(/{/g, '{{')
140
+ .replace(/}/g, '}}');
141
+ this.write(escaped);
142
+ } else {
143
+ this.write('{');
144
+ this.visitNode(part);
145
+ this.write('}');
146
+ }
147
+ });
148
+ this.write('"');
149
+ },
150
+
151
+ visitRangeLiteral(node) {
152
+ this.write('mimo.range(');
153
+ this.visitNode(node.start);
154
+ this.write(', ');
155
+ this.visitNode(node.end);
156
+ this.write(')');
157
+ },
158
+
159
+ visitAnonymousFunction(node) {
160
+ const defaults = node.defaults || {};
161
+ const paramParts = (node.params || []).map((p) => {
162
+ const defaultNode = defaults[p.name];
163
+ if (defaultNode !== undefined && defaultNode !== null) {
164
+ return `${p.name}=${this._exprToString(defaultNode)}`;
165
+ }
166
+ return p.name;
167
+ });
168
+ if (node.restParam) {
169
+ paramParts.push(`*${node.restParam.name}`);
170
+ }
171
+ const paramStr = paramParts.join(', ');
172
+
173
+ // Single-expression body with only a return → try Python lambda.
174
+ // But if the body expression itself causes hoisting (nested anon functions),
175
+ // we must hoist this function too (can't have sub-defs inside a lambda).
176
+ if (
177
+ node.body.length === 1 &&
178
+ node.body[0].type === 'ReturnStatement'
179
+ ) {
180
+ const bodyExpr = node.body[0].argument;
181
+
182
+ // Probe whether visiting the body would cause hoisting
183
+ const savedOutput = this.output;
184
+ const savedHoisted = this._hoistedFunctions.length;
185
+ this.output = '';
186
+ if (bodyExpr) {
187
+ this.visitNode(bodyExpr);
188
+ } else {
189
+ this.write('None');
190
+ }
191
+ const bodyStr = this.output;
192
+ this.output = savedOutput;
193
+ const newlyHoisted = this._hoistedFunctions.splice(savedHoisted);
194
+
195
+ if (newlyHoisted.length === 0) {
196
+ // No sub-hoisting: safe to emit as lambda
197
+ this.write(`(lambda ${paramStr}: ${bodyStr})`);
198
+ } else {
199
+ // Sub-hoisting occurred: hoist this function as a named def instead.
200
+ const funcName = `__fn_${this._lambdaCounter++}`;
201
+ this._hoistedFunctions.push({
202
+ name: funcName,
203
+ params: paramStr,
204
+ body: node.body,
205
+ preHoisted: newlyHoisted,
206
+ });
207
+ this.write(funcName);
208
+ }
209
+ } else {
210
+ // Multi-statement body: hoist as a named inner function.
211
+ const funcName = `__fn_${this._lambdaCounter++}`;
212
+ this._hoistedFunctions.push({ name: funcName, params: paramStr, body: node.body });
213
+ this.write(funcName);
214
+ }
215
+ },
216
+
217
+ visitCallExpression(node) {
218
+ this.visitCallee(node.callee);
219
+ this.write('(');
220
+ this.emitArgs(node.arguments);
221
+ this.write(')');
222
+ },
223
+
224
+ visitSafeCallExpression(node) {
225
+ // func?.(...args) → (func(...args) if func is not None else None)
226
+ this.write('(');
227
+ this.visitNode(node.callee);
228
+ this.write('(');
229
+ this.emitArgs(node.arguments || []);
230
+ this.write(') if ');
231
+ this.visitNode(node.callee);
232
+ this.write(' is not None else None)');
233
+ },
234
+
235
+ visitModuleAccess(node) {
236
+ this.write(`${node.module}.${node.property}`);
237
+ },
238
+
239
+ visitPropertyAccess(node) {
240
+ // Mimo objects are Python dicts, so use mimo.get() for property access.
241
+ this.write('mimo.get(');
242
+ this.visitNode(node.object);
243
+ this.write(`, "${node.property}")`);
244
+ },
245
+
246
+ visitSafePropertyAccess(node) {
247
+ // obj?.prop → (mimo.get(obj, "prop") if obj is not None else None)
248
+ this.write('(mimo.get(');
249
+ this.visitNode(node.object);
250
+ this.write(`, "${node.property}") if `);
251
+ this.visitNode(node.object);
252
+ this.write(' is not None else None)');
253
+ },
254
+
255
+ visitArrayAccess(node) {
256
+ this.visitNode(node.object);
257
+ this.write('[');
258
+ this.visitNode(node.index);
259
+ this.write(']');
260
+ },
261
+
262
+ visitSafeArrayAccess(node) {
263
+ // arr?.[i] → (mimo.get(arr, i) if arr is not None else None)
264
+ this.write('(mimo.get(');
265
+ this.visitNode(node.object);
266
+ this.write(', ');
267
+ this.visitNode(node.index);
268
+ this.write(') if ');
269
+ this.visitNode(node.object);
270
+ this.write(' is not None else None)');
271
+ },
272
+ };
@@ -0,0 +1,100 @@
1
+ /**
2
+ * Pattern matching and destructuring visitors for the Mimo → Python converter.
3
+ * Mixed into MimoToPyConverter via Object.assign.
4
+ */
5
+ export const patternVisitors = {
6
+ visitArrayPattern(node) {
7
+ // Python tuple/list unpacking: a, b, c = ...
8
+ this.write(node.elements.map((e) => e.name).join(', '));
9
+ },
10
+
11
+ visitObjectPattern(node) {
12
+ // Python doesn't have object destructuring — just emit target names.
13
+ this.write(node.properties.map((p) => p.name).join(', '));
14
+ },
15
+
16
+ visitDecorator(_node) {
17
+ // Handled inside visitFunctionDeclaration
18
+ },
19
+
20
+ visitMatchStatement(node) {
21
+ const tempVar = `__match_${this._matchCounter++}`;
22
+ this.write(`${this.currentIndent}${tempVar} = `);
23
+ this.visitNode(node.discriminant);
24
+ this.write('\n');
25
+
26
+ let first = true;
27
+ for (const caseNode of node.cases) {
28
+ this._visitCaseClause(caseNode, tempVar, first);
29
+ first = false;
30
+ }
31
+ },
32
+
33
+ _visitCaseClause(node, matchVar, isFirst) {
34
+ if (!node.pattern) {
35
+ // default
36
+ this.writeLine('else:');
37
+ } else {
38
+ const prefix = isFirst ? 'if ' : 'elif ';
39
+ this.write(`${this.currentIndent}${prefix}`);
40
+ this._emitMatchCondition(node.pattern, matchVar);
41
+ // `when` guard
42
+ if (node.guard) {
43
+ this.write(' and (');
44
+ this.visitNode(node.guard);
45
+ this.write(')');
46
+ }
47
+ this.write(':\n');
48
+ this.indent();
49
+ this._emitMatchBindings(node.pattern, matchVar);
50
+ this.dedent();
51
+ }
52
+ this.visitBlock(node.consequent);
53
+ },
54
+
55
+ _emitMatchCondition(pattern, matchVar) {
56
+ if (!pattern) { this.write('True'); return; }
57
+ switch (pattern.type) {
58
+ case 'Literal':
59
+ this.write(`mimo.eq(${matchVar}, `);
60
+ this.visitNode(pattern);
61
+ this.write(')');
62
+ break;
63
+ case 'Identifier':
64
+ this.write('True');
65
+ break;
66
+ case 'ArrayPattern':
67
+ this.write(`isinstance(${matchVar}, list) and len(${matchVar}) == ${pattern.elements.length}`);
68
+ pattern.elements.forEach((el, i) => {
69
+ this.write(' and ');
70
+ this._emitMatchCondition(el, `${matchVar}[${i}]`);
71
+ });
72
+ break;
73
+ case 'ObjectPattern':
74
+ this.write(`isinstance(${matchVar}, dict)`);
75
+ break;
76
+ default:
77
+ this.write('True');
78
+ }
79
+ },
80
+
81
+ _emitMatchBindings(pattern, matchVar) {
82
+ if (!pattern) return;
83
+ switch (pattern.type) {
84
+ case 'Identifier':
85
+ this.writeLine(`${pattern.name} = ${matchVar}`);
86
+ break;
87
+ case 'ArrayPattern':
88
+ pattern.elements.forEach((el, i) =>
89
+ this._emitMatchBindings(el, `${matchVar}[${i}]`)
90
+ );
91
+ break;
92
+ case 'ObjectPattern':
93
+ pattern.properties.forEach((prop) => {
94
+ const key = prop.key || prop.name;
95
+ this.writeLine(`${prop.name} = ${matchVar}.get("${key}")`);
96
+ });
97
+ break;
98
+ }
99
+ },
100
+ };