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,93 @@
1
+ import { ErrorHandler } from "../ErrorHandler.js";
2
+ import { Environment } from "../environment.js";
3
+ import { stringify, isTruthy } from "../Utils.js";
4
+ import { BaseExecutor } from "./BaseExecutor.js";
5
+
6
+ export class PatternMatchExecutor extends BaseExecutor {
7
+ executeMatchStatement(node) {
8
+ const discriminant = this.interpreter.visitNode(node.discriminant);
9
+
10
+ for (const caseClause of node.cases) {
11
+ const bindings = {};
12
+ // The `matchesPattern` method might throw if a pattern is invalid, let it bubble up.
13
+ if (this.matchesPattern(discriminant, caseClause.pattern, bindings)) {
14
+ // Create new block scope for case body
15
+ const caseEnv = new Environment(this.interpreter.currentEnv);
16
+
17
+ // Bind all captured variables to the case scope
18
+ for (const [name, value] of Object.entries(bindings)) {
19
+ // Define the variable in the case's scope
20
+ // Default to 'let' kind for bindings, but could be configurable
21
+ caseEnv.define(name, value, "let"); // Assume 'let' kind for pattern bindings
22
+ }
23
+
24
+ let guardPassed = true;
25
+ if (caseClause.guard) {
26
+ const previousEnv = this.interpreter.currentEnv;
27
+ this.interpreter.currentEnv = caseEnv;
28
+ try {
29
+ guardPassed = isTruthy(this.interpreter.visitNode(caseClause.guard));
30
+ } finally {
31
+ this.interpreter.currentEnv = previousEnv;
32
+ }
33
+ }
34
+
35
+ if (guardPassed) {
36
+ return this.interpreter.executeBlock(caseClause.consequent, caseEnv);
37
+ }
38
+ }
39
+ }
40
+
41
+ // No match found and no default case
42
+ throw this.interpreter.errorHandler.createRuntimeError(
43
+ `No matching pattern found for value: ${stringify(discriminant)}`,
44
+ node.discriminant, // Point to the discriminant expression
45
+ "MATCH001",
46
+ `Add an 'default:' clause or ensure all possible values are covered by 'case' clauses.`
47
+ );
48
+ }
49
+
50
+ matchesPattern(value, pattern, bindings = {}) {
51
+ // Default case (pattern is null)
52
+ if (pattern === null) {
53
+ return true;
54
+ }
55
+
56
+ switch (pattern.type) {
57
+ case "Literal":
58
+ return value === pattern.value;
59
+
60
+ case "Identifier":
61
+ // Identifier patterns always match and bind the value
62
+ bindings[pattern.name] = value;
63
+ return true;
64
+
65
+ case "ArrayPattern":
66
+ if (!Array.isArray(value)) {
67
+ return false;
68
+ }
69
+
70
+ // Check if array length matches
71
+ if (value.length !== pattern.elements.length) {
72
+ return false;
73
+ }
74
+
75
+ // Check each element and collect bindings
76
+ for (let i = 0; i < pattern.elements.length; i++) {
77
+ if (!this.matchesPattern(value[i], pattern.elements[i], bindings)) {
78
+ return false;
79
+ }
80
+ }
81
+ return true;
82
+
83
+ // TODO: Add ObjectPattern, TypePattern etc.
84
+ default:
85
+ throw this.interpreter.errorHandler.createRuntimeError(
86
+ `Internal error: Unknown pattern type '${pattern.type}' encountered during matching.`,
87
+ pattern, // The AST node for the problematic pattern
88
+ "INT003",
89
+ "This indicates a bug in the Mimo interpreter. Please report this error."
90
+ );
91
+ }
92
+ }
93
+ }
@@ -0,0 +1,144 @@
1
+ // In: interpreter/executors/VariableExecutor.js
2
+
3
+ import { ErrorHandler } from "../ErrorHandler.js";
4
+ import { Environment } from "../environment.js";
5
+ import { BaseExecutor } from "./BaseExecutor.js";
6
+
7
+ export class VariableExecutor extends BaseExecutor {
8
+ executeVariableDeclaration(node) {
9
+ const value = this.interpreter.visitNode(node.value);
10
+
11
+ const defineOrAssign = (varName, varValue) => {
12
+ switch (node.kind) {
13
+ case "global":
14
+ this.interpreter.globalEnv.define(varName, varValue, 'global');
15
+ break;
16
+ case "let":
17
+ case "const":
18
+ this.interpreter.currentEnv.define(varName, varValue, node.kind);
19
+ break;
20
+ default: // 'set'
21
+ const existingVarInfo = this.interpreter.currentEnv.getVariableInfo(varName);
22
+ if (existingVarInfo) {
23
+ try {
24
+ existingVarInfo.env.assign(varName, varValue);
25
+ } catch (error) {
26
+ throw this.interpreter.errorHandler.createRuntimeError(
27
+ error.message,
28
+ node,
29
+ "TYPE002",
30
+ "Verify variable mutability and declaration scope."
31
+ );
32
+ }
33
+ } else {
34
+ this.interpreter.currentEnv.define(varName, varValue, 'set');
35
+ }
36
+ break;
37
+ }
38
+ };
39
+
40
+ if (typeof node.identifier === "object" && node.identifier !== null && node.identifier.type.endsWith("Pattern")) {
41
+ const pattern = node.identifier;
42
+ if (pattern.type === 'ArrayPattern') {
43
+ if (!Array.isArray(value)) {
44
+ throw this.interpreter.errorHandler.createRuntimeError(`Cannot destructure non-array value into an array pattern.`, node.value, 'TYPE002');
45
+ }
46
+ pattern.elements.forEach((varNode, i) => {
47
+ defineOrAssign(varNode.name, value[i] ?? null);
48
+ });
49
+ } else if (pattern.type === 'ObjectPattern') {
50
+ if (typeof value !== 'object' || value === null || Array.isArray(value)) {
51
+ throw this.interpreter.errorHandler.createRuntimeError(`Cannot destructure non-object value into an object pattern.`, node.value, 'TYPE002');
52
+ }
53
+ pattern.properties.forEach(propIdentifierNode => {
54
+ const propName = propIdentifierNode.name;
55
+ defineOrAssign(propName, value[propName] ?? null);
56
+ });
57
+ }
58
+ return value;
59
+ }
60
+
61
+ defineOrAssign(node.identifier, value);
62
+ return value;
63
+ }
64
+
65
+ executePropertyAssignment(node) {
66
+ const targetObject = this.interpreter.visitNode(node.object);
67
+ const valueToAssign = this.interpreter.visitNode(node.value);
68
+
69
+ if (targetObject === null || typeof targetObject !== 'object') {
70
+ throw this.interpreter.errorHandler.createRuntimeError(
71
+ `Cannot set property '${node.property}' on a non-object value (got ${typeof targetObject}).`,
72
+ node.object, "TYPE004", "Ensure the target is an object before setting its properties."
73
+ );
74
+ }
75
+ targetObject[node.property] = valueToAssign;
76
+ return valueToAssign;
77
+ }
78
+
79
+ executeBracketAssignment(node) {
80
+ const object = this.interpreter.visitNode(node.object);
81
+ const index = this.interpreter.visitNode(node.index);
82
+ const value = this.interpreter.visitNode(node.value);
83
+
84
+ if (Array.isArray(object)) {
85
+ if (typeof index !== "number" || !Number.isInteger(index)) {
86
+ throw this.interpreter.errorHandler.createRuntimeError(`Array index for assignment must be an integer. Got '${typeof index}'.`, node.index, "TYPE001");
87
+ }
88
+ if (index < 0) {
89
+ throw this.interpreter.errorHandler.createRuntimeError(`Array index cannot be negative. Got ${index}.`, node.index, "INDEX001");
90
+ }
91
+ object[index] = value;
92
+ return value;
93
+ }
94
+
95
+ if (typeof object === "object" && object !== null) {
96
+ const key = String(index);
97
+ object[key] = value;
98
+ return value;
99
+ }
100
+
101
+ throw this.interpreter.errorHandler.createRuntimeError(`Cannot set property on value of type '${typeof object}'.`, node.object, "TYPE002");
102
+ }
103
+
104
+ executeDestructuringAssignment(node) {
105
+ const value = this.interpreter.visitNode(node.expression);
106
+ const pattern = node.pattern; // The pattern node is now passed directly
107
+
108
+ // Helper function to assign a value to a variable using 'set' semantics
109
+ const assignOrDefine = (varNode, varValue) => {
110
+ const varName = varNode.name;
111
+ const existingVarInfo = this.interpreter.currentEnv.getVariableInfo(varName);
112
+ if (existingVarInfo) {
113
+ existingVarInfo.env.assign(varName, varValue);
114
+ } else {
115
+ this.interpreter.currentEnv.define(varName, varValue, 'set');
116
+ }
117
+ };
118
+
119
+ // --- Main Logic ---
120
+
121
+ if (pattern.type === 'ArrayPattern') {
122
+ if (!Array.isArray(value)) {
123
+ throw this.interpreter.errorHandler.createRuntimeError(`Cannot destructure non-array value into an array pattern.`, node.expression, 'TYPE002');
124
+ }
125
+ pattern.elements.forEach((varNode, i) => {
126
+ assignOrDefine(varNode, value[i] ?? null); // Assign null if index is out of bounds
127
+ });
128
+ } else if (pattern.type === 'ObjectPattern') {
129
+ if (typeof value !== 'object' || value === null || Array.isArray(value)) {
130
+ throw this.interpreter.errorHandler.createRuntimeError(`Cannot destructure non-object value into an object pattern.`, node.expression, 'TYPE002');
131
+ }
132
+ pattern.properties.forEach(propIdentifierNode => {
133
+ const propName = propIdentifierNode.name;
134
+ // Assign the property's value, or null if it doesn't exist on the object
135
+ assignOrDefine(propIdentifierNode, value[propName] ?? null);
136
+ });
137
+ } else {
138
+ // This case should be unreachable if the parser is correct
139
+ throw this.interpreter.errorHandler.createRuntimeError(`Unsupported pattern type '${pattern.type}' for destructuring.`, pattern, 'INT003');
140
+ }
141
+
142
+ return value; // The destructuring statement itself returns the original value
143
+ }
144
+ }
@@ -0,0 +1,8 @@
1
+ export { Interpreter } from './Interpreter.js';
2
+ export { Environment } from './environment.js';
3
+ export { ReturnValue, FunctionValue } from './Values.js';
4
+ export { ExpressionEvaluator } from './ExpressionEvaluator.js';
5
+ export { StatementExecutor } from './StatementExecutor.js';
6
+ export { isTruthy, stringify } from './Utils.js';
7
+ export { BuiltinFunction } from './BuiltinFunction.js';
8
+ export { builtinFunctions, initializeBuiltins } from './coreBuiltins.js';
@@ -0,0 +1,61 @@
1
+ import { BuiltinFunction } from '../../BuiltinFunction.js';
2
+ import { expectArray, expectNumber } from './arrayUtils.js';
3
+
4
+ // --- Access and Extraction Functions ---
5
+
6
+ export const arraySlice = new BuiltinFunction("slice",
7
+ (args, interpreter, callNode) => {
8
+ const [arr, beginIndexArg, endIndexArg] = args;
9
+ expectArray(arr, "slice", interpreter, callNode, 1);
10
+
11
+ let beginIndex = 0;
12
+ // If beginIndexArg is provided and NOT null, then validate it as a number.
13
+ if (beginIndexArg !== undefined && beginIndexArg !== null) {
14
+ expectNumber(beginIndexArg, "slice", interpreter, callNode, 2);
15
+ beginIndex = beginIndexArg;
16
+ }
17
+ // If beginIndexArg IS undefined or null, beginIndex remains its default (0).
18
+
19
+ let endIndex = arr.length;
20
+ // If endIndexArg is provided and NOT null, then validate it as a number.
21
+ if (endIndexArg !== undefined && endIndexArg !== null) {
22
+ expectNumber(endIndexArg, "slice", interpreter, callNode, 3);
23
+ endIndex = endIndexArg;
24
+ }
25
+ // If endIndexArg IS undefined or null, endIndex remains its default (arr.length).
26
+
27
+ // The native JavaScript Array.prototype.slice method already handles
28
+ // undefined/null for its arguments gracefully, so we can pass `beginIndex`
29
+ // and `endIndex` directly.
30
+ // Note: JS `slice` also handles negative indices which is great for your test.
31
+ return arr.slice(beginIndex, endIndex);
32
+ },
33
+ [1, 3] // array, [beginIndex], [endIndex] - arity remains 1 to 3
34
+ );
35
+
36
+ export const arrayFirst = new BuiltinFunction("first",
37
+ (args, interpreter, callNode) => {
38
+ const [arr] = args;
39
+ expectArray(arr, "first", interpreter, callNode, 1);
40
+ return arr.length > 0 ? arr[0] : null;
41
+ },
42
+ 1
43
+ );
44
+
45
+ export const arrayLast = new BuiltinFunction("last",
46
+ (args, interpreter, callNode) => {
47
+ const [arr] = args;
48
+ expectArray(arr, "last", interpreter, callNode, 1);
49
+ return arr.length > 0 ? arr[arr.length - 1] : null;
50
+ },
51
+ 1
52
+ );
53
+
54
+ export const arrayIsEmpty = new BuiltinFunction("is_empty",
55
+ (args, interpreter, callNode) => {
56
+ const [arr] = args;
57
+ expectArray(arr, "is_empty", interpreter, callNode, 1);
58
+ return arr.length === 0;
59
+ },
60
+ 1
61
+ );
@@ -0,0 +1,36 @@
1
+ import { FunctionValue } from '../../Values.js';
2
+
3
+ // --- Helper for type checking ---
4
+ export function expectArray(arg, funcName, interpreter, callNode, argPosition = 1) {
5
+ if (!Array.isArray(arg)) {
6
+ throw interpreter.errorHandler.createRuntimeError(
7
+ `${funcName}() expects an array as argument ${argPosition}. Got '${typeof arg}'.`,
8
+ callNode,
9
+ 'TYPE001',
10
+ `Ensure argument ${argPosition} for '${funcName}' is an array.`
11
+ );
12
+ }
13
+ }
14
+
15
+ export function expectMimoFunction(arg, funcName, interpreter, callNode, argPosition = 1) {
16
+ if (!(arg instanceof FunctionValue)) {
17
+ throw interpreter.errorHandler.createRuntimeError(
18
+ `${funcName}() expects a Mimo function as argument ${argPosition}. Got '${typeof arg}'.`,
19
+ callNode,
20
+ 'TYPE001',
21
+ `Ensure argument ${argPosition} for '${funcName}' is a function.`
22
+ );
23
+ }
24
+ }
25
+
26
+ // Helper for number type checking
27
+ export function expectNumber(arg, funcName, interpreter, callNode, argPosition = 1) {
28
+ if (typeof arg !== 'number') {
29
+ throw interpreter.errorHandler.createRuntimeError(
30
+ `${funcName}() expects a number as argument ${argPosition}. Got '${typeof arg}'.`,
31
+ callNode,
32
+ 'TYPE001',
33
+ `Ensure argument ${argPosition} for '${funcName}' is a number.`
34
+ );
35
+ }
36
+ }
@@ -0,0 +1,285 @@
1
+ import { BuiltinFunction } from '../../BuiltinFunction.js';
2
+ import { expectArray, expectMimoFunction } from './arrayUtils.js';
3
+
4
+ // --- Higher-Order Functions ---
5
+
6
+ export const arrayMap = new BuiltinFunction("map",
7
+ (args, interpreter, callNode) => {
8
+ const [arr, callbackFn] = args;
9
+ expectArray(arr, "map", interpreter, callNode, 1);
10
+ expectMimoFunction(callbackFn, "map", interpreter, callNode, 2);
11
+
12
+ const result = [];
13
+ for (let i = 0; i < arr.length; i++) {
14
+ // Call the Mimo callback function: callbackFn(item, index, array)
15
+ const fullArgs = [arr[i], i, arr];
16
+ // Only pass as many args as the function declares
17
+ const callArgs = fullArgs.slice(0, callbackFn.declaration.params.length);
18
+ const mappedValue = callbackFn.call(interpreter, callArgs);
19
+ result.push(mappedValue);
20
+ }
21
+ return result;
22
+ },
23
+ 2 // array, callbackFunction
24
+ );
25
+
26
+ export const arrayFilter = new BuiltinFunction("filter",
27
+ (args, interpreter, callNode) => {
28
+ const [arr, callbackFn] = args;
29
+ expectArray(arr, "filter", interpreter, callNode, 1);
30
+ expectMimoFunction(callbackFn, "filter", interpreter, callNode, 2);
31
+
32
+ const result = [];
33
+ for (let i = 0; i < arr.length; i++) {
34
+ // Call the Mimo callback function: callbackFn(item, index, array)
35
+ const fullArgs = [arr[i], i, arr];
36
+ // Only pass as many args as the function declares
37
+ const callArgs = fullArgs.slice(0, callbackFn.declaration.params.length);
38
+ const shouldInclude = callbackFn.call(interpreter, callArgs);
39
+
40
+ // Use the interpreter's truthy logic
41
+ if (interpreter.expressionEvaluator.isTruthy(shouldInclude)) {
42
+ result.push(arr[i]);
43
+ }
44
+ }
45
+ return result;
46
+ },
47
+ 2 // array, callbackFunction
48
+ );
49
+
50
+ export const arrayReduce = new BuiltinFunction("reduce",
51
+ (args, interpreter, callNode) => {
52
+ const [arr, callbackFn, initialValue] = args;
53
+ expectArray(arr, "reduce", interpreter, callNode, 1);
54
+ expectMimoFunction(callbackFn, "reduce", interpreter, callNode, 2);
55
+
56
+ if (arr.length === 0 && initialValue === undefined) {
57
+ throw interpreter.errorHandler.createRuntimeError(
58
+ `reduce() of empty array with no initial value.`,
59
+ callNode,
60
+ 'ARG001',
61
+ `Provide an initial value to reduce() when operating on an empty array.`
62
+ );
63
+ }
64
+
65
+ let accumulator = initialValue;
66
+ let startIndex = 0;
67
+
68
+ if (initialValue === undefined) {
69
+ accumulator = arr[0];
70
+ startIndex = 1;
71
+ }
72
+
73
+ for (let i = startIndex; i < arr.length; i++) {
74
+ // Call the Mimo callback function: callbackFn(accumulator, currentValue, index, array)
75
+ const fullArgs = [accumulator, arr[i], i, arr];
76
+ const callArgs = fullArgs.slice(0, callbackFn.declaration.params.length);
77
+ accumulator = callbackFn.call(interpreter, callArgs);
78
+ }
79
+
80
+ return accumulator;
81
+ },
82
+ [2, 3] // array, callbackFunction, [initialValue]
83
+ );
84
+
85
+ export const arrayForEach = new BuiltinFunction("for_each",
86
+ (args, interpreter, callNode) => {
87
+ const [arr, callbackFn] = args;
88
+ expectArray(arr, "for_each", interpreter, callNode, 1);
89
+ expectMimoFunction(callbackFn, "for_each", interpreter, callNode, 2);
90
+
91
+ for (let i = 0; i < arr.length; i++) {
92
+ const fullArgs = [arr[i], i, arr];
93
+ const callArgs = fullArgs.slice(0, callbackFn.declaration.params.length);
94
+ callbackFn.call(interpreter, callArgs);
95
+ }
96
+
97
+ return null; // for_each returns null/undefined
98
+ },
99
+ 2 // array, callbackFunction
100
+ );
101
+
102
+ export const arrayFind = new BuiltinFunction("find",
103
+ (args, interpreter, callNode) => {
104
+ const [arr, callbackFn] = args;
105
+ expectArray(arr, "find", interpreter, callNode, 1);
106
+ expectMimoFunction(callbackFn, "find", interpreter, callNode, 2);
107
+
108
+ for (let i = 0; i < arr.length; i++) {
109
+ const fullArgs = [arr[i], i, arr];
110
+ const callArgs = fullArgs.slice(0, callbackFn.declaration.params.length);
111
+ const isMatch = callbackFn.call(interpreter, callArgs);
112
+ if (interpreter.expressionEvaluator.isTruthy(isMatch)) {
113
+ return arr[i];
114
+ }
115
+ }
116
+
117
+ return null; // Not found
118
+ },
119
+ 2 // array, callbackFunction
120
+ );
121
+
122
+ export const arrayFindIndex = new BuiltinFunction("find_index",
123
+ (args, interpreter, callNode) => {
124
+ const [arr, callbackFn] = args;
125
+ expectArray(arr, "find_index", interpreter, callNode, 1);
126
+ expectMimoFunction(callbackFn, "find_index", interpreter, callNode, 2);
127
+
128
+ for (let i = 0; i < arr.length; i++) {
129
+ const fullArgs = [arr[i], i, arr];
130
+ const callArgs = fullArgs.slice(0, callbackFn.declaration.params.length);
131
+ const isMatch = callbackFn.call(interpreter, callArgs);
132
+ if (interpreter.expressionEvaluator.isTruthy(isMatch)) {
133
+ return i;
134
+ }
135
+ }
136
+
137
+ return -1; // Not found
138
+ },
139
+ 2 // array, callbackFunction
140
+ );
141
+
142
+ export const arrayFlat = new BuiltinFunction("flat",
143
+ (args, interpreter, callNode) => {
144
+ const [arr, depthArg] = args;
145
+ expectArray(arr, "flat", interpreter, callNode, 1);
146
+ const depth = depthArg === undefined ? 1 : depthArg;
147
+ if (typeof depth !== "number" || !Number.isInteger(depth) || depth < 0) {
148
+ throw interpreter.errorHandler.createRuntimeError(
149
+ "flat() depth must be a non-negative integer.",
150
+ callNode,
151
+ "TYPE001",
152
+ "Provide a non-negative integer for flat depth."
153
+ );
154
+ }
155
+
156
+ const flatten = (input, currentDepth) => {
157
+ if (currentDepth === 0) return [...input];
158
+ const result = [];
159
+ for (const item of input) {
160
+ if (Array.isArray(item)) {
161
+ result.push(...flatten(item, currentDepth - 1));
162
+ } else {
163
+ result.push(item);
164
+ }
165
+ }
166
+ return result;
167
+ };
168
+
169
+ return flatten(arr, depth);
170
+ },
171
+ [1, 2]
172
+ );
173
+
174
+ export const arrayFlatMap = new BuiltinFunction("flat_map",
175
+ (args, interpreter, callNode) => {
176
+ const [arr, callbackFn] = args;
177
+ expectArray(arr, "flat_map", interpreter, callNode, 1);
178
+ expectMimoFunction(callbackFn, "flat_map", interpreter, callNode, 2);
179
+
180
+ const result = [];
181
+ for (let i = 0; i < arr.length; i++) {
182
+ const fullArgs = [arr[i], i, arr];
183
+ const callArgs = fullArgs.slice(0, callbackFn.declaration.params.length);
184
+ const mapped = callbackFn.call(interpreter, callArgs);
185
+ if (Array.isArray(mapped)) {
186
+ result.push(...mapped);
187
+ } else {
188
+ result.push(mapped);
189
+ }
190
+ }
191
+ return result;
192
+ },
193
+ 2
194
+ );
195
+
196
+ export const arrayGroupBy = new BuiltinFunction("group_by",
197
+ (args, interpreter, callNode) => {
198
+ const [arr, callbackFn] = args;
199
+ expectArray(arr, "group_by", interpreter, callNode, 1);
200
+ expectMimoFunction(callbackFn, "group_by", interpreter, callNode, 2);
201
+
202
+ const groups = {};
203
+ for (let i = 0; i < arr.length; i++) {
204
+ const fullArgs = [arr[i], i, arr];
205
+ const callArgs = fullArgs.slice(0, callbackFn.declaration.params.length);
206
+ const keyValue = callbackFn.call(interpreter, callArgs);
207
+ const key = String(keyValue);
208
+ if (!Object.prototype.hasOwnProperty.call(groups, key)) {
209
+ groups[key] = [];
210
+ }
211
+ groups[key].push(arr[i]);
212
+ }
213
+ return groups;
214
+ },
215
+ 2
216
+ );
217
+
218
+ export const arrayCount = new BuiltinFunction("count",
219
+ (args, interpreter, callNode) => {
220
+ const [arr, callbackFn] = args;
221
+ expectArray(arr, "count", interpreter, callNode, 1);
222
+
223
+ if (callbackFn === undefined) {
224
+ return arr.length;
225
+ }
226
+ expectMimoFunction(callbackFn, "count", interpreter, callNode, 2);
227
+
228
+ let matched = 0;
229
+ for (let i = 0; i < arr.length; i++) {
230
+ const fullArgs = [arr[i], i, arr];
231
+ const callArgs = fullArgs.slice(0, callbackFn.declaration.params.length);
232
+ const keep = callbackFn.call(interpreter, callArgs);
233
+ if (interpreter.expressionEvaluator.isTruthy(keep)) {
234
+ matched++;
235
+ }
236
+ }
237
+ return matched;
238
+ },
239
+ [1, 2]
240
+ );
241
+
242
+ export const arrayZip = new BuiltinFunction("zip",
243
+ (args, interpreter, callNode) => {
244
+ if (args.length < 2) {
245
+ throw interpreter.errorHandler.createRuntimeError(
246
+ "zip() expects at least 2 arrays.",
247
+ callNode,
248
+ "BUILTIN001",
249
+ "Provide at least two arrays to zip()."
250
+ );
251
+ }
252
+
253
+ args.forEach((arg, index) => expectArray(arg, "zip", interpreter, callNode, index + 1));
254
+
255
+ const minLen = Math.min(...args.map((a) => a.length));
256
+ const zipped = [];
257
+ for (let i = 0; i < minLen; i++) {
258
+ zipped.push(args.map((a) => a[i]));
259
+ }
260
+ return zipped;
261
+ },
262
+ [2, Infinity]
263
+ );
264
+
265
+ export const arrayChunk = new BuiltinFunction("chunk",
266
+ (args, interpreter, callNode) => {
267
+ const [arr, size] = args;
268
+ expectArray(arr, "chunk", interpreter, callNode, 1);
269
+ if (typeof size !== "number" || !Number.isInteger(size) || size <= 0) {
270
+ throw interpreter.errorHandler.createRuntimeError(
271
+ "chunk() size must be a positive integer.",
272
+ callNode,
273
+ "TYPE001",
274
+ "Provide a positive integer as chunk size."
275
+ );
276
+ }
277
+
278
+ const chunks = [];
279
+ for (let i = 0; i < arr.length; i += size) {
280
+ chunks.push(arr.slice(i, i + size));
281
+ }
282
+ return chunks;
283
+ },
284
+ 2
285
+ );