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,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
|
+
};
|