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