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
package/tools/linter.js
ADDED
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { fileURLToPath } from 'url';
|
|
5
|
+
|
|
6
|
+
import { Parser } from '../parser/Parser.js';
|
|
7
|
+
import { Lexer } from '../lexer/Lexer.js';
|
|
8
|
+
import { Linter } from './lint/Linter.js';
|
|
9
|
+
import { loadConfig, findConfigFile, mergeRuleConfigs } from './lint/config.js';
|
|
10
|
+
|
|
11
|
+
const DEFAULT_RULES = {
|
|
12
|
+
'no-unused-vars': true,
|
|
13
|
+
'prefer-const': true,
|
|
14
|
+
'no-magic-numbers': false,
|
|
15
|
+
'no-empty-function': false,
|
|
16
|
+
'max-depth': false,
|
|
17
|
+
'no-shadow': false,
|
|
18
|
+
'consistent-return': false,
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
// ---------------------------------------------------------------------------
|
|
22
|
+
// Helpers
|
|
23
|
+
// ---------------------------------------------------------------------------
|
|
24
|
+
|
|
25
|
+
/** Synchronous fs reader — injected into config helpers so they stay bundler-safe. */
|
|
26
|
+
const fsReadFile = (p) => fs.readFileSync(p, 'utf-8');
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Resolve the merged rule config for a given file path.
|
|
30
|
+
* Priority: DEFAULT_RULES < .mimorc < caller-supplied `rules` overrides.
|
|
31
|
+
*/
|
|
32
|
+
function resolveRules(filePath, callerRules = {}) {
|
|
33
|
+
const fileDir = path.dirname(path.resolve(filePath));
|
|
34
|
+
const rootDir = path.resolve('/');
|
|
35
|
+
const pathJoin = path.join.bind(path);
|
|
36
|
+
|
|
37
|
+
const configPath = findConfigFile(fileDir, rootDir, fsReadFile, pathJoin);
|
|
38
|
+
const fileConfig = configPath ? loadConfig(configPath, fsReadFile) : { rules: {} };
|
|
39
|
+
|
|
40
|
+
return mergeRuleConfigs(DEFAULT_RULES, fileConfig.rules, callerRules);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Parse source into an AST. Throws on syntax errors. */
|
|
44
|
+
function parseSource(source, filePath) {
|
|
45
|
+
const lexer = new Lexer(source, filePath);
|
|
46
|
+
const tokens = [];
|
|
47
|
+
let token;
|
|
48
|
+
while ((token = lexer.nextToken()) !== null) {
|
|
49
|
+
tokens.push(token);
|
|
50
|
+
}
|
|
51
|
+
const parser = new Parser(tokens, filePath);
|
|
52
|
+
return parser.parse();
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// ---------------------------------------------------------------------------
|
|
56
|
+
// ANSI helpers
|
|
57
|
+
// ---------------------------------------------------------------------------
|
|
58
|
+
const RESET = '\x1b[0m';
|
|
59
|
+
const CYAN = '\x1b[36m';
|
|
60
|
+
const YELLOW = '\x1b[33m';
|
|
61
|
+
const RED = '\x1b[31m';
|
|
62
|
+
const DIM = '\x1b[90m';
|
|
63
|
+
|
|
64
|
+
function severityLabel(severity) {
|
|
65
|
+
return severity === 'error'
|
|
66
|
+
? `${RED}error${RESET}`
|
|
67
|
+
: `${YELLOW}warning${RESET}`;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function squiggleColor(severity) {
|
|
71
|
+
return severity === 'error' ? RED : YELLOW;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// ---------------------------------------------------------------------------
|
|
75
|
+
// Public API
|
|
76
|
+
// ---------------------------------------------------------------------------
|
|
77
|
+
|
|
78
|
+
export function lintFile(filePath, options = {}) {
|
|
79
|
+
const { quiet = false, rules = {} } = options;
|
|
80
|
+
const enabledRules = resolveRules(filePath, rules);
|
|
81
|
+
|
|
82
|
+
if (!quiet) {
|
|
83
|
+
console.log(`Linting ${filePath}...`);
|
|
84
|
+
}
|
|
85
|
+
try {
|
|
86
|
+
const source = fs.readFileSync(filePath, 'utf-8');
|
|
87
|
+
const ast = parseSource(source, filePath);
|
|
88
|
+
|
|
89
|
+
const linter = new Linter({ rules: enabledRules });
|
|
90
|
+
const messages = linter.verify(ast, source, filePath);
|
|
91
|
+
|
|
92
|
+
const errors = messages.filter(m => m.severity === 'error');
|
|
93
|
+
const warnings = messages.filter(m => m.severity !== 'error');
|
|
94
|
+
|
|
95
|
+
if (messages.length === 0) {
|
|
96
|
+
if (!quiet) {
|
|
97
|
+
console.log('No problems found.');
|
|
98
|
+
}
|
|
99
|
+
return { ok: true, messages, errorCount: 0, warningCount: 0, file: filePath };
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
console.log(`\nFound ${messages.length} problem(s) in ${filePath}:`);
|
|
103
|
+
|
|
104
|
+
const sourceLines = source.split('\n');
|
|
105
|
+
messages.forEach(msg => {
|
|
106
|
+
const color = squiggleColor(msg.severity);
|
|
107
|
+
console.log(`\n ${CYAN}${filePath}:${msg.line}:${msg.column}${RESET}`);
|
|
108
|
+
console.log(` ${severityLabel(msg.severity)} ${msg.message} ${DIM}${msg.ruleId}${RESET}`);
|
|
109
|
+
|
|
110
|
+
const line = sourceLines[msg.line - 1];
|
|
111
|
+
if (line) {
|
|
112
|
+
console.log(`\n ${msg.line} | ${line}`);
|
|
113
|
+
const padding = ' '.repeat(String(msg.line).length + 3 + msg.column - 1);
|
|
114
|
+
const squiggles = '^'.repeat(Math.max(1, msg.endColumn - msg.column));
|
|
115
|
+
console.log(` ${padding}${color}${squiggles}${RESET}`);
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
return {
|
|
120
|
+
ok: true,
|
|
121
|
+
messages,
|
|
122
|
+
errorCount: errors.length,
|
|
123
|
+
warningCount: warnings.length,
|
|
124
|
+
file: filePath,
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
} catch (err) {
|
|
128
|
+
console.error(`\n${RED}Error linting file ${filePath}:${RESET}`);
|
|
129
|
+
if (err.format) {
|
|
130
|
+
const snippet = fs.readFileSync(filePath, 'utf-8').split('\n')[err.location?.line - 1] || '';
|
|
131
|
+
console.error(err.format(snippet));
|
|
132
|
+
} else {
|
|
133
|
+
console.error(err.message);
|
|
134
|
+
}
|
|
135
|
+
return {
|
|
136
|
+
ok: false,
|
|
137
|
+
messages: [],
|
|
138
|
+
errorCount: 0,
|
|
139
|
+
warningCount: 0,
|
|
140
|
+
file: filePath,
|
|
141
|
+
error: {
|
|
142
|
+
message: err.message,
|
|
143
|
+
line: err.location?.line,
|
|
144
|
+
column: err.location?.column,
|
|
145
|
+
},
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export function lintFileJson(filePath, options = {}) {
|
|
151
|
+
const { rules = {} } = options;
|
|
152
|
+
const enabledRules = resolveRules(filePath, rules);
|
|
153
|
+
|
|
154
|
+
try {
|
|
155
|
+
const source = fs.readFileSync(filePath, 'utf-8');
|
|
156
|
+
const ast = parseSource(source, filePath);
|
|
157
|
+
|
|
158
|
+
const linter = new Linter({ rules: enabledRules });
|
|
159
|
+
const messages = linter.verify(ast, source, filePath);
|
|
160
|
+
|
|
161
|
+
return {
|
|
162
|
+
ok: true,
|
|
163
|
+
file: filePath,
|
|
164
|
+
messages: messages.map(msg => ({
|
|
165
|
+
line: msg.line,
|
|
166
|
+
column: msg.column,
|
|
167
|
+
endColumn: msg.endColumn,
|
|
168
|
+
message: msg.message,
|
|
169
|
+
ruleId: msg.ruleId,
|
|
170
|
+
severity: msg.severity, // use actual severity, not hard-coded 'warning'
|
|
171
|
+
})),
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
} catch (err) {
|
|
175
|
+
return {
|
|
176
|
+
ok: false,
|
|
177
|
+
file: filePath,
|
|
178
|
+
error: {
|
|
179
|
+
message: err.message,
|
|
180
|
+
line: err.location?.line || 1,
|
|
181
|
+
column: err.location?.column || 1,
|
|
182
|
+
},
|
|
183
|
+
messages: [],
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Parse `--rule:name=true|false` flags from a CLI args array.
|
|
190
|
+
* Supports an optional severity suffix: `--rule:name=error` or `--rule:name=warning`.
|
|
191
|
+
*/
|
|
192
|
+
export function parseRuleFlags(args) {
|
|
193
|
+
const rules = {};
|
|
194
|
+
for (const arg of args) {
|
|
195
|
+
if (arg.startsWith('--rule:')) {
|
|
196
|
+
const ruleDef = arg.slice(7);
|
|
197
|
+
const eqIndex = ruleDef.indexOf('=');
|
|
198
|
+
if (eqIndex !== -1) {
|
|
199
|
+
const ruleName = ruleDef.slice(0, eqIndex);
|
|
200
|
+
const ruleValue = ruleDef.slice(eqIndex + 1).toLowerCase();
|
|
201
|
+
if (ruleValue === 'false' || ruleValue === '0') {
|
|
202
|
+
rules[ruleName] = false;
|
|
203
|
+
} else if (ruleValue === 'true' || ruleValue === '1') {
|
|
204
|
+
rules[ruleName] = true;
|
|
205
|
+
} else if (ruleValue === 'error' || ruleValue === 'warning') {
|
|
206
|
+
rules[ruleName] = { severity: ruleValue };
|
|
207
|
+
} else {
|
|
208
|
+
rules[ruleName] = true; // unknown value — treat as enable
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
return rules;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// ---------------------------------------------------------------------------
|
|
217
|
+
// Main (direct invocation)
|
|
218
|
+
// ---------------------------------------------------------------------------
|
|
219
|
+
if (process.argv[1] === fileURLToPath(import.meta.url)) {
|
|
220
|
+
const args = process.argv.slice(2);
|
|
221
|
+
const quiet = args.includes('--quiet');
|
|
222
|
+
const json = args.includes('--json');
|
|
223
|
+
const failOnWarning = args.includes('--fail-on-warning');
|
|
224
|
+
const rules = parseRuleFlags(args);
|
|
225
|
+
const filePaths = args.filter(arg => !arg.startsWith('--'));
|
|
226
|
+
|
|
227
|
+
if (filePaths.length === 0) {
|
|
228
|
+
console.error('Error: No file path provided.');
|
|
229
|
+
console.log('Usage: node tools/linter.js [--fail-on-warning] [--quiet] [--json] [--rule:name=true|false|error|warning] <file1.mimo> ...');
|
|
230
|
+
process.exit(1);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
if (json) {
|
|
234
|
+
const results = filePaths.map(p => lintFileJson(p, { rules }));
|
|
235
|
+
console.log(JSON.stringify(results.length === 1 ? results[0] : results));
|
|
236
|
+
const hasParseErrors = results.some(r => !r.ok);
|
|
237
|
+
const errorCount = results.reduce((sum, r) => sum + r.messages.filter(m => m.severity === 'error').length, 0);
|
|
238
|
+
const warningCount = results.reduce((sum, r) => sum + r.messages.filter(m => m.severity !== 'error').length, 0);
|
|
239
|
+
if (hasParseErrors || errorCount > 0 || (failOnWarning && warningCount > 0)) {
|
|
240
|
+
process.exit(1);
|
|
241
|
+
}
|
|
242
|
+
} else {
|
|
243
|
+
let hasParseErrors = false;
|
|
244
|
+
let errorCount = 0;
|
|
245
|
+
let warningCount = 0;
|
|
246
|
+
|
|
247
|
+
filePaths.forEach(p => {
|
|
248
|
+
const result = lintFile(p, { quiet, rules });
|
|
249
|
+
if (!result.ok) {
|
|
250
|
+
hasParseErrors = true;
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
errorCount += result.errorCount;
|
|
254
|
+
warningCount += result.warningCount;
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
if (hasParseErrors || errorCount > 0 || (failOnWarning && warningCount > 0)) {
|
|
258
|
+
process.exit(1);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { FunctionValue } from '../interpreter/Values.js';
|
|
2
|
+
import { BuiltinFunction } from '../interpreter/BuiltinFunction.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Formats a Mimo value for REPL output.
|
|
6
|
+
* @param {any} value The value to format
|
|
7
|
+
* @param {boolean} useColors Whether to use ANSI colors
|
|
8
|
+
* @param {number} indent Current indentation level
|
|
9
|
+
* @returns {string} Colored or plain string
|
|
10
|
+
*/
|
|
11
|
+
export function formatValue(value, useColors = true, indent = 0) {
|
|
12
|
+
const spacing = " ".repeat(indent);
|
|
13
|
+
|
|
14
|
+
const colors = {
|
|
15
|
+
grey: useColors ? "\x1b[90m" : "",
|
|
16
|
+
cyan: useColors ? "\x1b[36m" : "",
|
|
17
|
+
green: useColors ? "\x1b[32m" : "",
|
|
18
|
+
yellow: useColors ? "\x1b[33m" : "",
|
|
19
|
+
magenta: useColors ? "\x1b[35m" : "",
|
|
20
|
+
blue: useColors ? "\x1b[34m" : "",
|
|
21
|
+
reset: useColors ? "\x1b[0m" : ""
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
if (value === null || value === undefined) return `${colors.grey}null${colors.reset}`;
|
|
25
|
+
|
|
26
|
+
// Handle Functions specially to avoid printing the AST
|
|
27
|
+
if (value instanceof FunctionValue || (value && value.constructor && value.constructor.name === "FunctionValue")) {
|
|
28
|
+
const name = value.name === '<anonymous>' ? 'anonymous' : value.name;
|
|
29
|
+
return `${colors.cyan}[Function: ${name}]${colors.reset}`;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (value instanceof BuiltinFunction || (value && value.constructor && value.constructor.name === "BuiltinFunction")) {
|
|
33
|
+
return `${colors.cyan}[BuiltinFunction: ${value.name}]${colors.reset}`;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (value instanceof Date) {
|
|
37
|
+
return `${colors.green}datetime(${value.toISOString()})${colors.reset}`;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const MAX_ITEMS = 20;
|
|
41
|
+
const MAX_KEYS = 20;
|
|
42
|
+
|
|
43
|
+
if (Array.isArray(value)) {
|
|
44
|
+
if (value.length === 0) return `${colors.grey}Array(0) []${colors.reset}`;
|
|
45
|
+
|
|
46
|
+
const shownItems = value.slice(0, MAX_ITEMS).map(v => formatValue(v, useColors, indent + 1));
|
|
47
|
+
const hiddenCount = Math.max(0, value.length - shownItems.length);
|
|
48
|
+
const flatItems = shownItems.join(`${colors.grey}, ${colors.reset}`);
|
|
49
|
+
|
|
50
|
+
if (flatItems.length < 60 && hiddenCount === 0) {
|
|
51
|
+
return `${colors.grey}Array(${value.length}) [${colors.reset}${flatItems}${colors.grey}]${colors.reset}`;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const multilineItems = [...shownItems];
|
|
55
|
+
if (hiddenCount > 0) {
|
|
56
|
+
multilineItems.push(`${colors.grey}... ${hiddenCount} more${colors.reset}`);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return `${colors.grey}Array(${value.length}) [${colors.reset}\n${spacing} ${multilineItems.join(`${colors.grey},${colors.reset}\n` + spacing + " ")}\n${spacing}${colors.grey}]${colors.reset}`;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (typeof value === "object") {
|
|
63
|
+
const keys = Object.keys(value);
|
|
64
|
+
if (keys.length === 0) return `${colors.grey}{}${colors.reset}`;
|
|
65
|
+
|
|
66
|
+
const shownKeys = keys.slice(0, MAX_KEYS);
|
|
67
|
+
const hiddenCount = Math.max(0, keys.length - shownKeys.length);
|
|
68
|
+
|
|
69
|
+
const pairs = shownKeys.map(k => {
|
|
70
|
+
const val = formatValue(value[k], useColors, indent + 1);
|
|
71
|
+
return `${colors.blue}${k}${colors.reset}${colors.grey}:${colors.reset} ${val}`;
|
|
72
|
+
});
|
|
73
|
+
if (hiddenCount > 0) {
|
|
74
|
+
pairs.push(`${colors.grey}... ${hiddenCount} more${colors.reset}`);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const flatPairs = pairs.join(`${colors.grey}, ${colors.reset}`);
|
|
78
|
+
|
|
79
|
+
// Use single-line if short enough
|
|
80
|
+
if (flatPairs.length < 50) {
|
|
81
|
+
return `${colors.grey}{${colors.reset}${flatPairs}${colors.grey}}${colors.reset}`;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Multi-line for large objects
|
|
85
|
+
return `${colors.grey}{${colors.reset}\n${spacing} ${pairs.join(`${colors.grey},${colors.reset}\n` + spacing + " ")}\n${spacing}${colors.grey}}${colors.reset}`;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (typeof value === "string") return `${colors.green}"${value}"${colors.reset}`;
|
|
89
|
+
if (typeof value === "number") return `${colors.yellow}${value}${colors.reset}`;
|
|
90
|
+
if (typeof value === "boolean") return `${colors.magenta}${value}${colors.reset}`;
|
|
91
|
+
|
|
92
|
+
return String(value);
|
|
93
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
// tools/stamp-version.js
|
|
3
|
+
// Reads the version from package.json and writes it as a static literal into
|
|
4
|
+
// bin/utils/version.js so that `bun build --compile` bakes the correct string
|
|
5
|
+
// into the standalone binary.
|
|
6
|
+
//
|
|
7
|
+
// Run automatically via the `prebuild:mimo` script, or manually:
|
|
8
|
+
// bun tools/stamp-version.js
|
|
9
|
+
|
|
10
|
+
import fs from 'node:fs';
|
|
11
|
+
import path from 'node:path';
|
|
12
|
+
import { fileURLToPath } from 'node:url';
|
|
13
|
+
|
|
14
|
+
const __dir = path.dirname(fileURLToPath(import.meta.url));
|
|
15
|
+
const root = path.resolve(__dir, '..');
|
|
16
|
+
|
|
17
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(root, 'package.json'), 'utf-8'));
|
|
18
|
+
const version = pkg.version;
|
|
19
|
+
|
|
20
|
+
const target = path.join(root, 'bin', 'utils', 'version.js');
|
|
21
|
+
const content = `// bin/utils/version.js
|
|
22
|
+
// This string is stamped by the \`prebuild:mimo\` script (tools/stamp-version.js).
|
|
23
|
+
// It is a static literal so it survives \`bun build --compile\` and global installs.
|
|
24
|
+
export const VERSION = '${version}';
|
|
25
|
+
|
|
26
|
+
export function getVersion() {
|
|
27
|
+
return VERSION;
|
|
28
|
+
}
|
|
29
|
+
`;
|
|
30
|
+
|
|
31
|
+
fs.writeFileSync(target, content, 'utf-8');
|
|
32
|
+
console.log(`Stamped version ${version} into bin/utils/version.js`);
|
package/web/index.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file The entry point for the Mimo web bundle.
|
|
3
|
+
*/
|
|
4
|
+
import { Mimo } from '../index.web.js';
|
|
5
|
+
import { browserAdapter } from '../adapters/browserAdapter.js';
|
|
6
|
+
|
|
7
|
+
// Expose the Mimo class and the browser adapter to the global scope
|
|
8
|
+
window.Mimo = Mimo;
|
|
9
|
+
window.mimoBrowserAdapter = browserAdapter;
|
package/bun.lockb
DELETED
|
Binary file
|
package/cli.js
DELETED
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
import fs from "fs";
|
|
4
|
-
import path from "path";
|
|
5
|
-
import Mimo from "./index.js";
|
|
6
|
-
|
|
7
|
-
// Get the directory of the current script
|
|
8
|
-
const __dirname = path.dirname(new URL(import.meta.url).pathname);
|
|
9
|
-
|
|
10
|
-
// Read the package.json file
|
|
11
|
-
const packageJson = JSON.parse(
|
|
12
|
-
fs.readFileSync(path.join(__dirname, "package.json"), "utf-8")
|
|
13
|
-
);
|
|
14
|
-
const { version } = packageJson;
|
|
15
|
-
|
|
16
|
-
// Check if the user asked for the version
|
|
17
|
-
if (process.argv.includes("-v") || process.argv.includes("--version")) {
|
|
18
|
-
console.log(version);
|
|
19
|
-
process.exit(0);
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
// Check if the user asked for help
|
|
23
|
-
if (process.argv.includes("-h") || process.argv.includes("--help")) {
|
|
24
|
-
console.log("Usage: mimo [FILENAME] [-o|--output] [-t|--time] [-h|--help] [-q|--quiet] [-d|--debug] [-v|--version]");
|
|
25
|
-
console.log("FILENAME: The file to process.");
|
|
26
|
-
console.log("-o, --output: Generate output file.");
|
|
27
|
-
console.log("-t, --time: Measure execution time.");
|
|
28
|
-
console.log("-h, --help: Show this help message.");
|
|
29
|
-
console.log("-q, --quiet: Suppress all non-essential output.");
|
|
30
|
-
console.log("-d, --debug: Display additional debug information.");
|
|
31
|
-
console.log("-v, --version: Display the version number.");
|
|
32
|
-
process.exit(0);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
// Check if a filename was provided
|
|
36
|
-
if (process.argv.length < 3) {
|
|
37
|
-
console.error("Error: No file name provided.\n");
|
|
38
|
-
console.log("Usage: mimo [FILENAME] [-o|--output] [-t|--time] [-h|--help] [-q|--quiet] [-d|--debug] [-v|--version]");
|
|
39
|
-
console.log("Use the --help flag for more information.\n\n");
|
|
40
|
-
process.exit(1);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
const filename = process.argv[2];
|
|
44
|
-
const shouldGenerateOutput = process.argv.includes("-o") || process.argv.includes("--output");
|
|
45
|
-
const shouldMeasureTime = process.argv.includes("-t") || process.argv.includes("--time");
|
|
46
|
-
const shouldSuppressOutput = process.argv.includes("-q") || process.argv.includes("--quiet");
|
|
47
|
-
const shouldDisplayDebugInfo = process.argv.includes("-d") || process.argv.includes("--debug");
|
|
48
|
-
|
|
49
|
-
// Check if the file exists
|
|
50
|
-
if (!fs.existsSync(filename)) {
|
|
51
|
-
console.error(`Error: File '${filename}' does not exist.`);
|
|
52
|
-
process.exit(1);
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
// Read the file
|
|
56
|
-
let code;
|
|
57
|
-
try {
|
|
58
|
-
code = fs.readFileSync(filename, "utf-8");
|
|
59
|
-
} catch (err) {
|
|
60
|
-
console.error(`Error reading file '${filename}':`, err.message);
|
|
61
|
-
process.exit(1);
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
// Run the code
|
|
65
|
-
const mimo = new Mimo();
|
|
66
|
-
try {
|
|
67
|
-
if (shouldMeasureTime) console.time("Execution time");
|
|
68
|
-
|
|
69
|
-
let { program } = await mimo.run(code);
|
|
70
|
-
|
|
71
|
-
if (shouldMeasureTime) console.timeEnd("Execution time");
|
|
72
|
-
|
|
73
|
-
if (shouldGenerateOutput) {
|
|
74
|
-
const outputFilename = path.join(
|
|
75
|
-
path.dirname(filename),
|
|
76
|
-
path.basename(filename, path.extname(filename)) + ".js"
|
|
77
|
-
);
|
|
78
|
-
fs.writeFileSync(outputFilename, mimo.toJS(program), "utf-8");
|
|
79
|
-
if (!shouldSuppressOutput) console.log(`Output written to: ${outputFilename}`);
|
|
80
|
-
}
|
|
81
|
-
} catch (err) {
|
|
82
|
-
if (shouldDisplayDebugInfo) console.error("Error running code:", err);
|
|
83
|
-
process.exit(1);
|
|
84
|
-
}
|
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
import { interpretBinary } from "./interpreters/binary.js";
|
|
2
|
-
import { interpretCall } from "./interpreters/call.js";
|
|
3
|
-
import { interpretIf } from "./interpreters/if.js";
|
|
4
|
-
import { interpretTryCatch } from "./interpreters/try-catch.js";
|
|
5
|
-
import { interpretWhile } from "./interpreters/while.js";
|
|
6
|
-
import { createFunction } from "./utils/createfunction.js";
|
|
7
|
-
import { evaluate } from "./utils/evaluate.js";
|
|
8
|
-
|
|
9
|
-
export async function interpretStatement(statement, env) {
|
|
10
|
-
if (Array.isArray(statement)) {
|
|
11
|
-
for (let i = 0; i < statement.length; i++) {
|
|
12
|
-
if (await interpretStatement(statement[i], env)) return true;
|
|
13
|
-
}
|
|
14
|
-
return;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
if (typeof statement === "object") {
|
|
18
|
-
if (statement.type === "try-catch") {
|
|
19
|
-
await interpretTryCatch(statement, env);
|
|
20
|
-
} else {
|
|
21
|
-
await interpretObjectStatement(statement, env);
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
async function interpretObjectStatement(statement, env) {
|
|
27
|
-
switch (statement.type) {
|
|
28
|
-
case "assignment":
|
|
29
|
-
env[statement.target] = evaluate(statement.value, env);
|
|
30
|
-
break;
|
|
31
|
-
case "binary":
|
|
32
|
-
await interpretBinary(statement, env);
|
|
33
|
-
break;
|
|
34
|
-
case "if":
|
|
35
|
-
await interpretIf(statement, env);
|
|
36
|
-
break;
|
|
37
|
-
case "while":
|
|
38
|
-
await interpretWhile(statement, env);
|
|
39
|
-
break;
|
|
40
|
-
case "function":
|
|
41
|
-
env[statement.name] = createFunction(
|
|
42
|
-
statement.params,
|
|
43
|
-
statement.body,
|
|
44
|
-
env
|
|
45
|
-
);
|
|
46
|
-
break;
|
|
47
|
-
case "return":
|
|
48
|
-
env["return"] = evaluate(statement.expression, env);
|
|
49
|
-
return true;
|
|
50
|
-
case "call":
|
|
51
|
-
await interpretCall(statement, env);
|
|
52
|
-
break;
|
|
53
|
-
case "print":
|
|
54
|
-
console.log(evaluate(statement.value, env));
|
|
55
|
-
break;
|
|
56
|
-
default:
|
|
57
|
-
console.log("Unknown statement type", statement.type);
|
|
58
|
-
break;
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
export async function interpret(program, env = {}) {
|
|
63
|
-
let index = 0;
|
|
64
|
-
while (index < program.length) {
|
|
65
|
-
if (await interpretStatement(program[index++], env)) break;
|
|
66
|
-
}
|
|
67
|
-
return env;
|
|
68
|
-
}
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import { evaluate } from "../utils/evaluate.js";
|
|
2
|
-
|
|
3
|
-
export async function interpretBinary(statement, env) {
|
|
4
|
-
if (env[statement.target] === undefined) {
|
|
5
|
-
env[statement.target] = null;
|
|
6
|
-
}
|
|
7
|
-
env[statement.target] = await operate(
|
|
8
|
-
statement.operator,
|
|
9
|
-
await evaluate(statement.left, env),
|
|
10
|
-
await evaluate(statement.right, env)
|
|
11
|
-
);
|
|
12
|
-
}
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import { evaluate } from "../utils/evaluate.js";
|
|
2
|
-
|
|
3
|
-
export async function interpretCall(statement, env) {
|
|
4
|
-
const func = env[statement.name];
|
|
5
|
-
if (typeof func !== "function") {
|
|
6
|
-
throw new Error(`${statement.name} is not a function`);
|
|
7
|
-
}
|
|
8
|
-
const args = statement.args.map((arg) => evaluate(arg, env));
|
|
9
|
-
env[statement.target] = await func(...args);
|
|
10
|
-
}
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import { interpretStatement } from "../interpreter.js";
|
|
2
|
-
import { evaluate } from "../utils/evaluate.js";
|
|
3
|
-
|
|
4
|
-
export async function interpretIf(statement, env) {
|
|
5
|
-
if (evaluate(statement.condition, env)) {
|
|
6
|
-
if (await interpretStatement(statement.consequent, env)) return true;
|
|
7
|
-
} else if (statement.alternate) {
|
|
8
|
-
if (await interpretStatement(statement.alternate, env)) return true;
|
|
9
|
-
}
|
|
10
|
-
}
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import { interpretStatement } from "../interpreter.js";
|
|
2
|
-
|
|
3
|
-
export async function interpretTryCatch(statement, env) {
|
|
4
|
-
try {
|
|
5
|
-
await interpretStatement(statement.tryBlock, env);
|
|
6
|
-
} catch (error) {
|
|
7
|
-
env[statement.error] = error;
|
|
8
|
-
await interpretStatement(statement.catchBlock, env);
|
|
9
|
-
}
|
|
10
|
-
}
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import { interpretStatement } from "../interpreter.js";
|
|
2
|
-
import { evaluate } from "../utils/evaluate.js";
|
|
3
|
-
|
|
4
|
-
export async function interpretWhile(statement, env) {
|
|
5
|
-
while (evaluate(statement.condition, env)) {
|
|
6
|
-
if (await interpretStatement(statement.body, env)) return true;
|
|
7
|
-
}
|
|
8
|
-
}
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import { interpret } from "../interpreter.js";
|
|
2
|
-
|
|
3
|
-
export function createFunction(params, body, env) {
|
|
4
|
-
return async function (...args) {
|
|
5
|
-
const newEnv = { ...env };
|
|
6
|
-
params.forEach((param, index) => {
|
|
7
|
-
newEnv[param] = args[index] ?? undefined;
|
|
8
|
-
});
|
|
9
|
-
return (await interpret(body, newEnv))["return"];
|
|
10
|
-
};
|
|
11
|
-
}
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import { operate } from "./operate.js";
|
|
2
|
-
|
|
3
|
-
export const evaluate = (expression, env) =>
|
|
4
|
-
expression.type === "literal"
|
|
5
|
-
? expression.value
|
|
6
|
-
: expression.type === "variable"
|
|
7
|
-
? env.hasOwnProperty(expression.name)
|
|
8
|
-
? env[expression.name]
|
|
9
|
-
: null
|
|
10
|
-
: expression.type === "list"
|
|
11
|
-
? expression.elements.map((element) => evaluate(element, env))
|
|
12
|
-
: expression.type === "indexAccess"
|
|
13
|
-
? env[expression.name][evaluate(expression.index, env)]
|
|
14
|
-
: expression.type === "binary"
|
|
15
|
-
? operate(
|
|
16
|
-
expression.operator,
|
|
17
|
-
evaluate(expression.left, env),
|
|
18
|
-
evaluate(expression.right, env)
|
|
19
|
-
)
|
|
20
|
-
: null;
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
const operations = new Map([
|
|
2
|
-
["+", (left, right) => left + right],
|
|
3
|
-
["-", (left, right) => left - right],
|
|
4
|
-
["*", (left, right) => left * right],
|
|
5
|
-
["/", (left, right) => left / right],
|
|
6
|
-
["%", (left, right) => left % right],
|
|
7
|
-
["**", (left, right) => left ** right],
|
|
8
|
-
[">", (left, right) => left > right],
|
|
9
|
-
["<", (left, right) => left < right],
|
|
10
|
-
[">=", (left, right) => left >= right],
|
|
11
|
-
["<=", (left, right) => left <= right],
|
|
12
|
-
["==", (left, right) => left == right],
|
|
13
|
-
["!=", (left, right) => left != right],
|
|
14
|
-
["!", (left) => !left],
|
|
15
|
-
]);
|
|
16
|
-
|
|
17
|
-
export const operate = (operator, left, right) => {
|
|
18
|
-
const operation = operations.get(operator);
|
|
19
|
-
if (!operation) {
|
|
20
|
-
throw new Error(`Invalid operator: ${operator}`);
|
|
21
|
-
}
|
|
22
|
-
return operation(left, right);
|
|
23
|
-
};
|