mimo-lang 1.1.0 → 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 (166) hide show
  1. package/.gitattributes +24 -0
  2. package/LICENSE +21 -0
  3. package/README.md +91 -6
  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 +49 -39
  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 -11
  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 -1
  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 -63
  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 -35
  153. package/compiler/parser/parser.js +0 -16
  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/i.js +0 -30
  165. package/jsconfig.json +0 -27
  166. package/webpack.config.js +0 -9
@@ -0,0 +1,490 @@
1
+ import { BuiltinFunction } from "./BuiltinFunction.js";
2
+ import { stringify } from "./Utils.js";
3
+ import { arraySlice as stdlibArraySlice } from "./stdlib/array/accessFunctions.js";
4
+
5
+ export const builtinFunctions = {
6
+ len: new BuiltinFunction(
7
+ "len",
8
+ ([value], interpreter, callNode) => {
9
+ if (Array.isArray(value)) return value.length;
10
+ if (typeof value === "string") return value.length;
11
+ throw interpreter.errorHandler.createRuntimeError(
12
+ `len() expects an array or string. Got '${typeof value}'.`,
13
+ callNode,
14
+ "TYPE001",
15
+ "Provide an array or string to len()."
16
+ );
17
+ },
18
+ 1
19
+ ),
20
+
21
+ get: new BuiltinFunction(
22
+ "get",
23
+ ([collection, key], interpreter, callNode) => {
24
+ if (Array.isArray(collection)) {
25
+ if (typeof key !== "number" || !Number.isInteger(key)) {
26
+ throw interpreter.errorHandler.createRuntimeError(
27
+ `Array index must be an integer. Got '${typeof key}'.`,
28
+ callNode,
29
+ "TYPE001"
30
+ );
31
+ }
32
+ if (key < 0 || key >= collection.length) {
33
+ // Return null for out-of-bounds access, a safe default.
34
+ return null;
35
+ }
36
+ return collection[key];
37
+ }
38
+ if (typeof collection === "object" && collection !== null) {
39
+ return collection[key] ?? null; // Return null if property doesn't exist
40
+ }
41
+ throw interpreter.errorHandler.createRuntimeError(
42
+ `Cannot 'get' from type '${typeof collection}'. Expected array or object.`,
43
+ callNode,
44
+ "TYPE002"
45
+ );
46
+ },
47
+ 2
48
+ ),
49
+
50
+ update: new BuiltinFunction(
51
+ "update",
52
+ ([collection, key, value], interpreter, callNode) => {
53
+ if (Array.isArray(collection)) {
54
+ if (typeof key !== "number" || !Number.isInteger(key)) {
55
+ throw interpreter.errorHandler.createRuntimeError(
56
+ `Array index must be an integer. Got '${typeof key}'.`,
57
+ callNode,
58
+ "TYPE001"
59
+ );
60
+ }
61
+ if (key < 0) {
62
+ // Allow assignment to the end of the array
63
+ throw interpreter.errorHandler.createRuntimeError(
64
+ `Index ${key} cannot be negative.`,
65
+ callNode,
66
+ "INDEX001"
67
+ );
68
+ }
69
+ // Allow growing the array
70
+ if (key > collection.length) {
71
+ // Fill sparse elements with null, like JS does with 'undefined'
72
+ for (let i = collection.length; i < key; i++) {
73
+ collection[i] = null;
74
+ }
75
+ }
76
+ collection[key] = value;
77
+ return value; // Return the assigned value
78
+ }
79
+ if (typeof collection === "object" && collection !== null) {
80
+ collection[key] = value;
81
+ return value;
82
+ }
83
+ throw interpreter.errorHandler.createRuntimeError(
84
+ `Cannot 'update' on type '${typeof collection}'. Expected array or object.`,
85
+ callNode,
86
+ "TYPE002"
87
+ );
88
+ },
89
+ 3
90
+ ),
91
+
92
+ type: new BuiltinFunction(
93
+ "type",
94
+ ([value], interpreter, callNode) => {
95
+ if (value === null) return "null";
96
+ if (Array.isArray(value)) return "array";
97
+ if (typeof value === "object" && value !== null) return "object";
98
+ return typeof value;
99
+ },
100
+ 1
101
+ ),
102
+
103
+ push: new BuiltinFunction(
104
+ "push",
105
+ ([array, value], interpreter, callNode) => {
106
+ if (!Array.isArray(array)) {
107
+ throw interpreter.errorHandler.createRuntimeError(
108
+ "push() requires an array as first argument",
109
+ callNode,
110
+ "TYPE001",
111
+ "Provide an array as the first argument to push()."
112
+ );
113
+ }
114
+ array.push(value);
115
+ return array;
116
+ },
117
+ 2
118
+ ),
119
+
120
+ pop: new BuiltinFunction(
121
+ "pop",
122
+ ([array], interpreter, callNode) => {
123
+ if (!Array.isArray(array)) {
124
+ throw interpreter.errorHandler.createRuntimeError(
125
+ "pop() requires an array",
126
+ callNode,
127
+ "TYPE001",
128
+ "Provide an array to pop()."
129
+ );
130
+ }
131
+ if (array.length === 0) {
132
+ throw interpreter.errorHandler.createRuntimeError(
133
+ "Cannot pop from empty array",
134
+ callNode,
135
+ "INDEX001",
136
+ "Check if the array has elements before calling pop()."
137
+ );
138
+ }
139
+ return array.pop();
140
+ },
141
+ 1
142
+ ),
143
+
144
+ slice: new BuiltinFunction(
145
+ "slice",
146
+ ([array, start, end], interpreter, callNode) => {
147
+ if (!Array.isArray(array)) {
148
+ throw interpreter.errorHandler.createRuntimeError(
149
+ "slice() requires an array as first argument",
150
+ callNode,
151
+ "TYPE001",
152
+ "Provide an array as the first argument to slice()."
153
+ );
154
+ }
155
+ return stdlibArraySlice.call(interpreter, [array, start, end], callNode);
156
+ },
157
+ [1, 3],
158
+ "Creates a shallow copy of a portion of an array into a new array object selected from `start` to `end` (end not included).",
159
+ [
160
+ { name: "array", type: "array", description: "The array to slice." },
161
+ {
162
+ name: "start",
163
+ type: "number",
164
+ description:
165
+ "The beginning index of the specified portion of the array. If negative, it is treated as an offset from the end of the array.",
166
+ },
167
+ {
168
+ name: "end",
169
+ type: "number",
170
+ optional: true,
171
+ description:
172
+ "The end index of the specified portion of the array. If omitted, slice extracts through the end of the array. If negative, it is treated as an offset from the end of the array.",
173
+ },
174
+ ],
175
+ "array"
176
+ ),
177
+
178
+ range: new BuiltinFunction(
179
+ "range",
180
+ (args, interpreter, callNode) => {
181
+ if (args.length < 1 || args.length > 3) {
182
+ throw interpreter.errorHandler.createRuntimeError(
183
+ "range() expects 1 to 3 arguments (end, [start], [step]).",
184
+ callNode,
185
+ "BUILTIN001",
186
+ "Provide a valid number of arguments to range()."
187
+ );
188
+ }
189
+
190
+ let start = 0;
191
+ let end;
192
+ let step = 1;
193
+
194
+ if (args.length === 1) {
195
+ end = args[0];
196
+ } else if (args.length === 2) {
197
+ start = args[0];
198
+ end = args[1];
199
+ } else {
200
+ // 3 arguments
201
+ start = args[0];
202
+ end = args[1];
203
+ step = args[2];
204
+ }
205
+
206
+ // Type checking
207
+ if (
208
+ typeof start !== "number" ||
209
+ typeof end !== "number" ||
210
+ typeof step !== "number"
211
+ ) {
212
+ throw interpreter.errorHandler.createRuntimeError(
213
+ "range() arguments must be numbers.",
214
+ callNode,
215
+ "TYPE001",
216
+ "Ensure start, end, and step values are numbers."
217
+ );
218
+ }
219
+
220
+ if (
221
+ !Number.isInteger(start) ||
222
+ !Number.isInteger(end) ||
223
+ !Number.isInteger(step)
224
+ ) {
225
+ throw interpreter.errorHandler.createRuntimeError(
226
+ "range() arguments must be integers.",
227
+ callNode,
228
+ "TYPE001",
229
+ "Ensure start, end, and step values are whole numbers."
230
+ );
231
+ }
232
+
233
+ if (step === 0) {
234
+ throw interpreter.errorHandler.createRuntimeError(
235
+ "range() step argument cannot be zero.",
236
+ callNode,
237
+ "ARG001",
238
+ "Provide a non-zero step value."
239
+ );
240
+ }
241
+
242
+ const result = [];
243
+ if (step > 0) {
244
+ for (let i = start; i < end; i += step) {
245
+ result.push(i);
246
+ }
247
+ } else {
248
+ // step < 0
249
+ for (let i = start; i > end; i += step) {
250
+ result.push(i);
251
+ }
252
+ }
253
+ return result;
254
+ },
255
+ [1, 3]
256
+ ),
257
+
258
+ join: new BuiltinFunction(
259
+ "join",
260
+ ([array, separator], interpreter, callNode) => {
261
+ if (!Array.isArray(array)) {
262
+ throw interpreter.errorHandler.createRuntimeError(
263
+ "join() requires an array as first argument",
264
+ callNode,
265
+ "TYPE001",
266
+ "Provide an array as the first argument to join()."
267
+ );
268
+ }
269
+ if (typeof separator !== "string") {
270
+ throw interpreter.errorHandler.createRuntimeError(
271
+ "join() requires a string separator as second argument",
272
+ callNode,
273
+ "TYPE001",
274
+ "Provide a string as the second argument to join()."
275
+ );
276
+ }
277
+ return array.map(stringify).join(separator);
278
+ },
279
+ 2
280
+ ),
281
+
282
+ // Object utility functions
283
+ has_property: new BuiltinFunction(
284
+ "has_property",
285
+ ([object, property], interpreter, callNode) => {
286
+ if (object === null || object === undefined) {
287
+ return false;
288
+ }
289
+ if (typeof object !== "object") {
290
+ return false;
291
+ }
292
+ const propertyKey = String(property);
293
+ return Object.prototype.hasOwnProperty.call(object, propertyKey);
294
+ },
295
+ 2
296
+ ),
297
+
298
+ keys: new BuiltinFunction(
299
+ "keys",
300
+ ([object], interpreter, callNode) => {
301
+ if (object === null || object === undefined) {
302
+ throw interpreter.errorHandler.createRuntimeError(
303
+ "keys() requires a non-null object.",
304
+ callNode,
305
+ "TYPE001",
306
+ "Provide an object or array to keys()."
307
+ );
308
+ }
309
+ if (typeof object !== "object") {
310
+ throw interpreter.errorHandler.createRuntimeError(
311
+ "keys() requires an object argument.",
312
+ callNode,
313
+ "TYPE001",
314
+ "Provide an object or array to keys()."
315
+ );
316
+ }
317
+ if (Array.isArray(object)) {
318
+ return Object.keys(object).map(Number);
319
+ }
320
+ return Object.keys(object);
321
+ },
322
+ 1
323
+ ),
324
+
325
+ values: new BuiltinFunction(
326
+ "values",
327
+ ([object], interpreter, callNode) => {
328
+ if (object === null || object === undefined) {
329
+ throw interpreter.errorHandler.createRuntimeError(
330
+ "values() requires a non-null object.",
331
+ callNode,
332
+ "TYPE001",
333
+ "Provide an object or array to values()."
334
+ );
335
+ }
336
+ if (typeof object !== "object") {
337
+ throw interpreter.errorHandler.createRuntimeError(
338
+ "values() requires an object argument.",
339
+ callNode,
340
+ "TYPE001",
341
+ "Provide an object or array to values()."
342
+ );
343
+ }
344
+ return Object.values(object);
345
+ },
346
+ 1
347
+ ),
348
+
349
+ entries: new BuiltinFunction(
350
+ "entries",
351
+ ([object], interpreter, callNode) => {
352
+ if (object === null || object === undefined) {
353
+ throw interpreter.errorHandler.createRuntimeError(
354
+ "entries() requires a non-null object.",
355
+ callNode,
356
+ "TYPE001",
357
+ "Provide an object or array to entries()."
358
+ );
359
+ }
360
+ if (typeof object !== "object") {
361
+ throw interpreter.errorHandler.createRuntimeError(
362
+ "entries() requires an object argument.",
363
+ callNode,
364
+ "TYPE001",
365
+ "Provide an object or array to entries()."
366
+ );
367
+ }
368
+ return Object.entries(object);
369
+ },
370
+ 1
371
+ ),
372
+
373
+ // Command Line Interface functions
374
+ get_arguments: new BuiltinFunction(
375
+ "get_arguments",
376
+ (args, interpreter, callNode) => {
377
+ return interpreter.adapter.getArguments();
378
+ },
379
+ 0
380
+ ),
381
+
382
+ get_env: new BuiltinFunction(
383
+ "get_env",
384
+ (args, interpreter, callNode) => {
385
+ const [variableName] = args;
386
+ if (typeof variableName !== "string") {
387
+ throw interpreter.errorHandler.createRuntimeError(
388
+ "get_env() expects a string variable name as its argument.",
389
+ callNode,
390
+ "TYPE001",
391
+ "Provide a string environment variable name."
392
+ );
393
+ }
394
+ if (typeof interpreter.adapter.getEnvVariable !== "function") {
395
+ throw interpreter.errorHandler.createRuntimeError(
396
+ "Host adapter does not support getEnvVariable().",
397
+ callNode,
398
+ "ADAPTER001",
399
+ "Use an adapter that supports environment variables."
400
+ );
401
+ }
402
+ return interpreter.adapter.getEnvVariable(variableName) ?? null;
403
+ },
404
+ 1
405
+ ),
406
+
407
+ exit_code: new BuiltinFunction(
408
+ "exit_code",
409
+ (args, interpreter, callNode) => {
410
+ const [code] = args;
411
+ if (typeof code !== "number") {
412
+ throw interpreter.errorHandler.createRuntimeError(
413
+ "exit_code() expects a numeric exit code as its argument.",
414
+ callNode,
415
+ "TYPE001",
416
+ "Provide a number for the exit code."
417
+ );
418
+ }
419
+ interpreter.adapter.exit(Math.floor(code));
420
+ return null;
421
+ },
422
+ 1
423
+ ),
424
+
425
+ coalesce: new BuiltinFunction(
426
+ "coalesce",
427
+ (args, interpreter, callNode) => {
428
+ const [value, defaultValue] = args;
429
+
430
+ if (value === null || value === undefined) {
431
+ return defaultValue;
432
+ }
433
+ return value;
434
+ },
435
+ 2
436
+ ),
437
+
438
+ get_property_safe: new BuiltinFunction(
439
+ "get_property_safe",
440
+ ([object, propertyName], interpreter, callNode) => {
441
+ if (object === null || object === undefined) {
442
+ return null; // Safe navigation returns null for null/undefined object
443
+ }
444
+ if (typeof propertyName !== "string") {
445
+ throw interpreter.errorHandler.createRuntimeError(
446
+ `get_property_safe() expects a string property name as argument 2. Got '${typeof propertyName}'.`,
447
+ callNode,
448
+ "TYPE001",
449
+ 'Provide a string literal for the property name (e.g., "name").'
450
+ );
451
+ }
452
+ // Allow safe access on strings (e.g., `my_string?.length`)
453
+ if (
454
+ typeof object !== "object" &&
455
+ typeof object !== "string" &&
456
+ typeof object !== "function"
457
+ ) {
458
+ // Functions are objects too
459
+ throw interpreter.errorHandler.createRuntimeError(
460
+ `Cannot safely access property '${propertyName}' of non-object/non-string value of type '${typeof object}'.`,
461
+ callNode,
462
+ "TYPE002",
463
+ "get_property_safe() is only applicable to objects, functions, or strings."
464
+ );
465
+ }
466
+ // If the property doesn't exist, JS returns undefined. Map to Mimo's null.
467
+ const value = object[propertyName];
468
+ return value === undefined ? null : value;
469
+ },
470
+ 2 // obj, property_name (as string)
471
+ ),
472
+
473
+ if_else: new BuiltinFunction(
474
+ "if_else",
475
+ ([condition, consequentValue, alternateValue], interpreter, callNode) => {
476
+ if (interpreter.expressionEvaluator.isTruthy(condition)) {
477
+ return consequentValue;
478
+ } else {
479
+ return alternateValue;
480
+ }
481
+ },
482
+ 3
483
+ ),
484
+ };
485
+
486
+ export function initializeBuiltins(environment) {
487
+ for (const [name, func] of Object.entries(builtinFunctions)) {
488
+ environment.define(name, func);
489
+ }
490
+ }
@@ -0,0 +1,99 @@
1
+ export class Environment {
2
+ constructor(parent = null, isGlobalScope = false, isFunctionContext = false) {
3
+ // <--- Add isFunctionContext
4
+ this.vars = new Map(); // name -> { value, kind, mutable }
5
+ this.parent = parent;
6
+ this.isGlobalScope = isGlobalScope;
7
+ this.isFunctionContext = isFunctionContext;
8
+ this.isModuleRoot = false;
9
+ }
10
+
11
+ define(name, value, kind = "set") {
12
+ // Check if variable already exists in current scope only
13
+ if (this.vars.has(name)) {
14
+ const existing = this.vars.get(name);
15
+ // Allow redeclaring 'set' variables in same scope, but not let/const
16
+ if (kind !== "set" || existing.kind !== "set") {
17
+ throw new Error(`Variable '${name}' is already declared in this scope`);
18
+ }
19
+ }
20
+
21
+ const mutable = kind !== "const";
22
+ this.vars.set(name, { value, kind, mutable });
23
+ }
24
+
25
+ defineGlobal(name, value, kind = "set") {
26
+ // Find global environment
27
+ let globalEnv = this;
28
+ while (globalEnv.parent !== null) {
29
+ globalEnv = globalEnv.parent;
30
+ }
31
+ globalEnv.define(name, value, kind);
32
+ }
33
+
34
+ assign(name, value) {
35
+ // Look for variable in current scope first
36
+ if (this.vars.has(name)) {
37
+ const variable = this.vars.get(name);
38
+ if (!variable.mutable) {
39
+ throw new Error(`Cannot assign to const variable '${name}'`);
40
+ }
41
+ variable.value = value;
42
+ return;
43
+ }
44
+
45
+ // Look in parent scopes
46
+ if (this.parent) {
47
+ this.parent.assign(name, value);
48
+ return;
49
+ }
50
+
51
+ throw new Error(`Undefined variable: ${name}`);
52
+ }
53
+
54
+ lookup(name) {
55
+ if (this.vars.has(name)) {
56
+ return this.vars.get(name).value;
57
+ }
58
+ if (this.parent) {
59
+ return this.parent.lookup(name);
60
+ }
61
+ throw new Error(`Undefined variable: ${name}`);
62
+ }
63
+
64
+ hasInCurrentScope(name) {
65
+ return this.vars.has(name);
66
+ }
67
+
68
+ getVariableInfo(name) {
69
+ // If found in current environment, return info and *this* environment
70
+ if (this.vars.has(name)) {
71
+ return { info: this.vars.get(name), env: this };
72
+ }
73
+
74
+ if (this.isModuleRoot || this.isGlobalScope) {
75
+ return null;
76
+ }
77
+
78
+ // If not found here, check parent.
79
+ if (this.parent) {
80
+ // The recursive call will correctly traverse up and return the { info, env } object
81
+ // from the environment where the variable was found. Just return that result directly.
82
+ return this.parent.getVariableInfo(name);
83
+ }
84
+ // If no parent and not found, return null.
85
+ return null;
86
+ }
87
+
88
+ getVisibleNames() {
89
+ const names = new Set();
90
+ let current = this;
91
+ while (current) {
92
+ for (const name of current.vars.keys()) {
93
+ names.add(name);
94
+ }
95
+ current = current.parent;
96
+ }
97
+ return [...names];
98
+ }
99
+ }
@@ -0,0 +1,111 @@
1
+ import { isTruthy } from '../Utils.js';
2
+ import { getMimoType } from '../suggestions.js';
3
+
4
+ export function evaluateBinaryExpression(interpreter, node) {
5
+ const left = interpreter.visitNode(node.left);
6
+ const right = interpreter.visitNode(node.right);
7
+
8
+ // Type checking for arithmetic operations
9
+ const isNumericOp = ['+', '-', '*', '/', '%', '<', '>', '<=', '>=', '??'].includes(node.operator);
10
+
11
+ if (isNumericOp && node.operator !== '??' && (typeof left !== 'number' || typeof right !== 'number')) {
12
+ // Special case: string concatenation with '+'
13
+ if (node.operator === '+' && (typeof left === 'string' || typeof right === 'string')) {
14
+ return String(left) + String(right);
15
+ }
16
+ throw interpreter.errorHandler.createRuntimeError(
17
+ `Type error for '${node.operator}': expected number operands, received ${getMimoType(left)} and ${getMimoType(right)}.`,
18
+ node,
19
+ 'TYPE001',
20
+ `Ensure both operands for '${node.operator}' are numbers.`
21
+ );
22
+ }
23
+
24
+ switch (node.operator) {
25
+ case '??':
26
+ return left !== null ? left : right;
27
+ case '+':
28
+ return left + right;
29
+ case '-':
30
+ return left - right;
31
+ case '*':
32
+ return left * right;
33
+ case '/':
34
+ if (right === 0) {
35
+ throw interpreter.errorHandler.createRuntimeError(
36
+ "Type error for '/': expected non-zero divisor, received 0.",
37
+ node,
38
+ 'MATH001',
39
+ "Ensure the divisor is not zero."
40
+ );
41
+ }
42
+ return left / right;
43
+ case '%':
44
+ if (right === 0) {
45
+ throw interpreter.errorHandler.createRuntimeError(
46
+ "Type error for '%': expected non-zero divisor, received 0.",
47
+ node,
48
+ 'MATH001',
49
+ "Ensure the divisor is not zero."
50
+ );
51
+ }
52
+ return left % right;
53
+ case '>':
54
+ return left > right;
55
+ case '<':
56
+ return left < right;
57
+ case '>=':
58
+ return left >= right;
59
+ case '<=':
60
+ return left <= right;
61
+ case '=':
62
+ case '==':
63
+ return left === right;
64
+ case '===':
65
+ return left === right;
66
+ case '!':
67
+ case '!=':
68
+ return left !== right;
69
+ case '!==':
70
+ return left !== right;
71
+ case 'and':
72
+ case '&&':
73
+ return isTruthy(left) && isTruthy(right);
74
+ case 'or':
75
+ case '||':
76
+ return isTruthy(left) || isTruthy(right);
77
+
78
+ default:
79
+ throw interpreter.errorHandler.createRuntimeError(
80
+ `Unknown binary operator: '${node.operator}'.`,
81
+ node,
82
+ 'OP001',
83
+ "Check for typos in the operator."
84
+ );
85
+ }
86
+ }
87
+
88
+ export function evaluateUnaryExpression(interpreter, node) {
89
+ const argument = interpreter.visitNode(node.argument);
90
+ switch (node.operator) {
91
+ case '-':
92
+ if (typeof argument !== 'number') {
93
+ throw interpreter.errorHandler.createRuntimeError(
94
+ `Type error for unary '-': expected number, received ${getMimoType(argument)}.`,
95
+ node,
96
+ 'TYPE001',
97
+ 'Provide a number for unary minus operation.'
98
+ );
99
+ }
100
+ return -argument;
101
+ case 'not':
102
+ return !isTruthy(argument);
103
+ default:
104
+ throw interpreter.errorHandler.createRuntimeError(
105
+ `Unknown unary operator: '${node.operator}'.`,
106
+ node,
107
+ 'OP001',
108
+ "Check for typos in the operator."
109
+ );
110
+ }
111
+ }