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,257 @@
1
+ /**
2
+ * Expression visitors for the Mimo → Alya converter.
3
+ *
4
+ * Each evaluator method returns the register NAME (without '@') that holds
5
+ * the computed result. Callers must call freeTemp() for every allocTemp()
6
+ * they triggered.
7
+ */
8
+
9
+ export const expressionVisitors = {
10
+ // =========================================================================
11
+ // Literals
12
+ // =========================================================================
13
+
14
+ evalLiteral(node) {
15
+ const val = node.value;
16
+
17
+ if (typeof val === 'number') {
18
+ const tmp = this.allocTemp();
19
+ if (Number.isInteger(val)) {
20
+ // Represent negatives via two's complement trick the assembler supports
21
+ if (val < 0) {
22
+ this.writeLine(`@${tmp} := -${Math.abs(val)}`);
23
+ } else {
24
+ this.writeLine(`@${tmp} := ${val}`);
25
+ }
26
+ } else {
27
+ // Float: embed as bit-pattern comment, store integer part only
28
+ this.writeLine(`; FLOAT ${val} — stored as truncated integer ${Math.trunc(val)}`);
29
+ this.writeLine(`@${tmp} := ${Math.trunc(val)}`);
30
+ }
31
+ return tmp;
32
+ }
33
+
34
+ if (typeof val === 'boolean') {
35
+ const tmp = this.allocTemp();
36
+ this.writeLine(`@${tmp} := ${val ? 1 : 0}`);
37
+ return tmp;
38
+ }
39
+
40
+ if (typeof val === 'string') {
41
+ // String literals: use LoadString + syscall
42
+ const tmp = this.allocTemp();
43
+ this.writeLine(`@${tmp} := "${val.replace(/"/g, '\\"')}"`);
44
+ return tmp;
45
+ }
46
+
47
+ if (val === null) {
48
+ const tmp = this.allocTemp();
49
+ this.writeLine(`@${tmp} := 0 ; null`);
50
+ return tmp;
51
+ }
52
+
53
+ const tmp = this.allocTemp();
54
+ this.writeLine(`; UNSUPPORTED literal type ${typeof val}`);
55
+ this.writeLine(`@${tmp} := 0`);
56
+ return tmp;
57
+ },
58
+
59
+ // =========================================================================
60
+ // Identifiers
61
+ // =========================================================================
62
+
63
+ evalIdentifier(node) {
64
+ // Return a "virtual register" that IS the variable.
65
+ // Alya allows @any_name so we don't need to copy.
66
+ return node.name;
67
+ },
68
+
69
+ // =========================================================================
70
+ // Binary expressions
71
+ // =========================================================================
72
+
73
+ evalBinary(node) {
74
+ const lReg = this.evalExpr(node.left);
75
+ const rReg = this.evalExpr(node.right);
76
+
77
+ const op = node.operator;
78
+ const tmp = this.allocTemp();
79
+
80
+ switch (op) {
81
+ // Arithmetic
82
+ case '+': this.writeLine(`@${tmp} := @${lReg} + @${rReg}`); break;
83
+ case '-': this.writeLine(`@${tmp} := @${lReg} - @${rReg}`); break;
84
+ case '*': this.writeLine(`@${tmp} := @${lReg} * @${rReg}`); break;
85
+ case '/': this.writeLine(`@${tmp} := @${lReg} / @${rReg}`); break;
86
+ case '%': this.writeLine(`@${tmp} := @${lReg} % @${rReg}`); break;
87
+
88
+ // Comparison — produce 0/1 integer result
89
+ case '=':
90
+ case '==': this._emitCmp(tmp, lReg, rReg, '=='); break;
91
+ case '!=':
92
+ case '!': this._emitCmp(tmp, lReg, rReg, '!='); break;
93
+ case '<': this._emitCmp(tmp, lReg, rReg, '<'); break;
94
+ case '>': this._emitCmp(tmp, lReg, rReg, '>'); break;
95
+ case '<=': this._emitCmp(tmp, lReg, rReg, '<='); break;
96
+ case '>=': this._emitCmp(tmp, lReg, rReg, '>='); break;
97
+
98
+ // Logical — short-circuit is expensive without branches;
99
+ // for simple boolean values we use bitwise AND/OR on 0/1 integers.
100
+ case 'and': this.writeLine(`@${tmp} := @${lReg} & @${rReg}`); break;
101
+ case 'or': this.writeLine(`@${tmp} := @${lReg} | @${rReg}`); break;
102
+
103
+ // Bitwise
104
+ case '&': this.writeLine(`@${tmp} := @${lReg} & @${rReg}`); break;
105
+ case '|': this.writeLine(`@${tmp} := @${lReg} | @${rReg}`); break;
106
+ case '^': this.writeLine(`@${tmp} := @${lReg} ^ @${rReg}`); break;
107
+ case '<<': this.writeLine(`@${tmp} := @${lReg} << @${rReg}`); break;
108
+ case '>>': this.writeLine(`@${tmp} := @${lReg} >> @${rReg}`); break;
109
+
110
+ default:
111
+ this.writeLine(`; UNSUPPORTED binary operator: ${op}`);
112
+ this.writeLine(`@${tmp} := 0`);
113
+ }
114
+
115
+ // Free operand temps (only if they are actual temps, not variable names)
116
+ this._maybeFreeTemp(rReg);
117
+ this._maybeFreeTemp(lReg);
118
+
119
+ return tmp;
120
+ },
121
+
122
+ /**
123
+ * Emit a comparison that leaves 1/0 in @dest.
124
+ * Pattern: load 1; if NOT condition goto skip; dest stays 1; skip: (else load 0)
125
+ * Actually simpler: load 1, then if cond holds goto done, else load 0; done:
126
+ */
127
+ _emitCmp(dest, lReg, rReg, op) {
128
+ const trueLabel = this.newLabel('cmpT');
129
+ const endLabel = this.newLabel('cmpE');
130
+ // Invert the operator to jump to the "false" path
131
+ const invertOp = { '==': '!=', '!=': '==', '<': '>=', '>': '<=', '<=': '>', '>=': '<' };
132
+ const jumpOp = invertOp[op] || '!=';
133
+ this.writeLine(`@${dest} := 1`);
134
+ this.writeLine(`if @${lReg} ${op} @${rReg} goto ${trueLabel}`);
135
+ this.writeLine(`@${dest} := 0`);
136
+ this.writeLine(`goto ${endLabel}`);
137
+ this.writeLine(`${trueLabel}:`);
138
+ this.writeLine(`${endLabel}:`);
139
+ },
140
+
141
+ // =========================================================================
142
+ // Unary expressions
143
+ // =========================================================================
144
+
145
+ evalUnary(node) {
146
+ const argReg = this.evalExpr(node.argument);
147
+ const tmp = this.allocTemp();
148
+
149
+ switch (node.operator) {
150
+ case '-':
151
+ // negate: 0 - x
152
+ this.writeLine(`@${tmp} := 0`);
153
+ this.writeLine(`@${tmp} := @${tmp} - @${argReg}`);
154
+ break;
155
+ case 'not':
156
+ case '!': {
157
+ // Boolean NOT: 1 if argReg == 0, else 0
158
+ const skipLabel = this.newLabel('notT');
159
+ const endLabel = this.newLabel('notE');
160
+ this.writeLine(`@${tmp} := 0`);
161
+ this.writeLine(`if @${argReg} != 0 goto ${skipLabel}`);
162
+ this.writeLine(`@${tmp} := 1`);
163
+ this.writeLine(`goto ${endLabel}`);
164
+ this.writeLine(`${skipLabel}:`);
165
+ this.writeLine(`${endLabel}:`);
166
+ break;
167
+ }
168
+ case '~':
169
+ this.writeLine(`@${tmp} := ~@${argReg}`);
170
+ break;
171
+ default:
172
+ this.writeLine(`; UNSUPPORTED unary: ${node.operator}`);
173
+ this.writeLine(`@${tmp} := @${argReg}`);
174
+ }
175
+
176
+ this._maybeFreeTemp(argReg);
177
+ return tmp;
178
+ },
179
+
180
+ // =========================================================================
181
+ // Inline if expression: if cond then a else b
182
+ // =========================================================================
183
+
184
+ evalInlineIf(node) {
185
+ const condReg = this.evalExpr(node.condition);
186
+ const result = this.allocTemp();
187
+ const elseLabel = this.newLabel('iiE');
188
+ const endLabel = this.newLabel('iiF');
189
+
190
+ this.writeLine(`if @${condReg} == 0 goto ${elseLabel}`);
191
+ this._maybeFreeTemp(condReg);
192
+
193
+ // Consequent
194
+ const thenReg = this.evalExpr(node.consequent);
195
+ this.writeLine(`@${result} := @${thenReg}`);
196
+ this._maybeFreeTemp(thenReg);
197
+ this.writeLine(`goto ${endLabel}`);
198
+
199
+ this.writeLine(`${elseLabel}:`);
200
+ const altReg = this.evalExpr(node.alternate);
201
+ this.writeLine(`@${result} := @${altReg}`);
202
+ this._maybeFreeTemp(altReg);
203
+
204
+ this.writeLine(`${endLabel}:`);
205
+ return result;
206
+ },
207
+
208
+ // =========================================================================
209
+ // Call expression (used when a call appears inside an expression)
210
+ // =========================================================================
211
+
212
+ evalCall(node) {
213
+ // Evaluate and pass arguments via @__a0, @__a1, …
214
+ const argRegs = (node.arguments || []).map((arg) => this.evalExpr(arg));
215
+ argRegs.forEach((reg, i) => {
216
+ this.writeLine(`@__a${i} := @${reg}`);
217
+ });
218
+ argRegs.forEach((reg) => this._maybeFreeTemp(reg));
219
+
220
+ // Get callee name
221
+ let calleeName = null;
222
+ if (node.callee.type === 'Identifier') {
223
+ calleeName = node.callee.name;
224
+ } else if (node.callee.type === 'ModuleAccess') {
225
+ calleeName = `${node.callee.module}__${node.callee.property}`;
226
+ }
227
+
228
+ if (!calleeName) {
229
+ this.writeLine(`; UNSUPPORTED: complex callee in expression`);
230
+ const tmp = this.allocTemp();
231
+ this.writeLine(`@${tmp} := 0`);
232
+ return tmp;
233
+ }
234
+
235
+ // If inside a function, save/restore live registers around the call
236
+ if (this._inFunction) {
237
+ this._emitCallWithSave(calleeName);
238
+ } else {
239
+ this.writeLine(`call ${calleeName}`);
240
+ }
241
+
242
+ // Return value is in @__ret — copy to a temp so the caller can use it
243
+ const tmp = this.allocTemp();
244
+ this.writeLine(`@${tmp} := @__ret`);
245
+ return tmp;
246
+ },
247
+
248
+ // =========================================================================
249
+ // Internal helper: only free if this is a temp (starts with __t)
250
+ // =========================================================================
251
+
252
+ _maybeFreeTemp(regName) {
253
+ if (regName && regName.startsWith('__t')) {
254
+ this.freeTemp();
255
+ }
256
+ },
257
+ };
@@ -0,0 +1,403 @@
1
+ /**
2
+ * Statement visitors for the Mimo → Alya converter.
3
+ * Mixed into MimoToAlyaConverter via Object.assign.
4
+ */
5
+
6
+ export const statementVisitors = {
7
+ // =========================================================================
8
+ // Program
9
+ // =========================================================================
10
+
11
+ visitProgram(node) {
12
+ // Handled by convert() — should not be called directly.
13
+ (node.body || []).forEach((stmt) => this.visitNode(stmt));
14
+ },
15
+
16
+ // =========================================================================
17
+ // show statement → print @reg (integers/booleans)
18
+ // syscall (strings)
19
+ // =========================================================================
20
+
21
+ visitShowStatement(node) {
22
+ const reg = this.evalExpr(node.expression);
23
+ const exprNode = node.expression;
24
+
25
+ // If the expression is a string literal, use syscall 2
26
+ if (exprNode.type === 'Literal' && typeof exprNode.value === 'string') {
27
+ this.writeLine(`; show (string)`);
28
+ this.writeLine(`@r0 := 2`);
29
+ this.writeLine(`@r1 := @${reg}`);
30
+ this.writeLine(`syscall`);
31
+ } else {
32
+ this.writeLine(`print @${reg}`);
33
+ }
34
+
35
+ this._maybeFreeTemp(reg);
36
+ },
37
+
38
+ // =========================================================================
39
+ // Variable declarations (set / let / const)
40
+ // =========================================================================
41
+
42
+ visitVariableDeclaration(node) {
43
+ // Track as a local if we're inside a function
44
+ this._trackLocal(node.identifier);
45
+
46
+ const reg = this.evalExpr(node.value);
47
+ if (reg !== node.identifier) {
48
+ this.writeLine(`@${node.identifier} := @${reg}`);
49
+ this._maybeFreeTemp(reg);
50
+ }
51
+ // If evalExpr returned the variable name itself (identifier → itself), no copy needed.
52
+ },
53
+
54
+ // =========================================================================
55
+ // Return statement
56
+ // =========================================================================
57
+
58
+ visitReturnStatement(node) {
59
+ if (node.argument) {
60
+ const reg = this.evalExpr(node.argument);
61
+ this.writeLine(`@__ret := @${reg}`);
62
+ this._maybeFreeTemp(reg);
63
+ } else {
64
+ this.writeLine(`@__ret := 0`);
65
+ }
66
+ this.writeLine(`return`);
67
+ },
68
+
69
+ // =========================================================================
70
+ // Function declaration — deferred to _emitFunction in to_alya.js
71
+ // =========================================================================
72
+
73
+ visitFunctionDeclaration(node) {
74
+ // Top-level functions are collected in convert() and emitted before __main.
75
+ // If we encounter a nested function declaration (not supported), warn and skip.
76
+ if (this._inFunction) {
77
+ this.writeLine(`; UNSUPPORTED: nested function declaration '${node.name}'`);
78
+ }
79
+ // Already handled in convert(); nothing to emit here.
80
+ },
81
+
82
+ // =========================================================================
83
+ // Call statement (standalone call, with optional destination)
84
+ // =========================================================================
85
+
86
+ visitCallStatement(node) {
87
+ // Evaluate args
88
+ const argRegs = (node.arguments || []).map((arg) => this.evalExpr(arg));
89
+ argRegs.forEach((reg, i) => {
90
+ this.writeLine(`@__a${i} := @${reg}`);
91
+ });
92
+ argRegs.forEach((reg) => this._maybeFreeTemp(reg));
93
+
94
+ // Callee
95
+ let calleeName = null;
96
+ if (node.callee.type === 'Identifier') {
97
+ calleeName = node.callee.name;
98
+ } else if (node.callee.type === 'ModuleAccess') {
99
+ calleeName = `${node.callee.module}__${node.callee.property}`;
100
+ }
101
+
102
+ if (!calleeName) {
103
+ this.writeLine(`; UNSUPPORTED: complex callee`);
104
+ return;
105
+ }
106
+
107
+ // If inside a function, save/restore live registers around the call
108
+ if (this._inFunction) {
109
+ this._emitCallWithSave(calleeName);
110
+ } else {
111
+ this.writeLine(`call ${calleeName}`);
112
+ }
113
+
114
+ // Capture return value if destination specified
115
+ if (node.destination) {
116
+ this.writeLine(`@${node.destination.name} := @__ret`);
117
+ }
118
+ },
119
+
120
+ // =========================================================================
121
+ // Assignment operators (+=, -=, *=, /=, etc.)
122
+ // These appear as VariableDeclaration nodes with kind 'set' after
123
+ // the variable is already declared, OR as separate AssignmentStatement
124
+ // nodes (if the parser emits them that way).
125
+ // =========================================================================
126
+
127
+ visitAssignmentStatement(node) {
128
+ // node.identifier: string name
129
+ // node.operator: '=', '+=', '-=', '*=', '/='
130
+ // node.value: expression node
131
+ const reg = this.evalExpr(node.value);
132
+ const op = node.operator;
133
+
134
+ switch (op) {
135
+ case '=':
136
+ if (reg !== node.identifier) {
137
+ this.writeLine(`@${node.identifier} := @${reg}`);
138
+ }
139
+ break;
140
+ case '+=': this.writeLine(`@${node.identifier} += @${reg}`); break;
141
+ case '-=': this.writeLine(`@${node.identifier} -= @${reg}`); break;
142
+ case '*=': this.writeLine(`@${node.identifier} *= @${reg}`); break;
143
+ case '/=': this.writeLine(`@${node.identifier} /= @${reg}`); break;
144
+ default:
145
+ // Fallback: evaluate and assign
146
+ this.writeLine(`; assignment op '${op}' mapped to plain assign`);
147
+ this.writeLine(`@${node.identifier} := @${reg}`);
148
+ }
149
+
150
+ this._maybeFreeTemp(reg);
151
+ },
152
+
153
+ visitPropertyAssignment(node) {
154
+ this.writeLine(`; UNSUPPORTED: PropertyAssignment`);
155
+ },
156
+
157
+ visitBracketAssignment(node) {
158
+ this.writeLine(`; UNSUPPORTED: BracketAssignment`);
159
+ },
160
+
161
+ visitDestructuringAssignment(node) {
162
+ this.writeLine(`; UNSUPPORTED: DestructuringAssignment`);
163
+ },
164
+
165
+ // =========================================================================
166
+ // if / else if / else
167
+ // =========================================================================
168
+
169
+ visitIfStatement(node) {
170
+ const endLabel = this.newLabel('ifEnd');
171
+ this._emitIfChain(node, endLabel);
172
+ this.writeLine(`${endLabel}:`);
173
+ },
174
+
175
+ _emitIfChain(node, endLabel) {
176
+ const condReg = this.evalExpr(node.condition);
177
+ const nextLabel = this.newLabel('ifNext');
178
+
179
+ // Jump past this branch if condition is false (== 0)
180
+ this.writeLine(`if @${condReg} == 0 goto ${nextLabel}`);
181
+ this._maybeFreeTemp(condReg);
182
+
183
+ // Consequent block
184
+ (node.consequent || []).forEach((stmt) => this.visitNode(stmt));
185
+ this.writeLine(`goto ${endLabel}`);
186
+
187
+ this.writeLine(`${nextLabel}:`);
188
+
189
+ if (node.alternate) {
190
+ if (node.alternate.type === 'IfStatement') {
191
+ // else if chain
192
+ this._emitIfChain(node.alternate, endLabel);
193
+ } else if (Array.isArray(node.alternate)) {
194
+ // plain else block
195
+ node.alternate.forEach((stmt) => this.visitNode(stmt));
196
+ } else {
197
+ // single statement alternate
198
+ this.visitNode(node.alternate);
199
+ }
200
+ }
201
+ },
202
+
203
+ visitGuardStatement(node) {
204
+ // guard condition else <block> end
205
+ // → if (!condition) { block }
206
+ const condReg = this.evalExpr(node.condition);
207
+ const skipLabel = this.newLabel('guardEnd');
208
+ this.writeLine(`if @${condReg} != 0 goto ${skipLabel}`);
209
+ this._maybeFreeTemp(condReg);
210
+ (node.alternate || node.elseBlock || []).forEach((stmt) => this.visitNode(stmt));
211
+ this.writeLine(`${skipLabel}:`);
212
+ },
213
+
214
+ // =========================================================================
215
+ // while loop
216
+ // =========================================================================
217
+
218
+ visitWhileStatement(node) {
219
+ const loopLabel = this.newLabel('wLoop');
220
+ const endLabel = this.newLabel('wEnd');
221
+
222
+ this.pushLoop(endLabel, loopLabel);
223
+
224
+ this.writeLine(`${loopLabel}:`);
225
+
226
+ const condReg = this.evalExpr(node.condition);
227
+ this.writeLine(`if @${condReg} == 0 goto ${endLabel}`);
228
+ this._maybeFreeTemp(condReg);
229
+
230
+ (node.body || []).forEach((stmt) => this.visitNode(stmt));
231
+ this.writeLine(`goto ${loopLabel}`);
232
+ this.writeLine(`${endLabel}:`);
233
+
234
+ this.popLoop();
235
+ },
236
+
237
+ // =========================================================================
238
+ // for … in <iterable>
239
+ //
240
+ // Only range() is supported. Produces:
241
+ // @var := start
242
+ // loop: if @var >= end goto end
243
+ // body
244
+ // @var += 1 (or step)
245
+ // goto loop
246
+ // end:
247
+ // =========================================================================
248
+
249
+ visitForStatement(node) {
250
+ const varName = node.variable.name;
251
+ const loopLabel = this.newLabel('fLoop');
252
+ const endLabel = this.newLabel('fEnd');
253
+ const iterable = node.iterable;
254
+
255
+ this.pushLoop(endLabel, loopLabel);
256
+
257
+ // Only support range() iterables
258
+ if (iterable.type === 'RangeLiteral' ||
259
+ (iterable.type === 'CallExpression' &&
260
+ iterable.callee?.name === 'range')) {
261
+ this._emitForRange(varName, loopLabel, endLabel, iterable, node.body || []);
262
+ } else if (iterable.type === 'RangeLiteral') {
263
+ this._emitForRange(varName, loopLabel, endLabel, iterable, node.body || []);
264
+ } else {
265
+ this.writeLine(`; UNSUPPORTED for-in: only range() is supported`);
266
+ }
267
+
268
+ this.popLoop();
269
+ },
270
+
271
+ _emitForRange(varName, loopLabel, endLabel, rangeNode, body) {
272
+ // RangeLiteral: { start, end } (step always 1 in Mimo)
273
+ // CallExpression to range(start, end) — also 1 step
274
+ let startNode, endNode;
275
+
276
+ if (rangeNode.type === 'RangeLiteral') {
277
+ startNode = rangeNode.start;
278
+ endNode = rangeNode.end;
279
+ } else {
280
+ // range(start, end) call
281
+ const args = rangeNode.arguments || [];
282
+ if (args.length === 1) {
283
+ startNode = { type: 'Literal', value: 0 };
284
+ endNode = args[0];
285
+ } else {
286
+ startNode = args[0];
287
+ endNode = args[1];
288
+ }
289
+ }
290
+
291
+ const startReg = this.evalExpr(startNode);
292
+ this.writeLine(`@${varName} := @${startReg}`);
293
+ this._maybeFreeTemp(startReg);
294
+
295
+ const endReg = this.evalExpr(endNode);
296
+ // Stash the end value in a dedicated register so the loop condition
297
+ // doesn't re-evaluate it on every iteration.
298
+ const endVar = `__fe${this._labelCounter}`;
299
+ this.writeLine(`@${endVar} := @${endReg}`);
300
+ this._maybeFreeTemp(endReg);
301
+
302
+ this.writeLine(`${loopLabel}:`);
303
+ this.writeLine(`if @${varName} >= @${endVar} goto ${endLabel}`);
304
+ body.forEach((stmt) => this.visitNode(stmt));
305
+ this.writeLine(`@${varName} += 1`);
306
+ this.writeLine(`goto ${loopLabel}`);
307
+ this.writeLine(`${endLabel}:`);
308
+ },
309
+
310
+ // =========================================================================
311
+ // loop … end (infinite loop with break/continue)
312
+ // =========================================================================
313
+
314
+ visitLoopStatement(node) {
315
+ const loopLabel = this.newLabel('loop');
316
+ const endLabel = this.newLabel('loopEnd');
317
+
318
+ this.pushLoop(endLabel, loopLabel);
319
+ this.writeLine(`${loopLabel}:`);
320
+ (node.body || []).forEach((stmt) => this.visitNode(stmt));
321
+ this.writeLine(`goto ${loopLabel}`);
322
+ this.writeLine(`${endLabel}:`);
323
+ this.popLoop();
324
+ },
325
+
326
+ // =========================================================================
327
+ // break / continue
328
+ // =========================================================================
329
+
330
+ visitBreakStatement(node) {
331
+ const loop = this.currentLoop();
332
+ if (loop) {
333
+ this.writeLine(`goto ${loop.breakLabel}`);
334
+ } else {
335
+ this.writeLine(`; break outside loop — ignored`);
336
+ }
337
+ },
338
+
339
+ visitContinueStatement(node) {
340
+ const loop = this.currentLoop();
341
+ if (loop) {
342
+ this.writeLine(`goto ${loop.continueLabel}`);
343
+ } else {
344
+ this.writeLine(`; continue outside loop — ignored`);
345
+ }
346
+ },
347
+
348
+ // =========================================================================
349
+ // Labeled statement (Mimo: label name ... end)
350
+ // =========================================================================
351
+
352
+ visitLabeledStatement(node) {
353
+ this.writeLine(`${node.label}:`);
354
+ this.visitNode(node.statement);
355
+ },
356
+
357
+ // =========================================================================
358
+ // try / catch / throw — not supported
359
+ // =========================================================================
360
+
361
+ visitTryStatement(node) {
362
+ this.writeLine(`; UNSUPPORTED: try/catch — executing try block only`);
363
+ (node.tryBlock || []).forEach((stmt) => this.visitNode(stmt));
364
+ },
365
+
366
+ visitThrowStatement(node) {
367
+ this.writeLine(`; UNSUPPORTED: throw — emitting halt`);
368
+ this.writeLine(`halt`);
369
+ },
370
+
371
+ // =========================================================================
372
+ // import — not supported
373
+ // =========================================================================
374
+
375
+ visitImportStatement(node) {
376
+ this.writeLine(`; UNSUPPORTED: import '${node.path}' as ${node.alias}`);
377
+ },
378
+
379
+ // =========================================================================
380
+ // Decorators — not supported
381
+ // =========================================================================
382
+
383
+ visitDecoratorStatement(node) {
384
+ this.writeLine(`; UNSUPPORTED: decorator`);
385
+ },
386
+
387
+ // =========================================================================
388
+ // Pipe expression statement (rare but possible)
389
+ // =========================================================================
390
+
391
+ visitPipeExpression(node) {
392
+ const reg = this.evalExpr(node);
393
+ this._maybeFreeTemp(reg);
394
+ },
395
+
396
+ // =========================================================================
397
+ // Match statement — not supported
398
+ // =========================================================================
399
+
400
+ visitMatchStatement(node) {
401
+ this.writeLine(`; UNSUPPORTED: match statement`);
402
+ },
403
+ };