mimo-lang 1.1.1 → 2.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (165) hide show
  1. package/.gitattributes +24 -0
  2. package/LICENSE +21 -0
  3. package/README.md +71 -39
  4. package/adapters/browserAdapter.js +86 -0
  5. package/adapters/nodeAdapter.js +101 -0
  6. package/bin/cli.js +80 -0
  7. package/bin/commands/convert.js +27 -0
  8. package/bin/commands/doctor.js +139 -0
  9. package/bin/commands/eval.js +39 -0
  10. package/bin/commands/fmt.js +109 -0
  11. package/bin/commands/help.js +72 -0
  12. package/bin/commands/lint.js +117 -0
  13. package/bin/commands/repl.js +24 -0
  14. package/bin/commands/run.js +64 -0
  15. package/bin/commands/test.js +126 -0
  16. package/bin/utils/colors.js +38 -0
  17. package/bin/utils/formatError.js +47 -0
  18. package/bin/utils/fs.js +57 -0
  19. package/bin/utils/version.js +8 -0
  20. package/build.js +18 -0
  21. package/bun.lock +74 -0
  22. package/index.js +48 -77
  23. package/index.web.js +364 -0
  24. package/interpreter/BuiltinFunction.js +32 -0
  25. package/interpreter/ErrorHandler.js +120 -0
  26. package/interpreter/ExpressionEvaluator.js +106 -0
  27. package/interpreter/Interpreter.js +172 -0
  28. package/interpreter/MimoError.js +112 -0
  29. package/interpreter/ModuleLoader.js +236 -0
  30. package/interpreter/StatementExecutor.js +107 -0
  31. package/interpreter/Utils.js +82 -0
  32. package/interpreter/Values.js +87 -0
  33. package/interpreter/coreBuiltins.js +490 -0
  34. package/interpreter/environment.js +99 -0
  35. package/interpreter/evaluators/binaryExpressionEvaluator.js +111 -0
  36. package/interpreter/evaluators/collectionEvaluator.js +151 -0
  37. package/interpreter/evaluators/functionCallEvaluator.js +76 -0
  38. package/interpreter/evaluators/literalEvaluator.js +27 -0
  39. package/interpreter/evaluators/moduleAccessEvaluator.js +25 -0
  40. package/interpreter/evaluators/templateLiteralEvaluator.js +20 -0
  41. package/interpreter/executors/BaseExecutor.js +37 -0
  42. package/interpreter/executors/ControlFlowExecutor.js +206 -0
  43. package/interpreter/executors/FunctionExecutor.js +126 -0
  44. package/interpreter/executors/PatternMatchExecutor.js +93 -0
  45. package/interpreter/executors/VariableExecutor.js +144 -0
  46. package/interpreter/index.js +8 -0
  47. package/interpreter/stdlib/array/accessFunctions.js +61 -0
  48. package/interpreter/stdlib/array/arrayUtils.js +36 -0
  49. package/interpreter/stdlib/array/higherOrderFunctions.js +285 -0
  50. package/interpreter/stdlib/array/searchFunctions.js +77 -0
  51. package/interpreter/stdlib/array/setFunctions.js +49 -0
  52. package/interpreter/stdlib/array/transformationFunctions.js +68 -0
  53. package/interpreter/stdlib/array.js +85 -0
  54. package/interpreter/stdlib/assert.js +143 -0
  55. package/interpreter/stdlib/datetime.js +170 -0
  56. package/interpreter/stdlib/env.js +54 -0
  57. package/interpreter/stdlib/fs.js +161 -0
  58. package/interpreter/stdlib/http.js +92 -0
  59. package/interpreter/stdlib/json.js +70 -0
  60. package/interpreter/stdlib/math.js +309 -0
  61. package/interpreter/stdlib/object.js +142 -0
  62. package/interpreter/stdlib/path.js +69 -0
  63. package/interpreter/stdlib/regex.js +134 -0
  64. package/interpreter/stdlib/string.js +260 -0
  65. package/interpreter/suggestions.js +46 -0
  66. package/lexer/Lexer.js +245 -0
  67. package/lexer/TokenTypes.js +131 -0
  68. package/lexer/createToken.js +11 -0
  69. package/lexer/tokenizers/commentTokenizer.js +45 -0
  70. package/lexer/tokenizers/literalTokenizer.js +163 -0
  71. package/lexer/tokenizers/symbolTokenizer.js +69 -0
  72. package/lexer/tokenizers/whitespaceTokenizer.js +36 -0
  73. package/package.json +29 -13
  74. package/parser/ASTNodes.js +448 -0
  75. package/parser/Parser.js +188 -0
  76. package/parser/expressions/atomicExpressions.js +165 -0
  77. package/parser/expressions/conditionalExpressions.js +0 -0
  78. package/parser/expressions/operatorExpressions.js +79 -0
  79. package/parser/expressions/primaryExpressions.js +77 -0
  80. package/parser/parseStatement.js +184 -0
  81. package/parser/parserExpressions.js +115 -0
  82. package/parser/parserUtils.js +19 -0
  83. package/parser/statements/controlFlowParsers.js +106 -0
  84. package/parser/statements/functionParsers.js +314 -0
  85. package/parser/statements/moduleParsers.js +57 -0
  86. package/parser/statements/patternMatchParsers.js +124 -0
  87. package/parser/statements/variableParsers.js +155 -0
  88. package/repl.js +325 -0
  89. package/test.js +47 -0
  90. package/tools/PrettyPrinter.js +3 -0
  91. package/tools/convert/Args.js +46 -0
  92. package/tools/convert/Registry.js +91 -0
  93. package/tools/convert/Transpiler.js +78 -0
  94. package/tools/convert/plugins/README.md +66 -0
  95. package/tools/convert/plugins/alya/index.js +10 -0
  96. package/tools/convert/plugins/alya/to_alya.js +289 -0
  97. package/tools/convert/plugins/alya/visitors/expressions.js +257 -0
  98. package/tools/convert/plugins/alya/visitors/statements.js +403 -0
  99. package/tools/convert/plugins/base_converter.js +228 -0
  100. package/tools/convert/plugins/javascript/index.js +10 -0
  101. package/tools/convert/plugins/javascript/mimo_runtime.js +265 -0
  102. package/tools/convert/plugins/javascript/to_js.js +155 -0
  103. package/tools/convert/plugins/javascript/visitors/expressions.js +197 -0
  104. package/tools/convert/plugins/javascript/visitors/patterns.js +102 -0
  105. package/tools/convert/plugins/javascript/visitors/statements.js +236 -0
  106. package/tools/convert/plugins/python/index.js +10 -0
  107. package/tools/convert/plugins/python/mimo_runtime.py +811 -0
  108. package/tools/convert/plugins/python/to_py.js +329 -0
  109. package/tools/convert/plugins/python/visitors/expressions.js +272 -0
  110. package/tools/convert/plugins/python/visitors/patterns.js +100 -0
  111. package/tools/convert/plugins/python/visitors/statements.js +257 -0
  112. package/tools/convert.js +102 -0
  113. package/tools/format/CommentAttacher.js +190 -0
  114. package/tools/format/CommentLexer.js +152 -0
  115. package/tools/format/Printer.js +849 -0
  116. package/tools/format/config.js +107 -0
  117. package/tools/formatter.js +169 -0
  118. package/tools/lint/Linter.js +391 -0
  119. package/tools/lint/config.js +114 -0
  120. package/tools/lint/rules/consistent-return.js +62 -0
  121. package/tools/lint/rules/max-depth.js +56 -0
  122. package/tools/lint/rules/no-empty-function.js +45 -0
  123. package/tools/lint/rules/no-magic-numbers.js +46 -0
  124. package/tools/lint/rules/no-shadow.js +113 -0
  125. package/tools/lint/rules/no-unused-vars.js +26 -0
  126. package/tools/lint/rules/prefer-const.js +19 -0
  127. package/tools/linter.js +261 -0
  128. package/tools/replFormatter.js +93 -0
  129. package/tools/stamp-version.js +32 -0
  130. package/web/index.js +9 -0
  131. package/bun.lockb +0 -0
  132. package/cli.js +0 -84
  133. package/compiler/execute/interpreter.js +0 -68
  134. package/compiler/execute/interpreters/binary.js +0 -12
  135. package/compiler/execute/interpreters/call.js +0 -10
  136. package/compiler/execute/interpreters/if.js +0 -10
  137. package/compiler/execute/interpreters/try-catch.js +0 -10
  138. package/compiler/execute/interpreters/while.js +0 -8
  139. package/compiler/execute/utils/createfunction.js +0 -11
  140. package/compiler/execute/utils/evaluate.js +0 -20
  141. package/compiler/execute/utils/operate.js +0 -23
  142. package/compiler/lexer/processToken.js +0 -40
  143. package/compiler/lexer/tokenTypes.js +0 -4
  144. package/compiler/lexer/tokenizer.js +0 -74
  145. package/compiler/parser/expression/comparison.js +0 -18
  146. package/compiler/parser/expression/identifier.js +0 -29
  147. package/compiler/parser/expression/number.js +0 -10
  148. package/compiler/parser/expression/operator.js +0 -21
  149. package/compiler/parser/expression/punctuation.js +0 -31
  150. package/compiler/parser/expression/string.js +0 -6
  151. package/compiler/parser/parseExpression.js +0 -27
  152. package/compiler/parser/parseStatement.js +0 -34
  153. package/compiler/parser/parser.js +0 -45
  154. package/compiler/parser/statement/call.js +0 -26
  155. package/compiler/parser/statement/function.js +0 -29
  156. package/compiler/parser/statement/if.js +0 -34
  157. package/compiler/parser/statement/return.js +0 -10
  158. package/compiler/parser/statement/set.js +0 -11
  159. package/compiler/parser/statement/show.js +0 -10
  160. package/compiler/parser/statement/try-catch.js +0 -25
  161. package/compiler/parser/statement/while.js +0 -22
  162. package/converter/go/convert.js +0 -110
  163. package/converter/js/convert.js +0 -107
  164. package/jsconfig.json +0 -27
  165. package/vite.config.js +0 -17
@@ -0,0 +1,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,10 @@
1
+ import { MimoToJsConverter } from './to_js.js';
2
+
3
+ export const config = {
4
+ name: 'javascript',
5
+ aliases: ['js', 'javascript'],
6
+ extension: '.js',
7
+ runtimeFile: 'mimo_runtime.js'
8
+ };
9
+
10
+ export { MimoToJsConverter as Converter };
@@ -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);