zexus 1.6.2

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 (227) hide show
  1. package/LICENSE +0 -0
  2. package/README.md +2513 -0
  3. package/bin/zexus +2 -0
  4. package/bin/zpics +2 -0
  5. package/bin/zpm +2 -0
  6. package/bin/zx +2 -0
  7. package/bin/zx-deploy +2 -0
  8. package/bin/zx-dev +2 -0
  9. package/bin/zx-run +2 -0
  10. package/package.json +66 -0
  11. package/scripts/README.md +24 -0
  12. package/scripts/postinstall.js +44 -0
  13. package/shared_config.json +24 -0
  14. package/src/README.md +1525 -0
  15. package/src/tests/run_zexus_tests.py +117 -0
  16. package/src/tests/test_all_phases.zx +346 -0
  17. package/src/tests/test_blockchain_features.zx +306 -0
  18. package/src/tests/test_complexity_features.zx +321 -0
  19. package/src/tests/test_core_integration.py +185 -0
  20. package/src/tests/test_phase10_ecosystem.zx +177 -0
  21. package/src/tests/test_phase1_modifiers.zx +87 -0
  22. package/src/tests/test_phase2_plugins.zx +80 -0
  23. package/src/tests/test_phase3_security.zx +97 -0
  24. package/src/tests/test_phase4_vfs.zx +116 -0
  25. package/src/tests/test_phase5_types.zx +117 -0
  26. package/src/tests/test_phase6_metaprogramming.zx +125 -0
  27. package/src/tests/test_phase7_optimization.zx +132 -0
  28. package/src/tests/test_phase9_advanced_types.zx +157 -0
  29. package/src/tests/test_security_features.py +419 -0
  30. package/src/tests/test_security_features.zx +276 -0
  31. package/src/tests/test_simple_zx.zx +1 -0
  32. package/src/tests/test_verification_simple.zx +69 -0
  33. package/src/zexus/__init__.py +28 -0
  34. package/src/zexus/__main__.py +5 -0
  35. package/src/zexus/__pycache__/__init__.cpython-312.pyc +0 -0
  36. package/src/zexus/__pycache__/advanced_types.cpython-312.pyc +0 -0
  37. package/src/zexus/__pycache__/builtin_modules.cpython-312.pyc +0 -0
  38. package/src/zexus/__pycache__/capability_system.cpython-312.pyc +0 -0
  39. package/src/zexus/__pycache__/complexity_system.cpython-312.pyc +0 -0
  40. package/src/zexus/__pycache__/concurrency_system.cpython-312.pyc +0 -0
  41. package/src/zexus/__pycache__/config.cpython-312.pyc +0 -0
  42. package/src/zexus/__pycache__/dependency_injection.cpython-312.pyc +0 -0
  43. package/src/zexus/__pycache__/ecosystem.cpython-312.pyc +0 -0
  44. package/src/zexus/__pycache__/environment.cpython-312.pyc +0 -0
  45. package/src/zexus/__pycache__/error_reporter.cpython-312.pyc +0 -0
  46. package/src/zexus/__pycache__/hybrid_orchestrator.cpython-312.pyc +0 -0
  47. package/src/zexus/__pycache__/lexer.cpython-312.pyc +0 -0
  48. package/src/zexus/__pycache__/metaprogramming.cpython-312.pyc +0 -0
  49. package/src/zexus/__pycache__/module_cache.cpython-312.pyc +0 -0
  50. package/src/zexus/__pycache__/object.cpython-312.pyc +0 -0
  51. package/src/zexus/__pycache__/optimization.cpython-312.pyc +0 -0
  52. package/src/zexus/__pycache__/plugin_system.cpython-312.pyc +0 -0
  53. package/src/zexus/__pycache__/policy_engine.cpython-312.pyc +0 -0
  54. package/src/zexus/__pycache__/security.cpython-312.pyc +0 -0
  55. package/src/zexus/__pycache__/stdlib_integration.cpython-312.pyc +0 -0
  56. package/src/zexus/__pycache__/strategy_recovery.cpython-312.pyc +0 -0
  57. package/src/zexus/__pycache__/syntax_validator.cpython-312.pyc +0 -0
  58. package/src/zexus/__pycache__/type_system.cpython-312.pyc +0 -0
  59. package/src/zexus/__pycache__/virtual_filesystem.cpython-312.pyc +0 -0
  60. package/src/zexus/__pycache__/zexus_ast.cpython-312.pyc +0 -0
  61. package/src/zexus/__pycache__/zexus_token.cpython-312.pyc +0 -0
  62. package/src/zexus/advanced_types.py +401 -0
  63. package/src/zexus/blockchain/__init__.py +40 -0
  64. package/src/zexus/blockchain/__pycache__/__init__.cpython-312.pyc +0 -0
  65. package/src/zexus/blockchain/__pycache__/crypto.cpython-312.pyc +0 -0
  66. package/src/zexus/blockchain/__pycache__/ledger.cpython-312.pyc +0 -0
  67. package/src/zexus/blockchain/__pycache__/transaction.cpython-312.pyc +0 -0
  68. package/src/zexus/blockchain/crypto.py +463 -0
  69. package/src/zexus/blockchain/ledger.py +255 -0
  70. package/src/zexus/blockchain/transaction.py +267 -0
  71. package/src/zexus/builtin_modules.py +284 -0
  72. package/src/zexus/builtin_plugins.py +317 -0
  73. package/src/zexus/capability_system.py +372 -0
  74. package/src/zexus/cli/__init__.py +2 -0
  75. package/src/zexus/cli/__pycache__/__init__.cpython-312.pyc +0 -0
  76. package/src/zexus/cli/__pycache__/main.cpython-312.pyc +0 -0
  77. package/src/zexus/cli/main.py +707 -0
  78. package/src/zexus/cli/zpm.py +203 -0
  79. package/src/zexus/compare_interpreter_compiler.py +146 -0
  80. package/src/zexus/compiler/__init__.py +169 -0
  81. package/src/zexus/compiler/__pycache__/__init__.cpython-312.pyc +0 -0
  82. package/src/zexus/compiler/__pycache__/lexer.cpython-312.pyc +0 -0
  83. package/src/zexus/compiler/__pycache__/parser.cpython-312.pyc +0 -0
  84. package/src/zexus/compiler/__pycache__/zexus_ast.cpython-312.pyc +0 -0
  85. package/src/zexus/compiler/bytecode.py +266 -0
  86. package/src/zexus/compiler/compat_runtime.py +277 -0
  87. package/src/zexus/compiler/lexer.py +257 -0
  88. package/src/zexus/compiler/parser.py +779 -0
  89. package/src/zexus/compiler/semantic.py +118 -0
  90. package/src/zexus/compiler/zexus_ast.py +454 -0
  91. package/src/zexus/complexity_system.py +575 -0
  92. package/src/zexus/concurrency_system.py +493 -0
  93. package/src/zexus/config.py +201 -0
  94. package/src/zexus/crypto_bridge.py +19 -0
  95. package/src/zexus/dependency_injection.py +423 -0
  96. package/src/zexus/ecosystem.py +434 -0
  97. package/src/zexus/environment.py +101 -0
  98. package/src/zexus/environment_manager.py +119 -0
  99. package/src/zexus/error_reporter.py +314 -0
  100. package/src/zexus/evaluator/__init__.py +12 -0
  101. package/src/zexus/evaluator/__pycache__/__init__.cpython-312.pyc +0 -0
  102. package/src/zexus/evaluator/__pycache__/bytecode_compiler.cpython-312.pyc +0 -0
  103. package/src/zexus/evaluator/__pycache__/core.cpython-312.pyc +0 -0
  104. package/src/zexus/evaluator/__pycache__/expressions.cpython-312.pyc +0 -0
  105. package/src/zexus/evaluator/__pycache__/functions.cpython-312.pyc +0 -0
  106. package/src/zexus/evaluator/__pycache__/integration.cpython-312.pyc +0 -0
  107. package/src/zexus/evaluator/__pycache__/statements.cpython-312.pyc +0 -0
  108. package/src/zexus/evaluator/__pycache__/utils.cpython-312.pyc +0 -0
  109. package/src/zexus/evaluator/bytecode_compiler.py +700 -0
  110. package/src/zexus/evaluator/core.py +891 -0
  111. package/src/zexus/evaluator/expressions.py +827 -0
  112. package/src/zexus/evaluator/functions.py +3989 -0
  113. package/src/zexus/evaluator/integration.py +396 -0
  114. package/src/zexus/evaluator/statements.py +4303 -0
  115. package/src/zexus/evaluator/utils.py +126 -0
  116. package/src/zexus/evaluator_original.py +2041 -0
  117. package/src/zexus/external_bridge.py +16 -0
  118. package/src/zexus/find_affected_imports.sh +155 -0
  119. package/src/zexus/hybrid_orchestrator.py +152 -0
  120. package/src/zexus/input_validation.py +259 -0
  121. package/src/zexus/lexer.py +571 -0
  122. package/src/zexus/logging.py +89 -0
  123. package/src/zexus/lsp/__init__.py +9 -0
  124. package/src/zexus/lsp/completion_provider.py +207 -0
  125. package/src/zexus/lsp/definition_provider.py +22 -0
  126. package/src/zexus/lsp/hover_provider.py +71 -0
  127. package/src/zexus/lsp/server.py +269 -0
  128. package/src/zexus/lsp/symbol_provider.py +31 -0
  129. package/src/zexus/metaprogramming.py +321 -0
  130. package/src/zexus/module_cache.py +89 -0
  131. package/src/zexus/module_manager.py +107 -0
  132. package/src/zexus/object.py +973 -0
  133. package/src/zexus/optimization.py +424 -0
  134. package/src/zexus/parser/__init__.py +31 -0
  135. package/src/zexus/parser/__pycache__/__init__.cpython-312.pyc +0 -0
  136. package/src/zexus/parser/__pycache__/parser.cpython-312.pyc +0 -0
  137. package/src/zexus/parser/__pycache__/strategy_context.cpython-312.pyc +0 -0
  138. package/src/zexus/parser/__pycache__/strategy_structural.cpython-312.pyc +0 -0
  139. package/src/zexus/parser/integration.py +86 -0
  140. package/src/zexus/parser/parser.py +3977 -0
  141. package/src/zexus/parser/strategy_context.py +7254 -0
  142. package/src/zexus/parser/strategy_structural.py +1033 -0
  143. package/src/zexus/persistence.py +391 -0
  144. package/src/zexus/plugin_system.py +290 -0
  145. package/src/zexus/policy_engine.py +365 -0
  146. package/src/zexus/profiler/__init__.py +5 -0
  147. package/src/zexus/profiler/profiler.py +233 -0
  148. package/src/zexus/purity_system.py +398 -0
  149. package/src/zexus/runtime/__init__.py +20 -0
  150. package/src/zexus/runtime/async_runtime.py +324 -0
  151. package/src/zexus/search_old_imports.sh +65 -0
  152. package/src/zexus/security.py +1407 -0
  153. package/src/zexus/stack_trace.py +233 -0
  154. package/src/zexus/stdlib/__init__.py +27 -0
  155. package/src/zexus/stdlib/blockchain.py +341 -0
  156. package/src/zexus/stdlib/compression.py +167 -0
  157. package/src/zexus/stdlib/crypto.py +124 -0
  158. package/src/zexus/stdlib/datetime.py +163 -0
  159. package/src/zexus/stdlib/db_mongo.py +199 -0
  160. package/src/zexus/stdlib/db_mysql.py +162 -0
  161. package/src/zexus/stdlib/db_postgres.py +163 -0
  162. package/src/zexus/stdlib/db_sqlite.py +133 -0
  163. package/src/zexus/stdlib/encoding.py +230 -0
  164. package/src/zexus/stdlib/fs.py +195 -0
  165. package/src/zexus/stdlib/http.py +219 -0
  166. package/src/zexus/stdlib/http_server.py +248 -0
  167. package/src/zexus/stdlib/json_module.py +61 -0
  168. package/src/zexus/stdlib/math.py +360 -0
  169. package/src/zexus/stdlib/os_module.py +265 -0
  170. package/src/zexus/stdlib/regex.py +148 -0
  171. package/src/zexus/stdlib/sockets.py +253 -0
  172. package/src/zexus/stdlib/test_framework.zx +208 -0
  173. package/src/zexus/stdlib/test_runner.zx +119 -0
  174. package/src/zexus/stdlib_integration.py +341 -0
  175. package/src/zexus/strategy_recovery.py +256 -0
  176. package/src/zexus/syntax_validator.py +356 -0
  177. package/src/zexus/testing/zpics.py +407 -0
  178. package/src/zexus/testing/zpics_runtime.py +369 -0
  179. package/src/zexus/type_system.py +374 -0
  180. package/src/zexus/validation_system.py +569 -0
  181. package/src/zexus/virtual_filesystem.py +355 -0
  182. package/src/zexus/vm/__init__.py +8 -0
  183. package/src/zexus/vm/__pycache__/__init__.cpython-312.pyc +0 -0
  184. package/src/zexus/vm/__pycache__/async_optimizer.cpython-312.pyc +0 -0
  185. package/src/zexus/vm/__pycache__/bytecode.cpython-312.pyc +0 -0
  186. package/src/zexus/vm/__pycache__/cache.cpython-312.pyc +0 -0
  187. package/src/zexus/vm/__pycache__/jit.cpython-312.pyc +0 -0
  188. package/src/zexus/vm/__pycache__/memory_manager.cpython-312.pyc +0 -0
  189. package/src/zexus/vm/__pycache__/memory_pool.cpython-312.pyc +0 -0
  190. package/src/zexus/vm/__pycache__/optimizer.cpython-312.pyc +0 -0
  191. package/src/zexus/vm/__pycache__/parallel_vm.cpython-312.pyc +0 -0
  192. package/src/zexus/vm/__pycache__/peephole_optimizer.cpython-312.pyc +0 -0
  193. package/src/zexus/vm/__pycache__/profiler.cpython-312.pyc +0 -0
  194. package/src/zexus/vm/__pycache__/register_allocator.cpython-312.pyc +0 -0
  195. package/src/zexus/vm/__pycache__/register_vm.cpython-312.pyc +0 -0
  196. package/src/zexus/vm/__pycache__/ssa_converter.cpython-312.pyc +0 -0
  197. package/src/zexus/vm/__pycache__/vm.cpython-312.pyc +0 -0
  198. package/src/zexus/vm/async_optimizer.py +420 -0
  199. package/src/zexus/vm/bytecode.py +428 -0
  200. package/src/zexus/vm/bytecode_converter.py +297 -0
  201. package/src/zexus/vm/cache.py +532 -0
  202. package/src/zexus/vm/jit.py +720 -0
  203. package/src/zexus/vm/memory_manager.py +520 -0
  204. package/src/zexus/vm/memory_pool.py +511 -0
  205. package/src/zexus/vm/optimizer.py +478 -0
  206. package/src/zexus/vm/parallel_vm.py +899 -0
  207. package/src/zexus/vm/peephole_optimizer.py +452 -0
  208. package/src/zexus/vm/profiler.py +527 -0
  209. package/src/zexus/vm/register_allocator.py +462 -0
  210. package/src/zexus/vm/register_vm.py +520 -0
  211. package/src/zexus/vm/ssa_converter.py +757 -0
  212. package/src/zexus/vm/vm.py +1392 -0
  213. package/src/zexus/zexus_ast.py +1782 -0
  214. package/src/zexus/zexus_token.py +253 -0
  215. package/src/zexus/zpm/__init__.py +15 -0
  216. package/src/zexus/zpm/installer.py +116 -0
  217. package/src/zexus/zpm/package_manager.py +208 -0
  218. package/src/zexus/zpm/publisher.py +98 -0
  219. package/src/zexus/zpm/registry.py +110 -0
  220. package/src/zexus.egg-info/PKG-INFO +2235 -0
  221. package/src/zexus.egg-info/SOURCES.txt +876 -0
  222. package/src/zexus.egg-info/dependency_links.txt +1 -0
  223. package/src/zexus.egg-info/entry_points.txt +3 -0
  224. package/src/zexus.egg-info/not-zip-safe +1 -0
  225. package/src/zexus.egg-info/requires.txt +14 -0
  226. package/src/zexus.egg-info/top_level.txt +2 -0
  227. package/zexus.json +14 -0
@@ -0,0 +1,3989 @@
1
+ # src/zexus/evaluator/functions.py
2
+ import sys
3
+ import os
4
+
5
+ from .. import zexus_ast
6
+ from ..zexus_ast import CallExpression, MethodCallExpression
7
+ from ..object import (
8
+ Environment, Integer, Float, String, List, Map, Boolean as BooleanObj,
9
+ Null, Builtin, Action, LambdaFunction, ReturnValue, DateTime, Math, File, Debug,
10
+ EvaluationError, EntityDefinition
11
+ )
12
+ from .utils import is_error, debug_log, NULL, TRUE, FALSE, _resolve_awaitable, _zexus_to_python, _python_to_zexus, _to_str
13
+
14
+ # Try to import backend, handle failure gracefully (as per your original code)
15
+ try:
16
+ from renderer import backend as _BACKEND
17
+ _BACKEND_AVAILABLE = True
18
+ except Exception:
19
+ _BACKEND_AVAILABLE = False
20
+ _BACKEND = None
21
+
22
+ class FunctionEvaluatorMixin:
23
+ """Handles function application, method calls, and defines all builtins."""
24
+
25
+ def __init__(self):
26
+ # Initialize registries
27
+ self.builtins = {}
28
+
29
+ # Renderer Registry (moved from global scope to instance scope)
30
+ self.render_registry = {
31
+ 'screens': {},
32
+ 'components': {},
33
+ 'themes': {},
34
+ 'canvases': {},
35
+ 'current_theme': None
36
+ }
37
+
38
+ # Register all functions
39
+ self._register_core_builtins()
40
+ self._register_main_entry_point_builtins()
41
+ self._register_renderer_builtins()
42
+
43
+ def eval_call_expression(self, node, env, stack_trace):
44
+ debug_log("🚀 CallExpression node", f"Calling {node.function}")
45
+
46
+ fn = self.eval_node(node.function, env, stack_trace)
47
+ if is_error(fn):
48
+ return fn
49
+
50
+ # Check if this is a generic type instantiation: Box<number>(42)
51
+ type_args = getattr(node, 'type_args', [])
52
+ if type_args and hasattr(fn, 'is_generic') and fn.is_generic:
53
+ debug_log(" Generic type instantiation", f"Type args: {type_args}")
54
+
55
+ # Create specialized constructor for this type combination
56
+ template = fn.generic_template
57
+ specialized_constructor = self._create_specialized_generic_constructor(
58
+ template, type_args, env, stack_trace
59
+ )
60
+
61
+ if is_error(specialized_constructor):
62
+ return specialized_constructor
63
+
64
+ # Now call the specialized constructor with the actual arguments
65
+ fn = specialized_constructor
66
+
67
+ # Check if arguments contain keyword arguments (AssignmentExpression nodes)
68
+ # This handles syntax like: Person(name="Bob", age=25)
69
+ from .. import zexus_ast
70
+ has_keyword_args = any(isinstance(arg, zexus_ast.AssignmentExpression) for arg in node.arguments)
71
+
72
+ if has_keyword_args:
73
+ # Process keyword arguments - build a dict of name->value
74
+ kwargs = {}
75
+ for arg in node.arguments:
76
+ if isinstance(arg, zexus_ast.AssignmentExpression):
77
+ # This is a keyword argument: name=value
78
+ param_name = arg.name.value if hasattr(arg.name, 'value') else str(arg.name)
79
+ param_value = self.eval_node(arg.value, env, stack_trace)
80
+ if is_error(param_value):
81
+ return param_value
82
+ kwargs[param_name] = param_value
83
+ else:
84
+ # Mixed positional and keyword args not supported yet
85
+ return EvaluationError("Cannot mix positional and keyword arguments")
86
+
87
+ # For entity constructors, pass kwargs as-is
88
+ from ..security import EntityDefinition as SecurityEntityDef
89
+ if isinstance(fn, SecurityEntityDef):
90
+ return fn.create_instance(kwargs)
91
+
92
+ # For other functions, would need to map kwargs to positional args
93
+ # Not implemented yet
94
+ return EvaluationError("Keyword arguments only supported for entity constructors currently")
95
+
96
+ # Regular positional arguments
97
+ args = self.eval_expressions(node.arguments, env)
98
+ if is_error(args):
99
+ return args
100
+
101
+ arg_count = len(args) if isinstance(args, (list, tuple)) else "unknown"
102
+ debug_log(" Arguments evaluated", f"{args} (count: {arg_count})")
103
+
104
+ # Contract instantiation check
105
+ from ..security import SmartContract
106
+ if isinstance(fn, SmartContract):
107
+ return fn.instantiate(args)
108
+
109
+ return self.apply_function(fn, args, env)
110
+
111
+ def _create_specialized_generic_constructor(self, template, type_args, env, stack_trace):
112
+ """Create a specialized constructor for a generic type with concrete type arguments
113
+
114
+ Example: Box<number> creates a specialized Box constructor with T = number
115
+ """
116
+ from ..object import EvaluationError, String, Map
117
+ from .. import zexus_ast
118
+
119
+ debug_log("_create_specialized_generic_constructor", f"Specializing with types: {type_args}")
120
+
121
+ type_params = template['type_params']
122
+
123
+ if len(type_args) != len(type_params):
124
+ return EvaluationError(
125
+ f"Generic type requires {len(type_params)} type argument(s), got {len(type_args)}"
126
+ )
127
+
128
+ # Create specialized type name
129
+ specialized_type_name = f"{template['type_name']}<{', '.join(type_args)}>"
130
+
131
+ # Check if we've already created this specialization (cache it)
132
+ existing = template['env'].get(specialized_type_name)
133
+ if existing and not is_error(existing):
134
+ debug_log(" Using cached specialization", specialized_type_name)
135
+ return existing
136
+
137
+ # Create type substitution map: T -> number, U -> string, etc.
138
+ type_subst = dict(zip(type_params, type_args))
139
+ debug_log(" Type substitution map", str(type_subst))
140
+
141
+ # Create a specialized version of the fields with type substitution
142
+ specialized_fields = []
143
+ for field in template['fields']:
144
+ # Create a copy of the field with substituted type
145
+ field_type = field.field_type
146
+
147
+ # If field type is a type parameter, substitute it
148
+ if field_type in type_subst:
149
+ field_type = type_subst[field_type]
150
+ debug_log(f" Substituted field type", f"{field.name}: {field_type}")
151
+
152
+ # Create new field with substituted type
153
+ specialized_field = zexus_ast.DataField(
154
+ name=field.name,
155
+ field_type=field_type,
156
+ default_value=field.default_value,
157
+ constraint=field.constraint,
158
+ computed=field.computed,
159
+ method_body=field.method_body,
160
+ method_params=field.method_params,
161
+ operator=field.operator,
162
+ decorators=field.decorators
163
+ )
164
+ specialized_fields.append(specialized_field)
165
+
166
+ # Create a specialized DataStatement node
167
+ specialized_node = zexus_ast.DataStatement(
168
+ name=zexus_ast.Identifier(specialized_type_name),
169
+ fields=specialized_fields,
170
+ modifiers=template['modifiers'],
171
+ parent=template['parent_type'],
172
+ decorators=template['decorators'],
173
+ type_params=[] # No longer generic after specialization
174
+ )
175
+
176
+ # Evaluate the specialized data statement to create the constructor
177
+ # This will register it in the environment
178
+ evaluator = template['evaluator']
179
+ result = evaluator.eval_data_statement(
180
+ specialized_node,
181
+ template['env'],
182
+ template['stack_trace']
183
+ )
184
+
185
+ if is_error(result):
186
+ return result
187
+
188
+ # Get the constructor from the environment (it was registered by eval_data_statement)
189
+ constructor = template['env'].get(specialized_type_name)
190
+
191
+ if constructor is None:
192
+ return EvaluationError(f"Failed to create specialized constructor for {specialized_type_name}")
193
+
194
+ return constructor
195
+
196
+ def apply_function(self, fn, args, env=None):
197
+ debug_log("apply_function", f"Calling {fn}")
198
+
199
+ # Phase 2 & 3: Trigger plugin hooks and check capabilities
200
+ if hasattr(self, 'integration_context'):
201
+ if isinstance(fn, (Action, LambdaFunction)):
202
+ func_name = fn.name if hasattr(fn, 'name') else str(fn)
203
+ # Trigger before-call hook
204
+ self.integration_context.plugins.before_action_call(func_name, {})
205
+
206
+ # Check required capabilities
207
+ try:
208
+ self.integration_context.capabilities.require_capability("core.language")
209
+ except PermissionError:
210
+ return EvaluationError(f"Permission denied: insufficient capabilities for {func_name}")
211
+
212
+ if isinstance(fn, (Action, LambdaFunction)):
213
+ debug_log(" Calling user-defined function")
214
+
215
+ # Check if this is an async action
216
+ is_async = getattr(fn, 'is_async', False)
217
+
218
+ if is_async:
219
+ # Create a coroutine that lazily executes the async action
220
+ from ..object import Coroutine
221
+ import sys
222
+
223
+ # print(f"[ASYNC CREATE] Creating coroutine for async action, fn.env has keys: {list(fn.env.store.keys()) if hasattr(fn.env, 'store') else 'N/A'}", file=sys.stderr)
224
+
225
+ def async_generator():
226
+ """Generator that executes the async action body"""
227
+ new_env = Environment(outer=fn.env)
228
+
229
+ # Bind parameters
230
+ for i, param in enumerate(fn.parameters):
231
+ if i < len(args):
232
+ param_name = param.value if hasattr(param, 'value') else str(param)
233
+ new_env.set(param_name, args[i])
234
+
235
+ # Yield control first (makes it a true generator)
236
+ yield None
237
+
238
+ try:
239
+ # Evaluate the function body
240
+ res = self.eval_node(fn.body, new_env)
241
+
242
+ # Unwrap ReturnValue if needed
243
+ if isinstance(res, ReturnValue):
244
+ result = res.value
245
+ else:
246
+ result = res
247
+
248
+ # Execute deferred cleanup
249
+ if hasattr(self, '_execute_deferred_cleanup'):
250
+ self._execute_deferred_cleanup(new_env, [])
251
+
252
+ # Return the result (will be caught by StopIteration)
253
+ return result
254
+ except Exception as e:
255
+ # Re-raise exception to be caught by coroutine
256
+ raise e
257
+
258
+ # Create and return coroutine
259
+ gen = async_generator()
260
+ coroutine = Coroutine(gen, fn)
261
+ return coroutine
262
+
263
+ # Synchronous function execution
264
+ new_env = Environment(outer=fn.env)
265
+
266
+ param_names = []
267
+ for i, param in enumerate(fn.parameters):
268
+ if i < len(args):
269
+ # Handle both Identifier objects and strings
270
+ param_name = param.value if hasattr(param, 'value') else str(param)
271
+ param_names.append(param_name)
272
+ new_env.set(param_name, args[i])
273
+ # Lightweight debug: show what is being bound
274
+ try:
275
+ debug_log(" Set parameter", f"{param_name} = {type(args[i]).__name__}")
276
+ except Exception:
277
+ pass
278
+
279
+ try:
280
+ if param_names:
281
+ debug_log(" Function parameters bound", f"{param_names}")
282
+ except Exception:
283
+ pass
284
+
285
+ try:
286
+ res = self.eval_node(fn.body, new_env)
287
+ res = _resolve_awaitable(res)
288
+
289
+ # Unwrap ReturnValue if needed
290
+ if isinstance(res, ReturnValue):
291
+ result = res.value
292
+ else:
293
+ result = res
294
+
295
+ return result
296
+ finally:
297
+ # CRITICAL: Execute deferred cleanup when function exits
298
+ # This happens in finally block to ensure cleanup runs even on errors
299
+ if hasattr(self, '_execute_deferred_cleanup'):
300
+ self._execute_deferred_cleanup(new_env, [])
301
+
302
+ # Phase 2: Trigger after-call hook
303
+ if hasattr(self, 'integration_context'):
304
+ func_name = fn.name if hasattr(fn, 'name') else str(fn)
305
+ self.integration_context.plugins.after_action_call(func_name, result)
306
+
307
+ elif isinstance(fn, Builtin):
308
+ debug_log(" Calling builtin function", f"{fn.name}")
309
+ # Sandbox enforcement: if current env is sandboxed, consult policy
310
+ try:
311
+ in_sandbox = False
312
+ policy_name = None
313
+ if env is not None:
314
+ try:
315
+ in_sandbox = bool(env.get('__in_sandbox__'))
316
+ policy_name = env.get('__sandbox_policy__')
317
+ except Exception:
318
+ in_sandbox = False
319
+
320
+ if in_sandbox:
321
+ from ..security import get_security_context
322
+ ctx = get_security_context()
323
+ policy = ctx.get_sandbox_policy(policy_name or 'default')
324
+ allowed = None if policy is None else policy.get('allowed_builtins')
325
+ # If allowed set exists and builtin not in it -> block
326
+ if allowed is not None and fn.name not in allowed:
327
+ return EvaluationError(f"Builtin '{fn.name}' not allowed inside sandbox policy '{policy_name or 'default'}'")
328
+ except Exception:
329
+ # If enforcement fails unexpectedly, proceed to call but log nothing
330
+ pass
331
+
332
+ try:
333
+ res = fn.fn(*args)
334
+ return _resolve_awaitable(res)
335
+ except Exception as e:
336
+ return EvaluationError(f"Builtin error: {str(e)}")
337
+
338
+ elif isinstance(fn, EntityDefinition):
339
+ debug_log(" Creating entity instance (old format)")
340
+ # Entity constructor: Person("Alice", 30)
341
+ # Create instance with positional arguments mapped to properties
342
+ from ..object import EntityInstance, String, Integer
343
+
344
+ values = {}
345
+ # Map positional arguments to property names
346
+ if isinstance(fn.properties, dict):
347
+ prop_names = list(fn.properties.keys())
348
+ else:
349
+ prop_names = [prop['name'] for prop in fn.properties]
350
+
351
+ for i, arg in enumerate(args):
352
+ if i < len(prop_names):
353
+ values[prop_names[i]] = arg
354
+
355
+ return EntityInstance(fn, values)
356
+
357
+ # Handle SecurityEntityDefinition (from security.py with methods support)
358
+ from ..security import EntityDefinition as SecurityEntityDef, EntityInstance as SecurityEntityInstance
359
+ if isinstance(fn, SecurityEntityDef):
360
+ debug_log(" Creating entity instance (with methods)")
361
+
362
+ values = {}
363
+ # Map positional arguments to property names, INCLUDING INHERITED PROPERTIES
364
+ # Use get_all_properties() to get the full property list in correct order
365
+ if hasattr(fn, 'get_all_properties'):
366
+ # Get all properties (parent + child) in correct order
367
+ all_props = fn.get_all_properties()
368
+ prop_names = list(all_props.keys())
369
+ else:
370
+ # Fallback for old-style properties
371
+ prop_names = list(fn.properties.keys()) if isinstance(fn.properties, dict) else [prop['name'] for prop in fn.properties]
372
+
373
+ for i, arg in enumerate(args):
374
+ if i < len(prop_names):
375
+ values[prop_names[i]] = arg
376
+
377
+ debug_log(f" Entity instance created with {len(values)} properties: {list(values.keys())}")
378
+ # Use create_instance to handle dependency injection
379
+ return fn.create_instance(values)
380
+
381
+ return EvaluationError(f"Not a function: {fn}")
382
+
383
+ def eval_method_call_expression(self, node, env, stack_trace):
384
+ debug_log(" MethodCallExpression node", f"{node.object}.{node.method}")
385
+
386
+ obj = self.eval_node(node.object, env, stack_trace)
387
+ if is_error(obj):
388
+ return obj
389
+
390
+ method_name = node.method.value
391
+
392
+ # === Builtin Static Methods ===
393
+ # Handle static methods on dataclass constructors (e.g., User.default(), User.fromJSON())
394
+ if isinstance(obj, Builtin):
395
+ if hasattr(obj, 'static_methods') and method_name in obj.static_methods:
396
+ static_method = obj.static_methods[method_name]
397
+ args = self.eval_expressions(node.arguments, env)
398
+ if is_error(args):
399
+ return args
400
+ return self.apply_function(static_method, args, env)
401
+ # If no static method found, fall through to error
402
+
403
+ # === List Methods ===
404
+ if isinstance(obj, List):
405
+ # For map/filter/reduce, we need to evaluate arguments first
406
+ if method_name in ["map", "filter", "reduce"]:
407
+ args = self.eval_expressions(node.arguments, env)
408
+ if is_error(args):
409
+ return args
410
+
411
+ if method_name == "reduce":
412
+ if len(args) < 1:
413
+ return EvaluationError("reduce() requires at least a lambda function")
414
+ lambda_fn = args[0]
415
+ initial = args[1] if len(args) > 1 else None
416
+ return self._array_reduce(obj, lambda_fn, initial)
417
+
418
+ elif method_name == "map":
419
+ if len(args) != 1:
420
+ return EvaluationError("map() requires exactly one lambda function")
421
+ return self._array_map(obj, args[0])
422
+
423
+ elif method_name == "filter":
424
+ if len(args) != 1:
425
+ return EvaluationError("filter() requires exactly one lambda function")
426
+ return self._array_filter(obj, args[0])
427
+
428
+ # Other list methods
429
+ args = self.eval_expressions(node.arguments, env)
430
+ if is_error(args):
431
+ return args
432
+
433
+ if method_name == "push":
434
+ obj.elements.append(args[0])
435
+ return obj
436
+ elif method_name == "count":
437
+ return Integer(len(obj.elements))
438
+ elif method_name == "contains":
439
+ target = args[0]
440
+ found = any(elem.value == target.value for elem in obj.elements
441
+ if hasattr(elem, 'value') and hasattr(target, 'value'))
442
+ return TRUE if found else FALSE
443
+
444
+ # === Coroutine Methods ===
445
+ from ..object import Coroutine
446
+ if isinstance(obj, Coroutine):
447
+ if method_name == "inspect":
448
+ # Return string representation of coroutine state
449
+ return String(obj.inspect())
450
+
451
+ # === Map Methods ===
452
+ if isinstance(obj, Map):
453
+ # First check if the method is a callable stored in the Map (for DATA dataclasses)
454
+ method_key = String(method_name)
455
+ if method_key in obj.pairs:
456
+ method_value = obj.pairs[method_key]
457
+ if isinstance(method_value, Builtin):
458
+ # This is a dataclass method - evaluate args and call it
459
+ args = self.eval_expressions(node.arguments, env)
460
+ if is_error(args):
461
+ return args
462
+ return self.apply_function(method_value, args, env)
463
+
464
+ # Otherwise handle built-in Map methods
465
+ args = self.eval_expressions(node.arguments, env)
466
+ if is_error(args):
467
+ return args
468
+
469
+ if method_name == "has":
470
+ key = args[0].value if hasattr(args[0], 'value') else str(args[0])
471
+ return TRUE if key in obj.pairs else FALSE
472
+ elif method_name == "get":
473
+ key = args[0].value if hasattr(args[0], 'value') else str(args[0])
474
+ default = args[1] if len(args) > 1 else NULL
475
+ return obj.pairs.get(key, default)
476
+
477
+ # === Module Methods ===
478
+ from ..complexity_system import Module, Package
479
+ if isinstance(obj, Module):
480
+ debug_log(" MethodCallExpression", f"Calling method '{method_name}' on module '{obj.name}'")
481
+ debug_log(" MethodCallExpression", f"Module members: {list(obj.members.keys())}")
482
+
483
+ # For module methods, get the member and call it if it's a function
484
+ member_value = obj.get(method_name)
485
+ if member_value is None:
486
+ return EvaluationError(f"Method '{method_name}' not found in module '{obj.name}'")
487
+
488
+ debug_log(" MethodCallExpression", f"Found member value: {member_value}")
489
+
490
+ # Evaluate arguments
491
+ args = self.eval_expressions(node.arguments, env)
492
+ if is_error(args):
493
+ return args
494
+
495
+ # Call the function/action using apply_function
496
+ return self.apply_function(member_value, args, env)
497
+
498
+ # === Package Methods ===
499
+ if isinstance(obj, Package):
500
+ debug_log(" MethodCallExpression", f"Calling method '{method_name}' on package '{obj.name}'")
501
+ debug_log(" MethodCallExpression", f"Package modules: {list(obj.modules.keys())}")
502
+
503
+ # For package methods, get the module/function and call it
504
+ member_value = obj.get(method_name)
505
+ if member_value is None:
506
+ return EvaluationError(f"Method '{method_name}' not found in package '{obj.name}'")
507
+
508
+ debug_log(" MethodCallExpression", f"Found member value: {member_value}")
509
+
510
+ # Evaluate arguments
511
+ args = self.eval_expressions(node.arguments, env)
512
+ if is_error(args):
513
+ return args
514
+
515
+ # Call the function/action using apply_function
516
+ return self.apply_function(member_value, args, env)
517
+
518
+ # === Entity Instance Methods ===
519
+ from ..security import EntityInstance as SecurityEntityInstance
520
+ if isinstance(obj, SecurityEntityInstance):
521
+ args = self.eval_expressions(node.arguments, env)
522
+ if is_error(args):
523
+ return args
524
+ return obj.call_method(method_name, args)
525
+
526
+ # === Contract Instance Methods ===
527
+ if hasattr(obj, 'call_method'):
528
+ args = self.eval_expressions(node.arguments, env)
529
+ if is_error(args):
530
+ return args
531
+ return obj.call_method(method_name, args)
532
+
533
+ # === Embedded Code Methods ===
534
+ from ..object import EmbeddedCode
535
+ if isinstance(obj, EmbeddedCode):
536
+ print(f"[EMBED] Executing {obj.language}.{method_name}")
537
+ return Integer(42) # Placeholder
538
+
539
+ # === Environment (Module) Methods ===
540
+ # Support for module.function() syntax (e.g., crypto.keccak256())
541
+ from ..object import Environment
542
+ if isinstance(obj, Environment):
543
+ # Look up the method in the environment's store
544
+ method_value = obj.get(method_name)
545
+ if method_value is None or method_value == NULL:
546
+ return EvaluationError(f"Module has no method '{method_name}'")
547
+
548
+ # Evaluate arguments
549
+ args = self.eval_expressions(node.arguments, env)
550
+ if is_error(args):
551
+ return args
552
+
553
+ # Call the function using apply_function
554
+ return self.apply_function(method_value, args, env)
555
+
556
+ obj_type = obj.type() if hasattr(obj, 'type') and callable(obj.type) else type(obj).__name__
557
+ return EvaluationError(f"Method '{method_name}' not supported for {obj_type}")
558
+
559
+ # --- Array Helpers (Internal) ---
560
+
561
+ def _array_reduce(self, array_obj, lambda_fn, initial_value=None):
562
+ if not isinstance(array_obj, List):
563
+ return EvaluationError("reduce() called on non-array")
564
+ if not isinstance(lambda_fn, (LambdaFunction, Action)):
565
+ return EvaluationError("reduce() requires lambda")
566
+
567
+ accumulator = initial_value if initial_value is not None else (
568
+ array_obj.elements[0] if array_obj.elements else NULL
569
+ )
570
+ start_index = 0 if initial_value is not None else 1
571
+
572
+ for i in range(start_index, len(array_obj.elements)):
573
+ element = array_obj.elements[i]
574
+ result = self.apply_function(lambda_fn, [accumulator, element])
575
+ if is_error(result):
576
+ return result
577
+ accumulator = result
578
+
579
+ return accumulator
580
+
581
+ def _array_map(self, array_obj, lambda_fn):
582
+ if not isinstance(array_obj, List):
583
+ return EvaluationError("map() called on non-array")
584
+ if not isinstance(lambda_fn, (LambdaFunction, Action)):
585
+ return EvaluationError("map() requires lambda")
586
+
587
+ mapped = []
588
+ for element in array_obj.elements:
589
+ result = self.apply_function(lambda_fn, [element])
590
+ if is_error(result):
591
+ return result
592
+ mapped.append(result)
593
+
594
+ return List(mapped)
595
+
596
+ def _array_filter(self, array_obj, lambda_fn):
597
+ if not isinstance(array_obj, List):
598
+ return EvaluationError("filter() called on non-array")
599
+ if not isinstance(lambda_fn, (LambdaFunction, Action)):
600
+ return EvaluationError("filter() requires lambda")
601
+
602
+ filtered = []
603
+ for element in array_obj.elements:
604
+ result = self.apply_function(lambda_fn, [element])
605
+ if is_error(result):
606
+ return result
607
+
608
+ # Use is_truthy from utils
609
+ from .utils import is_truthy
610
+ if is_truthy(result):
611
+ filtered.append(element)
612
+
613
+ return List(filtered)
614
+
615
+ # --- BUILTIN IMPLEMENTATIONS ---
616
+
617
+ def _register_core_builtins(self):
618
+ # Date & Time
619
+ def _now(*a):
620
+ return DateTime.now()
621
+
622
+ def _timestamp(*a):
623
+ if len(a) == 0:
624
+ return DateTime.now().to_timestamp()
625
+ if len(a) == 1 and isinstance(a[0], DateTime):
626
+ return a[0].to_timestamp()
627
+ return EvaluationError("timestamp() takes 0 or 1 DateTime")
628
+
629
+ # Math
630
+ def _random(*a):
631
+ if len(a) == 0:
632
+ return Math.random_int(0, 100)
633
+ if len(a) == 1 and isinstance(a[0], Integer):
634
+ return Math.random_int(0, a[0].value)
635
+ if len(a) == 2 and all(isinstance(x, Integer) for x in a):
636
+ return Math.random_int(a[0].value, a[1].value)
637
+ return EvaluationError("random() takes 0, 1, or 2 integer arguments")
638
+
639
+ def _to_hex(*a):
640
+ if len(a) != 1:
641
+ return EvaluationError("to_hex() takes exactly 1 argument")
642
+ return Math.to_hex_string(a[0])
643
+
644
+ def _from_hex(*a):
645
+ if len(a) != 1 or not isinstance(a[0], String):
646
+ return EvaluationError("from_hex() takes exactly 1 string argument")
647
+ return Math.hex_to_int(a[0])
648
+
649
+ def _sqrt(*a):
650
+ if len(a) != 1:
651
+ return EvaluationError("sqrt() takes exactly 1 argument")
652
+ return Math.sqrt(a[0])
653
+
654
+ # File I/O
655
+ def _read_text(*a):
656
+ if len(a) != 1 or not isinstance(a[0], String):
657
+ return EvaluationError("file_read_text() takes exactly 1 string argument")
658
+ return File.read_text(a[0])
659
+
660
+ def _write_text(*a):
661
+ if len(a) != 2 or not all(isinstance(x, String) for x in a):
662
+ return EvaluationError("file_write_text() takes exactly 2 string arguments")
663
+ return File.write_text(a[0], a[1])
664
+
665
+ def _exists(*a):
666
+ if len(a) != 1 or not isinstance(a[0], String):
667
+ return EvaluationError("file_exists() takes exactly 1 string argument")
668
+ return File.exists(a[0])
669
+
670
+ def _read_json(*a):
671
+ if len(a) != 1 or not isinstance(a[0], String):
672
+ return EvaluationError("file_read_json() takes exactly 1 string argument")
673
+ return File.read_json(a[0])
674
+
675
+ def _write_json(*a):
676
+ if len(a) != 2 or not isinstance(a[0], String):
677
+ return EvaluationError("file_write_json() takes path string and data")
678
+ return File.write_json(a[0], a[1])
679
+
680
+ def _file_append(*a):
681
+ if len(a) != 2 or not all(isinstance(x, String) for x in a):
682
+ return EvaluationError("file_append() takes exactly 2 string arguments")
683
+ return File.append_text(a[0], a[1])
684
+
685
+ def _list_dir(*a):
686
+ if len(a) != 1 or not isinstance(a[0], String):
687
+ return EvaluationError("file_list_dir() takes exactly 1 string argument")
688
+ return File.list_directory(a[0])
689
+
690
+ # Extended File System Operations
691
+ def _fs_is_file(*a):
692
+ if len(a) != 1 or not isinstance(a[0], String):
693
+ return EvaluationError("fs_is_file() takes exactly 1 string argument")
694
+ import os
695
+ return BooleanObj(os.path.isfile(a[0].value))
696
+
697
+ def _fs_is_dir(*a):
698
+ if len(a) != 1 or not isinstance(a[0], String):
699
+ return EvaluationError("fs_is_dir() takes exactly 1 string argument")
700
+ import os
701
+ return BooleanObj(os.path.isdir(a[0].value))
702
+
703
+ def _fs_mkdir(*a):
704
+ if len(a) < 1 or not isinstance(a[0], String):
705
+ return EvaluationError("fs_mkdir() takes path string and optional parents boolean")
706
+ from pathlib import Path
707
+ parents = True # Default to creating parent directories
708
+ if len(a) >= 2 and isinstance(a[1], BooleanObj):
709
+ parents = a[1].value
710
+ try:
711
+ Path(a[0].value).mkdir(parents=parents, exist_ok=True)
712
+ return BooleanObj(True)
713
+ except Exception as e:
714
+ return EvaluationError(f"fs_mkdir() error: {str(e)}")
715
+
716
+ def _fs_remove(*a):
717
+ if len(a) != 1 or not isinstance(a[0], String):
718
+ return EvaluationError("fs_remove() takes exactly 1 string argument")
719
+ import os
720
+ try:
721
+ os.remove(a[0].value)
722
+ return BooleanObj(True)
723
+ except Exception as e:
724
+ return EvaluationError(f"fs_remove() error: {str(e)}")
725
+
726
+ def _fs_rmdir(*a):
727
+ if len(a) < 1 or not isinstance(a[0], String):
728
+ return EvaluationError("fs_rmdir() takes path string and optional recursive boolean")
729
+ import os
730
+ import shutil
731
+ recursive = False
732
+ if len(a) >= 2 and isinstance(a[1], BooleanObj):
733
+ recursive = a[1].value
734
+ try:
735
+ if recursive:
736
+ shutil.rmtree(a[0].value)
737
+ else:
738
+ os.rmdir(a[0].value)
739
+ return BooleanObj(True)
740
+ except Exception as e:
741
+ return EvaluationError(f"fs_rmdir() error: {str(e)}")
742
+
743
+ def _fs_rename(*a):
744
+ if len(a) != 2 or not all(isinstance(x, String) for x in a):
745
+ return EvaluationError("fs_rename() takes exactly 2 string arguments: old_path, new_path")
746
+ import os
747
+ try:
748
+ os.rename(a[0].value, a[1].value)
749
+ return BooleanObj(True)
750
+ except Exception as e:
751
+ return EvaluationError(f"fs_rename() error: {str(e)}")
752
+
753
+ def _fs_copy(*a):
754
+ if len(a) != 2 or not all(isinstance(x, String) for x in a):
755
+ return EvaluationError("fs_copy() takes exactly 2 string arguments: src, dst")
756
+ import shutil
757
+ import os
758
+ try:
759
+ src = a[0].value
760
+ dst = a[1].value
761
+ if os.path.isfile(src):
762
+ shutil.copy2(src, dst)
763
+ elif os.path.isdir(src):
764
+ shutil.copytree(src, dst)
765
+ else:
766
+ return EvaluationError(f"fs_copy() source does not exist: {src}")
767
+ return BooleanObj(True)
768
+ except Exception as e:
769
+ return EvaluationError(f"fs_copy() error: {str(e)}")
770
+
771
+ # Socket/TCP Primitives
772
+ def _socket_create_server(*a):
773
+ """Create TCP server: socket_create_server(host, port, handler, backlog?)"""
774
+ if len(a) < 3:
775
+ return EvaluationError("socket_create_server() requires at least 3 arguments: host, port, handler")
776
+
777
+ if not isinstance(a[0], String):
778
+ return EvaluationError("socket_create_server() host must be a string")
779
+ if not isinstance(a[1], Integer):
780
+ return EvaluationError("socket_create_server() port must be an integer")
781
+ if not isinstance(a[2], Action):
782
+ return EvaluationError("socket_create_server() handler must be an action")
783
+
784
+ host = a[0].value
785
+ port = a[1].value
786
+ handler = a[2]
787
+ backlog = 5
788
+
789
+ if len(a) >= 4 and isinstance(a[3], Integer):
790
+ backlog = a[3].value
791
+
792
+ try:
793
+ from ..stdlib.sockets import SocketModule
794
+
795
+ # Wrap the Zexus handler
796
+ def python_handler(conn):
797
+ # Create Zexus builtin wrappers for connection methods
798
+ def _conn_send(*args):
799
+ if len(args) != 1:
800
+ return EvaluationError("send() takes 1 argument")
801
+ data = args[0].value if isinstance(args[0], String) else str(args[0])
802
+ try:
803
+ conn.send_string(data)
804
+ return NULL
805
+ except Exception as e:
806
+ return EvaluationError(f"send() error: {str(e)}")
807
+
808
+ def _conn_receive(*args):
809
+ size = 4096
810
+ if len(args) >= 1 and isinstance(args[0], Integer):
811
+ size = args[0].value
812
+ try:
813
+ data = conn.receive_string(size)
814
+ return String(data)
815
+ except Exception as e:
816
+ return EvaluationError(f"receive() error: {str(e)}")
817
+
818
+ def _conn_close(*args):
819
+ try:
820
+ conn.close()
821
+ return NULL
822
+ except Exception as e:
823
+ return EvaluationError(f"close() error: {str(e)}")
824
+
825
+ # Convert Python connection to Zexus Map
826
+ conn_obj = Map({
827
+ String("send"): Builtin(_conn_send, "send"),
828
+ String("receive"): Builtin(_conn_receive, "receive"),
829
+ String("close"): Builtin(_conn_close, "close"),
830
+ String("host"): String(conn.host),
831
+ String("port"): Integer(conn.port)
832
+ })
833
+
834
+ # Call Zexus handler
835
+ self.apply_function(handler, [conn_obj])
836
+
837
+ server = SocketModule.create_server(host, port, python_handler, backlog)
838
+ server.start()
839
+
840
+ # Create builtins for server methods
841
+ def _server_stop(*args):
842
+ try:
843
+ server.stop()
844
+ return NULL
845
+ except Exception as e:
846
+ return EvaluationError(f"stop() error: {str(e)}")
847
+
848
+ def _server_is_running(*args):
849
+ return BooleanObj(server.is_running())
850
+
851
+ # Return server object as Map
852
+ return Map({
853
+ String("stop"): Builtin(_server_stop, "stop"),
854
+ String("is_running"): Builtin(_server_is_running, "is_running"),
855
+ String("host"): String(server.host),
856
+ String("port"): Integer(server.port)
857
+ })
858
+ except Exception as e:
859
+ return EvaluationError(f"socket_create_server() error: {str(e)}")
860
+
861
+ def _socket_create_connection(*a):
862
+ """Create TCP client: socket_create_connection(host, port, timeout?)"""
863
+ if len(a) < 2:
864
+ return EvaluationError("socket_create_connection() requires at least 2 arguments: host, port")
865
+
866
+ if not isinstance(a[0], String):
867
+ return EvaluationError("socket_create_connection() host must be a string")
868
+ if not isinstance(a[1], Integer):
869
+ return EvaluationError("socket_create_connection() port must be an integer")
870
+
871
+ host = a[0].value
872
+ port = a[1].value
873
+ timeout = 5.0
874
+
875
+ if len(a) >= 3 and isinstance(a[2], (Integer, Float)):
876
+ timeout = float(a[2].value)
877
+
878
+ try:
879
+ from ..stdlib.sockets import SocketModule
880
+ conn = SocketModule.create_connection(host, port, timeout)
881
+
882
+ # Create Zexus builtin wrappers for connection methods
883
+ def _conn_send(*args):
884
+ if len(args) != 1:
885
+ return EvaluationError("send() takes 1 argument")
886
+ data = args[0].value if isinstance(args[0], String) else str(args[0])
887
+ try:
888
+ conn.send_string(data)
889
+ return NULL
890
+ except Exception as e:
891
+ return EvaluationError(f"send() error: {str(e)}")
892
+
893
+ def _conn_receive(*args):
894
+ size = 4096
895
+ if len(args) >= 1 and isinstance(args[0], Integer):
896
+ size = args[0].value
897
+ try:
898
+ data = conn.receive_string(size)
899
+ return String(data)
900
+ except Exception as e:
901
+ return EvaluationError(f"receive() error: {str(e)}")
902
+
903
+ def _conn_close(*args):
904
+ try:
905
+ conn.close()
906
+ return NULL
907
+ except Exception as e:
908
+ return EvaluationError(f"close() error: {str(e)}")
909
+
910
+ def _conn_is_connected(*args):
911
+ return BooleanObj(conn.is_connected())
912
+
913
+ # Return connection object as Map with Builtin functions
914
+ return Map({
915
+ String("send"): Builtin(_conn_send, "send"),
916
+ String("receive"): Builtin(_conn_receive, "receive"),
917
+ String("close"): Builtin(_conn_close, "close"),
918
+ String("is_connected"): Builtin(_conn_is_connected, "is_connected"),
919
+ String("host"): String(conn.host),
920
+ String("port"): Integer(conn.port)
921
+ })
922
+ except Exception as e:
923
+ return EvaluationError(f"socket_create_connection() error: {str(e)}")
924
+
925
+ # HTTP Server
926
+ def _http_server(*a):
927
+ """Create HTTP server: http_server(port, host?)"""
928
+ if len(a) < 1:
929
+ return EvaluationError("http_server() requires at least 1 argument: port")
930
+
931
+ if not isinstance(a[0], Integer):
932
+ return EvaluationError("http_server() port must be an integer")
933
+
934
+ port = a[0].value
935
+ host = "0.0.0.0"
936
+
937
+ if len(a) >= 2 and isinstance(a[1], String):
938
+ host = a[1].value
939
+
940
+ try:
941
+ from ..stdlib.http_server import HTTPServer
942
+ server = HTTPServer(host, port)
943
+
944
+ # Create builtins for server methods
945
+ def _server_get(*args):
946
+ if len(args) != 2 or not isinstance(args[0], String) or not isinstance(args[1], Action):
947
+ return EvaluationError("get() takes 2 arguments: path, handler")
948
+
949
+ path = args[0].value
950
+ handler = args[1]
951
+
952
+ # Wrap Zexus handler for Python
953
+ def python_handler(req, res):
954
+ # Convert request to Zexus Map
955
+ req_map = Map({
956
+ String("method"): String(req.method),
957
+ String("path"): String(req.path),
958
+ String("headers"): _python_to_zexus(req.headers),
959
+ String("body"): String(req.body),
960
+ String("query"): _python_to_zexus(req.query)
961
+ })
962
+
963
+ # Create response builtins
964
+ def _res_status(*a):
965
+ if len(a) != 1 or not isinstance(a[0], Integer):
966
+ return EvaluationError("status() takes 1 integer argument")
967
+ res.set_status(a[0].value)
968
+ return NULL
969
+
970
+ def _res_send(*a):
971
+ if len(a) != 1:
972
+ return EvaluationError("send() takes 1 argument")
973
+ data = a[0].value if isinstance(a[0], String) else str(a[0])
974
+ res.send(data)
975
+ return NULL
976
+
977
+ def _res_json(*a):
978
+ if len(a) != 1:
979
+ return EvaluationError("json() takes 1 argument")
980
+ data = _zexus_to_python(a[0])
981
+ res.json(data)
982
+ return NULL
983
+
984
+ res_map = Map({
985
+ String("status"): Builtin(_res_status, "status"),
986
+ String("send"): Builtin(_res_send, "send"),
987
+ String("json"): Builtin(_res_json, "json")
988
+ })
989
+
990
+ # Call Zexus handler
991
+ self.apply_function(handler, [req_map, res_map])
992
+
993
+ server.get(path, python_handler)
994
+ return NULL
995
+
996
+ def _server_post(*args):
997
+ if len(args) != 2 or not isinstance(args[0], String) or not isinstance(args[1], Action):
998
+ return EvaluationError("post() takes 2 arguments: path, handler")
999
+ path = args[0].value
1000
+ handler = args[1]
1001
+
1002
+ def python_handler(req, res):
1003
+ req_map = Map({
1004
+ String("method"): String(req.method),
1005
+ String("path"): String(req.path),
1006
+ String("headers"): _python_to_zexus(req.headers),
1007
+ String("body"): String(req.body),
1008
+ String("query"): _python_to_zexus(req.query)
1009
+ })
1010
+
1011
+ def _res_status(*a):
1012
+ if len(a) == 1 and isinstance(a[0], Integer):
1013
+ res.set_status(a[0].value)
1014
+ return NULL
1015
+
1016
+ def _res_send(*a):
1017
+ if len(a) == 1:
1018
+ data = a[0].value if isinstance(a[0], String) else str(a[0])
1019
+ res.send(data)
1020
+ return NULL
1021
+
1022
+ def _res_json(*a):
1023
+ if len(a) == 1:
1024
+ data = _zexus_to_python(a[0])
1025
+ res.json(data)
1026
+ return NULL
1027
+
1028
+ res_map = Map({
1029
+ String("status"): Builtin(_res_status, "status"),
1030
+ String("send"): Builtin(_res_send, "send"),
1031
+ String("json"): Builtin(_res_json, "json")
1032
+ })
1033
+
1034
+ self.apply_function(handler, [req_map, res_map])
1035
+
1036
+ server.post(path, python_handler)
1037
+ return NULL
1038
+
1039
+ def _server_listen(*args):
1040
+ try:
1041
+ # Start server in background thread
1042
+ import threading
1043
+ thread = threading.Thread(target=server.listen, daemon=True)
1044
+ thread.start()
1045
+ return NULL
1046
+ except Exception as e:
1047
+ return EvaluationError(f"listen() error: {str(e)}")
1048
+
1049
+ def _server_stop(*args):
1050
+ try:
1051
+ server.stop()
1052
+ return NULL
1053
+ except Exception as e:
1054
+ return EvaluationError(f"stop() error: {str(e)}")
1055
+
1056
+ # Return server object as Map
1057
+ return Map({
1058
+ String("get"): Builtin(_server_get, "get"),
1059
+ String("post"): Builtin(_server_post, "post"),
1060
+ String("listen"): Builtin(_server_listen, "listen"),
1061
+ String("stop"): Builtin(_server_stop, "stop"),
1062
+ String("host"): String(host),
1063
+ String("port"): Integer(port)
1064
+ })
1065
+ except Exception as e:
1066
+ return EvaluationError(f"http_server() error: {str(e)}")
1067
+
1068
+ # Database - SQLite
1069
+ def _sqlite_connect(*a):
1070
+ """Connect to SQLite database: sqlite_connect(database_path)"""
1071
+ if len(a) != 1 or not isinstance(a[0], String):
1072
+ return EvaluationError("sqlite_connect() takes 1 string argument: database path")
1073
+
1074
+ try:
1075
+ from ..stdlib.db_sqlite import SQLiteConnection
1076
+ db = SQLiteConnection(a[0].value)
1077
+
1078
+ if not db.connect():
1079
+ return EvaluationError("Failed to connect to SQLite database")
1080
+
1081
+ # Store db in a list to keep it alive (prevent garbage collection)
1082
+ db_ref = [db]
1083
+
1084
+ # Create database methods as Zexus builtins
1085
+ def _db_execute(*args):
1086
+ if len(args) < 1 or not isinstance(args[0], String):
1087
+ return EvaluationError("execute() takes query string and optional params")
1088
+
1089
+ query = args[0].value
1090
+ params = None
1091
+
1092
+ if len(args) >= 2:
1093
+ # Convert Zexus List to Python tuple for params
1094
+ if isinstance(args[1], List):
1095
+ params = tuple(_zexus_to_python(args[1]))
1096
+ else:
1097
+ params = (_zexus_to_python(args[1]),)
1098
+
1099
+ result = db_ref[0].execute(query, params)
1100
+ return BooleanObj(result)
1101
+
1102
+ def _db_query(*args):
1103
+ if len(args) < 1 or not isinstance(args[0], String):
1104
+ return EvaluationError("query() takes query string and optional params")
1105
+
1106
+ query = args[0].value
1107
+ params = None
1108
+
1109
+ if len(args) >= 2:
1110
+ if isinstance(args[1], List):
1111
+ params = tuple(_zexus_to_python(args[1]))
1112
+ else:
1113
+ params = (_zexus_to_python(args[1]),)
1114
+
1115
+ results = db_ref[0].query(query, params)
1116
+ # Convert Python list of dicts to Zexus List of Maps
1117
+ return _python_to_zexus(results)
1118
+
1119
+ def _db_query_one(*args):
1120
+ if len(args) < 1 or not isinstance(args[0], String):
1121
+ return EvaluationError("query_one() takes query string and optional params")
1122
+
1123
+ query = args[0].value
1124
+ params = None
1125
+
1126
+ if len(args) >= 2:
1127
+ if isinstance(args[1], List):
1128
+ params = tuple(_zexus_to_python(args[1]))
1129
+ else:
1130
+ params = (_zexus_to_python(args[1]),)
1131
+
1132
+ result = db_ref[0].query_one(query, params)
1133
+ return _python_to_zexus(result) if result else NULL
1134
+
1135
+ def _db_last_insert_id(*args):
1136
+ return Integer(db_ref[0].last_insert_id())
1137
+
1138
+ def _db_affected_rows(*args):
1139
+ return Integer(db_ref[0].affected_rows())
1140
+
1141
+ def _db_begin(*args):
1142
+ return BooleanObj(db_ref[0].begin_transaction())
1143
+
1144
+ def _db_commit(*args):
1145
+ return BooleanObj(db_ref[0].commit())
1146
+
1147
+ def _db_rollback(*args):
1148
+ return BooleanObj(db_ref[0].rollback())
1149
+
1150
+ def _db_close(*args):
1151
+ return BooleanObj(db_ref[0].close())
1152
+
1153
+ # Return database connection as Map
1154
+ return Map({
1155
+ String("execute"): Builtin(_db_execute, "execute"),
1156
+ String("query"): Builtin(_db_query, "query"),
1157
+ String("query_one"): Builtin(_db_query_one, "query_one"),
1158
+ String("last_insert_id"): Builtin(_db_last_insert_id, "last_insert_id"),
1159
+ String("affected_rows"): Builtin(_db_affected_rows, "affected_rows"),
1160
+ String("begin"): Builtin(_db_begin, "begin"),
1161
+ String("commit"): Builtin(_db_commit, "commit"),
1162
+ String("rollback"): Builtin(_db_rollback, "rollback"),
1163
+ String("close"): Builtin(_db_close, "close"),
1164
+ String("database"): String(a[0].value)
1165
+ })
1166
+
1167
+ except Exception as e:
1168
+ return EvaluationError(f"sqlite_connect() error: {str(e)}")
1169
+
1170
+ # Database - PostgreSQL
1171
+ def _postgres_connect(*a):
1172
+ """Connect to PostgreSQL: postgres_connect(host, port, database, user, password)"""
1173
+ if len(a) < 1:
1174
+ return EvaluationError("postgres_connect() requires at least database name")
1175
+
1176
+ # Parse parameters
1177
+ host = "localhost"
1178
+ port = 5432
1179
+ database = a[0].value if isinstance(a[0], String) else "postgres"
1180
+ user = "postgres"
1181
+ password = ""
1182
+
1183
+ if len(a) >= 2 and isinstance(a[1], String):
1184
+ user = a[1].value
1185
+ if len(a) >= 3 and isinstance(a[2], String):
1186
+ password = a[2].value
1187
+ if len(a) >= 4 and isinstance(a[3], String):
1188
+ host = a[3].value
1189
+ if len(a) >= 5 and isinstance(a[4], Integer):
1190
+ port = a[4].value
1191
+
1192
+ try:
1193
+ from ..stdlib.db_postgres import PostgreSQLConnection
1194
+ db = PostgreSQLConnection(host, port, database, user, password)
1195
+
1196
+ if not db.connect():
1197
+ return EvaluationError("Failed to connect to PostgreSQL database")
1198
+
1199
+ db_ref = [db]
1200
+
1201
+ # Same methods as SQLite
1202
+ def _db_execute(*args):
1203
+ if len(args) < 1 or not isinstance(args[0], String):
1204
+ return EvaluationError("execute() takes query string and optional params")
1205
+ query = args[0].value
1206
+ params = None
1207
+ if len(args) >= 2:
1208
+ if isinstance(args[1], List):
1209
+ params = tuple(_zexus_to_python(args[1]))
1210
+ else:
1211
+ params = (_zexus_to_python(args[1]),)
1212
+ result = db_ref[0].execute(query, params)
1213
+ return BooleanObj(result)
1214
+
1215
+ def _db_query(*args):
1216
+ if len(args) < 1 or not isinstance(args[0], String):
1217
+ return EvaluationError("query() takes query string and optional params")
1218
+ query = args[0].value
1219
+ params = None
1220
+ if len(args) >= 2:
1221
+ if isinstance(args[1], List):
1222
+ params = tuple(_zexus_to_python(args[1]))
1223
+ else:
1224
+ params = (_zexus_to_python(args[1]),)
1225
+ results = db_ref[0].query(query, params)
1226
+ return _python_to_zexus(results)
1227
+
1228
+ def _db_query_one(*args):
1229
+ if len(args) < 1 or not isinstance(args[0], String):
1230
+ return EvaluationError("query_one() takes query string and optional params")
1231
+ query = args[0].value
1232
+ params = None
1233
+ if len(args) >= 2:
1234
+ if isinstance(args[1], List):
1235
+ params = tuple(_zexus_to_python(args[1]))
1236
+ else:
1237
+ params = (_zexus_to_python(args[1]),)
1238
+ result = db_ref[0].query_one(query, params)
1239
+ return _python_to_zexus(result) if result else NULL
1240
+
1241
+ def _db_last_insert_id(*args):
1242
+ return Integer(db_ref[0].last_insert_id())
1243
+
1244
+ def _db_affected_rows(*args):
1245
+ return Integer(db_ref[0].affected_rows())
1246
+
1247
+ def _db_begin(*args):
1248
+ return BooleanObj(db_ref[0].begin_transaction())
1249
+
1250
+ def _db_commit(*args):
1251
+ return BooleanObj(db_ref[0].commit())
1252
+
1253
+ def _db_rollback(*args):
1254
+ return BooleanObj(db_ref[0].rollback())
1255
+
1256
+ def _db_close(*args):
1257
+ return BooleanObj(db_ref[0].close())
1258
+
1259
+ return Map({
1260
+ String("execute"): Builtin(_db_execute, "execute"),
1261
+ String("query"): Builtin(_db_query, "query"),
1262
+ String("query_one"): Builtin(_db_query_one, "query_one"),
1263
+ String("last_insert_id"): Builtin(_db_last_insert_id, "last_insert_id"),
1264
+ String("affected_rows"): Builtin(_db_affected_rows, "affected_rows"),
1265
+ String("begin"): Builtin(_db_begin, "begin"),
1266
+ String("commit"): Builtin(_db_commit, "commit"),
1267
+ String("rollback"): Builtin(_db_rollback, "rollback"),
1268
+ String("close"): Builtin(_db_close, "close"),
1269
+ String("database"): String(database),
1270
+ String("type"): String("postgresql")
1271
+ })
1272
+
1273
+ except Exception as e:
1274
+ return EvaluationError(f"postgres_connect() error: {str(e)}")
1275
+
1276
+ # Database - MySQL
1277
+ def _mysql_connect(*a):
1278
+ """Connect to MySQL: mysql_connect(database, user, password, host?, port?)"""
1279
+ if len(a) < 1:
1280
+ return EvaluationError("mysql_connect() requires at least database name")
1281
+
1282
+ host = "localhost"
1283
+ port = 3306
1284
+ database = a[0].value if isinstance(a[0], String) else "mysql"
1285
+ user = "root"
1286
+ password = ""
1287
+
1288
+ if len(a) >= 2 and isinstance(a[1], String):
1289
+ user = a[1].value
1290
+ if len(a) >= 3 and isinstance(a[2], String):
1291
+ password = a[2].value
1292
+ if len(a) >= 4 and isinstance(a[3], String):
1293
+ host = a[3].value
1294
+ if len(a) >= 5 and isinstance(a[4], Integer):
1295
+ port = a[4].value
1296
+
1297
+ try:
1298
+ from ..stdlib.db_mysql import MySQLConnection
1299
+ db = MySQLConnection(host, port, database, user, password)
1300
+
1301
+ if not db.connect():
1302
+ return EvaluationError("Failed to connect to MySQL database")
1303
+
1304
+ db_ref = [db]
1305
+
1306
+ # Same interface as SQLite/PostgreSQL
1307
+ def _db_execute(*args):
1308
+ if len(args) < 1 or not isinstance(args[0], String):
1309
+ return EvaluationError("execute() takes query string and optional params")
1310
+ query = args[0].value
1311
+ params = None
1312
+ if len(args) >= 2:
1313
+ if isinstance(args[1], List):
1314
+ params = tuple(_zexus_to_python(args[1]))
1315
+ else:
1316
+ params = (_zexus_to_python(args[1]),)
1317
+ result = db_ref[0].execute(query, params)
1318
+ return BooleanObj(result)
1319
+
1320
+ def _db_query(*args):
1321
+ if len(args) < 1 or not isinstance(args[0], String):
1322
+ return EvaluationError("query() takes query string and optional params")
1323
+ query = args[0].value
1324
+ params = None
1325
+ if len(args) >= 2:
1326
+ if isinstance(args[1], List):
1327
+ params = tuple(_zexus_to_python(args[1]))
1328
+ else:
1329
+ params = (_zexus_to_python(args[1]),)
1330
+ results = db_ref[0].query(query, params)
1331
+ return _python_to_zexus(results)
1332
+
1333
+ def _db_query_one(*args):
1334
+ if len(args) < 1 or not isinstance(args[0], String):
1335
+ return EvaluationError("query_one() takes query string and optional params")
1336
+ query = args[0].value
1337
+ params = None
1338
+ if len(args) >= 2:
1339
+ if isinstance(args[1], List):
1340
+ params = tuple(_zexus_to_python(args[1]))
1341
+ else:
1342
+ params = (_zexus_to_python(args[1]),)
1343
+ result = db_ref[0].query_one(query, params)
1344
+ return _python_to_zexus(result) if result else NULL
1345
+
1346
+ def _db_last_insert_id(*args):
1347
+ return Integer(db_ref[0].last_insert_id())
1348
+
1349
+ def _db_affected_rows(*args):
1350
+ return Integer(db_ref[0].affected_rows())
1351
+
1352
+ def _db_begin(*args):
1353
+ return BooleanObj(db_ref[0].begin_transaction())
1354
+
1355
+ def _db_commit(*args):
1356
+ return BooleanObj(db_ref[0].commit())
1357
+
1358
+ def _db_rollback(*args):
1359
+ return BooleanObj(db_ref[0].rollback())
1360
+
1361
+ def _db_close(*args):
1362
+ return BooleanObj(db_ref[0].close())
1363
+
1364
+ return Map({
1365
+ String("execute"): Builtin(_db_execute, "execute"),
1366
+ String("query"): Builtin(_db_query, "query"),
1367
+ String("query_one"): Builtin(_db_query_one, "query_one"),
1368
+ String("last_insert_id"): Builtin(_db_last_insert_id, "last_insert_id"),
1369
+ String("affected_rows"): Builtin(_db_affected_rows, "affected_rows"),
1370
+ String("begin"): Builtin(_db_begin, "begin"),
1371
+ String("commit"): Builtin(_db_commit, "commit"),
1372
+ String("rollback"): Builtin(_db_rollback, "rollback"),
1373
+ String("close"): Builtin(_db_close, "close"),
1374
+ String("database"): String(database),
1375
+ String("type"): String("mysql")
1376
+ })
1377
+
1378
+ except Exception as e:
1379
+ return EvaluationError(f"mysql_connect() error: {str(e)}")
1380
+
1381
+ # Database - MongoDB
1382
+ def _mongo_connect(*a):
1383
+ """Connect to MongoDB: mongo_connect(database, host?, port?, username?, password?)"""
1384
+ if len(a) < 1:
1385
+ return EvaluationError("mongo_connect() requires at least database name")
1386
+
1387
+ database = a[0].value if isinstance(a[0], String) else "test"
1388
+ host = "localhost"
1389
+ port = 27017
1390
+ username = None
1391
+ password = None
1392
+
1393
+ if len(a) >= 2 and isinstance(a[1], String):
1394
+ host = a[1].value
1395
+ if len(a) >= 3 and isinstance(a[2], Integer):
1396
+ port = a[2].value
1397
+ if len(a) >= 4 and isinstance(a[3], String):
1398
+ username = a[3].value
1399
+ if len(a) >= 5 and isinstance(a[4], String):
1400
+ password = a[4].value
1401
+
1402
+ try:
1403
+ from ..stdlib.db_mongo import MongoDBConnection
1404
+ db = MongoDBConnection(host, port, database, username, password)
1405
+
1406
+ if not db.connect():
1407
+ return EvaluationError("Failed to connect to MongoDB database")
1408
+
1409
+ db_ref = [db]
1410
+
1411
+ # MongoDB-specific operations
1412
+ def _db_insert_one(*args):
1413
+ if len(args) < 2 or not isinstance(args[0], String) or not isinstance(args[1], Map):
1414
+ return EvaluationError("insert_one() takes collection name and document")
1415
+ collection = args[0].value
1416
+ document = _zexus_to_python(args[1])
1417
+ result = db_ref[0].insert_one(collection, document)
1418
+ return String(result) if result else NULL
1419
+
1420
+ def _db_insert_many(*args):
1421
+ if len(args) < 2 or not isinstance(args[0], String) or not isinstance(args[1], List):
1422
+ return EvaluationError("insert_many() takes collection name and list of documents")
1423
+ collection = args[0].value
1424
+ documents = _zexus_to_python(args[1])
1425
+ result = db_ref[0].insert_many(collection, documents)
1426
+ return _python_to_zexus(result) if result else NULL
1427
+
1428
+ def _db_find(*args):
1429
+ if len(args) < 1 or not isinstance(args[0], String):
1430
+ return EvaluationError("find() takes collection name and optional query")
1431
+ collection = args[0].value
1432
+ query = None
1433
+ if len(args) >= 2 and isinstance(args[1], Map):
1434
+ query = _zexus_to_python(args[1])
1435
+ results = db_ref[0].find(collection, query)
1436
+ return _python_to_zexus(results)
1437
+
1438
+ def _db_find_one(*args):
1439
+ if len(args) < 1 or not isinstance(args[0], String):
1440
+ return EvaluationError("find_one() takes collection name and optional query")
1441
+ collection = args[0].value
1442
+ query = None
1443
+ if len(args) >= 2 and isinstance(args[1], Map):
1444
+ query = _zexus_to_python(args[1])
1445
+ result = db_ref[0].find_one(collection, query)
1446
+ return _python_to_zexus(result) if result else NULL
1447
+
1448
+ def _db_update_one(*args):
1449
+ if len(args) < 3 or not isinstance(args[0], String) or not isinstance(args[1], Map) or not isinstance(args[2], Map):
1450
+ return EvaluationError("update_one() takes collection, query, and update")
1451
+ collection = args[0].value
1452
+ query = _zexus_to_python(args[1])
1453
+ update = _zexus_to_python(args[2])
1454
+ result = db_ref[0].update_one(collection, query, update)
1455
+ return Integer(result)
1456
+
1457
+ def _db_update_many(*args):
1458
+ if len(args) < 3 or not isinstance(args[0], String) or not isinstance(args[1], Map) or not isinstance(args[2], Map):
1459
+ return EvaluationError("update_many() takes collection, query, and update")
1460
+ collection = args[0].value
1461
+ query = _zexus_to_python(args[1])
1462
+ update = _zexus_to_python(args[2])
1463
+ result = db_ref[0].update_many(collection, query, update)
1464
+ return Integer(result)
1465
+
1466
+ def _db_delete_one(*args):
1467
+ if len(args) < 2 or not isinstance(args[0], String) or not isinstance(args[1], Map):
1468
+ return EvaluationError("delete_one() takes collection and query")
1469
+ collection = args[0].value
1470
+ query = _zexus_to_python(args[1])
1471
+ result = db_ref[0].delete_one(collection, query)
1472
+ return Integer(result)
1473
+
1474
+ def _db_delete_many(*args):
1475
+ if len(args) < 2 or not isinstance(args[0], String) or not isinstance(args[1], Map):
1476
+ return EvaluationError("delete_many() takes collection and query")
1477
+ collection = args[0].value
1478
+ query = _zexus_to_python(args[1])
1479
+ result = db_ref[0].delete_many(collection, query)
1480
+ return Integer(result)
1481
+
1482
+ def _db_count(*args):
1483
+ if len(args) < 1 or not isinstance(args[0], String):
1484
+ return EvaluationError("count() takes collection name and optional query")
1485
+ collection = args[0].value
1486
+ query = None
1487
+ if len(args) >= 2 and isinstance(args[1], Map):
1488
+ query = _zexus_to_python(args[1])
1489
+ result = db_ref[0].count(collection, query)
1490
+ return Integer(result)
1491
+
1492
+ def _db_close(*args):
1493
+ return BooleanObj(db_ref[0].close())
1494
+
1495
+ return Map({
1496
+ String("insert_one"): Builtin(_db_insert_one, "insert_one"),
1497
+ String("insert_many"): Builtin(_db_insert_many, "insert_many"),
1498
+ String("find"): Builtin(_db_find, "find"),
1499
+ String("find_one"): Builtin(_db_find_one, "find_one"),
1500
+ String("update_one"): Builtin(_db_update_one, "update_one"),
1501
+ String("update_many"): Builtin(_db_update_many, "update_many"),
1502
+ String("delete_one"): Builtin(_db_delete_one, "delete_one"),
1503
+ String("delete_many"): Builtin(_db_delete_many, "delete_many"),
1504
+ String("count"): Builtin(_db_count, "count"),
1505
+ String("close"): Builtin(_db_close, "close"),
1506
+ String("database"): String(database),
1507
+ String("type"): String("mongodb")
1508
+ })
1509
+
1510
+ except Exception as e:
1511
+ return EvaluationError(f"mongo_connect() error: {str(e)}")
1512
+
1513
+ # HTTP Client
1514
+ def _http_get(*a):
1515
+ """HTTP GET request: http_get(url, headers?, timeout?)"""
1516
+ if len(a) < 1:
1517
+ return EvaluationError("http_get() requires at least 1 argument: url")
1518
+
1519
+ url = a[0].value if isinstance(a[0], String) else str(a[0])
1520
+ headers = None
1521
+ timeout = 30
1522
+
1523
+ # Parse optional headers (Map)
1524
+ if len(a) >= 2 and isinstance(a[1], Map):
1525
+ headers = _zexus_to_python(a[1])
1526
+
1527
+ # Parse optional timeout (Integer)
1528
+ if len(a) >= 3 and isinstance(a[2], Integer):
1529
+ timeout = a[2].value
1530
+
1531
+ try:
1532
+ from ..stdlib.http import HttpModule
1533
+ result = HttpModule.get(url, headers, timeout)
1534
+ return _python_to_zexus(result)
1535
+ except Exception as e:
1536
+ return EvaluationError(f"HTTP GET error: {str(e)}")
1537
+
1538
+ def _http_post(*a):
1539
+ """HTTP POST request: http_post(url, data, headers?, timeout?)"""
1540
+ if len(a) < 2:
1541
+ return EvaluationError("http_post() requires at least 2 arguments: url, data")
1542
+
1543
+ url = a[0].value if isinstance(a[0], String) else str(a[0])
1544
+ data = _zexus_to_python(a[1])
1545
+ headers = None
1546
+ timeout = 30
1547
+
1548
+ # Parse optional headers (Map)
1549
+ if len(a) >= 3 and isinstance(a[2], Map):
1550
+ headers = _zexus_to_python(a[2])
1551
+
1552
+ # Parse optional timeout (Integer)
1553
+ if len(a) >= 4 and isinstance(a[3], Integer):
1554
+ timeout = a[3].value
1555
+
1556
+ try:
1557
+ from ..stdlib.http import HttpModule
1558
+ # Determine if data should be sent as JSON
1559
+ json_mode = isinstance(a[1], (Map, List))
1560
+ result = HttpModule.post(url, data, headers, json=json_mode, timeout=timeout)
1561
+ return _python_to_zexus(result)
1562
+ except Exception as e:
1563
+ return EvaluationError(f"HTTP POST error: {str(e)}")
1564
+
1565
+ def _http_put(*a):
1566
+ """HTTP PUT request: http_put(url, data, headers?, timeout?)"""
1567
+ if len(a) < 2:
1568
+ return EvaluationError("http_put() requires at least 2 arguments: url, data")
1569
+
1570
+ url = a[0].value if isinstance(a[0], String) else str(a[0])
1571
+ data = _zexus_to_python(a[1])
1572
+ headers = None
1573
+ timeout = 30
1574
+
1575
+ if len(a) >= 3 and isinstance(a[2], Map):
1576
+ headers = _zexus_to_python(a[2])
1577
+
1578
+ if len(a) >= 4 and isinstance(a[3], Integer):
1579
+ timeout = a[3].value
1580
+
1581
+ try:
1582
+ from ..stdlib.http import HttpModule
1583
+ json_mode = isinstance(a[1], (Map, List))
1584
+ result = HttpModule.put(url, data, headers, json=json_mode, timeout=timeout)
1585
+ return _python_to_zexus(result)
1586
+ except Exception as e:
1587
+ return EvaluationError(f"HTTP PUT error: {str(e)}")
1588
+
1589
+ def _http_delete(*a):
1590
+ """HTTP DELETE request: http_delete(url, headers?, timeout?)"""
1591
+ if len(a) < 1:
1592
+ return EvaluationError("http_delete() requires at least 1 argument: url")
1593
+
1594
+ url = a[0].value if isinstance(a[0], String) else str(a[0])
1595
+ headers = None
1596
+ timeout = 30
1597
+
1598
+ if len(a) >= 2 and isinstance(a[1], Map):
1599
+ headers = _zexus_to_python(a[1])
1600
+
1601
+ if len(a) >= 3 and isinstance(a[2], Integer):
1602
+ timeout = a[2].value
1603
+
1604
+ try:
1605
+ from ..stdlib.http import HttpModule
1606
+ result = HttpModule.delete(url, headers, timeout)
1607
+ return _python_to_zexus(result)
1608
+ except Exception as e:
1609
+ return EvaluationError(f"HTTP DELETE error: {str(e)}")
1610
+
1611
+ # Debug
1612
+ def _debug(*a):
1613
+ """Simple debug function that works like print"""
1614
+ if len(a) == 0:
1615
+ return EvaluationError("debug() requires at least 1 argument")
1616
+ msg = a[0]
1617
+ # Convert to string representation
1618
+ if isinstance(msg, String):
1619
+ output = msg.value
1620
+ elif isinstance(msg, (Integer, Float)):
1621
+ output = str(msg.value)
1622
+ elif isinstance(msg, BooleanObj):
1623
+ output = "true" if msg.value else "false"
1624
+ elif msg == NULL:
1625
+ output = "null"
1626
+ elif isinstance(msg, (List, Map)):
1627
+ output = msg.inspect()
1628
+ else:
1629
+ output = str(msg)
1630
+ # Output the debug information
1631
+ print(output, flush=True)
1632
+ return msg # Return the original value for use in expressions
1633
+
1634
+ def _debug_log(*a):
1635
+ if len(a) == 0:
1636
+ return EvaluationError("debug_log() requires at least a message")
1637
+ msg = a[0]
1638
+ val = a[1] if len(a) > 1 else None
1639
+ return Debug.log(msg, val)
1640
+
1641
+ def _debug_trace(*a):
1642
+ if len(a) != 1 or not isinstance(a[0], String):
1643
+ return EvaluationError("debug_trace() takes exactly 1 string argument")
1644
+ return Debug.trace(a[0])
1645
+
1646
+ # String & Utility
1647
+ def _string(*a):
1648
+ from ..object import EntityInstance
1649
+ if len(a) != 1:
1650
+ return EvaluationError(f"string() takes 1 arg ({len(a)} given)")
1651
+ arg = a[0]
1652
+ if isinstance(arg, Integer) or isinstance(arg, Float):
1653
+ return String(str(arg.value))
1654
+ if isinstance(arg, String):
1655
+ return arg
1656
+ if isinstance(arg, BooleanObj):
1657
+ return String("true" if arg.value else "false")
1658
+ if isinstance(arg, (List, Map)):
1659
+ return String(arg.inspect())
1660
+ if isinstance(arg, EntityInstance):
1661
+ return String(arg.inspect())
1662
+ if arg == NULL:
1663
+ return String("null")
1664
+ # For any object with an inspect method
1665
+ if hasattr(arg, 'inspect') and callable(arg.inspect):
1666
+ return String(arg.inspect())
1667
+ return String(str(arg))
1668
+
1669
+ def _int(*a):
1670
+ """Convert value to integer"""
1671
+ if len(a) != 1:
1672
+ return EvaluationError(f"int() takes 1 arg ({len(a)} given)")
1673
+ arg = a[0]
1674
+ if isinstance(arg, Integer):
1675
+ return arg
1676
+ if isinstance(arg, Float):
1677
+ return Integer(int(arg.value))
1678
+ if isinstance(arg, String):
1679
+ try:
1680
+ return Integer(int(arg.value))
1681
+ except ValueError:
1682
+ return EvaluationError(f"Cannot convert '{arg.value}' to integer")
1683
+ if isinstance(arg, BooleanObj):
1684
+ return Integer(1 if arg.value else 0)
1685
+ return EvaluationError(f"Cannot convert {type(arg).__name__} to integer")
1686
+
1687
+ def _float(*a):
1688
+ """Convert value to float"""
1689
+ if len(a) != 1:
1690
+ return EvaluationError(f"float() takes 1 arg ({len(a)} given)")
1691
+ arg = a[0]
1692
+ if isinstance(arg, Float):
1693
+ return arg
1694
+ if isinstance(arg, Integer):
1695
+ return Float(float(arg.value))
1696
+ if isinstance(arg, String):
1697
+ try:
1698
+ return Float(float(arg.value))
1699
+ except ValueError:
1700
+ return EvaluationError(f"Cannot convert '{arg.value}' to float")
1701
+ if isinstance(arg, BooleanObj):
1702
+ return Float(1.0 if arg.value else 0.0)
1703
+ return EvaluationError(f"Cannot convert {type(arg).__name__} to float")
1704
+
1705
+ def _uppercase(*a):
1706
+ """Convert string to uppercase"""
1707
+ if len(a) != 1:
1708
+ return EvaluationError(f"uppercase() takes 1 arg ({len(a)} given)")
1709
+ arg = a[0]
1710
+ if isinstance(arg, String):
1711
+ return String(arg.value.upper())
1712
+ return EvaluationError(f"uppercase() requires a string argument")
1713
+
1714
+ def _lowercase(*a):
1715
+ """Convert string to lowercase"""
1716
+ if len(a) != 1:
1717
+ return EvaluationError(f"lowercase() takes 1 arg ({len(a)} given)")
1718
+ arg = a[0]
1719
+ if isinstance(arg, String):
1720
+ return String(arg.value.lower())
1721
+ return EvaluationError(f"lowercase() requires a string argument")
1722
+
1723
+ def _random(*a):
1724
+ """Generate random number. random() -> 0-1, random(max) -> 0 to max-1"""
1725
+ import random
1726
+ if len(a) == 0:
1727
+ return Float(random.random())
1728
+ elif len(a) == 1:
1729
+ if isinstance(a[0], Integer):
1730
+ return Integer(random.randint(0, a[0].value - 1))
1731
+ elif isinstance(a[0], Float):
1732
+ return Float(random.random() * a[0].value)
1733
+ return EvaluationError("random() argument must be a number")
1734
+ else:
1735
+ return EvaluationError(f"random() takes 0 or 1 arg ({len(a)} given)")
1736
+
1737
+ def _persist_set(*a):
1738
+ """Store a value in persistent storage: persist_set(key, value)"""
1739
+ if len(a) != 2:
1740
+ return EvaluationError(f"persist_set() takes 2 args (key, value), got {len(a)}")
1741
+ if not isinstance(a[0], String):
1742
+ return EvaluationError("persist_set() key must be a string")
1743
+
1744
+ import json
1745
+ import os
1746
+
1747
+ key = a[0].value
1748
+ value = a[1]
1749
+
1750
+ # Create persistence directory if it doesn't exist
1751
+ persist_dir = os.path.join(os.getcwd(), '.zexus_persist')
1752
+ os.makedirs(persist_dir, exist_ok=True)
1753
+
1754
+ # Convert value to JSON-serializable format
1755
+ json_value = _to_python_value(value)
1756
+
1757
+ # Save to file
1758
+ persist_file = os.path.join(persist_dir, f'{key}.json')
1759
+ try:
1760
+ with open(persist_file, 'w') as f:
1761
+ json.dump(json_value, f)
1762
+ return TRUE
1763
+ except Exception as e:
1764
+ return EvaluationError(f"persist_set() error: {str(e)}")
1765
+
1766
+ def _persist_get(*a):
1767
+ """Retrieve a value from persistent storage: persist_get(key)"""
1768
+ if len(a) != 1:
1769
+ return EvaluationError(f"persist_get() takes 1 arg (key), got {len(a)}")
1770
+ if not isinstance(a[0], String):
1771
+ return EvaluationError("persist_get() key must be a string")
1772
+
1773
+ import json
1774
+ import os
1775
+
1776
+ key = a[0].value
1777
+ persist_dir = os.path.join(os.getcwd(), '.zexus_persist')
1778
+ persist_file = os.path.join(persist_dir, f'{key}.json')
1779
+
1780
+ if not os.path.exists(persist_file):
1781
+ return NULL
1782
+
1783
+ try:
1784
+ with open(persist_file, 'r') as f:
1785
+ json_value = json.load(f)
1786
+
1787
+ # Convert back to Zexus object
1788
+ return _from_python_value(json_value)
1789
+ except Exception as e:
1790
+ return EvaluationError(f"persist_get() error: {str(e)}")
1791
+
1792
+ def _to_python_value(obj):
1793
+ """Helper to convert Zexus object to Python value"""
1794
+ from ..security import EntityInstance as SecurityEntityInstance
1795
+
1796
+ if isinstance(obj, String):
1797
+ return obj.value
1798
+ elif isinstance(obj, (Integer, Float)):
1799
+ return obj.value
1800
+ elif isinstance(obj, BooleanObj):
1801
+ return obj.value
1802
+ elif isinstance(obj, List):
1803
+ return [_to_python_value(v) for v in obj.elements]
1804
+ elif isinstance(obj, Map):
1805
+ return {str(k): _to_python_value(v) for k, v in obj.pairs.items()}
1806
+ elif isinstance(obj, SecurityEntityInstance):
1807
+ # Convert entity to a dict with its properties
1808
+ result = {}
1809
+ for key, value in obj.data.items():
1810
+ result[key] = _to_python_value(value)
1811
+ return result
1812
+ elif obj == NULL:
1813
+ return None
1814
+ else:
1815
+ return str(obj)
1816
+
1817
+ def _from_python_value(val):
1818
+ """Helper to convert Python value to Zexus object"""
1819
+ if isinstance(val, bool):
1820
+ return BooleanObj(val)
1821
+ elif isinstance(val, int):
1822
+ return Integer(val)
1823
+ elif isinstance(val, float):
1824
+ return Float(val)
1825
+ elif isinstance(val, str):
1826
+ return String(val)
1827
+ elif isinstance(val, list):
1828
+ return List([_from_python_value(v) for v in val])
1829
+ elif isinstance(val, dict):
1830
+ # Convert dict to Map with String keys
1831
+ pairs = {}
1832
+ for k, v in val.items():
1833
+ key = String(str(k)) if not isinstance(k, str) else String(k)
1834
+ pairs[key] = _from_python_value(v)
1835
+ return Map(pairs)
1836
+ elif val is None:
1837
+ return NULL
1838
+ else:
1839
+ return String(str(val))
1840
+
1841
+ def _len(*a):
1842
+ if len(a) != 1:
1843
+ return EvaluationError("len() takes 1 arg")
1844
+ arg = a[0]
1845
+ if isinstance(arg, String):
1846
+ return Integer(len(arg.value))
1847
+ if isinstance(arg, List):
1848
+ return Integer(len(arg.elements))
1849
+ # Handle Python list (shouldn't happen, but defensive)
1850
+ if isinstance(arg, list):
1851
+ return Integer(len(arg))
1852
+ arg_type = arg.type() if hasattr(arg, 'type') else type(arg).__name__
1853
+ return EvaluationError(f"len() not supported for {arg_type}")
1854
+
1855
+ def _type(*a):
1856
+ """Return the type name of the argument"""
1857
+ if len(a) != 1:
1858
+ return EvaluationError("type() takes exactly 1 argument")
1859
+ arg = a[0]
1860
+ if isinstance(arg, Integer):
1861
+ return String("Integer")
1862
+ elif isinstance(arg, Float):
1863
+ return String("Float")
1864
+ elif isinstance(arg, String):
1865
+ return String("String")
1866
+ elif isinstance(arg, BooleanObj):
1867
+ return String("Boolean")
1868
+ elif isinstance(arg, List):
1869
+ return String("List")
1870
+ elif isinstance(arg, Map):
1871
+ return String("Map")
1872
+ elif isinstance(arg, Action):
1873
+ return String("Action")
1874
+ elif isinstance(arg, LambdaFunction):
1875
+ return String("Lambda")
1876
+ elif isinstance(arg, Builtin):
1877
+ return String("Builtin")
1878
+ elif isinstance(arg, Null):
1879
+ return String("Null")
1880
+ else:
1881
+ return String(type(arg).__name__)
1882
+
1883
+ # List Utils (Builtin versions of methods)
1884
+ def _first(*a):
1885
+ if not isinstance(a[0], List):
1886
+ return EvaluationError("first() expects a list")
1887
+ return a[0].elements[0] if a[0].elements else NULL
1888
+
1889
+ def _rest(*a):
1890
+ if not isinstance(a[0], List):
1891
+ return EvaluationError("rest() expects a list")
1892
+ return List(a[0].elements[1:]) if len(a[0].elements) > 0 else List([])
1893
+
1894
+ def _push(*a):
1895
+ if len(a) != 2 or not isinstance(a[0], List):
1896
+ return EvaluationError("push(list, item)")
1897
+ return List(a[0].elements + [a[1]])
1898
+
1899
+ def _append(*a):
1900
+ """Mutating append: modifies list in-place and returns it"""
1901
+ if len(a) != 2:
1902
+ return EvaluationError("append() takes 2 arguments: append(list, item)")
1903
+ if not isinstance(a[0], List):
1904
+ return EvaluationError("append() first argument must be a list")
1905
+ # Mutate the list in-place
1906
+ a[0].append(a[1])
1907
+ return a[0]
1908
+
1909
+ def _extend(*a):
1910
+ """Mutating extend: modifies list in-place by adding elements from another list"""
1911
+ if len(a) != 2:
1912
+ return EvaluationError("extend() takes 2 arguments: extend(list, other_list)")
1913
+ if not isinstance(a[0], List):
1914
+ return EvaluationError("extend() first argument must be a list")
1915
+ if not isinstance(a[1], List):
1916
+ return EvaluationError("extend() second argument must be a list")
1917
+ # Mutate the list in-place
1918
+ a[0].extend(a[1])
1919
+ return a[0]
1920
+
1921
+ def _reduce(*a):
1922
+ if len(a) < 2:
1923
+ return EvaluationError("reduce(arr, fn, [init])")
1924
+ return self._array_reduce(a[0], a[1], a[2] if len(a) > 2 else None)
1925
+
1926
+ def _map(*a):
1927
+ if len(a) != 2:
1928
+ return EvaluationError("map(arr, fn)")
1929
+ return self._array_map(a[0], a[1])
1930
+
1931
+ def _filter(*a):
1932
+ if len(a) != 2:
1933
+ return EvaluationError("filter(arr, fn)")
1934
+ return self._array_filter(a[0], a[1])
1935
+
1936
+ # File object creation (for RAII using statements)
1937
+ def _file(*a):
1938
+ if len(a) == 0 or len(a) > 2:
1939
+ return EvaluationError("file() takes 1 or 2 arguments: file(path) or file(path, mode)")
1940
+ if not isinstance(a[0], String):
1941
+ return EvaluationError("file() path must be a string")
1942
+
1943
+ from ..object import File as FileObject
1944
+ path = a[0].value
1945
+ mode = a[1].value if len(a) > 1 and isinstance(a[1], String) else 'r'
1946
+
1947
+ try:
1948
+ file_obj = FileObject(path, mode)
1949
+ file_obj.open()
1950
+ return file_obj
1951
+ except Exception as e:
1952
+ return EvaluationError(f"file() error: {str(e)}")
1953
+
1954
+ def _read_file(*a):
1955
+ """Read entire file contents as string"""
1956
+ if len(a) != 1:
1957
+ return EvaluationError("read_file() takes exactly 1 argument: path")
1958
+ if not isinstance(a[0], String):
1959
+ return EvaluationError("read_file() path must be a string")
1960
+
1961
+ import os
1962
+ path = a[0].value
1963
+
1964
+ # Normalize path
1965
+ if not os.path.isabs(path):
1966
+ path = os.path.join(os.getcwd(), path)
1967
+
1968
+ try:
1969
+ with open(path, 'r') as f:
1970
+ content = f.read()
1971
+ return String(content)
1972
+ except FileNotFoundError:
1973
+ return EvaluationError(f"File not found: {path}")
1974
+ except Exception as e:
1975
+ return EvaluationError(f"read_file() error: {str(e)}")
1976
+
1977
+ def _eval_file(*a):
1978
+ """Execute code from a file based on its extension"""
1979
+ if len(a) < 1 or len(a) > 2:
1980
+ return EvaluationError("eval_file() takes 1-2 arguments: eval_file(path) or eval_file(path, language)")
1981
+ if not isinstance(a[0], String):
1982
+ return EvaluationError("eval_file() path must be a string")
1983
+
1984
+ import os
1985
+ import subprocess
1986
+ path = a[0].value
1987
+
1988
+ import os
1989
+ import subprocess
1990
+ path = a[0].value
1991
+
1992
+ # Normalize path
1993
+ if not os.path.isabs(path):
1994
+ path = os.path.join(os.getcwd(), path)
1995
+
1996
+ # Determine language from extension or argument
1997
+ if len(a) == 2 and isinstance(a[1], String):
1998
+ language = a[1].value.lower()
1999
+ else:
2000
+ _, ext = os.path.splitext(path)
2001
+ language = ext[1:].lower() if ext else "unknown"
2002
+
2003
+ # Read file content
2004
+ try:
2005
+ with open(path, 'r') as f:
2006
+ content = f.read()
2007
+ except FileNotFoundError:
2008
+ return EvaluationError(f"File not found: {path}")
2009
+ except Exception as e:
2010
+ return EvaluationError(f"eval_file() read error: {str(e)}")
2011
+
2012
+ # Execute based on language
2013
+ if language == "zx" or language == "zexus":
2014
+ # Execute Zexus code
2015
+ from ..parser.parser import UltimateParser
2016
+ from ..lexer import Lexer
2017
+
2018
+ try:
2019
+ lexer = Lexer(content)
2020
+ parser = UltimateParser(lexer)
2021
+ program = parser.parse_program()
2022
+
2023
+ if parser.errors:
2024
+ return EvaluationError(f"Parse errors: {parser.errors[0]}")
2025
+
2026
+ # Use the current evaluator instance to execute in a new environment
2027
+ from ..object import Environment
2028
+ new_env = Environment()
2029
+
2030
+ # Copy builtins to new environment
2031
+ for name, builtin in self.builtins.items():
2032
+ new_env.set(name, builtin)
2033
+
2034
+ result = NULL
2035
+ for stmt in program.statements:
2036
+ result = self.eval_node(stmt, new_env, [])
2037
+ if is_error(result):
2038
+ return result
2039
+
2040
+ # Export all defined functions/actions to global builtins
2041
+ # This allows cross-file code reuse
2042
+ for key in new_env.store.keys():
2043
+ if key not in ['__file__', '__FILE__', '__MODULE__', '__DIR__', '__ARGS__', '__ARGV__', '__PACKAGE__']:
2044
+ val = new_env.get(key)
2045
+ if val and not is_error(val):
2046
+ # Add to builtins so it's available globally
2047
+ self.builtins[key] = val
2048
+
2049
+ return result if result else NULL
2050
+ except Exception as e:
2051
+ return EvaluationError(f"eval_file() zexus execution error: {str(e)}")
2052
+
2053
+ elif language == "py" or language == "python":
2054
+ # Execute Python code
2055
+ try:
2056
+ exec_globals = {}
2057
+ exec(content, exec_globals)
2058
+ # Return the result if there's a 'result' variable
2059
+ if 'result' in exec_globals:
2060
+ result_val = exec_globals['result']
2061
+ # Convert Python types to Zexus types
2062
+ if isinstance(result_val, str):
2063
+ return String(result_val)
2064
+ elif isinstance(result_val, int):
2065
+ return Integer(result_val)
2066
+ elif isinstance(result_val, float):
2067
+ return Float(result_val)
2068
+ elif isinstance(result_val, bool):
2069
+ return Boolean(result_val)
2070
+ elif isinstance(result_val, list):
2071
+ return List([Integer(x) if isinstance(x, int) else String(str(x)) for x in result_val])
2072
+ return NULL
2073
+ except Exception as e:
2074
+ return EvaluationError(f"eval_file() python execution error: {str(e)}")
2075
+
2076
+ elif language in ["cpp", "c++", "c", "rs", "rust", "go"]:
2077
+ # For compiled languages, try to compile and run
2078
+ return EvaluationError(f"eval_file() for {language} requires compilation - not yet implemented")
2079
+
2080
+ elif language == "js" or language == "javascript":
2081
+ # Execute JavaScript (if Node.js is available)
2082
+ try:
2083
+ result = subprocess.run(['node', '-e', content],
2084
+ capture_output=True,
2085
+ text=True,
2086
+ timeout=5)
2087
+ if result.returncode != 0:
2088
+ return EvaluationError(f"JavaScript error: {result.stderr}")
2089
+ return String(result.stdout.strip())
2090
+ except FileNotFoundError:
2091
+ return EvaluationError("Node.js not found - cannot execute JavaScript")
2092
+ except Exception as e:
2093
+ return EvaluationError(f"eval_file() js execution error: {str(e)}")
2094
+
2095
+ else:
2096
+ return EvaluationError(f"Unsupported language: {language}")
2097
+
2098
+ # Register mappings
2099
+ self.builtins.update({
2100
+ "now": Builtin(_now, "now"),
2101
+ "timestamp": Builtin(_timestamp, "timestamp"),
2102
+ "random": Builtin(_random, "random"),
2103
+ "to_hex": Builtin(_to_hex, "to_hex"),
2104
+ "from_hex": Builtin(_from_hex, "from_hex"),
2105
+ "sqrt": Builtin(_sqrt, "sqrt"),
2106
+ "file": Builtin(_file, "file"),
2107
+ "file_read_text": Builtin(_read_text, "file_read_text"),
2108
+ "file_write_text": Builtin(_write_text, "file_write_text"),
2109
+ "file_exists": Builtin(_exists, "file_exists"),
2110
+ "file_read_json": Builtin(_read_json, "file_read_json"),
2111
+ "file_write_json": Builtin(_write_json, "file_write_json"),
2112
+ "file_append": Builtin(_file_append, "file_append"),
2113
+ "file_list_dir": Builtin(_list_dir, "file_list_dir"),
2114
+ "fs_is_file": Builtin(_fs_is_file, "fs_is_file"),
2115
+ "fs_is_dir": Builtin(_fs_is_dir, "fs_is_dir"),
2116
+ "fs_mkdir": Builtin(_fs_mkdir, "fs_mkdir"),
2117
+ "fs_remove": Builtin(_fs_remove, "fs_remove"),
2118
+ "fs_rmdir": Builtin(_fs_rmdir, "fs_rmdir"),
2119
+ "fs_rename": Builtin(_fs_rename, "fs_rename"),
2120
+ "fs_copy": Builtin(_fs_copy, "fs_copy"),
2121
+ "socket_create_server": Builtin(_socket_create_server, "socket_create_server"),
2122
+ "socket_create_connection": Builtin(_socket_create_connection, "socket_create_connection"),
2123
+ "http_server": Builtin(_http_server, "http_server"),
2124
+ "sqlite_connect": Builtin(_sqlite_connect, "sqlite_connect"),
2125
+ "postgres_connect": Builtin(_postgres_connect, "postgres_connect"),
2126
+ "mysql_connect": Builtin(_mysql_connect, "mysql_connect"),
2127
+ "mongo_connect": Builtin(_mongo_connect, "mongo_connect"),
2128
+ "http_get": Builtin(_http_get, "http_get"),
2129
+ "http_post": Builtin(_http_post, "http_post"),
2130
+ "http_put": Builtin(_http_put, "http_put"),
2131
+ "http_delete": Builtin(_http_delete, "http_delete"),
2132
+ "read_file": Builtin(_read_file, "read_file"),
2133
+ "eval_file": Builtin(_eval_file, "eval_file"),
2134
+ "debug": Builtin(_debug, "debug"),
2135
+ "debug_log": Builtin(_debug_log, "debug_log"),
2136
+ "debug_trace": Builtin(_debug_trace, "debug_trace"),
2137
+ "string": Builtin(_string, "string"),
2138
+ "int": Builtin(_int, "int"),
2139
+ "float": Builtin(_float, "float"),
2140
+ "uppercase": Builtin(_uppercase, "uppercase"),
2141
+ "lowercase": Builtin(_lowercase, "lowercase"),
2142
+ "random": Builtin(_random, "random"),
2143
+ "persist_set": Builtin(_persist_set, "persist_set"),
2144
+ "persist_get": Builtin(_persist_get, "persist_get"),
2145
+ "len": Builtin(_len, "len"),
2146
+ "type": Builtin(_type, "type"),
2147
+ "first": Builtin(_first, "first"),
2148
+ "rest": Builtin(_rest, "rest"),
2149
+ "push": Builtin(_push, "push"),
2150
+ "append": Builtin(_append, "append"), # Mutating list append
2151
+ "extend": Builtin(_extend, "extend"), # Mutating list extend
2152
+ "reduce": Builtin(_reduce, "reduce"),
2153
+ "map": Builtin(_map, "map"),
2154
+ "filter": Builtin(_filter, "filter"),
2155
+ })
2156
+
2157
+ # Register concurrency builtins
2158
+ self._register_concurrency_builtins()
2159
+
2160
+ # Register blockchain builtins
2161
+ self._register_blockchain_builtins()
2162
+
2163
+ # Register verification helper builtins
2164
+ self._register_verification_builtins()
2165
+
2166
+ def _register_concurrency_builtins(self):
2167
+ """Register concurrency operations as builtin functions"""
2168
+
2169
+ def _send(*a):
2170
+ """Send value to channel: send(channel, value)"""
2171
+ if len(a) != 2:
2172
+ return EvaluationError("send() requires 2 arguments: channel, value")
2173
+
2174
+ channel = a[0]
2175
+ value = a[1]
2176
+
2177
+ # Check if it's a valid channel object
2178
+ if not hasattr(channel, 'send'):
2179
+ return EvaluationError(f"send() first argument must be a channel, got {type(channel).__name__}")
2180
+
2181
+ try:
2182
+ channel.send(value, timeout=5.0)
2183
+ return NULL # send returns nothing on success
2184
+ except Exception as e:
2185
+ return EvaluationError(f"send() error: {str(e)}")
2186
+
2187
+ def _receive(*a):
2188
+ """Receive value from channel: value = receive(channel)"""
2189
+ if len(a) != 1:
2190
+ return EvaluationError("receive() requires 1 argument: channel")
2191
+
2192
+ channel = a[0]
2193
+
2194
+ # Check if it's a valid channel object
2195
+ if not hasattr(channel, 'receive'):
2196
+ return EvaluationError(f"receive() first argument must be a channel, got {type(channel).__name__}")
2197
+
2198
+ try:
2199
+ value = channel.receive(timeout=5.0)
2200
+ return value if value is not None else NULL
2201
+ except Exception as e:
2202
+ return EvaluationError(f"receive() error: {str(e)}")
2203
+
2204
+ def _close_channel(*a):
2205
+ """Close a channel: close_channel(channel)"""
2206
+ if len(a) != 1:
2207
+ return EvaluationError("close_channel() requires 1 argument: channel")
2208
+
2209
+ channel = a[0]
2210
+
2211
+ if not hasattr(channel, 'close'):
2212
+ return EvaluationError(f"close_channel() argument must be a channel, got {type(channel).__name__}")
2213
+
2214
+ try:
2215
+ channel.close()
2216
+ return NULL
2217
+ except Exception as e:
2218
+ return EvaluationError(f"close_channel() error: {str(e)}")
2219
+
2220
+ def _async(*a):
2221
+ """Execute action asynchronously in background thread: async action_call()
2222
+
2223
+ Example: async producer()
2224
+
2225
+ Accepts either:
2226
+ 1. A Coroutine (from calling an async action)
2227
+ 2. A regular value (from calling a regular action) - will execute in thread
2228
+ """
2229
+ import threading
2230
+ import sys
2231
+
2232
+ if len(a) != 1:
2233
+ return EvaluationError("async() requires 1 argument: result of action call")
2234
+
2235
+ result = a[0]
2236
+
2237
+ # If it's already a Coroutine, start it in a thread
2238
+ if hasattr(result, '__class__') and result.__class__.__name__ == 'Coroutine':
2239
+
2240
+ def run_coroutine():
2241
+ try:
2242
+ # Prime the generator
2243
+ val = next(result.generator)
2244
+ # Execute until completion
2245
+ try:
2246
+ while True:
2247
+ val = next(result.generator)
2248
+ except StopIteration as e:
2249
+ # Coroutine completed successfully
2250
+ pass
2251
+ except Exception as e:
2252
+ # Print error to stderr for visibility
2253
+ print(f"[ASYNC ERROR] Coroutine execution failed: {str(e)}", file=sys.stderr)
2254
+ import traceback
2255
+ traceback.print_exc(file=sys.stderr)
2256
+
2257
+ thread = threading.Thread(target=run_coroutine, daemon=True)
2258
+ thread.start()
2259
+ return NULL
2260
+
2261
+ # For regular (non-async) actions, the action has already executed!
2262
+ # This is because producer() executes immediately and returns its result.
2263
+ # So async(producer()) just receives the result.
2264
+ # We need a different approach - we can't retroactively make it async.
2265
+
2266
+ # The solution: If they want async execution, the action itself must be async.
2267
+ # For now, just return NULL to indicate "completed" (it already ran).
2268
+ return NULL
2269
+
2270
+ def _sleep(*a):
2271
+ """Sleep for specified seconds: sleep(seconds)"""
2272
+ import time
2273
+
2274
+ if len(a) != 1:
2275
+ return EvaluationError("sleep() requires 1 argument: seconds")
2276
+
2277
+ seconds = a[0]
2278
+ if isinstance(seconds, (Integer, Float)):
2279
+ time.sleep(float(seconds.value))
2280
+ return NULL
2281
+
2282
+ return EvaluationError(f"sleep() argument must be a number, got {type(seconds).__name__}")
2283
+
2284
+ def _spawn(*a):
2285
+ """Spawn an async task and return a coroutine that can be awaited: task = spawn async_func()
2286
+
2287
+ Example:
2288
+ async function asyncTask(id) {
2289
+ await sleep(1)
2290
+ return id * 10
2291
+ }
2292
+ let task1 = spawn asyncTask(1)
2293
+ let result = await task1
2294
+ """
2295
+ import threading
2296
+ import sys
2297
+
2298
+ if len(a) != 1:
2299
+ return EvaluationError("spawn() requires 1 argument: coroutine or async function call result")
2300
+
2301
+ result = a[0]
2302
+
2303
+ # If it's a Coroutine, we need to wrap it for async execution
2304
+ from ..object import Coroutine
2305
+ if isinstance(result, Coroutine):
2306
+ # Create a wrapper coroutine that executes in background
2307
+ # The original coroutine will be run in a thread
2308
+ task_result = [None] # Mutable container to store result
2309
+ task_error = [None]
2310
+ task_complete = [False]
2311
+
2312
+ def run_coroutine():
2313
+ try:
2314
+ # Prime the generator
2315
+ next(result.generator)
2316
+ # Execute until completion
2317
+ try:
2318
+ while True:
2319
+ next(result.generator)
2320
+ except StopIteration as e:
2321
+ # Coroutine completed, store result
2322
+ task_result[0] = e.value if hasattr(e, 'value') else NULL
2323
+ task_complete[0] = True
2324
+ except Exception as e:
2325
+ # Store error
2326
+ task_error[0] = e
2327
+ task_complete[0] = True
2328
+ print(f"[SPAWN ERROR] Task execution failed: {str(e)}", file=sys.stderr)
2329
+ import traceback
2330
+ traceback.print_exc(file=sys.stderr)
2331
+
2332
+ # Start execution in background thread
2333
+ thread = threading.Thread(target=run_coroutine, daemon=False)
2334
+ thread.start()
2335
+
2336
+ # Create a new coroutine that waits for the result
2337
+ def result_generator():
2338
+ yield None # Make it a generator
2339
+ # Wait for completion
2340
+ thread.join(timeout=30) # 30 second timeout
2341
+ if task_error[0]:
2342
+ raise task_error[0]
2343
+ return task_result[0] if task_result[0] is not None else NULL
2344
+
2345
+ return Coroutine(result_generator(), result.fn if hasattr(result, 'fn') else None)
2346
+
2347
+ # If not a coroutine, return error
2348
+ return EvaluationError(f"spawn() argument must be a coroutine (async function call), got {type(result).__name__}")
2349
+
2350
+ def _wait_group(*a):
2351
+ """Create a wait group for synchronizing async operations: wg = wait_group()
2352
+
2353
+ Example:
2354
+ let wg = wait_group()
2355
+ wg.add(2) # Expecting 2 tasks
2356
+ async task1()
2357
+ async task2()
2358
+ wg.wait() # Blocks until both tasks call wg.done()
2359
+ """
2360
+ from ..concurrency_system import WaitGroup
2361
+
2362
+ if len(a) != 0:
2363
+ return EvaluationError("wait_group() takes no arguments")
2364
+
2365
+ return WaitGroup()
2366
+
2367
+ def _wg_add(*a):
2368
+ """Add delta to wait group counter: wg.add(delta)"""
2369
+ if len(a) != 2:
2370
+ return EvaluationError("wg_add() requires 2 arguments: wait_group, delta")
2371
+
2372
+ wg = a[0]
2373
+ delta_obj = a[1]
2374
+
2375
+ if not hasattr(wg, 'add'):
2376
+ return EvaluationError(f"wg_add() first argument must be a WaitGroup, got {type(wg).__name__}")
2377
+
2378
+ if isinstance(delta_obj, Integer):
2379
+ try:
2380
+ wg.add(delta_obj.value)
2381
+ return NULL
2382
+ except Exception as e:
2383
+ return EvaluationError(f"wg_add() error: {str(e)}")
2384
+ elif isinstance(delta_obj, int):
2385
+ try:
2386
+ wg.add(delta_obj)
2387
+ return NULL
2388
+ except Exception as e:
2389
+ return EvaluationError(f"wg_add() error: {str(e)}")
2390
+
2391
+ return EvaluationError(f"wg_add() delta must be an integer, got {type(delta_obj).__name__}")
2392
+
2393
+ def _wg_done(*a):
2394
+ """Decrement wait group counter: wg.done()"""
2395
+ if len(a) != 1:
2396
+ return EvaluationError("wg_done() requires 1 argument: wait_group")
2397
+
2398
+ wg = a[0]
2399
+
2400
+ if not hasattr(wg, 'done'):
2401
+ return EvaluationError(f"wg_done() argument must be a WaitGroup, got {type(wg).__name__}")
2402
+
2403
+ try:
2404
+ wg.done()
2405
+ return NULL
2406
+ except Exception as e:
2407
+ return EvaluationError(f"wg_done() error: {str(e)}")
2408
+
2409
+ def _wg_wait(*a):
2410
+ """Wait for wait group counter to reach zero: wg.wait()"""
2411
+ if len(a) < 1 or len(a) > 2:
2412
+ return EvaluationError("wg_wait() requires 1 or 2 arguments: wait_group [, timeout]")
2413
+
2414
+ wg = a[0]
2415
+ timeout = None
2416
+
2417
+ if len(a) == 2:
2418
+ timeout_obj = a[1]
2419
+ if isinstance(timeout_obj, (Integer, Float)):
2420
+ timeout = float(timeout_obj.value)
2421
+ elif isinstance(timeout_obj, (int, float)):
2422
+ timeout = float(timeout_obj)
2423
+ else:
2424
+ return EvaluationError(f"wg_wait() timeout must be a number, got {type(timeout_obj).__name__}")
2425
+
2426
+ if not hasattr(wg, 'wait'):
2427
+ return EvaluationError(f"wg_wait() first argument must be a WaitGroup, got {type(wg).__name__}")
2428
+
2429
+ try:
2430
+ success = wg.wait(timeout=timeout)
2431
+ return TRUE if success else FALSE
2432
+ except Exception as e:
2433
+ return EvaluationError(f"wg_wait() error: {str(e)}")
2434
+
2435
+ def _barrier(*a):
2436
+ """Create a barrier for synchronizing N tasks: barrier = barrier(parties)
2437
+
2438
+ Example:
2439
+ let barrier = barrier(2) # Wait for 2 tasks
2440
+ async task1() # Will call barrier.wait()
2441
+ async task2() # Will call barrier.wait()
2442
+ # Both released once both reach barrier
2443
+ """
2444
+ from ..concurrency_system import Barrier
2445
+
2446
+ if len(a) != 1:
2447
+ return EvaluationError("barrier() requires 1 argument: parties")
2448
+
2449
+ parties_obj = a[0]
2450
+ if isinstance(parties_obj, Integer):
2451
+ try:
2452
+ return Barrier(parties=parties_obj.value)
2453
+ except Exception as e:
2454
+ return EvaluationError(f"barrier() error: {str(e)}")
2455
+
2456
+ return EvaluationError(f"barrier() parties must be an integer, got {type(parties_obj).__name__}")
2457
+
2458
+ def _barrier_wait(*a):
2459
+ """Wait at barrier until all parties arrive: barrier.wait()"""
2460
+ if len(a) < 1 or len(a) > 2:
2461
+ return EvaluationError("barrier_wait() requires 1 or 2 arguments: barrier [, timeout]")
2462
+
2463
+ barrier = a[0]
2464
+ timeout = None
2465
+
2466
+ if len(a) == 2:
2467
+ timeout_obj = a[1]
2468
+ if isinstance(timeout_obj, (Integer, Float)):
2469
+ timeout = float(timeout_obj.value)
2470
+ else:
2471
+ return EvaluationError(f"barrier_wait() timeout must be a number, got {type(timeout_obj).__name__}")
2472
+
2473
+ if not hasattr(barrier, 'wait'):
2474
+ return EvaluationError(f"barrier_wait() first argument must be a Barrier, got {type(barrier).__name__}")
2475
+
2476
+ try:
2477
+ generation = barrier.wait(timeout=timeout)
2478
+ return Integer(generation)
2479
+ except Exception as e:
2480
+ return EvaluationError(f"barrier_wait() error: {str(e)}")
2481
+
2482
+ def _barrier_reset(*a):
2483
+ """Reset barrier to initial state: barrier.reset()"""
2484
+ if len(a) != 1:
2485
+ return EvaluationError("barrier_reset() requires 1 argument: barrier")
2486
+
2487
+ barrier = a[0]
2488
+
2489
+ if not hasattr(barrier, 'reset'):
2490
+ return EvaluationError(f"barrier_reset() argument must be a Barrier, got {type(barrier).__name__}")
2491
+
2492
+ try:
2493
+ barrier.reset()
2494
+ return NULL
2495
+ except Exception as e:
2496
+ return EvaluationError(f"barrier_reset() error: {str(e)}")
2497
+
2498
+ # Register concurrency builtins
2499
+ self.builtins.update({
2500
+ "send": Builtin(_send, "send"),
2501
+ "receive": Builtin(_receive, "receive"),
2502
+ "close_channel": Builtin(_close_channel, "close_channel"),
2503
+ "async": Builtin(_async, "async"),
2504
+ "sleep": Builtin(_sleep, "sleep"),
2505
+ "spawn": Builtin(_spawn, "spawn"),
2506
+ "wait_group": Builtin(_wait_group, "wait_group"),
2507
+ "wg_add": Builtin(_wg_add, "wg_add"),
2508
+ "wg_done": Builtin(_wg_done, "wg_done"),
2509
+ "wg_wait": Builtin(_wg_wait, "wg_wait"),
2510
+ "barrier": Builtin(_barrier, "barrier"),
2511
+ "barrier_wait": Builtin(_barrier_wait, "barrier_wait"),
2512
+ "barrier_reset": Builtin(_barrier_reset, "barrier_reset"),
2513
+ })
2514
+
2515
+ def _register_blockchain_builtins(self):
2516
+ """Register blockchain cryptographic and utility functions"""
2517
+ from ..blockchain.crypto import CryptoPlugin
2518
+ from ..blockchain.transaction import get_current_tx, create_tx_context
2519
+
2520
+ # hash(data, algorithm?)
2521
+ def _hash(*a):
2522
+ if len(a) < 1:
2523
+ return EvaluationError("hash() requires at least 1 argument: data, [algorithm]")
2524
+
2525
+ data = a[0].value if hasattr(a[0], 'value') else str(a[0])
2526
+ algorithm = a[1].value if len(a) > 1 and hasattr(a[1], 'value') else 'SHA256'
2527
+
2528
+ try:
2529
+ result = CryptoPlugin.hash_data(data, algorithm)
2530
+ return String(result)
2531
+ except Exception as e:
2532
+ return EvaluationError(f"Hash error: {str(e)}")
2533
+
2534
+ # keccak256(data)
2535
+ def _keccak256(*a):
2536
+ if len(a) != 1:
2537
+ return EvaluationError("keccak256() expects 1 argument: data")
2538
+
2539
+ data = a[0].value if hasattr(a[0], 'value') else str(a[0])
2540
+
2541
+ try:
2542
+ result = CryptoPlugin.keccak256(data)
2543
+ return String(result)
2544
+ except Exception as e:
2545
+ return EvaluationError(f"Keccak256 error: {str(e)}")
2546
+
2547
+ # signature(data, private_key, algorithm?)
2548
+ def _signature(*a):
2549
+ if len(a) < 2:
2550
+ return EvaluationError("signature() requires at least 2 arguments: data, private_key, [algorithm]")
2551
+
2552
+ data = a[0].value if hasattr(a[0], 'value') else str(a[0])
2553
+ private_key = a[1].value if hasattr(a[1], 'value') else str(a[1])
2554
+ algorithm = a[2].value if len(a) > 2 and hasattr(a[2], 'value') else 'ECDSA'
2555
+
2556
+ try:
2557
+ result = CryptoPlugin.sign_data(data, private_key, algorithm)
2558
+ return String(result)
2559
+ except Exception as e:
2560
+ return EvaluationError(f"Signature error: {str(e)}")
2561
+
2562
+ # verify_sig(data, signature, public_key, algorithm?)
2563
+ def _verify_sig(*a):
2564
+ if len(a) < 3:
2565
+ return EvaluationError("verify_sig() requires at least 3 arguments: data, signature, public_key, [algorithm]")
2566
+
2567
+ data = a[0].value if hasattr(a[0], 'value') else str(a[0])
2568
+ signature = a[1].value if hasattr(a[1], 'value') else str(a[1])
2569
+ public_key = a[2].value if hasattr(a[2], 'value') else str(a[2])
2570
+ algorithm = a[3].value if len(a) > 3 and hasattr(a[3], 'value') else 'ECDSA'
2571
+
2572
+ try:
2573
+ result = CryptoPlugin.verify_signature(data, signature, public_key, algorithm)
2574
+ return TRUE if result else FALSE
2575
+ except Exception as e:
2576
+ return EvaluationError(f"Verification error: {str(e)}")
2577
+
2578
+ # tx object - returns transaction context
2579
+ def _tx(*a):
2580
+ # Get or create TX context
2581
+ tx = get_current_tx()
2582
+ if tx is None:
2583
+ tx = create_tx_context(caller="system", gas_limit=1000000)
2584
+
2585
+ # Return as Map object
2586
+ return Map({
2587
+ String("caller"): String(tx.caller),
2588
+ String("timestamp"): Integer(int(tx.timestamp)),
2589
+ String("block_hash"): String(tx.block_hash),
2590
+ String("gas_used"): Integer(tx.gas_used),
2591
+ String("gas_remaining"): Integer(tx.gas_remaining),
2592
+ String("gas_limit"): Integer(tx.gas_limit)
2593
+ })
2594
+
2595
+ # gas object - returns gas tracking info
2596
+ def _gas(*a):
2597
+ # Get or create TX context
2598
+ tx = get_current_tx()
2599
+ if tx is None:
2600
+ tx = create_tx_context(caller="system", gas_limit=1000000)
2601
+
2602
+ # Return as Map object
2603
+ return Map({
2604
+ String("used"): Integer(tx.gas_used),
2605
+ String("remaining"): Integer(tx.gas_remaining),
2606
+ String("limit"): Integer(tx.gas_limit)
2607
+ })
2608
+
2609
+ self.builtins.update({
2610
+ "hash": Builtin(_hash, "hash"),
2611
+ "keccak256": Builtin(_keccak256, "keccak256"),
2612
+ "signature": Builtin(_signature, "signature"),
2613
+ "verify_sig": Builtin(_verify_sig, "verify_sig"),
2614
+ "tx": Builtin(_tx, "tx"),
2615
+ "gas": Builtin(_gas, "gas"),
2616
+ })
2617
+
2618
+ # Register advanced feature builtins
2619
+ self._register_advanced_feature_builtins()
2620
+
2621
+ def _register_advanced_feature_builtins(self):
2622
+ """Register builtins for persistence, policy, and dependency injection"""
2623
+
2624
+ # === PERSISTENCE & MEMORY BUILTINS ===
2625
+
2626
+ def _persistent_set(*a):
2627
+ """Set a persistent variable: persistent_set(name, value)"""
2628
+ if len(a) != 2:
2629
+ return EvaluationError("persistent_set() takes 2 arguments: name, value")
2630
+ if not isinstance(a[0], String):
2631
+ return EvaluationError("persistent_set() name must be a string")
2632
+
2633
+ # Get current environment from evaluator context
2634
+ env = getattr(self, '_current_env', None)
2635
+ if env and hasattr(env, 'set_persistent'):
2636
+ name = a[0].value
2637
+ value = a[1]
2638
+ env.set_persistent(name, value)
2639
+ return String(f"Persistent variable '{name}' set")
2640
+ return EvaluationError("Persistence not enabled in this environment")
2641
+
2642
+ def _persistent_get(*a):
2643
+ """Get a persistent variable: persistent_get(name, [default])"""
2644
+ if len(a) < 1 or len(a) > 2:
2645
+ return EvaluationError("persistent_get() takes 1 or 2 arguments: name, [default]")
2646
+ if not isinstance(a[0], String):
2647
+ return EvaluationError("persistent_get() name must be a string")
2648
+
2649
+ env = getattr(self, '_current_env', None)
2650
+ if env and hasattr(env, 'get_persistent'):
2651
+ name = a[0].value
2652
+ default = a[1] if len(a) > 1 else NULL
2653
+ value = env.get_persistent(name, default)
2654
+ return value if value is not None else default
2655
+ return NULL
2656
+
2657
+ def _persistent_delete(*a):
2658
+ """Delete a persistent variable: persistent_delete(name)"""
2659
+ if len(a) != 1:
2660
+ return EvaluationError("persistent_delete() takes 1 argument: name")
2661
+ if not isinstance(a[0], String):
2662
+ return EvaluationError("persistent_delete() name must be a string")
2663
+
2664
+ env = getattr(self, '_current_env', None)
2665
+ if env and hasattr(env, 'delete_persistent'):
2666
+ name = a[0].value
2667
+ env.delete_persistent(name)
2668
+ return String(f"Persistent variable '{name}' deleted")
2669
+ return NULL
2670
+
2671
+ def _memory_stats(*a):
2672
+ """Get memory tracking statistics: memory_stats()"""
2673
+ import sys
2674
+ import gc
2675
+
2676
+ # Get process memory usage
2677
+ try:
2678
+ import psutil
2679
+ process = psutil.Process()
2680
+ mem_info = process.memory_info()
2681
+ current_bytes = mem_info.rss # Resident Set Size
2682
+ peak_bytes = getattr(mem_info, 'peak_wset', mem_info.rss) # Windows has peak_wset
2683
+ except (ImportError, AttributeError):
2684
+ # Fallback: use Python's internal memory tracking
2685
+ current_bytes = sys.getsizeof(gc.get_objects())
2686
+ peak_bytes = current_bytes
2687
+
2688
+ # Get GC statistics
2689
+ gc_count = len(gc.get_objects())
2690
+ gc_collections = sum(gc.get_count())
2691
+
2692
+ # Get environment-specific tracking if available
2693
+ env = getattr(self, '_current_env', None)
2694
+ tracked_objects = 0
2695
+ if env and hasattr(env, 'get_memory_stats'):
2696
+ stats = env.get_memory_stats()
2697
+ tracked_objects = stats.get("tracked_objects", 0)
2698
+
2699
+ return Map({
2700
+ String("current"): Integer(current_bytes),
2701
+ String("peak"): Integer(peak_bytes),
2702
+ String("gc_count"): Integer(gc_collections),
2703
+ String("objects"): Integer(gc_count),
2704
+ String("tracked_objects"): Integer(tracked_objects)
2705
+ })
2706
+
2707
+ # === POLICY & PROTECTION BUILTINS ===
2708
+
2709
+ def _create_policy(*a):
2710
+ """Create a protection policy: create_policy(name, rules_map)"""
2711
+ if len(a) != 2:
2712
+ return EvaluationError("create_policy() takes 2 arguments: name, rules")
2713
+ if not isinstance(a[0], String):
2714
+ return EvaluationError("create_policy() name must be a string")
2715
+ if not isinstance(a[1], Map):
2716
+ return EvaluationError("create_policy() rules must be a Map")
2717
+
2718
+ from ..policy_engine import get_policy_registry, PolicyBuilder, EnforcementLevel
2719
+
2720
+ name = a[0].value
2721
+ rules = a[1].pairs
2722
+
2723
+ builder = PolicyBuilder(name)
2724
+ builder.set_enforcement(EnforcementLevel.STRICT)
2725
+
2726
+ # Parse rules from Map
2727
+ for key, value in rules.items():
2728
+ key_str = key.value if hasattr(key, 'value') else str(key)
2729
+ if key_str == "verify" and isinstance(value, List):
2730
+ for cond in value.elements:
2731
+ cond_str = cond.value if hasattr(cond, 'value') else str(cond)
2732
+ builder.add_verify_rule(cond_str)
2733
+ elif key_str == "restrict" and isinstance(value, Map):
2734
+ for field, constraints in value.pairs.items():
2735
+ field_str = field.value if hasattr(field, 'value') else str(field)
2736
+ constraint_list = []
2737
+ if isinstance(constraints, List):
2738
+ for c in constraints.elements:
2739
+ constraint_list.append(c.value if hasattr(c, 'value') else str(c))
2740
+ builder.add_restrict_rule(field_str, constraint_list)
2741
+
2742
+ policy = builder.build()
2743
+ registry = get_policy_registry()
2744
+ registry.register(name, policy)
2745
+
2746
+ return String(f"Policy '{name}' created and registered")
2747
+
2748
+ def _check_policy(*a):
2749
+ """Check policy enforcement: check_policy(target, context_map)"""
2750
+ if len(a) != 2:
2751
+ return EvaluationError("check_policy() takes 2 arguments: target, context")
2752
+ if not isinstance(a[0], String):
2753
+ return EvaluationError("check_policy() target must be a string")
2754
+ if not isinstance(a[1], Map):
2755
+ return EvaluationError("check_policy() context must be a Map")
2756
+
2757
+ from ..policy_engine import get_policy_registry
2758
+
2759
+ target = a[0].value
2760
+ context = {}
2761
+ for k, v in a[1].pairs.items():
2762
+ key_str = k.value if hasattr(k, 'value') else str(k)
2763
+ val = v.value if hasattr(v, 'value') else v
2764
+ context[key_str] = val
2765
+
2766
+ registry = get_policy_registry()
2767
+ policy = registry.get(target)
2768
+
2769
+ if policy is None:
2770
+ return String(f"No policy found for '{target}'")
2771
+
2772
+ result = policy.enforce(context)
2773
+ if result["success"]:
2774
+ return TRUE
2775
+ else:
2776
+ return String(f"Policy violation: {result['message']}")
2777
+
2778
+ # === DEPENDENCY INJECTION BUILTINS ===
2779
+
2780
+ def _register_dependency(*a):
2781
+ """Register a dependency: register_dependency(name, value, [module])"""
2782
+ if len(a) < 2 or len(a) > 3:
2783
+ return EvaluationError("register_dependency() takes 2 or 3 arguments: name, value, [module]")
2784
+ if not isinstance(a[0], String):
2785
+ return EvaluationError("register_dependency() name must be a string")
2786
+
2787
+ from ..dependency_injection import get_di_registry
2788
+
2789
+ name = a[0].value
2790
+ value = a[1]
2791
+ module = a[2].value if len(a) > 2 and isinstance(a[2], String) else "__main__"
2792
+
2793
+ registry = get_di_registry()
2794
+ container = registry.get_container(module)
2795
+ if not container:
2796
+ # Create container if it doesn't exist
2797
+ registry.register_module(module)
2798
+ container = registry.get_container(module)
2799
+ # Declare and provide the dependency
2800
+ container.declare_dependency(name, "any", False)
2801
+ container.provide(name, value)
2802
+
2803
+ return String(f"Dependency '{name}' registered in module '{module}'")
2804
+
2805
+ def _mock_dependency(*a):
2806
+ """Create a mock for dependency: mock_dependency(name, mock_value, [module])"""
2807
+ if len(a) < 2 or len(a) > 3:
2808
+ return EvaluationError("mock_dependency() takes 2 or 3 arguments: name, mock, [module]")
2809
+ if not isinstance(a[0], String):
2810
+ return EvaluationError("mock_dependency() name must be a string")
2811
+
2812
+ from ..dependency_injection import get_di_registry, ExecutionMode
2813
+
2814
+ name = a[0].value
2815
+ mock = a[1]
2816
+ module = a[2].value if len(a) > 2 and isinstance(a[2], String) else "__main__"
2817
+
2818
+ registry = get_di_registry()
2819
+ container = registry.get_container(module)
2820
+ if not container:
2821
+ # Create container if it doesn't exist
2822
+ registry.register_module(module)
2823
+ container = registry.get_container(module)
2824
+ # Declare and mock the dependency
2825
+ if name not in container.contracts:
2826
+ container.declare_dependency(name, "any", False)
2827
+ container.mock(name, mock)
2828
+
2829
+ return String(f"Mock for '{name}' registered in module '{module}'")
2830
+
2831
+ def _clear_mocks(*a):
2832
+ """Clear all mocks: clear_mocks([module])"""
2833
+ from ..dependency_injection import get_di_registry
2834
+
2835
+ module = a[0].value if len(a) > 0 and isinstance(a[0], String) else "__main__"
2836
+
2837
+ registry = get_di_registry()
2838
+ container = registry.get_container(module)
2839
+ if container:
2840
+ container.clear_mocks()
2841
+ return String(f"All mocks cleared in module '{module}'")
2842
+ return String(f"Module '{module}' not registered")
2843
+
2844
+ def _set_execution_mode(*a):
2845
+ """Set execution mode: set_execution_mode(mode_string)"""
2846
+ if len(a) != 1:
2847
+ return EvaluationError("set_execution_mode() takes 1 argument: mode")
2848
+ if not isinstance(a[0], String):
2849
+ return EvaluationError("set_execution_mode() mode must be a string")
2850
+
2851
+ from ..dependency_injection import ExecutionMode
2852
+
2853
+ mode_str = a[0].value.upper()
2854
+ try:
2855
+ mode = ExecutionMode[mode_str]
2856
+ # Store in current environment
2857
+ env = getattr(self, '_current_env', None)
2858
+ if env:
2859
+ env.set("__execution_mode__", String(mode_str))
2860
+ return String(f"Execution mode set to {mode.name}")
2861
+ except KeyError:
2862
+ return EvaluationError(f"Invalid execution mode: {mode_str}. Valid: PRODUCTION, DEBUG, TEST, SANDBOX")
2863
+
2864
+ # Register all advanced feature builtins
2865
+ self.builtins.update({
2866
+ # Persistence
2867
+ "persistent_set": Builtin(_persistent_set, "persistent_set"),
2868
+ "persistent_get": Builtin(_persistent_get, "persistent_get"),
2869
+ "persistent_delete": Builtin(_persistent_delete, "persistent_delete"),
2870
+ "memory_stats": Builtin(_memory_stats, "memory_stats"),
2871
+ # Policy
2872
+ "create_policy": Builtin(_create_policy, "create_policy"),
2873
+ "check_policy": Builtin(_check_policy, "check_policy"),
2874
+ # Dependency Injection
2875
+ "register_dependency": Builtin(_register_dependency, "register_dependency"),
2876
+ "mock_dependency": Builtin(_mock_dependency, "mock_dependency"),
2877
+ "clear_mocks": Builtin(_clear_mocks, "clear_mocks"),
2878
+ "set_execution_mode": Builtin(_set_execution_mode, "set_execution_mode"),
2879
+ })
2880
+
2881
+ def _register_main_entry_point_builtins(self):
2882
+ """Register builtins for main entry point pattern and continuous execution"""
2883
+ import signal
2884
+ import time as time_module
2885
+
2886
+ # Storage for lifecycle hooks and signal handlers
2887
+ self._lifecycle_hooks = {'on_start': [], 'on_exit': []}
2888
+ self._signal_handlers = {}
2889
+
2890
+ def _run(*a):
2891
+ """
2892
+ Keep the program running until interrupted (Ctrl+C).
2893
+ Useful for servers, event loops, or long-running programs.
2894
+
2895
+ Enhanced version supports:
2896
+ - callback with arguments
2897
+ - interval timing
2898
+ - lifecycle hooks (on_start, on_exit)
2899
+
2900
+ Usage:
2901
+ if __MODULE__ == "__main__":
2902
+ run()
2903
+
2904
+ or with a callback:
2905
+ if __MODULE__ == "__main__":
2906
+ run(lambda: print("Still running..."))
2907
+
2908
+ or with callback and interval:
2909
+ if __MODULE__ == "__main__":
2910
+ run(callback, 0.5) # Run every 500ms
2911
+
2912
+ or with callback and arguments:
2913
+ if __MODULE__ == "__main__":
2914
+ run(server.process_requests, 1.0, [port, host])
2915
+ """
2916
+ callback = None
2917
+ interval = 1.0 # Default interval in seconds
2918
+ callback_args = []
2919
+
2920
+ if len(a) >= 1:
2921
+ # First argument is the callback function
2922
+ callback = a[0]
2923
+ if not isinstance(callback, (Action, LambdaFunction)):
2924
+ return EvaluationError("run() callback must be a function")
2925
+
2926
+ if len(a) >= 2:
2927
+ # Second argument is the interval
2928
+ interval_obj = a[1]
2929
+ if isinstance(interval_obj, (Integer, Float)):
2930
+ interval = float(interval_obj.value)
2931
+ else:
2932
+ return EvaluationError("run() interval must be a number")
2933
+
2934
+ if len(a) >= 3:
2935
+ # Third argument is callback arguments
2936
+ if isinstance(a[2], List):
2937
+ callback_args = a[2].elements
2938
+ else:
2939
+ callback_args = [a[2]]
2940
+
2941
+ print("🚀 Program running. Press Ctrl+C to exit.")
2942
+
2943
+ # Execute on_start hooks
2944
+ for hook in self._lifecycle_hooks.get('on_start', []):
2945
+ try:
2946
+ result = self.apply_function(hook, [])
2947
+ if is_error(result):
2948
+ print(f"⚠️ on_start hook error: {result.message}")
2949
+ except Exception as e:
2950
+ print(f"⚠️ on_start hook error: {str(e)}")
2951
+
2952
+ # Setup signal handler for graceful shutdown
2953
+ shutdown_requested = [False] # Use list for closure mutability
2954
+
2955
+ def signal_handler(sig, frame):
2956
+ shutdown_requested[0] = True
2957
+ print("\n⏹️ Shutdown requested. Cleaning up...")
2958
+
2959
+ # Execute custom signal handlers if registered
2960
+ sig_name = signal.Signals(sig).name
2961
+ if sig_name in self._signal_handlers:
2962
+ for handler in self._signal_handlers[sig_name]:
2963
+ try:
2964
+ self.apply_function(handler, [String(sig_name)])
2965
+ except Exception as e:
2966
+ print(f"⚠️ Signal handler error: {str(e)}")
2967
+
2968
+ signal.signal(signal.SIGINT, signal_handler)
2969
+ signal.signal(signal.SIGTERM, signal_handler)
2970
+
2971
+ try:
2972
+ # Keep running until interrupted
2973
+ while not shutdown_requested[0]:
2974
+ if callback:
2975
+ # Execute callback function with arguments
2976
+ result = self.apply_function(callback, callback_args)
2977
+ if is_error(result):
2978
+ print(f"⚠️ Callback error: {result.message}")
2979
+
2980
+ # Sleep for the interval
2981
+ time_module.sleep(interval)
2982
+
2983
+ except KeyboardInterrupt:
2984
+ print("\n⏹️ Interrupted. Exiting...")
2985
+
2986
+ except Exception as e:
2987
+ print(f"❌ Error in run loop: {str(e)}")
2988
+ return EvaluationError(f"run() error: {str(e)}")
2989
+
2990
+ finally:
2991
+ # Execute on_exit hooks
2992
+ for hook in self._lifecycle_hooks.get('on_exit', []):
2993
+ try:
2994
+ result = self.apply_function(hook, [])
2995
+ if is_error(result):
2996
+ print(f"⚠️ on_exit hook error: {result.message}")
2997
+ except Exception as e:
2998
+ print(f"⚠️ on_exit hook error: {str(e)}")
2999
+
3000
+ print("✅ Program terminated gracefully.")
3001
+
3002
+ return NULL
3003
+
3004
+ def _execute(*a):
3005
+ """
3006
+ Alias for run() - keeps the program executing until interrupted.
3007
+
3008
+ Usage:
3009
+ if __MODULE__ == "__main__":
3010
+ execute()
3011
+ """
3012
+ return _run(*a)
3013
+
3014
+ def _is_main(*a):
3015
+ """
3016
+ Check if the current module is being run as the main program.
3017
+ Returns true if __MODULE__ == "__main__", false otherwise.
3018
+
3019
+ Usage:
3020
+ if is_main():
3021
+ print("Running as main program")
3022
+ """
3023
+ env = getattr(self, '_current_env', None)
3024
+ if env:
3025
+ module_name = env.get('__MODULE__')
3026
+ if module_name and isinstance(module_name, String):
3027
+ return TRUE if module_name.value == "__main__" else FALSE
3028
+ return FALSE
3029
+
3030
+ def _exit_program(*a):
3031
+ """
3032
+ Exit the program with an optional exit code.
3033
+
3034
+ Usage:
3035
+ exit_program() # Exit with code 0
3036
+ exit_program(1) # Exit with code 1
3037
+ """
3038
+ exit_code = 0
3039
+ if len(a) >= 1:
3040
+ if isinstance(a[0], Integer):
3041
+ exit_code = a[0].value
3042
+ else:
3043
+ return EvaluationError("exit_program() exit code must be an integer")
3044
+
3045
+ # Execute on_exit hooks before exiting
3046
+ for hook in self._lifecycle_hooks.get('on_exit', []):
3047
+ try:
3048
+ result = self.apply_function(hook, [])
3049
+ if is_error(result):
3050
+ print(f"⚠️ on_exit hook error: {result.message}")
3051
+ except Exception as e:
3052
+ print(f"⚠️ on_exit hook error: {str(e)}")
3053
+
3054
+ print(f"👋 Exiting with code {exit_code}")
3055
+ sys.exit(exit_code)
3056
+
3057
+ def _on_start(*a):
3058
+ """
3059
+ Register a callback to run when the program starts (before run loop).
3060
+
3061
+ Usage:
3062
+ on_start(lambda: print("Starting up..."))
3063
+ on_start(initialize_database)
3064
+ """
3065
+ if len(a) != 1:
3066
+ return EvaluationError("on_start() requires exactly one function argument")
3067
+
3068
+ callback = a[0]
3069
+ if not isinstance(callback, (Action, LambdaFunction)):
3070
+ return EvaluationError("on_start() argument must be a function")
3071
+
3072
+ self._lifecycle_hooks['on_start'].append(callback)
3073
+ return NULL
3074
+
3075
+ def _on_exit(*a):
3076
+ """
3077
+ Register a callback to run when the program exits (after run loop).
3078
+
3079
+ Usage:
3080
+ on_exit(lambda: print("Cleaning up..."))
3081
+ on_exit(close_connections)
3082
+ """
3083
+ if len(a) != 1:
3084
+ return EvaluationError("on_exit() requires exactly one function argument")
3085
+
3086
+ callback = a[0]
3087
+ if not isinstance(callback, (Action, LambdaFunction)):
3088
+ return EvaluationError("on_exit() argument must be a function")
3089
+
3090
+ self._lifecycle_hooks['on_exit'].append(callback)
3091
+ return NULL
3092
+
3093
+ def _signal_handler(*a):
3094
+ """
3095
+ Register a custom signal handler for specific signals.
3096
+
3097
+ Usage:
3098
+ signal_handler("SIGINT", lambda sig: print("Caught SIGINT"))
3099
+ signal_handler("SIGTERM", cleanup_handler)
3100
+ """
3101
+ if len(a) != 2:
3102
+ return EvaluationError("signal_handler() requires signal name and callback function")
3103
+
3104
+ signal_name = _to_str(a[0])
3105
+ callback = a[1]
3106
+
3107
+ if not isinstance(callback, (Action, LambdaFunction)):
3108
+ return EvaluationError("signal_handler() callback must be a function")
3109
+
3110
+ if signal_name not in self._signal_handlers:
3111
+ self._signal_handlers[signal_name] = []
3112
+
3113
+ self._signal_handlers[signal_name].append(callback)
3114
+ return NULL
3115
+
3116
+ def _schedule(*a):
3117
+ """
3118
+ Schedule multiple tasks with different intervals to run in parallel.
3119
+
3120
+ Usage:
3121
+ schedule([
3122
+ {interval: 1, action: check_queue},
3123
+ {interval: 5, action: save_state},
3124
+ {interval: 60, action: cleanup}
3125
+ ])
3126
+
3127
+ Returns: List of task IDs
3128
+ """
3129
+ if len(a) != 1:
3130
+ return EvaluationError("schedule() requires a list of task definitions")
3131
+
3132
+ tasks_arg = a[0]
3133
+ if not isinstance(tasks_arg, List):
3134
+ return EvaluationError("schedule() argument must be a list")
3135
+
3136
+ import threading
3137
+ import time as time_module
3138
+
3139
+ task_ids = []
3140
+
3141
+ for i, task_def in enumerate(tasks_arg.elements):
3142
+ if not isinstance(task_def, Map):
3143
+ return EvaluationError(f"Task {i} must be a map with 'interval' and 'action' keys")
3144
+
3145
+ # Extract interval and action - map keys can be strings or String objects
3146
+ interval_obj = None
3147
+ action_obj = None
3148
+
3149
+ for key, value in task_def.pairs.items():
3150
+ key_str = key if isinstance(key, str) else (key.value if hasattr(key, 'value') else str(key))
3151
+ if key_str == "interval":
3152
+ interval_obj = value
3153
+ elif key_str == "action":
3154
+ action_obj = value
3155
+
3156
+ if not interval_obj or not action_obj:
3157
+ return EvaluationError(f"Task {i} must have 'interval' and 'action' keys")
3158
+
3159
+ if isinstance(interval_obj, (Integer, Float)):
3160
+ interval = float(interval_obj.value)
3161
+ else:
3162
+ return EvaluationError(f"Task {i} interval must be a number")
3163
+
3164
+ if not isinstance(action_obj, (Action, LambdaFunction)):
3165
+ return EvaluationError(f"Task {i} action must be a function")
3166
+
3167
+ # Create task thread
3168
+ task_id = f"task_{i}_{id(action_obj)}"
3169
+ task_ids.append(String(task_id))
3170
+
3171
+ def task_worker(action, interval_sec, task_id):
3172
+ """Worker function that runs the task at specified interval"""
3173
+ while True:
3174
+ try:
3175
+ time_module.sleep(interval_sec)
3176
+ result = self.apply_function(action, [])
3177
+ if is_error(result):
3178
+ print(f"⚠️ Task {task_id} error: {result.message}")
3179
+ except Exception as e:
3180
+ print(f"⚠️ Task {task_id} exception: {str(e)}")
3181
+ break
3182
+
3183
+ # Start thread in daemon mode so it exits when main program exits
3184
+ thread = threading.Thread(
3185
+ target=task_worker,
3186
+ args=(action_obj, interval, task_id),
3187
+ daemon=True
3188
+ )
3189
+ thread.start()
3190
+
3191
+ return List(task_ids)
3192
+
3193
+ def _sleep(*args):
3194
+ """
3195
+ Sleep for specified seconds.
3196
+
3197
+ Usage:
3198
+ sleep(2) # Sleep for 2 seconds
3199
+ sleep(0.5) # Sleep for 0.5 seconds
3200
+ """
3201
+ if len(args) != 1:
3202
+ return EvaluationError("sleep() requires exactly 1 argument (seconds)")
3203
+
3204
+ seconds_arg = args[0]
3205
+ if isinstance(seconds_arg, (Integer, Float)):
3206
+ try:
3207
+ time_module.sleep(float(seconds_arg.value))
3208
+ return NULL
3209
+ except Exception as e:
3210
+ return EvaluationError(f"sleep() error: {str(e)}")
3211
+ else:
3212
+ return EvaluationError("sleep() argument must be a number")
3213
+
3214
+ def _daemonize(*args):
3215
+ """
3216
+ Run the current process as a background daemon.
3217
+
3218
+ Detaches from terminal and runs in background. On Unix systems, this
3219
+ performs a double fork to properly daemonize. On Windows, it's a no-op.
3220
+
3221
+ Usage:
3222
+ if is_main() {
3223
+ daemonize()
3224
+ # Now running as daemon
3225
+ run(my_server_task)
3226
+ }
3227
+
3228
+ Optional arguments:
3229
+ daemonize() # Use defaults
3230
+ daemonize(working_dir) # Set working directory
3231
+ """
3232
+ import os
3233
+ import sys
3234
+
3235
+ # Check if we're on a Unix-like system
3236
+ if not hasattr(os, 'fork'):
3237
+ return EvaluationError("daemonize() is only supported on Unix-like systems")
3238
+
3239
+ # Get optional working directory
3240
+ working_dir = None
3241
+ if len(args) > 0:
3242
+ if isinstance(args[0], String):
3243
+ working_dir = args[0].value
3244
+ else:
3245
+ return EvaluationError("daemonize() working_dir must be a string")
3246
+
3247
+ try:
3248
+ # First fork
3249
+ pid = os.fork()
3250
+ if pid > 0:
3251
+ # Parent process - exit
3252
+ sys.exit(0)
3253
+ except OSError as e:
3254
+ return EvaluationError(f"First fork failed: {str(e)}")
3255
+
3256
+ # Decouple from parent environment
3257
+ os.chdir(working_dir if working_dir else '/')
3258
+ os.setsid()
3259
+ os.umask(0)
3260
+
3261
+ # Second fork to prevent acquiring a controlling terminal
3262
+ try:
3263
+ pid = os.fork()
3264
+ if pid > 0:
3265
+ # Parent of second fork - exit
3266
+ sys.exit(0)
3267
+ except OSError as e:
3268
+ return EvaluationError(f"Second fork failed: {str(e)}")
3269
+
3270
+ # Redirect standard file descriptors to /dev/null
3271
+ sys.stdout.flush()
3272
+ sys.stderr.flush()
3273
+
3274
+ # Open /dev/null
3275
+ dev_null = os.open(os.devnull, os.O_RDWR)
3276
+
3277
+ # Redirect stdin, stdout, stderr
3278
+ os.dup2(dev_null, sys.stdin.fileno())
3279
+ os.dup2(dev_null, sys.stdout.fileno())
3280
+ os.dup2(dev_null, sys.stderr.fileno())
3281
+
3282
+ # Close the dev_null file descriptor
3283
+ if dev_null > 2:
3284
+ os.close(dev_null)
3285
+
3286
+ return NULL
3287
+
3288
+ def _watch_and_reload(*args):
3289
+ """
3290
+ Watch files for changes and reload modules automatically.
3291
+ Useful for development to see code changes without restarting.
3292
+
3293
+ Usage:
3294
+ watch_and_reload([__file__]) # Watch current file
3295
+ watch_and_reload([file1, file2]) # Watch multiple files
3296
+ watch_and_reload([__file__], 1.0) # Custom check interval
3297
+ watch_and_reload([__file__], 1.0, my_callback) # With callback
3298
+
3299
+ Returns: Map with watch info
3300
+ """
3301
+ import os
3302
+ import time as time_module
3303
+ import threading
3304
+
3305
+ if len(args) < 1:
3306
+ return EvaluationError("watch_and_reload() requires at least 1 argument (files)")
3307
+
3308
+ # Parse arguments
3309
+ files_arg = args[0]
3310
+ check_interval = 1.0 # Default: check every second
3311
+ reload_callback = None
3312
+
3313
+ if not isinstance(files_arg, List):
3314
+ return EvaluationError("watch_and_reload() files must be a list")
3315
+
3316
+ if len(args) >= 2:
3317
+ interval_obj = args[1]
3318
+ if isinstance(interval_obj, (Integer, Float)):
3319
+ check_interval = float(interval_obj.value)
3320
+ else:
3321
+ return EvaluationError("watch_and_reload() interval must be a number")
3322
+
3323
+ if len(args) >= 3:
3324
+ callback_obj = args[2]
3325
+ if isinstance(callback_obj, (Action, LambdaFunction)):
3326
+ reload_callback = callback_obj
3327
+ else:
3328
+ return EvaluationError("watch_and_reload() callback must be a function")
3329
+
3330
+ # Extract file paths
3331
+ file_paths = []
3332
+ for file_obj in files_arg.elements:
3333
+ if isinstance(file_obj, String):
3334
+ path = file_obj.value
3335
+ if os.path.exists(path):
3336
+ file_paths.append(path)
3337
+ else:
3338
+ return EvaluationError(f"File not found: {path}")
3339
+ else:
3340
+ return EvaluationError("watch_and_reload() file paths must be strings")
3341
+
3342
+ if not file_paths:
3343
+ return EvaluationError("No valid files to watch")
3344
+
3345
+ # Get initial modification times
3346
+ file_mtimes = {}
3347
+ for path in file_paths:
3348
+ try:
3349
+ file_mtimes[path] = os.path.getmtime(path)
3350
+ except OSError as e:
3351
+ return EvaluationError(f"Cannot stat {path}: {str(e)}")
3352
+
3353
+ reload_count = [0] # Use list for closure mutability
3354
+
3355
+ def watch_worker():
3356
+ """Background thread that watches for file changes"""
3357
+ while True:
3358
+ time_module.sleep(check_interval)
3359
+
3360
+ for path in file_paths:
3361
+ try:
3362
+ current_mtime = os.path.getmtime(path)
3363
+ if current_mtime > file_mtimes[path]:
3364
+ # File was modified!
3365
+ print(f"\n🔄 File changed: {path}")
3366
+ file_mtimes[path] = current_mtime
3367
+ reload_count[0] += 1
3368
+
3369
+ # Execute reload callback if provided
3370
+ if reload_callback:
3371
+ try:
3372
+ result = self.apply_function(reload_callback, [String(path)])
3373
+ if is_error(result):
3374
+ print(f"⚠️ Reload callback error: {result.message}")
3375
+ except Exception as e:
3376
+ print(f"⚠️ Reload callback exception: {str(e)}")
3377
+ else:
3378
+ print(f" Reload #{reload_count[0]} - No auto-reload callback set")
3379
+ print(f" Tip: Restart the program to see changes")
3380
+ except OSError:
3381
+ # File might have been deleted/renamed
3382
+ pass
3383
+
3384
+ # Start watch thread
3385
+ watch_thread = threading.Thread(target=watch_worker, daemon=True)
3386
+ watch_thread.start()
3387
+
3388
+ print(f"👁️ Watching {len(file_paths)} file(s) for changes...")
3389
+ for path in file_paths:
3390
+ print(f" - {path}")
3391
+ print(f" Check interval: {check_interval}s")
3392
+
3393
+ # Return watch info
3394
+ return Map({
3395
+ "files": files_arg,
3396
+ "interval": Float(check_interval),
3397
+ "active": BooleanObj(True)
3398
+ })
3399
+
3400
+ def _get_module_name(*a):
3401
+ """
3402
+ Get the current module name (__MODULE__).
3403
+
3404
+ Usage:
3405
+ name = get_module_name()
3406
+ print("Module: " + name)
3407
+ """
3408
+ env = getattr(self, '_current_env', None)
3409
+ if not env:
3410
+ return String("")
3411
+
3412
+ module = env.get('__MODULE__')
3413
+ return module if module else String("")
3414
+
3415
+ def _get_module_path(*a):
3416
+ """
3417
+ Get the current module file path (__file__).
3418
+
3419
+ Usage:
3420
+ path = get_module_path()
3421
+ print("Path: " + path)
3422
+ """
3423
+ env = getattr(self, '_current_env', None)
3424
+ if not env:
3425
+ return String("")
3426
+
3427
+ file_path = env.get('__file__')
3428
+ if not file_path:
3429
+ file_path = env.get('__FILE__')
3430
+ return file_path if file_path else String("")
3431
+
3432
+ def _module_info(*a):
3433
+ """
3434
+ Get information about the current module.
3435
+ Returns a map with module metadata.
3436
+
3437
+ Usage:
3438
+ info = module_info()
3439
+ print(info["name"]) # Module name
3440
+ print(info["file"]) # File path
3441
+ print(info["dir"]) # Directory
3442
+ print(info["package"]) # Package name
3443
+ """
3444
+ env = getattr(self, '_current_env', None)
3445
+ if not env:
3446
+ return Map({})
3447
+
3448
+ result = {}
3449
+
3450
+ # Get module variables
3451
+ for var_name in ['__MODULE__', '__file__', '__FILE__', '__DIR__', '__PACKAGE__']:
3452
+ val = env.get(var_name)
3453
+ if val:
3454
+ key = var_name.strip('_').lower()
3455
+ result[String(key)] = val
3456
+
3457
+ return Map(result)
3458
+
3459
+ def _list_imports(*a):
3460
+ """
3461
+ List all imported modules in the current environment.
3462
+
3463
+ Usage:
3464
+ imports = list_imports()
3465
+ print(imports) # ["math", "json", "./utils"]
3466
+ """
3467
+ env = getattr(self, '_current_env', None)
3468
+ if not env:
3469
+ return List([])
3470
+
3471
+ # Collect all imported module names (this is a simplified version)
3472
+ # In a more complete implementation, we'd track imports explicitly
3473
+ imports = []
3474
+
3475
+ # Look for common module indicators in the environment
3476
+ for name, value in env.store.items():
3477
+ # Skip special variables and builtins
3478
+ if name.startswith('__') or name in self.builtins:
3479
+ continue
3480
+ # Check if it looks like an imported module
3481
+ if isinstance(value, Map) and len(value.pairs) > 3:
3482
+ imports.append(String(name))
3483
+
3484
+ return List(imports)
3485
+
3486
+ def _get_exported_names(*a):
3487
+ """
3488
+ Get all exported variable names from the current module.
3489
+
3490
+ Usage:
3491
+ exports = get_exported_names()
3492
+ print(exports) # ["myFunction", "MY_CONSTANT", "MyClass"]
3493
+ """
3494
+ env = getattr(self, '_current_env', None)
3495
+ if not env:
3496
+ return List([])
3497
+
3498
+ exports = []
3499
+
3500
+ # Get all user-defined names (skip special variables and builtins)
3501
+ for name in env.store.keys():
3502
+ if not name.startswith('__') and name not in self.builtins:
3503
+ exports.append(String(name))
3504
+
3505
+ return List(exports)
3506
+
3507
+ # Register the builtins
3508
+ self.builtins.update({
3509
+ "run": Builtin(_run, "run"),
3510
+ "execute": Builtin(_execute, "execute"),
3511
+ "is_main": Builtin(_is_main, "is_main"),
3512
+ "exit_program": Builtin(_exit_program, "exit_program"),
3513
+ "on_start": Builtin(_on_start, "on_start"),
3514
+ "on_exit": Builtin(_on_exit, "on_exit"),
3515
+ "signal_handler": Builtin(_signal_handler, "signal_handler"),
3516
+ "schedule": Builtin(_schedule, "schedule"),
3517
+ "sleep": Builtin(_sleep, "sleep"),
3518
+ "daemonize": Builtin(_daemonize, "daemonize"),
3519
+ "watch_and_reload": Builtin(_watch_and_reload, "watch_and_reload"),
3520
+ "get_module_name": Builtin(_get_module_name, "get_module_name"),
3521
+ "get_module_path": Builtin(_get_module_path, "get_module_path"),
3522
+ "module_info": Builtin(_module_info, "module_info"),
3523
+ "list_imports": Builtin(_list_imports, "list_imports"),
3524
+ "get_exported_names": Builtin(_get_exported_names, "get_exported_names"),
3525
+ })
3526
+
3527
+ def _register_renderer_builtins(self):
3528
+ """Logic extracted from the original RENDER_REGISTRY and helper functions."""
3529
+
3530
+ # Mix
3531
+ def builtin_mix(*args):
3532
+ if len(args) != 3:
3533
+ return EvaluationError("mix(colorA, colorB, ratio)")
3534
+ a, b, ratio = args
3535
+ a_name = _to_str(a)
3536
+ b_name = _to_str(b)
3537
+
3538
+ try:
3539
+ ratio_val = float(ratio.value) if isinstance(ratio, (Integer, Float)) else float(str(ratio))
3540
+ except Exception:
3541
+ ratio_val = 0.5
3542
+
3543
+ if _BACKEND_AVAILABLE:
3544
+ try:
3545
+ res = _BACKEND.mix(a_name, b_name, ratio_val)
3546
+ return String(str(res))
3547
+ except Exception:
3548
+ pass
3549
+
3550
+ return String(f"mix({a_name},{b_name},{ratio_val})")
3551
+
3552
+ # Define Screen
3553
+ def builtin_define_screen(*args):
3554
+ if len(args) < 1:
3555
+ return EvaluationError("define_screen() requires at least a name")
3556
+
3557
+ name = _to_str(args[0])
3558
+ props = _zexus_to_python(args[1]) if len(args) > 1 else {}
3559
+
3560
+ if _BACKEND_AVAILABLE:
3561
+ try:
3562
+ _BACKEND.define_screen(name, props)
3563
+ return NULL
3564
+ except Exception as e:
3565
+ return EvaluationError(str(e))
3566
+
3567
+ self.render_registry['screens'].setdefault(name, {
3568
+ 'properties': props,
3569
+ 'components': [],
3570
+ 'theme': None
3571
+ })
3572
+ return NULL
3573
+
3574
+ # Define Component
3575
+ def builtin_define_component(*args):
3576
+ if len(args) < 1:
3577
+ return EvaluationError("define_component() requires at least a name")
3578
+
3579
+ name = _to_str(args[0])
3580
+ props = _zexus_to_python(args[1]) if len(args) > 1 else {}
3581
+
3582
+ if _BACKEND_AVAILABLE:
3583
+ try:
3584
+ _BACKEND.define_component(name, props)
3585
+ return NULL
3586
+ except Exception as e:
3587
+ return EvaluationError(str(e))
3588
+
3589
+ self.render_registry['components'][name] = props
3590
+ return NULL
3591
+
3592
+ # Add to Screen
3593
+ def builtin_add_to_screen(*args):
3594
+ if len(args) != 2:
3595
+ return EvaluationError("add_to_screen() requires (screen_name, component_name)")
3596
+
3597
+ screen = _to_str(args[0])
3598
+ comp = _to_str(args[1])
3599
+
3600
+ if _BACKEND_AVAILABLE:
3601
+ try:
3602
+ _BACKEND.add_to_screen(screen, comp)
3603
+ return NULL
3604
+ except Exception as e:
3605
+ return EvaluationError(str(e))
3606
+
3607
+ if screen not in self.render_registry['screens']:
3608
+ return EvaluationError(f"Screen '{screen}' not found")
3609
+
3610
+ self.render_registry['screens'][screen]['components'].append(comp)
3611
+ return NULL
3612
+
3613
+ # Render Screen
3614
+ def builtin_render_screen(*args):
3615
+ if len(args) != 1:
3616
+ return EvaluationError("render_screen() requires exactly 1 argument")
3617
+
3618
+ name = _to_str(args[0])
3619
+
3620
+ if _BACKEND_AVAILABLE:
3621
+ try:
3622
+ out = _BACKEND.render_screen(name)
3623
+ return String(str(out))
3624
+ except Exception as e:
3625
+ return String(f"<render error: {str(e)}>")
3626
+
3627
+ screen = self.render_registry['screens'].get(name)
3628
+ if not screen:
3629
+ return String(f"<missing screen: {name}>")
3630
+
3631
+ return String(f"Screen:{name} props={screen.get('properties')} components={screen.get('components')}")
3632
+
3633
+ # Set Theme
3634
+ def builtin_set_theme(*args):
3635
+ if len(args) == 1:
3636
+ theme_name = _to_str(args[0])
3637
+ if _BACKEND_AVAILABLE:
3638
+ try:
3639
+ _BACKEND.set_theme(theme_name)
3640
+ return NULL
3641
+ except Exception as e:
3642
+ return EvaluationError(str(e))
3643
+
3644
+ self.render_registry['current_theme'] = theme_name
3645
+ return NULL
3646
+
3647
+ if len(args) == 2:
3648
+ target = _to_str(args[0])
3649
+ theme_name = _to_str(args[1])
3650
+
3651
+ if _BACKEND_AVAILABLE:
3652
+ try:
3653
+ _BACKEND.set_theme(target, theme_name)
3654
+ return NULL
3655
+ except Exception as e:
3656
+ return EvaluationError(str(e))
3657
+
3658
+ if target in self.render_registry['screens']:
3659
+ self.render_registry['screens'][target]['theme'] = theme_name
3660
+ else:
3661
+ self.render_registry['themes'].setdefault(theme_name, {})
3662
+
3663
+ return NULL
3664
+
3665
+ return EvaluationError("set_theme() requires 1 (theme) or 2 (target,theme) args")
3666
+
3667
+ # Canvas Ops
3668
+ def builtin_create_canvas(*args):
3669
+ if len(args) != 2:
3670
+ return EvaluationError("create_canvas(width, height)")
3671
+
3672
+ try:
3673
+ wid = int(args[0].value) if isinstance(args[0], Integer) else int(str(args[0]))
3674
+ hei = int(args[1].value) if isinstance(args[1], Integer) else int(str(args[1]))
3675
+ except Exception:
3676
+ return EvaluationError("Invalid numeric arguments to create_canvas()")
3677
+
3678
+ if _BACKEND_AVAILABLE:
3679
+ try:
3680
+ cid = _BACKEND.create_canvas(wid, hei)
3681
+ return String(str(cid))
3682
+ except Exception as e:
3683
+ return EvaluationError(str(e))
3684
+
3685
+ cid = f"canvas_{len(self.render_registry['canvases'])+1}"
3686
+ self.render_registry['canvases'][cid] = {
3687
+ 'width': wid,
3688
+ 'height': hei,
3689
+ 'draw_ops': []
3690
+ }
3691
+ return String(cid)
3692
+
3693
+ def builtin_draw_line(*args):
3694
+ if len(args) != 5:
3695
+ return EvaluationError("draw_line(canvas_id,x1,y1,x2,y2)")
3696
+
3697
+ cid = _to_str(args[0])
3698
+ try:
3699
+ coords = [int(a.value) if isinstance(a, Integer) else int(str(a)) for a in args[1:]]
3700
+ except Exception:
3701
+ return EvaluationError("Invalid coordinates in draw_line()")
3702
+
3703
+ if _BACKEND_AVAILABLE:
3704
+ try:
3705
+ _BACKEND.draw_line(cid, *coords)
3706
+ return NULL
3707
+ except Exception as e:
3708
+ return EvaluationError(str(e))
3709
+
3710
+ canvas = self.render_registry['canvases'].get(cid)
3711
+ if not canvas:
3712
+ return EvaluationError(f"Canvas {cid} not found")
3713
+
3714
+ canvas['draw_ops'].append(('line', coords))
3715
+ return NULL
3716
+
3717
+ def builtin_draw_text(*args):
3718
+ if len(args) != 4:
3719
+ return EvaluationError("draw_text(canvas_id,x,y,text)")
3720
+
3721
+ cid = _to_str(args[0])
3722
+ try:
3723
+ x = int(args[1].value) if isinstance(args[1], Integer) else int(str(args[1]))
3724
+ y = int(args[2].value) if isinstance(args[2], Integer) else int(str(args[2]))
3725
+ except Exception:
3726
+ return EvaluationError("Invalid coordinates in draw_text()")
3727
+
3728
+ text = _to_str(args[3])
3729
+
3730
+ if _BACKEND_AVAILABLE:
3731
+ try:
3732
+ _BACKEND.draw_text(cid, x, y, text)
3733
+ return NULL
3734
+ except Exception as e:
3735
+ return EvaluationError(str(e))
3736
+
3737
+ canvas = self.render_registry['canvases'].get(cid)
3738
+ if not canvas:
3739
+ return EvaluationError(f"Canvas {cid} not found")
3740
+
3741
+ canvas['draw_ops'].append(('text', (x, y, text)))
3742
+ return NULL
3743
+
3744
+ # Register renderer builtins
3745
+ self.builtins.update({
3746
+ "mix": Builtin(builtin_mix, "mix"),
3747
+ "define_screen": Builtin(builtin_define_screen, "define_screen"),
3748
+ "define_component": Builtin(builtin_define_component, "define_component"),
3749
+ "add_to_screen": Builtin(builtin_add_to_screen, "add_to_screen"),
3750
+ "render_screen": Builtin(builtin_render_screen, "render_screen"),
3751
+ "set_theme": Builtin(builtin_set_theme, "set_theme"),
3752
+ "create_canvas": Builtin(builtin_create_canvas, "create_canvas"),
3753
+ "draw_line": Builtin(builtin_draw_line, "draw_line"),
3754
+ "draw_text": Builtin(builtin_draw_text, "draw_text"),
3755
+ })
3756
+
3757
+ def _register_verification_builtins(self):
3758
+ """Register verification helper functions for VERIFY keyword"""
3759
+ import re
3760
+ import os
3761
+
3762
+ def _is_email(*a):
3763
+ """Check if string is valid email format"""
3764
+ if len(a) != 1:
3765
+ return EvaluationError("is_email() takes 1 argument")
3766
+
3767
+ val = a[0]
3768
+ email_str = val.value if isinstance(val, String) else str(val)
3769
+
3770
+ # Simple email validation pattern
3771
+ pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
3772
+ is_valid = bool(re.match(pattern, email_str))
3773
+ return TRUE if is_valid else FALSE
3774
+
3775
+ def _is_url(*a):
3776
+ """Check if string is valid URL format"""
3777
+ if len(a) != 1:
3778
+ return EvaluationError("is_url() takes 1 argument")
3779
+
3780
+ val = a[0]
3781
+ url_str = val.value if isinstance(val, String) else str(val)
3782
+
3783
+ # Simple URL validation pattern
3784
+ pattern = r'^https?://[^\s/$.?#].[^\s]*$'
3785
+ is_valid = bool(re.match(pattern, url_str))
3786
+ return TRUE if is_valid else FALSE
3787
+
3788
+ def _is_phone(*a):
3789
+ """Check if string is valid phone number format"""
3790
+ if len(a) != 1:
3791
+ return EvaluationError("is_phone() takes 1 argument")
3792
+
3793
+ val = a[0]
3794
+ phone_str = val.value if isinstance(val, String) else str(val)
3795
+
3796
+ # Remove common separators
3797
+ clean = re.sub(r'[\s\-\(\)\.]', '', phone_str)
3798
+
3799
+ # Check if it's digits and reasonable length
3800
+ is_valid = clean.isdigit() and 10 <= len(clean) <= 15
3801
+ return TRUE if is_valid else FALSE
3802
+
3803
+ def _is_numeric(*a):
3804
+ """Check if string contains only numbers"""
3805
+ if len(a) != 1:
3806
+ return EvaluationError("is_numeric() takes 1 argument")
3807
+
3808
+ val = a[0]
3809
+ if isinstance(val, (Integer, Float)):
3810
+ return TRUE
3811
+
3812
+ str_val = val.value if isinstance(val, String) else str(val)
3813
+
3814
+ try:
3815
+ float(str_val)
3816
+ return TRUE
3817
+ except ValueError:
3818
+ return FALSE
3819
+
3820
+ def _is_alpha(*a):
3821
+ """Check if string contains only alphabetic characters"""
3822
+ if len(a) != 1:
3823
+ return EvaluationError("is_alpha() takes 1 argument")
3824
+
3825
+ val = a[0]
3826
+ str_val = val.value if isinstance(val, String) else str(val)
3827
+
3828
+ is_valid = str_val.isalpha()
3829
+ return TRUE if is_valid else FALSE
3830
+
3831
+ def _is_alphanumeric(*a):
3832
+ """Check if string contains only alphanumeric characters"""
3833
+ if len(a) != 1:
3834
+ return EvaluationError("is_alphanumeric() takes 1 argument")
3835
+
3836
+ val = a[0]
3837
+ str_val = val.value if isinstance(val, String) else str(val)
3838
+
3839
+ is_valid = str_val.isalnum()
3840
+ return TRUE if is_valid else FALSE
3841
+
3842
+ def _matches_pattern(*a):
3843
+ """Check if string matches regex pattern: matches_pattern(value, pattern)"""
3844
+ if len(a) != 2:
3845
+ return EvaluationError("matches_pattern() takes 2 arguments: value, pattern")
3846
+
3847
+ val = a[0]
3848
+ pattern_obj = a[1]
3849
+
3850
+ str_val = val.value if isinstance(val, String) else str(val)
3851
+ pattern = pattern_obj.value if isinstance(pattern_obj, String) else str(pattern_obj)
3852
+
3853
+ try:
3854
+ is_valid = bool(re.match(pattern, str_val))
3855
+ return TRUE if is_valid else FALSE
3856
+ except Exception as e:
3857
+ return EvaluationError(f"Pattern matching error: {str(e)}")
3858
+
3859
+ def _env_get(*a):
3860
+ """Get environment variable: env_get("VAR_NAME") or env_get("VAR_NAME", "default")"""
3861
+ if len(a) < 1 or len(a) > 2:
3862
+ return EvaluationError("env_get() takes 1 or 2 arguments: var_name, [default]")
3863
+
3864
+ var_name_obj = a[0]
3865
+ var_name = var_name_obj.value if isinstance(var_name_obj, String) else str(var_name_obj)
3866
+
3867
+ default = a[1] if len(a) == 2 else None
3868
+
3869
+ value = os.environ.get(var_name)
3870
+
3871
+ if value is None:
3872
+ return default if default is not None else NULL
3873
+
3874
+ return String(value)
3875
+
3876
+ def _env_set(*a):
3877
+ """Set environment variable: env_set("VAR_NAME", "value")"""
3878
+ if len(a) != 2:
3879
+ return EvaluationError("env_set() takes 2 arguments: var_name, value")
3880
+
3881
+ var_name_obj = a[0]
3882
+ value_obj = a[1]
3883
+
3884
+ var_name = var_name_obj.value if isinstance(var_name_obj, String) else str(var_name_obj)
3885
+ value = value_obj.value if isinstance(value_obj, String) else str(value_obj)
3886
+
3887
+ os.environ[var_name] = value
3888
+ return TRUE
3889
+
3890
+ def _env_exists(*a):
3891
+ """Check if environment variable exists: env_exists("VAR_NAME")"""
3892
+ if len(a) != 1:
3893
+ return EvaluationError("env_exists() takes 1 argument: var_name")
3894
+
3895
+ var_name_obj = a[0]
3896
+ var_name = var_name_obj.value if isinstance(var_name_obj, String) else str(var_name_obj)
3897
+
3898
+ exists = var_name in os.environ
3899
+ return TRUE if exists else FALSE
3900
+
3901
+ def _password_strength(*a):
3902
+ """Check password strength: password_strength(password) -> "weak"/"medium"/"strong" """
3903
+ if len(a) != 1:
3904
+ return EvaluationError("password_strength() takes 1 argument")
3905
+
3906
+ val = a[0]
3907
+ password = val.value if isinstance(val, String) else str(val)
3908
+
3909
+ score = 0
3910
+ length = len(password)
3911
+
3912
+ # Length check
3913
+ if length >= 8:
3914
+ score += 1
3915
+ if length >= 12:
3916
+ score += 1
3917
+
3918
+ # Complexity checks
3919
+ if re.search(r'[a-z]', password):
3920
+ score += 1
3921
+ if re.search(r'[A-Z]', password):
3922
+ score += 1
3923
+ if re.search(r'[0-9]', password):
3924
+ score += 1
3925
+ if re.search(r'[^a-zA-Z0-9]', password):
3926
+ score += 1
3927
+
3928
+ if score <= 2:
3929
+ return String("weak")
3930
+ elif score <= 4:
3931
+ return String("medium")
3932
+ else:
3933
+ return String("strong")
3934
+
3935
+ def _sanitize_input(*a):
3936
+ """Sanitize user input by removing dangerous characters"""
3937
+ if len(a) != 1:
3938
+ return EvaluationError("sanitize_input() takes 1 argument")
3939
+
3940
+ val = a[0]
3941
+ input_str = val.value if isinstance(val, String) else str(val)
3942
+
3943
+ # Remove potentially dangerous characters
3944
+ # Remove HTML tags
3945
+ sanitized = re.sub(r'<[^>]+>', '', input_str)
3946
+ # Remove script tags
3947
+ sanitized = re.sub(r'<script[^>]*>.*?</script>', '', sanitized, flags=re.IGNORECASE)
3948
+ # Remove SQL injection patterns
3949
+ sanitized = re.sub(r'(;|--|\'|\"|\bOR\b|\bAND\b)', '', sanitized, flags=re.IGNORECASE)
3950
+
3951
+ return String(sanitized)
3952
+
3953
+ def _validate_length(*a):
3954
+ """Validate string length: validate_length(value, min, max)"""
3955
+ if len(a) != 3:
3956
+ return EvaluationError("validate_length() takes 3 arguments: value, min, max")
3957
+
3958
+ val = a[0]
3959
+ min_len_obj = a[1]
3960
+ max_len_obj = a[2]
3961
+
3962
+ str_val = val.value if isinstance(val, String) else str(val)
3963
+ min_len = min_len_obj.value if isinstance(min_len_obj, Integer) else int(min_len_obj)
3964
+ max_len = max_len_obj.value if isinstance(max_len_obj, Integer) else int(max_len_obj)
3965
+
3966
+ length = len(str_val)
3967
+ is_valid = min_len <= length <= max_len
3968
+
3969
+ return TRUE if is_valid else FALSE
3970
+
3971
+ # Register verification builtins
3972
+ self.builtins.update({
3973
+ "is_email": Builtin(_is_email, "is_email"),
3974
+ "is_url": Builtin(_is_url, "is_url"),
3975
+ "is_phone": Builtin(_is_phone, "is_phone"),
3976
+ "is_numeric": Builtin(_is_numeric, "is_numeric"),
3977
+ "is_alpha": Builtin(_is_alpha, "is_alpha"),
3978
+ "is_alphanumeric": Builtin(_is_alphanumeric, "is_alphanumeric"),
3979
+ "matches_pattern": Builtin(_matches_pattern, "matches_pattern"),
3980
+ "env_get": Builtin(_env_get, "env_get"),
3981
+ "env_set": Builtin(_env_set, "env_set"),
3982
+ "env_exists": Builtin(_env_exists, "env_exists"),
3983
+ "password_strength": Builtin(_password_strength, "password_strength"),
3984
+ "sanitize_input": Builtin(_sanitize_input, "sanitize_input"),
3985
+ "validate_length": Builtin(_validate_length, "validate_length"),
3986
+ })
3987
+
3988
+ # Register main entry point and event loop builtins
3989
+ self._register_main_entry_point_builtins()