mimo-lang 1.1.1 → 2.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (165) hide show
  1. package/.gitattributes +24 -0
  2. package/LICENSE +21 -0
  3. package/README.md +71 -39
  4. package/adapters/browserAdapter.js +86 -0
  5. package/adapters/nodeAdapter.js +101 -0
  6. package/bin/cli.js +80 -0
  7. package/bin/commands/convert.js +27 -0
  8. package/bin/commands/doctor.js +139 -0
  9. package/bin/commands/eval.js +39 -0
  10. package/bin/commands/fmt.js +109 -0
  11. package/bin/commands/help.js +72 -0
  12. package/bin/commands/lint.js +117 -0
  13. package/bin/commands/repl.js +24 -0
  14. package/bin/commands/run.js +64 -0
  15. package/bin/commands/test.js +126 -0
  16. package/bin/utils/colors.js +38 -0
  17. package/bin/utils/formatError.js +47 -0
  18. package/bin/utils/fs.js +57 -0
  19. package/bin/utils/version.js +8 -0
  20. package/build.js +18 -0
  21. package/bun.lock +74 -0
  22. package/index.js +48 -77
  23. package/index.web.js +364 -0
  24. package/interpreter/BuiltinFunction.js +32 -0
  25. package/interpreter/ErrorHandler.js +120 -0
  26. package/interpreter/ExpressionEvaluator.js +106 -0
  27. package/interpreter/Interpreter.js +172 -0
  28. package/interpreter/MimoError.js +112 -0
  29. package/interpreter/ModuleLoader.js +236 -0
  30. package/interpreter/StatementExecutor.js +107 -0
  31. package/interpreter/Utils.js +82 -0
  32. package/interpreter/Values.js +87 -0
  33. package/interpreter/coreBuiltins.js +490 -0
  34. package/interpreter/environment.js +99 -0
  35. package/interpreter/evaluators/binaryExpressionEvaluator.js +111 -0
  36. package/interpreter/evaluators/collectionEvaluator.js +151 -0
  37. package/interpreter/evaluators/functionCallEvaluator.js +76 -0
  38. package/interpreter/evaluators/literalEvaluator.js +27 -0
  39. package/interpreter/evaluators/moduleAccessEvaluator.js +25 -0
  40. package/interpreter/evaluators/templateLiteralEvaluator.js +20 -0
  41. package/interpreter/executors/BaseExecutor.js +37 -0
  42. package/interpreter/executors/ControlFlowExecutor.js +206 -0
  43. package/interpreter/executors/FunctionExecutor.js +126 -0
  44. package/interpreter/executors/PatternMatchExecutor.js +93 -0
  45. package/interpreter/executors/VariableExecutor.js +144 -0
  46. package/interpreter/index.js +8 -0
  47. package/interpreter/stdlib/array/accessFunctions.js +61 -0
  48. package/interpreter/stdlib/array/arrayUtils.js +36 -0
  49. package/interpreter/stdlib/array/higherOrderFunctions.js +285 -0
  50. package/interpreter/stdlib/array/searchFunctions.js +77 -0
  51. package/interpreter/stdlib/array/setFunctions.js +49 -0
  52. package/interpreter/stdlib/array/transformationFunctions.js +68 -0
  53. package/interpreter/stdlib/array.js +85 -0
  54. package/interpreter/stdlib/assert.js +143 -0
  55. package/interpreter/stdlib/datetime.js +170 -0
  56. package/interpreter/stdlib/env.js +54 -0
  57. package/interpreter/stdlib/fs.js +161 -0
  58. package/interpreter/stdlib/http.js +92 -0
  59. package/interpreter/stdlib/json.js +70 -0
  60. package/interpreter/stdlib/math.js +309 -0
  61. package/interpreter/stdlib/object.js +142 -0
  62. package/interpreter/stdlib/path.js +69 -0
  63. package/interpreter/stdlib/regex.js +134 -0
  64. package/interpreter/stdlib/string.js +260 -0
  65. package/interpreter/suggestions.js +46 -0
  66. package/lexer/Lexer.js +245 -0
  67. package/lexer/TokenTypes.js +131 -0
  68. package/lexer/createToken.js +11 -0
  69. package/lexer/tokenizers/commentTokenizer.js +45 -0
  70. package/lexer/tokenizers/literalTokenizer.js +163 -0
  71. package/lexer/tokenizers/symbolTokenizer.js +69 -0
  72. package/lexer/tokenizers/whitespaceTokenizer.js +36 -0
  73. package/package.json +29 -13
  74. package/parser/ASTNodes.js +448 -0
  75. package/parser/Parser.js +188 -0
  76. package/parser/expressions/atomicExpressions.js +165 -0
  77. package/parser/expressions/conditionalExpressions.js +0 -0
  78. package/parser/expressions/operatorExpressions.js +79 -0
  79. package/parser/expressions/primaryExpressions.js +77 -0
  80. package/parser/parseStatement.js +184 -0
  81. package/parser/parserExpressions.js +115 -0
  82. package/parser/parserUtils.js +19 -0
  83. package/parser/statements/controlFlowParsers.js +106 -0
  84. package/parser/statements/functionParsers.js +314 -0
  85. package/parser/statements/moduleParsers.js +57 -0
  86. package/parser/statements/patternMatchParsers.js +124 -0
  87. package/parser/statements/variableParsers.js +155 -0
  88. package/repl.js +325 -0
  89. package/test.js +47 -0
  90. package/tools/PrettyPrinter.js +3 -0
  91. package/tools/convert/Args.js +46 -0
  92. package/tools/convert/Registry.js +91 -0
  93. package/tools/convert/Transpiler.js +78 -0
  94. package/tools/convert/plugins/README.md +66 -0
  95. package/tools/convert/plugins/alya/index.js +10 -0
  96. package/tools/convert/plugins/alya/to_alya.js +289 -0
  97. package/tools/convert/plugins/alya/visitors/expressions.js +257 -0
  98. package/tools/convert/plugins/alya/visitors/statements.js +403 -0
  99. package/tools/convert/plugins/base_converter.js +228 -0
  100. package/tools/convert/plugins/javascript/index.js +10 -0
  101. package/tools/convert/plugins/javascript/mimo_runtime.js +265 -0
  102. package/tools/convert/plugins/javascript/to_js.js +155 -0
  103. package/tools/convert/plugins/javascript/visitors/expressions.js +197 -0
  104. package/tools/convert/plugins/javascript/visitors/patterns.js +102 -0
  105. package/tools/convert/plugins/javascript/visitors/statements.js +236 -0
  106. package/tools/convert/plugins/python/index.js +10 -0
  107. package/tools/convert/plugins/python/mimo_runtime.py +811 -0
  108. package/tools/convert/plugins/python/to_py.js +329 -0
  109. package/tools/convert/plugins/python/visitors/expressions.js +272 -0
  110. package/tools/convert/plugins/python/visitors/patterns.js +100 -0
  111. package/tools/convert/plugins/python/visitors/statements.js +257 -0
  112. package/tools/convert.js +102 -0
  113. package/tools/format/CommentAttacher.js +190 -0
  114. package/tools/format/CommentLexer.js +152 -0
  115. package/tools/format/Printer.js +849 -0
  116. package/tools/format/config.js +107 -0
  117. package/tools/formatter.js +169 -0
  118. package/tools/lint/Linter.js +391 -0
  119. package/tools/lint/config.js +114 -0
  120. package/tools/lint/rules/consistent-return.js +62 -0
  121. package/tools/lint/rules/max-depth.js +56 -0
  122. package/tools/lint/rules/no-empty-function.js +45 -0
  123. package/tools/lint/rules/no-magic-numbers.js +46 -0
  124. package/tools/lint/rules/no-shadow.js +113 -0
  125. package/tools/lint/rules/no-unused-vars.js +26 -0
  126. package/tools/lint/rules/prefer-const.js +19 -0
  127. package/tools/linter.js +261 -0
  128. package/tools/replFormatter.js +93 -0
  129. package/tools/stamp-version.js +32 -0
  130. package/web/index.js +9 -0
  131. package/bun.lockb +0 -0
  132. package/cli.js +0 -84
  133. package/compiler/execute/interpreter.js +0 -68
  134. package/compiler/execute/interpreters/binary.js +0 -12
  135. package/compiler/execute/interpreters/call.js +0 -10
  136. package/compiler/execute/interpreters/if.js +0 -10
  137. package/compiler/execute/interpreters/try-catch.js +0 -10
  138. package/compiler/execute/interpreters/while.js +0 -8
  139. package/compiler/execute/utils/createfunction.js +0 -11
  140. package/compiler/execute/utils/evaluate.js +0 -20
  141. package/compiler/execute/utils/operate.js +0 -23
  142. package/compiler/lexer/processToken.js +0 -40
  143. package/compiler/lexer/tokenTypes.js +0 -4
  144. package/compiler/lexer/tokenizer.js +0 -74
  145. package/compiler/parser/expression/comparison.js +0 -18
  146. package/compiler/parser/expression/identifier.js +0 -29
  147. package/compiler/parser/expression/number.js +0 -10
  148. package/compiler/parser/expression/operator.js +0 -21
  149. package/compiler/parser/expression/punctuation.js +0 -31
  150. package/compiler/parser/expression/string.js +0 -6
  151. package/compiler/parser/parseExpression.js +0 -27
  152. package/compiler/parser/parseStatement.js +0 -34
  153. package/compiler/parser/parser.js +0 -45
  154. package/compiler/parser/statement/call.js +0 -26
  155. package/compiler/parser/statement/function.js +0 -29
  156. package/compiler/parser/statement/if.js +0 -34
  157. package/compiler/parser/statement/return.js +0 -10
  158. package/compiler/parser/statement/set.js +0 -11
  159. package/compiler/parser/statement/show.js +0 -10
  160. package/compiler/parser/statement/try-catch.js +0 -25
  161. package/compiler/parser/statement/while.js +0 -22
  162. package/converter/go/convert.js +0 -110
  163. package/converter/js/convert.js +0 -107
  164. package/jsconfig.json +0 -27
  165. package/vite.config.js +0 -17
@@ -0,0 +1,811 @@
1
+ """
2
+ The Mimo Python runtime.
3
+ Provides Python implementations for Mimo's built-ins and standard library.
4
+ """
5
+ import os
6
+ import sys
7
+ import re
8
+ import json
9
+ import math
10
+ import random
11
+ import datetime
12
+ import urllib.request
13
+ import urllib.error
14
+ from pathlib import Path
15
+ from typing import Any, List, Dict, Callable, Optional, Union
16
+
17
+
18
+ def is_equal(a: Any, b: Any) -> bool:
19
+ """Deep equality check for Mimo values."""
20
+ if a is b:
21
+ return True
22
+ if type(a) != type(b):
23
+ return False
24
+ if isinstance(a, (list, tuple)):
25
+ if len(a) != len(b):
26
+ return False
27
+ return all(is_equal(x, y) for x, y in zip(a, b))
28
+ if isinstance(a, dict):
29
+ if set(a.keys()) != set(b.keys()):
30
+ return False
31
+ return all(is_equal(a[k], b[k]) for k in a.keys())
32
+ return a == b
33
+
34
+
35
+ def stringify(value: Any) -> str:
36
+ """Convert a value to Mimo's string representation."""
37
+ if value is None:
38
+ return 'null'
39
+ if isinstance(value, bool):
40
+ return 'true' if value else 'false'
41
+ if isinstance(value, str):
42
+ return value
43
+ if isinstance(value, (list, tuple)):
44
+ elements = [stringify(v) for v in value]
45
+ return f"[{', '.join(elements)}]"
46
+ if isinstance(value, dict):
47
+ pairs = [f"{k}: {stringify(v)}" for k, v in value.items()]
48
+ return f"{{{', '.join(pairs)}}}"
49
+ if isinstance(value, datetime.datetime):
50
+ return f"datetime({value.isoformat()})"
51
+ return str(value)
52
+
53
+
54
+ class MimoRuntime:
55
+ """Main Mimo runtime class containing all built-ins and standard library modules."""
56
+
57
+ def __init__(self):
58
+ self.setup_stdlib()
59
+
60
+ # --- Core IO & Utils ---
61
+ def show(self, *args):
62
+ """Print values to stdout."""
63
+ output = ' '.join(stringify(arg) for arg in args)
64
+ print(output)
65
+
66
+ # --- Core Built-ins ---
67
+ def len(self, collection):
68
+ """Get length of a collection."""
69
+ if collection is None:
70
+ return 0
71
+ if hasattr(collection, '__len__'):
72
+ return len(collection)
73
+ if isinstance(collection, dict):
74
+ return len(collection.keys())
75
+ return 0
76
+
77
+ def add(self, a, b):
78
+ """Mimo + operator: concatenates if either operand is a string, otherwise adds."""
79
+ if isinstance(a, str) or isinstance(b, str):
80
+ return stringify(a) + stringify(b)
81
+ return a + b
82
+
83
+ def get(self, collection, key):
84
+ """Get value from collection by key, return None if not found."""
85
+ if collection is None:
86
+ return None
87
+ try:
88
+ if isinstance(collection, (list, tuple)):
89
+ return collection[key] if 0 <= key < len(collection) else None
90
+ elif isinstance(collection, dict):
91
+ return collection.get(key)
92
+ else:
93
+ return getattr(collection, key, None)
94
+ except (KeyError, IndexError, TypeError):
95
+ return None
96
+
97
+ def update(self, collection, key, value):
98
+ """Update collection at key with value."""
99
+ if isinstance(collection, list) and isinstance(key, int):
100
+ if 0 <= key < len(collection):
101
+ collection[key] = value
102
+ elif isinstance(collection, dict):
103
+ collection[key] = value
104
+ else:
105
+ setattr(collection, key, value)
106
+ return value
107
+
108
+ def type(self, value):
109
+ """Get Mimo type of value."""
110
+ if value is None:
111
+ return 'null'
112
+ elif isinstance(value, bool):
113
+ return 'boolean'
114
+ elif isinstance(value, (int, float)):
115
+ return 'number'
116
+ elif isinstance(value, str):
117
+ return 'string'
118
+ elif isinstance(value, (list, tuple)):
119
+ return 'array'
120
+ elif isinstance(value, dict):
121
+ return 'object'
122
+ elif callable(value):
123
+ return 'function'
124
+ else:
125
+ return 'object'
126
+
127
+ def push(self, array, value):
128
+ """Add value to end of array."""
129
+ if isinstance(array, list):
130
+ array.append(value)
131
+ return array
132
+
133
+ def pop(self, array):
134
+ """Remove and return last element from array."""
135
+ if isinstance(array, list) and len(array) > 0:
136
+ return array.pop()
137
+ return None
138
+
139
+ def range(self, *args):
140
+ """Generate range of numbers."""
141
+ if len(args) == 1:
142
+ return list(range(args[0]))
143
+ elif len(args) == 2:
144
+ return list(range(args[0], args[1]))
145
+ elif len(args) == 3:
146
+ return list(range(args[0], args[1], args[2]))
147
+ else:
148
+ return []
149
+
150
+ def join(self, array, separator):
151
+ """Join array elements with separator."""
152
+ if not isinstance(array, (list, tuple)):
153
+ return ""
154
+ return separator.join(stringify(item) for item in array)
155
+
156
+ def slice(self, collection, start, end=None):
157
+ """Slice a collection."""
158
+ if end is None:
159
+ return collection[start:]
160
+ return collection[start:end]
161
+
162
+ # --- Logical Operators ---
163
+ def eq(self, a, b):
164
+ return is_equal(a, b)
165
+
166
+ def neq(self, a, b):
167
+ return not is_equal(a, b)
168
+
169
+ def and_(self, a, b):
170
+ return a and b
171
+
172
+ def or_(self, a, b):
173
+ return a or b
174
+
175
+ # --- Utility functions ---
176
+ def has_property(self, obj, prop):
177
+ if isinstance(obj, dict):
178
+ return prop in obj
179
+ else:
180
+ return hasattr(obj, prop)
181
+
182
+ def get_property_safe(self, obj, prop):
183
+ """Safely get a property, returning None if not found."""
184
+ return self.get(obj, prop)
185
+
186
+ def keys(self, obj):
187
+ if isinstance(obj, dict):
188
+ return list(obj.keys())
189
+ return []
190
+
191
+ def values(self, obj):
192
+ if isinstance(obj, dict):
193
+ return list(obj.values())
194
+ return []
195
+
196
+ def entries(self, obj):
197
+ if isinstance(obj, dict):
198
+ return [[k, v] for k, v in obj.items()]
199
+ return []
200
+
201
+ def get_arguments(self):
202
+ return sys.argv[1:]
203
+
204
+ def get_env(self, name):
205
+ return os.environ.get(name)
206
+
207
+ def coalesce(self, *args):
208
+ """Return first non-None value."""
209
+ for arg in args:
210
+ if arg is not None:
211
+ return arg
212
+ return None
213
+
214
+ def if_else(self, condition, then_val, else_val):
215
+ """Ternary if_else built-in."""
216
+ return then_val if condition else else_val
217
+
218
+ def exit_code(self, code=0):
219
+ """Exit the program with the given code."""
220
+ sys.exit(code)
221
+
222
+ def setup_stdlib(self):
223
+ """Setup standard library modules."""
224
+
225
+ # File system module
226
+ class FSModule:
227
+ @staticmethod
228
+ def read_file(path: str) -> str:
229
+ try:
230
+ with open(path, 'r', encoding='utf-8') as f:
231
+ return f.read()
232
+ except Exception as e:
233
+ raise Exception(f"Failed to read file {path}: {str(e)}")
234
+
235
+ @staticmethod
236
+ def write_file(path: str, data: str) -> None:
237
+ try:
238
+ with open(path, 'w', encoding='utf-8') as f:
239
+ f.write(data)
240
+ except Exception as e:
241
+ raise Exception(f"Failed to write file {path}: {str(e)}")
242
+
243
+ @staticmethod
244
+ def exists(path: str) -> bool:
245
+ return Path(path).exists()
246
+
247
+ @staticmethod
248
+ def list_dir(path: str) -> List[str]:
249
+ try:
250
+ return list(os.listdir(path))
251
+ except Exception as e:
252
+ raise Exception(f"Failed to list directory {path}: {str(e)}")
253
+
254
+ @staticmethod
255
+ def make_dir(path: str, recursive: bool = False) -> None:
256
+ try:
257
+ if recursive:
258
+ Path(path).mkdir(parents=True, exist_ok=True)
259
+ else:
260
+ Path(path).mkdir(exist_ok=True)
261
+ except Exception as e:
262
+ raise Exception(f"Failed to create directory {path}: {str(e)}")
263
+
264
+ @staticmethod
265
+ def remove_file(path: str) -> None:
266
+ try:
267
+ Path(path).unlink()
268
+ except Exception as e:
269
+ raise Exception(f"Failed to remove file {path}: {str(e)}")
270
+
271
+ @staticmethod
272
+ def remove_dir(path: str) -> None:
273
+ try:
274
+ Path(path).rmdir()
275
+ except Exception as e:
276
+ raise Exception(f"Failed to remove directory {path}: {str(e)}")
277
+
278
+ # JSON module
279
+ class JSONModule:
280
+ @staticmethod
281
+ def parse(text: str):
282
+ try:
283
+ return json.loads(text)
284
+ except json.JSONDecodeError as e:
285
+ raise Exception(f"Failed to parse JSON: {str(e)}")
286
+
287
+ @staticmethod
288
+ def stringify(obj, indent: Optional[int] = None):
289
+ try:
290
+ return json.dumps(obj, indent=indent, ensure_ascii=False)
291
+ except Exception as e:
292
+ raise Exception(f"Failed to stringify JSON: {str(e)}")
293
+
294
+ # DateTime module
295
+ class DateTimeModule:
296
+ @staticmethod
297
+ def now():
298
+ return datetime.datetime.now()
299
+
300
+ @staticmethod
301
+ def get_timestamp(dt):
302
+ if isinstance(dt, datetime.datetime):
303
+ return int(dt.timestamp() * 1000)
304
+ return 0
305
+
306
+ @staticmethod
307
+ def from_timestamp(ts):
308
+ return datetime.datetime.fromtimestamp(ts / 1000)
309
+
310
+ @staticmethod
311
+ def to_iso_string(dt):
312
+ if isinstance(dt, datetime.datetime):
313
+ return dt.isoformat()
314
+ return ""
315
+
316
+ @staticmethod
317
+ def format(dt, fmt):
318
+ if not isinstance(dt, datetime.datetime):
319
+ return ""
320
+ result = fmt
321
+ result = result.replace('YYYY', str(dt.year))
322
+ result = result.replace('MM', f"{dt.month:02d}")
323
+ result = result.replace('DD', f"{dt.day:02d}")
324
+ result = result.replace('hh', f"{dt.hour:02d}")
325
+ result = result.replace('mm', f"{dt.minute:02d}")
326
+ result = result.replace('ss', f"{dt.second:02d}")
327
+ return result
328
+
329
+ # Math module
330
+ class MathModule:
331
+ PI = math.pi
332
+ E = math.e
333
+
334
+ def __getattr__(self, name):
335
+ if hasattr(math, name):
336
+ return getattr(math, name)
337
+ raise AttributeError(f"Math module has no attribute '{name}'")
338
+
339
+ # String module
340
+ class StringModule:
341
+ @staticmethod
342
+ def to_upper(s: str) -> str:
343
+ return s.upper()
344
+
345
+ @staticmethod
346
+ def to_lower(s: str) -> str:
347
+ return s.lower()
348
+
349
+ @staticmethod
350
+ def to_title_case(s: str) -> str:
351
+ return ' '.join(w.capitalize() for w in s.lower().split(' '))
352
+
353
+ @staticmethod
354
+ def capitalize(s: str) -> str:
355
+ return s[0].upper() + s[1:] if s else s
356
+
357
+ @staticmethod
358
+ def trim(s: str) -> str:
359
+ return s.strip()
360
+
361
+ @staticmethod
362
+ def trim_start(s: str) -> str:
363
+ return s.lstrip()
364
+
365
+ @staticmethod
366
+ def trim_end(s: str) -> str:
367
+ return s.rstrip()
368
+
369
+ @staticmethod
370
+ def pad_start(s: str, length: int, pad: str = ' ') -> str:
371
+ return s.rjust(length, pad)
372
+
373
+ @staticmethod
374
+ def pad_end(s: str, length: int, pad: str = ' ') -> str:
375
+ return s.ljust(length, pad)
376
+
377
+ @staticmethod
378
+ def contains(s: str, sub: str, pos: int = 0) -> bool:
379
+ return sub in s[pos:]
380
+
381
+ @staticmethod
382
+ def starts_with(s: str, sub: str, pos: int = 0) -> bool:
383
+ return s.startswith(sub, pos)
384
+
385
+ @staticmethod
386
+ def ends_with(s: str, sub: str, end: Optional[int] = None) -> bool:
387
+ return s.endswith(sub, 0, end)
388
+
389
+ @staticmethod
390
+ def index_of(s: str, sub: str, from_idx: int = 0) -> int:
391
+ try:
392
+ return s.index(sub, from_idx)
393
+ except ValueError:
394
+ return -1
395
+
396
+ @staticmethod
397
+ def last_index_of(s: str, sub: str, from_idx: Optional[int] = None) -> int:
398
+ try:
399
+ if from_idx is not None:
400
+ return s.rindex(sub, 0, from_idx + 1)
401
+ return s.rindex(sub)
402
+ except ValueError:
403
+ return -1
404
+
405
+ @staticmethod
406
+ def substring(s: str, start: int, end: Optional[int] = None) -> str:
407
+ return s[start:end]
408
+
409
+ @staticmethod
410
+ def slice(s: str, start: int, end: Optional[int] = None) -> str:
411
+ return s[start:end]
412
+
413
+ @staticmethod
414
+ def split(s: str, sep=None, limit: Optional[int] = None) -> List[str]:
415
+ if limit is not None:
416
+ return s.split(sep, limit)
417
+ return s.split(sep)
418
+
419
+ @staticmethod
420
+ def replace(s: str, find: str, rep: str) -> str:
421
+ return s.replace(find, rep, 1)
422
+
423
+ @staticmethod
424
+ def replace_all(s: str, find: str, rep: str) -> str:
425
+ return s.replace(find, rep)
426
+
427
+ @staticmethod
428
+ def repeat(s: str, n: int) -> str:
429
+ return s * n
430
+
431
+ @staticmethod
432
+ def char_at(s: str, i: int) -> str:
433
+ return s[i] if 0 <= i < len(s) else ''
434
+
435
+ @staticmethod
436
+ def is_empty(s: str) -> bool:
437
+ return len(s) == 0
438
+
439
+ @staticmethod
440
+ def is_blank(s: str) -> bool:
441
+ return len(s.strip()) == 0
442
+
443
+ # Array module
444
+ class ArrayModule:
445
+ @staticmethod
446
+ def map(array: List, callback: Callable) -> List:
447
+ return [callback(item) for item in array]
448
+
449
+ @staticmethod
450
+ def filter(array: List, callback: Callable) -> List:
451
+ return [item for item in array if callback(item)]
452
+
453
+ @staticmethod
454
+ def reduce(array: List, callback: Callable, initial=None):
455
+ if initial is not None:
456
+ result = initial
457
+ start = 0
458
+ else:
459
+ result = array[0] if array else None
460
+ start = 1
461
+ for i in range(start, len(array)):
462
+ result = callback(result, array[i])
463
+ return result
464
+
465
+ @staticmethod
466
+ def flat(array: List, depth: int = 1) -> List:
467
+ def _flat(arr, d):
468
+ result = []
469
+ for item in arr:
470
+ if isinstance(item, list) and d > 0:
471
+ result.extend(_flat(item, d - 1))
472
+ else:
473
+ result.append(item)
474
+ return result
475
+ return _flat(array, depth)
476
+
477
+ @staticmethod
478
+ def flat_map(array: List, callback: Callable) -> List:
479
+ result = []
480
+ for item in array:
481
+ mapped = callback(item)
482
+ if isinstance(mapped, list):
483
+ result.extend(mapped)
484
+ else:
485
+ result.append(mapped)
486
+ return result
487
+
488
+ @staticmethod
489
+ def group_by(array: List, callback: Callable) -> Dict:
490
+ result = {}
491
+ for item in array:
492
+ key = str(callback(item))
493
+ if key not in result:
494
+ result[key] = []
495
+ result[key].append(item)
496
+ return result
497
+
498
+ @staticmethod
499
+ def zip(*arrays) -> List:
500
+ return [list(group) for group in zip(*arrays)]
501
+
502
+ @staticmethod
503
+ def chunk(array: List, size: int) -> List:
504
+ return [array[i:i + size] for i in range(0, len(array), size)]
505
+
506
+ @staticmethod
507
+ def count(array: List, callback: Optional[Callable] = None) -> int:
508
+ if callback:
509
+ return sum(1 for item in array if callback(item))
510
+ return len(array)
511
+
512
+ @staticmethod
513
+ def for_each(array: List, callback: Callable) -> None:
514
+ for item in array:
515
+ callback(item)
516
+
517
+ @staticmethod
518
+ def find(array: List, callback: Callable):
519
+ for item in array:
520
+ if callback(item):
521
+ return item
522
+ return None
523
+
524
+ @staticmethod
525
+ def find_index(array: List, callback: Callable) -> int:
526
+ for i, item in enumerate(array):
527
+ if callback(item):
528
+ return i
529
+ return -1
530
+
531
+ @staticmethod
532
+ def includes(array: List, value) -> bool:
533
+ return value in array
534
+
535
+ @staticmethod
536
+ def index_of(array: List, value, from_idx: int = 0) -> int:
537
+ try:
538
+ return array.index(value, from_idx)
539
+ except ValueError:
540
+ return -1
541
+
542
+ @staticmethod
543
+ def last_index_of(array: List, value, from_idx: Optional[int] = None) -> int:
544
+ search = array[:from_idx + 1] if from_idx is not None else array
545
+ for i in range(len(search) - 1, -1, -1):
546
+ if search[i] == value:
547
+ return i
548
+ return -1
549
+
550
+ @staticmethod
551
+ def slice(array: List, start: int, end: Optional[int] = None) -> List:
552
+ return array[start:end]
553
+
554
+ @staticmethod
555
+ def first(array: List):
556
+ return array[0] if array else None
557
+
558
+ @staticmethod
559
+ def last(array: List):
560
+ return array[-1] if array else None
561
+
562
+ @staticmethod
563
+ def is_empty(array: List) -> bool:
564
+ return len(array) == 0
565
+
566
+ @staticmethod
567
+ def sort(array: List, callback: Optional[Callable] = None) -> List:
568
+ if callback:
569
+ import functools
570
+ return sorted(array, key=functools.cmp_to_key(callback))
571
+ return sorted(array)
572
+
573
+ @staticmethod
574
+ def reverse(array: List) -> List:
575
+ return list(reversed(array))
576
+
577
+ @staticmethod
578
+ def shuffle(array: List) -> List:
579
+ result = list(array)
580
+ random.shuffle(result)
581
+ return result
582
+
583
+ @staticmethod
584
+ def concat(*arrays) -> List:
585
+ result = []
586
+ for arr in arrays:
587
+ result.extend(arr)
588
+ return result
589
+
590
+ @staticmethod
591
+ def unique(array: List) -> List:
592
+ seen = []
593
+ result = []
594
+ for item in array:
595
+ if item not in seen:
596
+ seen.append(item)
597
+ result.append(item)
598
+ return result
599
+
600
+ @staticmethod
601
+ def intersection(a: List, b: List) -> List:
602
+ return [v for v in a if v in b]
603
+
604
+ @staticmethod
605
+ def union(a: List, b: List) -> List:
606
+ result = list(a)
607
+ for item in b:
608
+ if item not in result:
609
+ result.append(item)
610
+ return result
611
+
612
+ @staticmethod
613
+ def difference(a: List, b: List) -> List:
614
+ return [v for v in a if v not in b]
615
+
616
+ # Path module
617
+ class PathModule:
618
+ @staticmethod
619
+ def join(*parts: str) -> str:
620
+ return os.path.join(*parts)
621
+
622
+ @staticmethod
623
+ def dirname(p: str) -> str:
624
+ return os.path.dirname(p)
625
+
626
+ @staticmethod
627
+ def basename(p: str, ext: Optional[str] = None) -> str:
628
+ base = os.path.basename(p)
629
+ if ext and base.endswith(ext):
630
+ base = base[:-len(ext)]
631
+ return base
632
+
633
+ @staticmethod
634
+ def extname(p: str) -> str:
635
+ return os.path.splitext(p)[1]
636
+
637
+ # Env module
638
+ class EnvModule:
639
+ @staticmethod
640
+ def get(name: str, fallback=None):
641
+ return os.environ.get(name, fallback)
642
+
643
+ @staticmethod
644
+ def has(name: str) -> bool:
645
+ return name in os.environ
646
+
647
+ @staticmethod
648
+ def all() -> Dict[str, str]:
649
+ return dict(os.environ)
650
+
651
+ # Regex module
652
+ class RegexModule:
653
+ @staticmethod
654
+ def find_matches(pattern: str, text: str, flags: str = '') -> Optional[List[str]]:
655
+ flag_map = {'i': re.IGNORECASE, 'm': re.MULTILINE, 's': re.DOTALL}
656
+ re_flags = 0
657
+ for ch in flags.replace('g', ''):
658
+ re_flags |= flag_map.get(ch, 0)
659
+ matches = re.findall(pattern, text, re_flags)
660
+ return matches if matches else None
661
+
662
+ @staticmethod
663
+ def is_match(pattern: str, text: str, flags: str = '') -> bool:
664
+ flag_map = {'i': re.IGNORECASE, 'm': re.MULTILINE, 's': re.DOTALL}
665
+ re_flags = 0
666
+ for ch in flags:
667
+ re_flags |= flag_map.get(ch, 0)
668
+ return bool(re.search(pattern, text, re_flags))
669
+
670
+ @staticmethod
671
+ def replace_all(text: str, pattern: str, replacement: str, flags: str = '') -> str:
672
+ flag_map = {'i': re.IGNORECASE, 'm': re.MULTILINE, 's': re.DOTALL}
673
+ re_flags = 0
674
+ for ch in flags.replace('g', ''):
675
+ re_flags |= flag_map.get(ch, 0)
676
+ return re.sub(pattern, replacement, text, flags=re_flags)
677
+
678
+ @staticmethod
679
+ def extract(pattern: str, text: str, flags: str = '') -> Optional[List[str]]:
680
+ flag_map = {'i': re.IGNORECASE, 'm': re.MULTILINE, 's': re.DOTALL}
681
+ re_flags = 0
682
+ for ch in flags:
683
+ re_flags |= flag_map.get(ch, 0)
684
+ m = re.search(pattern, text, re_flags)
685
+ if m is None:
686
+ return None
687
+ return [m.group(0)] + list(m.groups())
688
+
689
+ # HTTP module
690
+ class HTTPModule:
691
+ @staticmethod
692
+ def get(url: str) -> str:
693
+ try:
694
+ with urllib.request.urlopen(url) as response:
695
+ return response.read().decode('utf-8')
696
+ except urllib.error.URLError as e:
697
+ raise Exception(f"HTTP GET request failed: {str(e)}")
698
+
699
+ @staticmethod
700
+ def post(url: str, body: str, headers: Optional[Dict] = None) -> str:
701
+ req_headers = {'Content-Type': 'application/json'}
702
+ if headers:
703
+ req_headers.update(headers)
704
+ data = body.encode('utf-8')
705
+ req = urllib.request.Request(url, data=data, headers=req_headers, method='POST')
706
+ try:
707
+ with urllib.request.urlopen(req) as response:
708
+ return response.read().decode('utf-8')
709
+ except urllib.error.URLError as e:
710
+ raise Exception(f"HTTP POST request failed: {str(e)}")
711
+
712
+ # Object module
713
+ class ObjectModule:
714
+ @staticmethod
715
+ def merge(*objs: Dict) -> Dict:
716
+ result = {}
717
+ for obj in objs:
718
+ result.update(obj)
719
+ return result
720
+
721
+ @staticmethod
722
+ def pick(obj: Dict, keys: List[str]) -> Dict:
723
+ return {k: obj[k] for k in keys if k in obj}
724
+
725
+ @staticmethod
726
+ def omit(obj: Dict, keys: List[str]) -> Dict:
727
+ excluded = set(keys)
728
+ return {k: v for k, v in obj.items() if k not in excluded}
729
+
730
+ @staticmethod
731
+ def map_values(obj: Dict, callback: Callable) -> Dict:
732
+ return {k: callback(v, k, obj) for k, v in obj.items()}
733
+
734
+ @staticmethod
735
+ def from_entries(entries: List) -> Dict:
736
+ return {str(entry[0]): entry[1] for entry in entries}
737
+
738
+ @staticmethod
739
+ def is_empty(obj: Dict) -> bool:
740
+ return len(obj) == 0
741
+
742
+ @staticmethod
743
+ def keys(obj: Dict) -> List[str]:
744
+ return list(obj.keys())
745
+
746
+ @staticmethod
747
+ def values(obj: Dict) -> List:
748
+ return list(obj.values())
749
+
750
+ @staticmethod
751
+ def entries(obj: Dict) -> List:
752
+ return [[k, v] for k, v in obj.items()]
753
+
754
+ # Assert module
755
+ class AssertModule:
756
+ @staticmethod
757
+ def eq(actual, expected, message: Optional[str] = None) -> bool:
758
+ if not is_equal(actual, expected):
759
+ msg = f": {message}" if message else ""
760
+ raise AssertionError(
761
+ f"Assertion Failed{msg}.\n Expected: {json.dumps(expected)}\n Actual: {json.dumps(actual)}"
762
+ )
763
+ return True
764
+
765
+ @staticmethod
766
+ def neq(actual, expected, message: Optional[str] = None) -> bool:
767
+ if is_equal(actual, expected):
768
+ msg = f": {message}" if message else ""
769
+ raise AssertionError(f"Assertion Failed{msg}. Expected values to be different.")
770
+ return True
771
+
772
+ @staticmethod
773
+ def true(condition, message: Optional[str] = None) -> bool:
774
+ if condition is not True:
775
+ msg = f": {message}" if message else ""
776
+ raise AssertionError(f"Assertion Failed{msg}. Expected true, got {condition!r}")
777
+ return True
778
+
779
+ @staticmethod
780
+ def false(condition, message: Optional[str] = None) -> bool:
781
+ if condition is not False:
782
+ msg = f": {message}" if message else ""
783
+ raise AssertionError(f"Assertion Failed{msg}. Expected false, got {condition!r}")
784
+ return True
785
+
786
+ @staticmethod
787
+ def throws(fn: Callable, message: Optional[str] = None) -> bool:
788
+ try:
789
+ fn()
790
+ except Exception:
791
+ return True
792
+ msg = f": {message}" if message else ""
793
+ raise AssertionError(f"Assertion Failed{msg}. Expected function to throw, but it did not.")
794
+
795
+ # Assign all modules
796
+ self.fs = FSModule()
797
+ self.json = JSONModule()
798
+ self.datetime = DateTimeModule()
799
+ self.math = MathModule()
800
+ self.string = StringModule()
801
+ self.array = ArrayModule()
802
+ self.path = PathModule()
803
+ self.env = EnvModule()
804
+ self.regex = RegexModule()
805
+ self.http = HTTPModule()
806
+ self.object = ObjectModule()
807
+ self.assert_ = AssertModule()
808
+
809
+
810
+ # Create global mimo instance
811
+ mimo = MimoRuntime()