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,78 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { Parser } from '../../parser/Parser.js';
4
+ import { Lexer } from '../../lexer/Lexer.js';
5
+
6
+ export class Transpiler {
7
+ constructor() {
8
+ this.processedFiles = new Set();
9
+ }
10
+
11
+ transpileMainFile(filePath, outPath, converterInfo) {
12
+ console.log(` -> Transpiling: ${filePath}`);
13
+ const source = fs.readFileSync(filePath, 'utf-8');
14
+ const sourcePath = path.resolve(filePath);
15
+
16
+ const output = this.transpileSource(source, sourcePath, converterInfo);
17
+ fs.writeFileSync(outPath, output, 'utf-8');
18
+ }
19
+
20
+ transpileSource(source, sourcePath, converterInfo) {
21
+ const lexer = new Lexer(source, sourcePath);
22
+ const tokens = [];
23
+ let token;
24
+ while ((token = lexer.nextToken()) !== null) tokens.push(token);
25
+
26
+ const parser = new Parser(tokens, sourcePath);
27
+ const ast = parser.parse();
28
+
29
+ const converter = new converterInfo.ConverterClass();
30
+ return converter.convert(ast);
31
+ }
32
+
33
+ transpileFile(filePath, outDir, converterInfo, targetExtension) {
34
+ if (this.processedFiles.has(filePath)) {
35
+ return;
36
+ }
37
+ this.processedFiles.add(filePath);
38
+ console.log(` -> Transpiling: ${filePath}`);
39
+
40
+ const source = fs.readFileSync(filePath, 'utf-8');
41
+ const sourcePath = path.resolve(filePath);
42
+
43
+ const lexer = new Lexer(source, sourcePath);
44
+ const tokens = [];
45
+ let token;
46
+ while ((token = lexer.nextToken()) !== null) tokens.push(token);
47
+
48
+ const parser = new Parser(tokens, sourcePath);
49
+ const ast = parser.parse();
50
+
51
+ // After parsing, check for more imports to process
52
+ ast.body.forEach(stmt => {
53
+ if (stmt.type === 'ImportStatement') {
54
+ const modulePath = stmt.path;
55
+ if (!['fs', 'math', 'string', 'array', 'json', 'datetime', 'path', 'env', 'regex', 'http', 'object', 'assert'].includes(modulePath)) {
56
+ let nextFilePath = path.resolve(path.dirname(filePath), modulePath);
57
+ if (!nextFilePath.endsWith('.mimo')) {
58
+ nextFilePath += '.mimo';
59
+ }
60
+
61
+ if (fs.existsSync(nextFilePath)) {
62
+ this.transpileFile(nextFilePath, outDir, converterInfo, targetExtension);
63
+ } else {
64
+ console.warn(`Warning: Imported file not found, skipping: ${nextFilePath}`);
65
+ }
66
+ }
67
+ }
68
+ });
69
+
70
+ const converter = new converterInfo.ConverterClass();
71
+ const output = converter.convert(ast);
72
+
73
+ const baseName = path.basename(filePath, '.mimo');
74
+ const outPath = path.join(outDir, `${baseName}${targetExtension}`);
75
+
76
+ fs.writeFileSync(outPath, output, 'utf-8');
77
+ }
78
+ }
@@ -0,0 +1,66 @@
1
+ # Mimo Language Converters
2
+
3
+ This directory contains converters for transpiling Mimo source code to various target languages.
4
+
5
+ ## Modular Plugin System
6
+
7
+ The Mimo converter uses a modular plugin system. Each language support is contained within its own directory under `tools/converters/`.
8
+
9
+ ### Directory Structure
10
+
11
+ ```
12
+ converters/
13
+ ├── base_converter.js # Base class for all converters
14
+ ├── javascript/
15
+ │ ├── index.js # Plugin entry point (config + class)
16
+ │ ├── to_js.js # JavaScript implementation
17
+ │ └── mimo_runtime.js # JavaScript runtime
18
+ ├── python/
19
+ │ ├── index.js # Plugin entry point
20
+ │ ├── to_py.js # Python implementation
21
+ │ └── mimo_runtime.py # Python runtime
22
+ └── README.md
23
+ ```
24
+
25
+ ## Adding a New Language
26
+
27
+ To add support for a new target language, you only need to create a new directory with an `index.js` file.
28
+
29
+ 1. **Create the directory**: `mkdir tools/converters/mylang`
30
+ 2. **Create the implementation**: Create `to_mylang.js` (extending `BaseConverter`)
31
+ 3. **Create the entry point**: Create `index.js` in your new directory:
32
+
33
+ ```javascript
34
+ import { MyConverter } from './to_mylang.js';
35
+
36
+ export const config = {
37
+ name: 'mylang', // Main name
38
+ aliases: ['ml', 'my'], // Optional aliases
39
+ extension: '.ml', // Target file extension
40
+ runtimeFile: 'runtime.ml' // Optional runtime file to copy
41
+ };
42
+
43
+ export { MyConverter as Converter };
44
+ ```
45
+
46
+ The `tools/convert.js` script will automatically discover and register your new language on the next run.
47
+
48
+ ## Supported Languages
49
+
50
+ ### JavaScript
51
+ - **Extensions**: `.js`
52
+ - **Aliases**: `js`, `javascript`
53
+
54
+ ### Python
55
+ - **Extensions**: `.py`
56
+ - **Aliases**: `py`, `python`
57
+
58
+ ## Testing
59
+
60
+ ```bash
61
+ # Auto-detect language from extension
62
+ node tools/convert.js --in program.mimo --out program.js
63
+
64
+ # Explicitly specify language
65
+ node tools/convert.js --in program.mimo --out output_dir/ --to python
66
+ ```
@@ -0,0 +1,10 @@
1
+ import { MimoToAlyaConverter } from './to_alya.js';
2
+
3
+ export const config = {
4
+ name: 'alya',
5
+ aliases: ['alya'],
6
+ extension: '.alya',
7
+ runtimeFile: null,
8
+ };
9
+
10
+ export { MimoToAlyaConverter as Converter };
@@ -0,0 +1,289 @@
1
+ /**
2
+ * Mimo → Alya assembly converter.
3
+ *
4
+ * Targets the Alya VM assembler syntax (.alya files).
5
+ *
6
+ * Scope (core subset only):
7
+ * ✅ Integer literals, arithmetic, boolean logic
8
+ * ✅ Variables via named @registers
9
+ * ✅ `show` (integer / boolean values) via `print`
10
+ * ✅ Variable declarations (`set`, `let`, `const`)
11
+ * ✅ Assignment operators (+= -= *= /= etc.)
12
+ * ✅ Unary negation / `not`
13
+ * ✅ if / else if / else → conditional jumps + labels
14
+ * ✅ while loops → loop label + conditional jump
15
+ * ✅ for…in range() → counter + loop label
16
+ * ✅ break / continue → goto
17
+ * ✅ Function declarations → call/return + label
18
+ * ✅ return statement
19
+ * ✅ Inline if expression → conditional move pattern
20
+ * ⚠️ Strings (literal only, via LoadString + syscall 2)
21
+ * ❌ Closures, higher-order functions, lambdas
22
+ * ❌ Objects, arrays, pattern matching
23
+ * ❌ Imports / stdlib
24
+ * ❌ try/catch/throw
25
+ * ❌ Decorators
26
+ *
27
+ * Design decisions:
28
+ * - Variables use named @registers (Alya allows any @name, not just @r0–@r15).
29
+ * - Expressions are evaluated into a temporary "virtual accumulator" register
30
+ * (@__t0, @__t1, …) that the generator allocates and recycles.
31
+ * - Calling convention:
32
+ * • Args in @__a0, @__a1, … (caller sets before `call`)
33
+ * • Return value in @__ret
34
+ * • Caller saves its temporaries if needed (currently: simple depth-first
35
+ * evaluation avoids conflicts within a single expression)
36
+ * - Labels are generated as __L<n> (loops, conditionals, etc.)
37
+ * - Function labels match function names verbatim.
38
+ * - `print @reg` is used for integer/boolean output.
39
+ * - `syscall` (ID 2 in @r0, string address in @r1) is used for string output.
40
+ */
41
+
42
+ import { BaseConverter } from '../base_converter.js';
43
+ import { statementVisitors } from './visitors/statements.js';
44
+ import { expressionVisitors } from './visitors/expressions.js';
45
+
46
+ export class MimoToAlyaConverter extends BaseConverter {
47
+ constructor() {
48
+ super();
49
+ this.indentation = ' ';
50
+
51
+ // Counter for unique labels
52
+ this._labelCounter = 0;
53
+
54
+ // Temporary register depth (for expression evaluation)
55
+ this._tempDepth = 0;
56
+ this._maxTempDepth = 0;
57
+
58
+ // Call-context stack: { breakLabel, continueLabel }
59
+ this._loopStack = [];
60
+
61
+ // Deferred function bodies (emit after main code)
62
+ this._functions = []; // [{ name, node }]
63
+
64
+ // Track which functions are being compiled (for recursion)
65
+ this._inFunction = false;
66
+ this._currentFunctionParams = null;
67
+ this._currentFunctionLocals = null;
68
+ }
69
+
70
+ // =========================================================================
71
+ // Entry point
72
+ // =========================================================================
73
+
74
+ convert(ast) {
75
+ this.output = '';
76
+
77
+ // Emit a jump to `main` so function bodies defined first don't run.
78
+ this.writeLine('; Generated by Mimo → Alya converter');
79
+ this.writeLine('; Supported subset: integers, arithmetic, variables, if/while/for, functions');
80
+ this.writeLine('');
81
+ this.writeLine('goto __main');
82
+ this.writeLine('');
83
+
84
+ // First pass: collect top-level function declarations so forward calls work.
85
+ // (They will be emitted later, after the main body.)
86
+ const body = ast.body || [];
87
+ const mainStmts = [];
88
+ for (const node of body) {
89
+ if (node.type === 'FunctionDeclaration') {
90
+ this._functions.push(node);
91
+ } else {
92
+ mainStmts.push(node);
93
+ }
94
+ }
95
+
96
+ // Emit all function bodies first (before main, after the goto)
97
+ for (const fn of this._functions) {
98
+ this._emitFunction(fn);
99
+ this.writeLine('');
100
+ }
101
+
102
+ // Main body
103
+ this.writeLine('__main:');
104
+ this.indent();
105
+ let prev = null;
106
+ for (const stmt of mainStmts) {
107
+ this.emitLineGap(stmt, prev);
108
+ this.visitNode(stmt);
109
+ prev = stmt;
110
+ }
111
+ this.writeLine('halt');
112
+ this.dedent();
113
+
114
+ return this.output;
115
+ }
116
+
117
+ // =========================================================================
118
+ // Label helpers
119
+ // =========================================================================
120
+
121
+ /** Allocate a unique label string. */
122
+ newLabel(prefix = 'L') {
123
+ return `__${prefix}${this._labelCounter++}`;
124
+ }
125
+
126
+ // =========================================================================
127
+ // Temporary register helpers
128
+ // =========================================================================
129
+
130
+ /**
131
+ * Allocate the next temporary register name and return it.
132
+ * Temporaries are @__t0, @__t1, … — they are sequential and
133
+ * the generator never nests two allocations for the same slot
134
+ * within a single expression evaluation path.
135
+ */
136
+ allocTemp() {
137
+ const name = `__t${this._tempDepth}`;
138
+ this._tempDepth++;
139
+ if (this._tempDepth > this._maxTempDepth) {
140
+ this._maxTempDepth = this._tempDepth;
141
+ }
142
+ return name;
143
+ }
144
+
145
+ freeTemp() {
146
+ this._tempDepth--;
147
+ }
148
+
149
+ // =========================================================================
150
+ // Expression evaluation → register
151
+ //
152
+ // Every expression visitor returns the NAME of the Alya register that
153
+ // holds the result (without the '@' prefix). The caller is responsible
154
+ // for freeing temporaries it allocated.
155
+ // =========================================================================
156
+
157
+ /**
158
+ * Evaluate `node` and return the register name that holds its value.
159
+ * This is the central dispatcher for expression codegen.
160
+ */
161
+ evalExpr(node) {
162
+ switch (node.type) {
163
+ case 'Literal': return this.evalLiteral(node);
164
+ case 'Identifier': return this.evalIdentifier(node);
165
+ case 'BinaryExpression': return this.evalBinary(node);
166
+ case 'UnaryExpression': return this.evalUnary(node);
167
+ case 'InlineIfExpression': return this.evalInlineIf(node);
168
+ case 'CallExpression': return this.evalCall(node);
169
+ case 'ModuleAccess': return this._unsupported(node, 'ModuleAccess');
170
+ case 'PropertyAccess': return this._unsupported(node, 'PropertyAccess');
171
+ case 'ArrayAccess': return this._unsupported(node, 'ArrayAccess');
172
+ default:
173
+ return this._unsupported(node, node.type);
174
+ }
175
+ }
176
+
177
+ _unsupported(node, label) {
178
+ const tmp = this.allocTemp();
179
+ this.writeLine(`; UNSUPPORTED: ${label} — emitting 0`);
180
+ this.writeLine(`@${tmp} := 0`);
181
+ return tmp;
182
+ }
183
+
184
+ // =========================================================================
185
+ // Function emission
186
+ // =========================================================================
187
+
188
+ _emitFunction(node) {
189
+ const savedIn = this._inFunction;
190
+ const savedParams = this._currentFunctionParams;
191
+ const savedLocals = this._currentFunctionLocals;
192
+
193
+ this._inFunction = true;
194
+ this._currentFunctionParams = new Set((node.params || []).map((p) => p.name));
195
+ this._currentFunctionLocals = new Set();
196
+
197
+ const params = node.params || [];
198
+ this.writeLine(`${node.name}:`);
199
+ this.indent();
200
+
201
+ // Move arg registers to named param registers
202
+ params.forEach((p, i) => {
203
+ this.writeLine(`@${p.name} := @__a${i}`);
204
+ });
205
+
206
+ // Emit body
207
+ let prev = null;
208
+ for (const stmt of node.body || []) {
209
+ this.emitLineGap(stmt, prev);
210
+ this.visitNode(stmt);
211
+ prev = stmt;
212
+ }
213
+
214
+ // Implicit return 0
215
+ this.writeLine(`@__ret := 0`);
216
+ this.writeLine(`return`);
217
+ this.dedent();
218
+
219
+ this._inFunction = savedIn;
220
+ this._currentFunctionParams = savedParams;
221
+ this._currentFunctionLocals = savedLocals;
222
+ }
223
+
224
+ /**
225
+ * Track a local variable name declared inside the current function.
226
+ * Used to determine what to push/pop around recursive calls.
227
+ */
228
+ _trackLocal(name) {
229
+ if (this._currentFunctionLocals) {
230
+ this._currentFunctionLocals.add(name);
231
+ }
232
+ }
233
+
234
+ /**
235
+ * Emit push/pop of all live registers (params + locals) around a call.
236
+ * Returns the list of registers pushed (in order) so the caller can pop.
237
+ */
238
+ _emitCallWithSave(calleeName) {
239
+ // Collect everything that must be preserved
240
+ const toSave = [
241
+ ...(this._currentFunctionParams || []),
242
+ ...(this._currentFunctionLocals || []),
243
+ ];
244
+
245
+ // Push in order
246
+ for (const reg of toSave) {
247
+ this.writeLine(`push @${reg}`);
248
+ }
249
+
250
+ this.writeLine(`call ${calleeName}`);
251
+
252
+ // Pop in reverse order
253
+ for (const reg of [...toSave].reverse()) {
254
+ this.writeLine(`@${reg} := pop`);
255
+ }
256
+ }
257
+
258
+ // =========================================================================
259
+ // Loop stack helpers
260
+ // =========================================================================
261
+
262
+ pushLoop(breakLabel, continueLabel) {
263
+ this._loopStack.push({ breakLabel, continueLabel });
264
+ }
265
+
266
+ popLoop() {
267
+ this._loopStack.pop();
268
+ }
269
+
270
+ currentLoop() {
271
+ return this._loopStack[this._loopStack.length - 1] || null;
272
+ }
273
+
274
+ // =========================================================================
275
+ // Overrides
276
+ // =========================================================================
277
+
278
+ onUndefinedVisitor(node) {
279
+ this.writeLine(`; UNSUPPORTED NODE: ${node.type}`);
280
+ }
281
+
282
+ /** Alya has no notion of blocks — we just emit flat instructions. */
283
+ visitBlock(statements) {
284
+ (statements || []).forEach((stmt) => this.visitNode(stmt));
285
+ }
286
+ }
287
+
288
+ // Mix in visitors
289
+ Object.assign(MimoToAlyaConverter.prototype, statementVisitors, expressionVisitors);