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,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,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);
|