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,228 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Base class for all Mimo language converters.
|
|
3
|
+
*
|
|
4
|
+
* Provides shared infrastructure:
|
|
5
|
+
* - output buffer management (write / writeLine)
|
|
6
|
+
* - indentation tracking (indent / dedent)
|
|
7
|
+
* - visitor dispatch (visitNode / visitBlock)
|
|
8
|
+
* - shared constants (CORE_BUILTINS, STDLIB_MODULES)
|
|
9
|
+
* - argument-list emitter (emitArgs)
|
|
10
|
+
*
|
|
11
|
+
* Every language-specific converter should extend this class and override:
|
|
12
|
+
* - convert(ast) — entry point, builds the header/footer and calls visitNode(ast)
|
|
13
|
+
* - any visitXxx(node) methods for language-specific code generation
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
/** Built-in functions that are provided by the Mimo runtime object. */
|
|
17
|
+
export const CORE_BUILTINS = new Set([
|
|
18
|
+
"len",
|
|
19
|
+
"get",
|
|
20
|
+
"update",
|
|
21
|
+
"type",
|
|
22
|
+
"push",
|
|
23
|
+
"pop",
|
|
24
|
+
"slice",
|
|
25
|
+
"range",
|
|
26
|
+
"join",
|
|
27
|
+
"has_property",
|
|
28
|
+
"keys",
|
|
29
|
+
"values",
|
|
30
|
+
"entries",
|
|
31
|
+
"get_arguments",
|
|
32
|
+
"get_env",
|
|
33
|
+
"exit_code",
|
|
34
|
+
"coalesce",
|
|
35
|
+
"get_property_safe",
|
|
36
|
+
"if_else",
|
|
37
|
+
]);
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* All known Mimo standard-library module names.
|
|
41
|
+
* When an import path matches one of these names it is a stdlib import,
|
|
42
|
+
* not a relative file import.
|
|
43
|
+
*/
|
|
44
|
+
export const STDLIB_MODULES = new Set([
|
|
45
|
+
"array",
|
|
46
|
+
"assert",
|
|
47
|
+
"datetime",
|
|
48
|
+
"env",
|
|
49
|
+
"fs",
|
|
50
|
+
"http",
|
|
51
|
+
"json",
|
|
52
|
+
"math",
|
|
53
|
+
"object",
|
|
54
|
+
"path",
|
|
55
|
+
"regex",
|
|
56
|
+
"string",
|
|
57
|
+
]);
|
|
58
|
+
|
|
59
|
+
export class BaseConverter {
|
|
60
|
+
constructor() {
|
|
61
|
+
this.output = "";
|
|
62
|
+
this.indentation = " ";
|
|
63
|
+
this.currentIndent = "";
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// -------------------------------------------------------------------------
|
|
67
|
+
// Output helpers
|
|
68
|
+
// -------------------------------------------------------------------------
|
|
69
|
+
|
|
70
|
+
/** Append raw text to the output buffer. */
|
|
71
|
+
write(text) {
|
|
72
|
+
this.output += text;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Append an indented line to the output buffer.
|
|
77
|
+
* Calling with no argument emits a blank line.
|
|
78
|
+
*/
|
|
79
|
+
writeLine(line = "") {
|
|
80
|
+
if (line === "") {
|
|
81
|
+
this.output += "\n";
|
|
82
|
+
} else {
|
|
83
|
+
this.output += this.currentIndent + line + "\n";
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// -------------------------------------------------------------------------
|
|
88
|
+
// Indentation helpers
|
|
89
|
+
// -------------------------------------------------------------------------
|
|
90
|
+
|
|
91
|
+
indent() {
|
|
92
|
+
this.currentIndent += this.indentation;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
dedent() {
|
|
96
|
+
this.currentIndent = this.currentIndent.slice(0, -this.indentation.length);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// -------------------------------------------------------------------------
|
|
100
|
+
// Spacing helpers
|
|
101
|
+
// -------------------------------------------------------------------------
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Emit blank lines to preserve the visual spacing between two consecutive
|
|
105
|
+
* statements from the original source.
|
|
106
|
+
*
|
|
107
|
+
* When the current statement starts more than one line after the previous
|
|
108
|
+
* statement ended, at least one blank line existed in the source. We emit
|
|
109
|
+
* exactly one blank line in that case (we don't replicate multiple blanks).
|
|
110
|
+
*
|
|
111
|
+
* @param {Object} currentNode - The AST node about to be emitted.
|
|
112
|
+
* @param {Object|null} prevNode - The previously emitted AST node, or null
|
|
113
|
+
* if this is the first statement in the block.
|
|
114
|
+
*/
|
|
115
|
+
emitLineGap(currentNode, prevNode) {
|
|
116
|
+
if (!prevNode || !currentNode) return;
|
|
117
|
+
const prevLine = (prevNode.line || 0);
|
|
118
|
+
const currLine = (currentNode.line || 0);
|
|
119
|
+
if (currLine - prevLine > 1) {
|
|
120
|
+
this.writeLine();
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// -------------------------------------------------------------------------
|
|
125
|
+
// Visitor dispatch
|
|
126
|
+
// -------------------------------------------------------------------------
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Dispatch to the appropriate visitor method based on node.type.
|
|
130
|
+
* Falls back to onUndefinedVisitor() when no handler exists.
|
|
131
|
+
*/
|
|
132
|
+
visitNode(node) {
|
|
133
|
+
if (!node || !node.type) return;
|
|
134
|
+
const visitor = this[`visit${node.type}`];
|
|
135
|
+
if (visitor) {
|
|
136
|
+
visitor.call(this, node);
|
|
137
|
+
} else {
|
|
138
|
+
this.onUndefinedVisitor(node);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Visit a list of statements with one extra level of indentation.
|
|
144
|
+
* Language-specific converters may override this (e.g. Python needs
|
|
145
|
+
* `pass` for empty blocks).
|
|
146
|
+
*/
|
|
147
|
+
visitBlock(statements) {
|
|
148
|
+
this.indent();
|
|
149
|
+
(statements || []).forEach((stmt) => this.visitNode(stmt));
|
|
150
|
+
this.dedent();
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Called when visitNode() cannot find a matching visitor.
|
|
155
|
+
* Override to log a warning or throw in strict mode.
|
|
156
|
+
*/
|
|
157
|
+
onUndefinedVisitor(node) {
|
|
158
|
+
// default: silent — sub-classes may warn
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// -------------------------------------------------------------------------
|
|
162
|
+
// Shared expression helpers
|
|
163
|
+
// -------------------------------------------------------------------------
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Emit a comma-separated argument list.
|
|
167
|
+
* Each element is visited via visitNode().
|
|
168
|
+
*/
|
|
169
|
+
emitArgs(args) {
|
|
170
|
+
(args || []).forEach((arg, i) => {
|
|
171
|
+
if (arg.type === "SpreadElement") {
|
|
172
|
+
this.emitSpread(arg);
|
|
173
|
+
} else {
|
|
174
|
+
this.visitNode(arg);
|
|
175
|
+
}
|
|
176
|
+
if (i < args.length - 1) this.write(", ");
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Emit a spread element. Sub-classes should override if they need
|
|
182
|
+
* language-specific spread syntax (e.g. Python uses `*expr`).
|
|
183
|
+
*/
|
|
184
|
+
emitSpread(node) {
|
|
185
|
+
this.write("...");
|
|
186
|
+
this.visitNode(node.argument);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Build the parameter name list for a function declaration or anonymous
|
|
191
|
+
* function node, respecting rest parameters.
|
|
192
|
+
* Returns an array of strings (already formatted for the target language).
|
|
193
|
+
*/
|
|
194
|
+
buildParamNames(node) {
|
|
195
|
+
const names = (node.params || []).map((p) => p.name);
|
|
196
|
+
if (node.restParam) {
|
|
197
|
+
names.push(`...${node.restParam.name}`);
|
|
198
|
+
}
|
|
199
|
+
return names;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Returns true when `modulePath` refers to a Mimo stdlib module.
|
|
204
|
+
*/
|
|
205
|
+
isStdlibModule(modulePath) {
|
|
206
|
+
return STDLIB_MODULES.has(modulePath);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Returns true when `name` is a Mimo core built-in.
|
|
211
|
+
*/
|
|
212
|
+
isCoreBuiltin(name) {
|
|
213
|
+
return CORE_BUILTINS.has(name);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// -------------------------------------------------------------------------
|
|
217
|
+
// Entry point (must be overridden)
|
|
218
|
+
// -------------------------------------------------------------------------
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Convert an AST to the target language source string.
|
|
222
|
+
* @param {Object} ast - The root Program node.
|
|
223
|
+
* @returns {string}
|
|
224
|
+
*/
|
|
225
|
+
convert(ast) {
|
|
226
|
+
throw new Error(`${this.constructor.name} must implement convert(ast)`);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Mimo JavaScript runtime (Full Version).
|
|
3
|
+
* Provides JS implementations for Mimo's built-ins and standard library.
|
|
4
|
+
*/
|
|
5
|
+
import fs from 'node:fs';
|
|
6
|
+
import nodePath from 'node:path';
|
|
7
|
+
|
|
8
|
+
// Helper for deep equality checks
|
|
9
|
+
function isEqual(a, b) {
|
|
10
|
+
if (a === b) return true;
|
|
11
|
+
if (a instanceof Date && b instanceof Date) return a.getTime() === b.getTime();
|
|
12
|
+
if (!a || !b || (typeof a !== 'object' && typeof b !== 'object')) return a === b;
|
|
13
|
+
if (a.prototype !== b.prototype) return false;
|
|
14
|
+
const keys = Object.keys(a);
|
|
15
|
+
if (keys.length !== Object.keys(b).length) return false;
|
|
16
|
+
return keys.every(k => isEqual(a[k], b[k]));
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Main runtime object
|
|
20
|
+
export const mimo = {
|
|
21
|
+
// --- Core IO & Utils ---
|
|
22
|
+
show: (...args) => console.log(...args.map(mimo.stringify)),
|
|
23
|
+
stringify: (value) => {
|
|
24
|
+
if (value === null) return 'null';
|
|
25
|
+
if (value === undefined) return 'undefined';
|
|
26
|
+
if (Array.isArray(value)) return `[${value.map(mimo.stringify).join(', ')}]`;
|
|
27
|
+
if (value instanceof Date) return `datetime(${value.toISOString()})`;
|
|
28
|
+
if (typeof value === 'object') {
|
|
29
|
+
const pairs = Object.entries(value).map(([k, v]) => `${k}: ${mimo.stringify(v)}`);
|
|
30
|
+
return `{${pairs.join(', ')}}`;
|
|
31
|
+
}
|
|
32
|
+
return String(value);
|
|
33
|
+
},
|
|
34
|
+
|
|
35
|
+
// --- Core Built-ins ---
|
|
36
|
+
len: (c) => c?.length ?? (typeof c === 'object' ? Object.keys(c).length : 0),
|
|
37
|
+
get: (c, k) => c?.[k] ?? null,
|
|
38
|
+
update: (c, k, v) => { c[k] = v; return v; },
|
|
39
|
+
type: (v) => v === null ? 'null' : (Array.isArray(v) ? 'array' : typeof v),
|
|
40
|
+
push: (arr, v) => { arr.push(v); return arr; },
|
|
41
|
+
pop: (arr) => arr.pop(),
|
|
42
|
+
range: (start, end, step = 1) => {
|
|
43
|
+
const result = [];
|
|
44
|
+
if (end === undefined) { end = start; start = 0; }
|
|
45
|
+
for (let i = start; step > 0 ? i < end : i > end; i += step) {
|
|
46
|
+
result.push(i);
|
|
47
|
+
}
|
|
48
|
+
return result;
|
|
49
|
+
},
|
|
50
|
+
join: (arr, sep) => arr.map(mimo.stringify).join(sep),
|
|
51
|
+
|
|
52
|
+
// --- Logical Operators (as functions) ---
|
|
53
|
+
eq: (a, b) => isEqual(a, b),
|
|
54
|
+
neq: (a, b) => !isEqual(a, b),
|
|
55
|
+
and: (a, b) => a && b,
|
|
56
|
+
or: (a, b) => a || b,
|
|
57
|
+
if_else: (cond, ifTrue, ifFalse) => {
|
|
58
|
+
if (typeof cond === 'function') cond = cond();
|
|
59
|
+
return cond ? ifTrue : ifFalse;
|
|
60
|
+
},
|
|
61
|
+
coalesce: (...args) => {
|
|
62
|
+
for (const arg of args) {
|
|
63
|
+
if (arg !== null && arg !== undefined) return arg;
|
|
64
|
+
}
|
|
65
|
+
return null;
|
|
66
|
+
},
|
|
67
|
+
get_property_safe: (obj, prop) => {
|
|
68
|
+
if (obj && typeof obj === 'object' && prop in obj) return obj[prop];
|
|
69
|
+
return null;
|
|
70
|
+
},
|
|
71
|
+
|
|
72
|
+
// --- Standard Library Modules ---
|
|
73
|
+
fs: {
|
|
74
|
+
read_file: (path) => fs.readFileSync(path, 'utf-8'),
|
|
75
|
+
write_file: (path, data) => fs.writeFileSync(path, data),
|
|
76
|
+
exists: (path) => fs.existsSync(path),
|
|
77
|
+
list_dir: (path) => fs.readdirSync(path),
|
|
78
|
+
make_dir: (path, opts) => fs.mkdirSync(path, opts),
|
|
79
|
+
remove_file: (path) => fs.unlinkSync(path),
|
|
80
|
+
remove_dir: (path, recursive) => fs.rmdirSync(path, { recursive }),
|
|
81
|
+
},
|
|
82
|
+
json: {
|
|
83
|
+
parse: (str) => JSON.parse(str),
|
|
84
|
+
stringify: (obj, indent) => JSON.stringify(obj, null, indent),
|
|
85
|
+
},
|
|
86
|
+
datetime: {
|
|
87
|
+
now: () => new Date(),
|
|
88
|
+
get_timestamp: (d) => d.getTime(),
|
|
89
|
+
from_timestamp: (ts) => new Date(ts),
|
|
90
|
+
to_iso_string: (d) => d.toISOString(),
|
|
91
|
+
format: (d, fmt) => {
|
|
92
|
+
return fmt
|
|
93
|
+
.replace(/YYYY/g, d.getFullYear())
|
|
94
|
+
.replace(/MM/g, String(d.getMonth() + 1).padStart(2, '0'))
|
|
95
|
+
.replace(/DD/g, String(d.getDate()).padStart(2, '0'))
|
|
96
|
+
.replace(/hh/g, String(d.getHours()).padStart(2, '0'))
|
|
97
|
+
.replace(/mm/g, String(d.getMinutes()).padStart(2, '0'))
|
|
98
|
+
.replace(/ss/g, String(d.getSeconds()).padStart(2, '0'));
|
|
99
|
+
},
|
|
100
|
+
},
|
|
101
|
+
math: { ...Math, PI: Math.PI, E: Math.E },
|
|
102
|
+
string: {
|
|
103
|
+
to_upper: (s) => s.toUpperCase(),
|
|
104
|
+
to_lower: (s) => s.toLowerCase(),
|
|
105
|
+
to_title_case: (s) => s.toLowerCase().split(' ').map(w => w.charAt(0).toUpperCase() + w.slice(1)).join(' '),
|
|
106
|
+
capitalize: (s) => s.charAt(0).toUpperCase() + s.slice(1),
|
|
107
|
+
trim: (s) => s.trim(),
|
|
108
|
+
trim_start: (s) => s.trimStart(),
|
|
109
|
+
trim_end: (s) => s.trimEnd(),
|
|
110
|
+
pad_start: (s, len, pad = ' ') => s.padStart(len, pad),
|
|
111
|
+
pad_end: (s, len, pad = ' ') => s.padEnd(len, pad),
|
|
112
|
+
contains: (s, sub, pos = 0) => s.includes(sub, pos),
|
|
113
|
+
starts_with: (s, sub, pos = 0) => s.startsWith(sub, pos),
|
|
114
|
+
ends_with: (s, sub, len) => s.endsWith(sub, len),
|
|
115
|
+
index_of: (s, sub, from = 0) => s.indexOf(sub, from),
|
|
116
|
+
last_index_of: (s, sub, from) => s.lastIndexOf(sub, from),
|
|
117
|
+
substring: (s, start, end) => s.substring(start, end),
|
|
118
|
+
slice: (s, start, end) => s.slice(start, end),
|
|
119
|
+
split: (s, sep, limit) => s.split(sep, limit),
|
|
120
|
+
replace: (s, find, rep) => s.replace(find, rep),
|
|
121
|
+
replace_all: (s, find, rep) => s.replaceAll(find, rep),
|
|
122
|
+
repeat: (s, n) => s.repeat(n),
|
|
123
|
+
char_at: (s, i) => s.charAt(i),
|
|
124
|
+
is_empty: (s) => s.length === 0,
|
|
125
|
+
is_blank: (s) => s.trim().length === 0,
|
|
126
|
+
},
|
|
127
|
+
array: {
|
|
128
|
+
map: (arr, cb) => arr.map(cb),
|
|
129
|
+
filter: (arr, cb) => arr.filter(cb),
|
|
130
|
+
reduce: (arr, cb, init) => init !== undefined ? arr.reduce(cb, init) : arr.reduce(cb),
|
|
131
|
+
flat: (arr, depth = 1) => arr.flat(depth),
|
|
132
|
+
flat_map: (arr, cb) => arr.flatMap(cb),
|
|
133
|
+
group_by: (arr, cb) => {
|
|
134
|
+
const result = {};
|
|
135
|
+
for (const item of arr) {
|
|
136
|
+
const key = String(cb(item));
|
|
137
|
+
(result[key] = result[key] || []).push(item);
|
|
138
|
+
}
|
|
139
|
+
return result;
|
|
140
|
+
},
|
|
141
|
+
zip: (...arrs) => {
|
|
142
|
+
const len = Math.min(...arrs.map(a => a.length));
|
|
143
|
+
return Array.from({ length: len }, (_, i) => arrs.map(a => a[i]));
|
|
144
|
+
},
|
|
145
|
+
chunk: (arr, size) => {
|
|
146
|
+
const result = [];
|
|
147
|
+
for (let i = 0; i < arr.length; i += size) result.push(arr.slice(i, i + size));
|
|
148
|
+
return result;
|
|
149
|
+
},
|
|
150
|
+
count: (arr, cb) => cb ? arr.filter(cb).length : arr.length,
|
|
151
|
+
for_each: (arr, cb) => arr.forEach(cb),
|
|
152
|
+
find: (arr, cb) => arr.find(cb) ?? null,
|
|
153
|
+
find_index: (arr, cb) => arr.findIndex(cb),
|
|
154
|
+
includes: (arr, val) => arr.includes(val),
|
|
155
|
+
index_of: (arr, val, from = 0) => arr.indexOf(val, from),
|
|
156
|
+
last_index_of: (arr, val, from) => from !== undefined ? arr.lastIndexOf(val, from) : arr.lastIndexOf(val),
|
|
157
|
+
slice: (arr, start, end) => arr.slice(start, end),
|
|
158
|
+
first: (arr) => arr.length > 0 ? arr[0] : null,
|
|
159
|
+
last: (arr) => arr.length > 0 ? arr[arr.length - 1] : null,
|
|
160
|
+
is_empty: (arr) => arr.length === 0,
|
|
161
|
+
sort: (arr, cb) => cb ? [...arr].sort(cb) : [...arr].sort(),
|
|
162
|
+
reverse: (arr) => [...arr].reverse(),
|
|
163
|
+
shuffle: (arr) => {
|
|
164
|
+
const a = [...arr];
|
|
165
|
+
for (let i = a.length - 1; i > 0; i--) {
|
|
166
|
+
const j = Math.floor(Math.random() * (i + 1));
|
|
167
|
+
[a[i], a[j]] = [a[j], a[i]];
|
|
168
|
+
}
|
|
169
|
+
return a;
|
|
170
|
+
},
|
|
171
|
+
concat: (...arrs) => [].concat(...arrs),
|
|
172
|
+
unique: (arr) => [...new Set(arr)],
|
|
173
|
+
intersection: (a, b) => a.filter(v => b.includes(v)),
|
|
174
|
+
union: (a, b) => [...new Set([...a, ...b])],
|
|
175
|
+
difference: (a, b) => a.filter(v => !b.includes(v)),
|
|
176
|
+
},
|
|
177
|
+
path: {
|
|
178
|
+
join: (...parts) => nodePath.join(...parts),
|
|
179
|
+
dirname: (p) => nodePath.dirname(p),
|
|
180
|
+
basename: (p, ext) => nodePath.basename(p, ext),
|
|
181
|
+
extname: (p) => nodePath.extname(p),
|
|
182
|
+
},
|
|
183
|
+
env: {
|
|
184
|
+
get: (name, fallback = null) => process.env[name] ?? fallback,
|
|
185
|
+
has: (name) => name in process.env,
|
|
186
|
+
all: () => ({ ...process.env }),
|
|
187
|
+
},
|
|
188
|
+
regex: {
|
|
189
|
+
find_matches: (pattern, text, flags = 'g') => {
|
|
190
|
+
const matches = text.match(new RegExp(pattern, flags));
|
|
191
|
+
return matches ? [...matches] : null;
|
|
192
|
+
},
|
|
193
|
+
is_match: (pattern, text, flags = '') => new RegExp(pattern, flags).test(text),
|
|
194
|
+
replace_all: (text, pattern, replacement, flags = 'g') => text.replace(new RegExp(pattern, flags), replacement),
|
|
195
|
+
extract: (pattern, text, flags = '') => {
|
|
196
|
+
const result = new RegExp(pattern, flags).exec(text);
|
|
197
|
+
return result ? [...result] : null;
|
|
198
|
+
},
|
|
199
|
+
},
|
|
200
|
+
http: {
|
|
201
|
+
get: (url) => {
|
|
202
|
+
// Synchronous HTTP not natively available in Node; users should use async patterns.
|
|
203
|
+
throw new Error('http.get() requires an async environment. Use fetch() directly.');
|
|
204
|
+
},
|
|
205
|
+
post: (url, body, headers = {}) => {
|
|
206
|
+
throw new Error('http.post() requires an async environment. Use fetch() directly.');
|
|
207
|
+
},
|
|
208
|
+
},
|
|
209
|
+
object: {
|
|
210
|
+
merge: (...objs) => Object.assign({}, ...objs),
|
|
211
|
+
pick: (obj, keys) => Object.fromEntries(keys.filter(k => k in obj).map(k => [k, obj[k]])),
|
|
212
|
+
omit: (obj, keys) => Object.fromEntries(Object.entries(obj).filter(([k]) => !keys.includes(k))),
|
|
213
|
+
map_values: (obj, cb) => Object.fromEntries(Object.entries(obj).map(([k, v]) => [k, cb(v, k, obj)])),
|
|
214
|
+
from_entries: (entries) => Object.fromEntries(entries.map(([k, v]) => [String(k), v])),
|
|
215
|
+
is_empty: (obj) => Object.keys(obj).length === 0,
|
|
216
|
+
keys: (obj) => Object.keys(obj),
|
|
217
|
+
values: (obj) => Object.values(obj),
|
|
218
|
+
entries: (obj) => Object.entries(obj).map(([k, v]) => [k, v]),
|
|
219
|
+
},
|
|
220
|
+
assert: {
|
|
221
|
+
eq: (actual, expected, message) => {
|
|
222
|
+
if (!isEqual(actual, expected)) {
|
|
223
|
+
const msg = message ? `: ${message}` : '';
|
|
224
|
+
throw new Error(`Assertion Failed${msg}.\n Expected: ${JSON.stringify(expected)}\n Actual: ${JSON.stringify(actual)}`);
|
|
225
|
+
}
|
|
226
|
+
return true;
|
|
227
|
+
},
|
|
228
|
+
neq: (actual, expected, message) => {
|
|
229
|
+
if (isEqual(actual, expected)) {
|
|
230
|
+
const msg = message ? `: ${message}` : '';
|
|
231
|
+
throw new Error(`Assertion Failed${msg}. Expected values to be different.`);
|
|
232
|
+
}
|
|
233
|
+
return true;
|
|
234
|
+
},
|
|
235
|
+
true: (condition, message) => {
|
|
236
|
+
if (condition !== true) {
|
|
237
|
+
const msg = message ? `: ${message}` : '';
|
|
238
|
+
throw new Error(`Assertion Failed${msg}. Expected true, got ${JSON.stringify(condition)}`);
|
|
239
|
+
}
|
|
240
|
+
return true;
|
|
241
|
+
},
|
|
242
|
+
false: (condition, message) => {
|
|
243
|
+
if (condition !== false) {
|
|
244
|
+
const msg = message ? `: ${message}` : '';
|
|
245
|
+
throw new Error(`Assertion Failed${msg}. Expected false, got ${JSON.stringify(condition)}`);
|
|
246
|
+
}
|
|
247
|
+
return true;
|
|
248
|
+
},
|
|
249
|
+
throws: (fn, message) => {
|
|
250
|
+
try {
|
|
251
|
+
fn();
|
|
252
|
+
} catch (_) {
|
|
253
|
+
return true;
|
|
254
|
+
}
|
|
255
|
+
const msg = message ? `: ${message}` : '';
|
|
256
|
+
throw new Error(`Assertion Failed${msg}. Expected function to throw, but it did not.`);
|
|
257
|
+
},
|
|
258
|
+
},
|
|
259
|
+
slice: (arr, start, end) => {
|
|
260
|
+
if (end === undefined) end = arr.length;
|
|
261
|
+
return arr.slice(start, end);
|
|
262
|
+
},
|
|
263
|
+
get_arguments: () => process.argv.slice(2),
|
|
264
|
+
get_env: (name) => process.env[name] || null,
|
|
265
|
+
};
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mimo → JavaScript transpiler.
|
|
3
|
+
*
|
|
4
|
+
* Converts a Mimo AST to idiomatic ES2022 JavaScript (ESM).
|
|
5
|
+
* Extends BaseConverter for shared infrastructure; visitor methods are
|
|
6
|
+
* mixed in from focused sub-modules.
|
|
7
|
+
*/
|
|
8
|
+
import { BaseConverter } from '../base_converter.js';
|
|
9
|
+
import { statementVisitors } from './visitors/statements.js';
|
|
10
|
+
import { expressionVisitors } from './visitors/expressions.js';
|
|
11
|
+
import { patternVisitors } from './visitors/patterns.js';
|
|
12
|
+
|
|
13
|
+
export class MimoToJsConverter extends BaseConverter {
|
|
14
|
+
constructor() {
|
|
15
|
+
super();
|
|
16
|
+
this.scopeStack = [new Set()];
|
|
17
|
+
this.moduleAliases = new Map();
|
|
18
|
+
this._matchCounter = 0;
|
|
19
|
+
this._lambdaCounter = 0;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// -------------------------------------------------------------------------
|
|
23
|
+
// Entry point
|
|
24
|
+
// -------------------------------------------------------------------------
|
|
25
|
+
|
|
26
|
+
convert(ast) {
|
|
27
|
+
this.output = `import { mimo } from './mimo_runtime.js';\n\n`;
|
|
28
|
+
this.visitNode(ast);
|
|
29
|
+
return this.output;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// -------------------------------------------------------------------------
|
|
33
|
+
// Scope helpers
|
|
34
|
+
// -------------------------------------------------------------------------
|
|
35
|
+
|
|
36
|
+
enterScope() {
|
|
37
|
+
this.scopeStack.push(new Set());
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
exitScope() {
|
|
41
|
+
this.scopeStack.pop();
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
declareVariable(name) {
|
|
45
|
+
this.scopeStack[this.scopeStack.length - 1].add(name);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
isVariableDeclared(name) {
|
|
49
|
+
for (let i = this.scopeStack.length - 1; i >= 0; i--) {
|
|
50
|
+
if (this.scopeStack[i].has(name)) return true;
|
|
51
|
+
}
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// -------------------------------------------------------------------------
|
|
56
|
+
// Overrides
|
|
57
|
+
// -------------------------------------------------------------------------
|
|
58
|
+
|
|
59
|
+
onUndefinedVisitor(node) {
|
|
60
|
+
console.warn(`[JS Converter] No visitor for AST node type: ${node.type}`);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** JS blocks don't need `pass`, so just use the base implementation. */
|
|
64
|
+
visitBlock(statements) {
|
|
65
|
+
this.indent();
|
|
66
|
+
let prev = null;
|
|
67
|
+
for (const stmt of statements || []) {
|
|
68
|
+
this.emitLineGap(stmt, prev);
|
|
69
|
+
this.visitNode(stmt);
|
|
70
|
+
prev = stmt;
|
|
71
|
+
}
|
|
72
|
+
this.dedent();
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
emitSpread(node) {
|
|
76
|
+
this.write('...');
|
|
77
|
+
this.visitNode(node.argument);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
escapeForTemplateLiteral(str) {
|
|
81
|
+
if (typeof str !== 'string') return str;
|
|
82
|
+
return str
|
|
83
|
+
.replace(/\\/g, '\\\\')
|
|
84
|
+
.replace(/`/g, '\\`')
|
|
85
|
+
.replace(/\${/g, '\\${');
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// -------------------------------------------------------------------------
|
|
89
|
+
// Callee helper — routes built-ins through mimo.xxx
|
|
90
|
+
// -------------------------------------------------------------------------
|
|
91
|
+
|
|
92
|
+
visitCallee(calleeNode) {
|
|
93
|
+
switch (calleeNode.type) {
|
|
94
|
+
case 'Identifier':
|
|
95
|
+
if (this.isCoreBuiltin(calleeNode.name)) {
|
|
96
|
+
this.write(`mimo.${calleeNode.name}`);
|
|
97
|
+
} else {
|
|
98
|
+
this.visitNode(calleeNode);
|
|
99
|
+
}
|
|
100
|
+
break;
|
|
101
|
+
default:
|
|
102
|
+
this.visitNode(calleeNode);
|
|
103
|
+
break;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// -------------------------------------------------------------------------
|
|
108
|
+
// Program
|
|
109
|
+
// -------------------------------------------------------------------------
|
|
110
|
+
|
|
111
|
+
visitProgram(node) {
|
|
112
|
+
let prev = null;
|
|
113
|
+
for (const stmt of node.body) {
|
|
114
|
+
this.emitLineGap(stmt, prev);
|
|
115
|
+
this.visitNode(stmt);
|
|
116
|
+
prev = stmt;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// -------------------------------------------------------------------------
|
|
121
|
+
// Private helpers
|
|
122
|
+
// -------------------------------------------------------------------------
|
|
123
|
+
|
|
124
|
+
/** Collect all variable names from a destructuring pattern. */
|
|
125
|
+
_collectPatternVars(pattern) {
|
|
126
|
+
if (!pattern) return [];
|
|
127
|
+
if (pattern.type === 'ArrayPattern') {
|
|
128
|
+
return pattern.elements.map((e) => e.name);
|
|
129
|
+
}
|
|
130
|
+
if (pattern.type === 'ObjectPattern') {
|
|
131
|
+
return pattern.properties.map((p) => p.name);
|
|
132
|
+
}
|
|
133
|
+
return [];
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Render an expression node to a string without writing to output.
|
|
138
|
+
* Used for default parameter values and decorator args.
|
|
139
|
+
*/
|
|
140
|
+
_exprToString(node) {
|
|
141
|
+
if (!node) return 'undefined';
|
|
142
|
+
const savedOutput = this.output;
|
|
143
|
+
const savedIndent = this.currentIndent;
|
|
144
|
+
this.output = '';
|
|
145
|
+
this.currentIndent = '';
|
|
146
|
+
this.visitNode(node);
|
|
147
|
+
const result = this.output;
|
|
148
|
+
this.output = savedOutput;
|
|
149
|
+
this.currentIndent = savedIndent;
|
|
150
|
+
return result;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Mix in visitor methods from sub-modules
|
|
155
|
+
Object.assign(MimoToJsConverter.prototype, statementVisitors, expressionVisitors, patternVisitors);
|