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.
- package/LICENSE +0 -0
- package/README.md +2513 -0
- package/bin/zexus +2 -0
- package/bin/zpics +2 -0
- package/bin/zpm +2 -0
- package/bin/zx +2 -0
- package/bin/zx-deploy +2 -0
- package/bin/zx-dev +2 -0
- package/bin/zx-run +2 -0
- package/package.json +66 -0
- package/scripts/README.md +24 -0
- package/scripts/postinstall.js +44 -0
- package/shared_config.json +24 -0
- package/src/README.md +1525 -0
- package/src/tests/run_zexus_tests.py +117 -0
- package/src/tests/test_all_phases.zx +346 -0
- package/src/tests/test_blockchain_features.zx +306 -0
- package/src/tests/test_complexity_features.zx +321 -0
- package/src/tests/test_core_integration.py +185 -0
- package/src/tests/test_phase10_ecosystem.zx +177 -0
- package/src/tests/test_phase1_modifiers.zx +87 -0
- package/src/tests/test_phase2_plugins.zx +80 -0
- package/src/tests/test_phase3_security.zx +97 -0
- package/src/tests/test_phase4_vfs.zx +116 -0
- package/src/tests/test_phase5_types.zx +117 -0
- package/src/tests/test_phase6_metaprogramming.zx +125 -0
- package/src/tests/test_phase7_optimization.zx +132 -0
- package/src/tests/test_phase9_advanced_types.zx +157 -0
- package/src/tests/test_security_features.py +419 -0
- package/src/tests/test_security_features.zx +276 -0
- package/src/tests/test_simple_zx.zx +1 -0
- package/src/tests/test_verification_simple.zx +69 -0
- package/src/zexus/__init__.py +28 -0
- package/src/zexus/__main__.py +5 -0
- package/src/zexus/__pycache__/__init__.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/advanced_types.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/builtin_modules.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/capability_system.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/complexity_system.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/concurrency_system.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/config.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/dependency_injection.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/ecosystem.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/environment.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/error_reporter.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/hybrid_orchestrator.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/lexer.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/metaprogramming.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/module_cache.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/object.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/optimization.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/plugin_system.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/policy_engine.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/security.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/stdlib_integration.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/strategy_recovery.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/syntax_validator.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/type_system.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/virtual_filesystem.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/zexus_ast.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/zexus_token.cpython-312.pyc +0 -0
- package/src/zexus/advanced_types.py +401 -0
- package/src/zexus/blockchain/__init__.py +40 -0
- package/src/zexus/blockchain/__pycache__/__init__.cpython-312.pyc +0 -0
- package/src/zexus/blockchain/__pycache__/crypto.cpython-312.pyc +0 -0
- package/src/zexus/blockchain/__pycache__/ledger.cpython-312.pyc +0 -0
- package/src/zexus/blockchain/__pycache__/transaction.cpython-312.pyc +0 -0
- package/src/zexus/blockchain/crypto.py +463 -0
- package/src/zexus/blockchain/ledger.py +255 -0
- package/src/zexus/blockchain/transaction.py +267 -0
- package/src/zexus/builtin_modules.py +284 -0
- package/src/zexus/builtin_plugins.py +317 -0
- package/src/zexus/capability_system.py +372 -0
- package/src/zexus/cli/__init__.py +2 -0
- package/src/zexus/cli/__pycache__/__init__.cpython-312.pyc +0 -0
- package/src/zexus/cli/__pycache__/main.cpython-312.pyc +0 -0
- package/src/zexus/cli/main.py +707 -0
- package/src/zexus/cli/zpm.py +203 -0
- package/src/zexus/compare_interpreter_compiler.py +146 -0
- package/src/zexus/compiler/__init__.py +169 -0
- package/src/zexus/compiler/__pycache__/__init__.cpython-312.pyc +0 -0
- package/src/zexus/compiler/__pycache__/lexer.cpython-312.pyc +0 -0
- package/src/zexus/compiler/__pycache__/parser.cpython-312.pyc +0 -0
- package/src/zexus/compiler/__pycache__/zexus_ast.cpython-312.pyc +0 -0
- package/src/zexus/compiler/bytecode.py +266 -0
- package/src/zexus/compiler/compat_runtime.py +277 -0
- package/src/zexus/compiler/lexer.py +257 -0
- package/src/zexus/compiler/parser.py +779 -0
- package/src/zexus/compiler/semantic.py +118 -0
- package/src/zexus/compiler/zexus_ast.py +454 -0
- package/src/zexus/complexity_system.py +575 -0
- package/src/zexus/concurrency_system.py +493 -0
- package/src/zexus/config.py +201 -0
- package/src/zexus/crypto_bridge.py +19 -0
- package/src/zexus/dependency_injection.py +423 -0
- package/src/zexus/ecosystem.py +434 -0
- package/src/zexus/environment.py +101 -0
- package/src/zexus/environment_manager.py +119 -0
- package/src/zexus/error_reporter.py +314 -0
- package/src/zexus/evaluator/__init__.py +12 -0
- package/src/zexus/evaluator/__pycache__/__init__.cpython-312.pyc +0 -0
- package/src/zexus/evaluator/__pycache__/bytecode_compiler.cpython-312.pyc +0 -0
- package/src/zexus/evaluator/__pycache__/core.cpython-312.pyc +0 -0
- package/src/zexus/evaluator/__pycache__/expressions.cpython-312.pyc +0 -0
- package/src/zexus/evaluator/__pycache__/functions.cpython-312.pyc +0 -0
- package/src/zexus/evaluator/__pycache__/integration.cpython-312.pyc +0 -0
- package/src/zexus/evaluator/__pycache__/statements.cpython-312.pyc +0 -0
- package/src/zexus/evaluator/__pycache__/utils.cpython-312.pyc +0 -0
- package/src/zexus/evaluator/bytecode_compiler.py +700 -0
- package/src/zexus/evaluator/core.py +891 -0
- package/src/zexus/evaluator/expressions.py +827 -0
- package/src/zexus/evaluator/functions.py +3989 -0
- package/src/zexus/evaluator/integration.py +396 -0
- package/src/zexus/evaluator/statements.py +4303 -0
- package/src/zexus/evaluator/utils.py +126 -0
- package/src/zexus/evaluator_original.py +2041 -0
- package/src/zexus/external_bridge.py +16 -0
- package/src/zexus/find_affected_imports.sh +155 -0
- package/src/zexus/hybrid_orchestrator.py +152 -0
- package/src/zexus/input_validation.py +259 -0
- package/src/zexus/lexer.py +571 -0
- package/src/zexus/logging.py +89 -0
- package/src/zexus/lsp/__init__.py +9 -0
- package/src/zexus/lsp/completion_provider.py +207 -0
- package/src/zexus/lsp/definition_provider.py +22 -0
- package/src/zexus/lsp/hover_provider.py +71 -0
- package/src/zexus/lsp/server.py +269 -0
- package/src/zexus/lsp/symbol_provider.py +31 -0
- package/src/zexus/metaprogramming.py +321 -0
- package/src/zexus/module_cache.py +89 -0
- package/src/zexus/module_manager.py +107 -0
- package/src/zexus/object.py +973 -0
- package/src/zexus/optimization.py +424 -0
- package/src/zexus/parser/__init__.py +31 -0
- package/src/zexus/parser/__pycache__/__init__.cpython-312.pyc +0 -0
- package/src/zexus/parser/__pycache__/parser.cpython-312.pyc +0 -0
- package/src/zexus/parser/__pycache__/strategy_context.cpython-312.pyc +0 -0
- package/src/zexus/parser/__pycache__/strategy_structural.cpython-312.pyc +0 -0
- package/src/zexus/parser/integration.py +86 -0
- package/src/zexus/parser/parser.py +3977 -0
- package/src/zexus/parser/strategy_context.py +7254 -0
- package/src/zexus/parser/strategy_structural.py +1033 -0
- package/src/zexus/persistence.py +391 -0
- package/src/zexus/plugin_system.py +290 -0
- package/src/zexus/policy_engine.py +365 -0
- package/src/zexus/profiler/__init__.py +5 -0
- package/src/zexus/profiler/profiler.py +233 -0
- package/src/zexus/purity_system.py +398 -0
- package/src/zexus/runtime/__init__.py +20 -0
- package/src/zexus/runtime/async_runtime.py +324 -0
- package/src/zexus/search_old_imports.sh +65 -0
- package/src/zexus/security.py +1407 -0
- package/src/zexus/stack_trace.py +233 -0
- package/src/zexus/stdlib/__init__.py +27 -0
- package/src/zexus/stdlib/blockchain.py +341 -0
- package/src/zexus/stdlib/compression.py +167 -0
- package/src/zexus/stdlib/crypto.py +124 -0
- package/src/zexus/stdlib/datetime.py +163 -0
- package/src/zexus/stdlib/db_mongo.py +199 -0
- package/src/zexus/stdlib/db_mysql.py +162 -0
- package/src/zexus/stdlib/db_postgres.py +163 -0
- package/src/zexus/stdlib/db_sqlite.py +133 -0
- package/src/zexus/stdlib/encoding.py +230 -0
- package/src/zexus/stdlib/fs.py +195 -0
- package/src/zexus/stdlib/http.py +219 -0
- package/src/zexus/stdlib/http_server.py +248 -0
- package/src/zexus/stdlib/json_module.py +61 -0
- package/src/zexus/stdlib/math.py +360 -0
- package/src/zexus/stdlib/os_module.py +265 -0
- package/src/zexus/stdlib/regex.py +148 -0
- package/src/zexus/stdlib/sockets.py +253 -0
- package/src/zexus/stdlib/test_framework.zx +208 -0
- package/src/zexus/stdlib/test_runner.zx +119 -0
- package/src/zexus/stdlib_integration.py +341 -0
- package/src/zexus/strategy_recovery.py +256 -0
- package/src/zexus/syntax_validator.py +356 -0
- package/src/zexus/testing/zpics.py +407 -0
- package/src/zexus/testing/zpics_runtime.py +369 -0
- package/src/zexus/type_system.py +374 -0
- package/src/zexus/validation_system.py +569 -0
- package/src/zexus/virtual_filesystem.py +355 -0
- package/src/zexus/vm/__init__.py +8 -0
- package/src/zexus/vm/__pycache__/__init__.cpython-312.pyc +0 -0
- package/src/zexus/vm/__pycache__/async_optimizer.cpython-312.pyc +0 -0
- package/src/zexus/vm/__pycache__/bytecode.cpython-312.pyc +0 -0
- package/src/zexus/vm/__pycache__/cache.cpython-312.pyc +0 -0
- package/src/zexus/vm/__pycache__/jit.cpython-312.pyc +0 -0
- package/src/zexus/vm/__pycache__/memory_manager.cpython-312.pyc +0 -0
- package/src/zexus/vm/__pycache__/memory_pool.cpython-312.pyc +0 -0
- package/src/zexus/vm/__pycache__/optimizer.cpython-312.pyc +0 -0
- package/src/zexus/vm/__pycache__/parallel_vm.cpython-312.pyc +0 -0
- package/src/zexus/vm/__pycache__/peephole_optimizer.cpython-312.pyc +0 -0
- package/src/zexus/vm/__pycache__/profiler.cpython-312.pyc +0 -0
- package/src/zexus/vm/__pycache__/register_allocator.cpython-312.pyc +0 -0
- package/src/zexus/vm/__pycache__/register_vm.cpython-312.pyc +0 -0
- package/src/zexus/vm/__pycache__/ssa_converter.cpython-312.pyc +0 -0
- package/src/zexus/vm/__pycache__/vm.cpython-312.pyc +0 -0
- package/src/zexus/vm/async_optimizer.py +420 -0
- package/src/zexus/vm/bytecode.py +428 -0
- package/src/zexus/vm/bytecode_converter.py +297 -0
- package/src/zexus/vm/cache.py +532 -0
- package/src/zexus/vm/jit.py +720 -0
- package/src/zexus/vm/memory_manager.py +520 -0
- package/src/zexus/vm/memory_pool.py +511 -0
- package/src/zexus/vm/optimizer.py +478 -0
- package/src/zexus/vm/parallel_vm.py +899 -0
- package/src/zexus/vm/peephole_optimizer.py +452 -0
- package/src/zexus/vm/profiler.py +527 -0
- package/src/zexus/vm/register_allocator.py +462 -0
- package/src/zexus/vm/register_vm.py +520 -0
- package/src/zexus/vm/ssa_converter.py +757 -0
- package/src/zexus/vm/vm.py +1392 -0
- package/src/zexus/zexus_ast.py +1782 -0
- package/src/zexus/zexus_token.py +253 -0
- package/src/zexus/zpm/__init__.py +15 -0
- package/src/zexus/zpm/installer.py +116 -0
- package/src/zexus/zpm/package_manager.py +208 -0
- package/src/zexus/zpm/publisher.py +98 -0
- package/src/zexus/zpm/registry.py +110 -0
- package/src/zexus.egg-info/PKG-INFO +2235 -0
- package/src/zexus.egg-info/SOURCES.txt +876 -0
- package/src/zexus.egg-info/dependency_links.txt +1 -0
- package/src/zexus.egg-info/entry_points.txt +3 -0
- package/src/zexus.egg-info/not-zip-safe +1 -0
- package/src/zexus.egg-info/requires.txt +14 -0
- package/src/zexus.egg-info/top_level.txt +2 -0
- package/zexus.json +14 -0
|
@@ -0,0 +1,827 @@
|
|
|
1
|
+
# src/zexus/evaluator/expressions.py
|
|
2
|
+
from ..zexus_ast import (
|
|
3
|
+
IntegerLiteral, FloatLiteral, StringLiteral, ListLiteral, MapLiteral,
|
|
4
|
+
Identifier, PrefixExpression, InfixExpression, IfExpression,
|
|
5
|
+
Boolean as AST_Boolean, EmbeddedLiteral, ActionLiteral, LambdaExpression
|
|
6
|
+
)
|
|
7
|
+
from ..object import (
|
|
8
|
+
Integer, Float, String, List, Map,
|
|
9
|
+
EvaluationError, Builtin, DateTime
|
|
10
|
+
)
|
|
11
|
+
from .utils import is_error, debug_log, NULL, TRUE, FALSE, is_truthy
|
|
12
|
+
|
|
13
|
+
class ExpressionEvaluatorMixin:
|
|
14
|
+
"""Handles evaluation of expressions: Literals, Math, Logic, Identifiers."""
|
|
15
|
+
|
|
16
|
+
def eval_identifier(self, node, env):
|
|
17
|
+
debug_log("eval_identifier", f"Looking up: {node.value}")
|
|
18
|
+
|
|
19
|
+
# Special case: 'this' keyword should be treated like ThisExpression
|
|
20
|
+
if node.value == "this":
|
|
21
|
+
# Look for contract instance first
|
|
22
|
+
contract_instance = env.get("__contract_instance__")
|
|
23
|
+
if contract_instance is not None:
|
|
24
|
+
return contract_instance
|
|
25
|
+
|
|
26
|
+
# Then look for data method instance
|
|
27
|
+
data_instance = env.get("this")
|
|
28
|
+
if data_instance is not None:
|
|
29
|
+
return data_instance
|
|
30
|
+
|
|
31
|
+
# First, check environment for user-defined variables (including DATA dataclasses)
|
|
32
|
+
val = env.get(node.value)
|
|
33
|
+
if val:
|
|
34
|
+
debug_log(" Found in environment", f"{node.value} = {val}")
|
|
35
|
+
return val
|
|
36
|
+
|
|
37
|
+
# Check builtins (self.builtins should be defined in FunctionEvaluatorMixin)
|
|
38
|
+
if hasattr(self, 'builtins'):
|
|
39
|
+
builtin = self.builtins.get(node.value)
|
|
40
|
+
if builtin:
|
|
41
|
+
debug_log(" Found builtin", f"{node.value} = {builtin}")
|
|
42
|
+
return builtin
|
|
43
|
+
|
|
44
|
+
# Special handling for TX - ONLY if not already defined by user
|
|
45
|
+
# This provides blockchain transaction context when TX is not a user dataclass
|
|
46
|
+
if node.value == "TX":
|
|
47
|
+
from ..blockchain.transaction import get_current_tx, create_tx_context
|
|
48
|
+
tx = get_current_tx()
|
|
49
|
+
if tx is None:
|
|
50
|
+
# Auto-create TX context if not exists
|
|
51
|
+
tx = create_tx_context(caller="system", gas_limit=1000000)
|
|
52
|
+
# Wrap TX context as a Zexus Map object for property access
|
|
53
|
+
# Use plain string keys (not String objects) for Map.get() compatibility
|
|
54
|
+
return Map({
|
|
55
|
+
"caller": String(tx.caller),
|
|
56
|
+
"timestamp": Integer(int(tx.timestamp)),
|
|
57
|
+
"block_hash": String(tx.block_hash),
|
|
58
|
+
"gas_used": Integer(tx.gas_used),
|
|
59
|
+
"gas_remaining": Integer(tx.gas_remaining),
|
|
60
|
+
"gas_limit": Integer(tx.gas_limit)
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
try:
|
|
64
|
+
env_keys = []
|
|
65
|
+
if hasattr(env, 'store'):
|
|
66
|
+
env_keys = list(env.store.keys())
|
|
67
|
+
# Use direct print to ensure visibility during debugging
|
|
68
|
+
import traceback as _tb
|
|
69
|
+
stack_snip = ''.join(_tb.format_stack(limit=5)[-3:])
|
|
70
|
+
# print(f"[DEBUG] Identifier not found: {node.value}; env_keys={env_keys}\nStack snippet:\n{stack_snip}")
|
|
71
|
+
except Exception:
|
|
72
|
+
pass # print(f"[DEBUG] Identifier not found: {node.value}")
|
|
73
|
+
|
|
74
|
+
# Try to find similar names for helpful suggestion
|
|
75
|
+
suggestion = None
|
|
76
|
+
if hasattr(env, 'store'):
|
|
77
|
+
env_keys = list(env.store.keys())
|
|
78
|
+
|
|
79
|
+
# Find similar variable names (simple approach)
|
|
80
|
+
def similarity(a, b):
|
|
81
|
+
a, b = a.lower(), b.lower()
|
|
82
|
+
if a == b:
|
|
83
|
+
return 1.0
|
|
84
|
+
if a in b or b in a:
|
|
85
|
+
return 0.8
|
|
86
|
+
if len(a) > 2 and len(b) > 2:
|
|
87
|
+
if a[:3] == b[:3] or a[-3:] == b[-3:]:
|
|
88
|
+
return 0.6
|
|
89
|
+
return 0.0
|
|
90
|
+
|
|
91
|
+
similar = [(key, similarity(node.value, key)) for key in env_keys]
|
|
92
|
+
similar = [(k, s) for k, s in similar if s > 0.5]
|
|
93
|
+
similar.sort(key=lambda x: x[1], reverse=True)
|
|
94
|
+
|
|
95
|
+
if similar:
|
|
96
|
+
suggestion = f"Did you mean '{similar[0][0]}'?"
|
|
97
|
+
elif env_keys:
|
|
98
|
+
suggestion = f"Declare the variable first with 'let' or 'const'. Available: {', '.join(env_keys[:5])}"
|
|
99
|
+
else:
|
|
100
|
+
suggestion = "No variables declared yet. Use 'let variableName = value' to create one."
|
|
101
|
+
|
|
102
|
+
return EvaluationError(
|
|
103
|
+
f"Identifier '{node.value}' not found",
|
|
104
|
+
suggestion=suggestion
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
def eval_integer_infix(self, operator, left, right):
|
|
108
|
+
left_val = left.value
|
|
109
|
+
right_val = right.value
|
|
110
|
+
|
|
111
|
+
if operator == "+":
|
|
112
|
+
return Integer(left_val + right_val)
|
|
113
|
+
elif operator == "-":
|
|
114
|
+
return Integer(left_val - right_val)
|
|
115
|
+
elif operator == "*":
|
|
116
|
+
return Integer(left_val * right_val)
|
|
117
|
+
elif operator == "/":
|
|
118
|
+
if right_val == 0:
|
|
119
|
+
return EvaluationError(
|
|
120
|
+
"Division by zero",
|
|
121
|
+
suggestion="Check your divisor value. Consider adding a condition: if (divisor != 0) { ... }"
|
|
122
|
+
)
|
|
123
|
+
return Integer(left_val // right_val)
|
|
124
|
+
elif operator == "%":
|
|
125
|
+
if right_val == 0:
|
|
126
|
+
return EvaluationError(
|
|
127
|
+
"Modulo by zero",
|
|
128
|
+
suggestion="Check your divisor value. Modulo operation requires a non-zero divisor."
|
|
129
|
+
)
|
|
130
|
+
return Integer(left_val % right_val)
|
|
131
|
+
elif operator == "<":
|
|
132
|
+
return TRUE if left_val < right_val else FALSE
|
|
133
|
+
elif operator == ">":
|
|
134
|
+
return TRUE if left_val > right_val else FALSE
|
|
135
|
+
elif operator == "<=":
|
|
136
|
+
return TRUE if left_val <= right_val else FALSE
|
|
137
|
+
elif operator == ">=":
|
|
138
|
+
return TRUE if left_val >= right_val else FALSE
|
|
139
|
+
elif operator == "==":
|
|
140
|
+
return TRUE if left_val == right_val else FALSE
|
|
141
|
+
elif operator == "!=":
|
|
142
|
+
return TRUE if left_val != right_val else FALSE
|
|
143
|
+
|
|
144
|
+
return EvaluationError(f"Unknown integer operator: {operator}")
|
|
145
|
+
|
|
146
|
+
def eval_float_infix(self, operator, left, right):
|
|
147
|
+
left_val = left.value
|
|
148
|
+
right_val = right.value
|
|
149
|
+
|
|
150
|
+
if operator == "+":
|
|
151
|
+
return Float(left_val + right_val)
|
|
152
|
+
elif operator == "-":
|
|
153
|
+
return Float(left_val - right_val)
|
|
154
|
+
elif operator == "*":
|
|
155
|
+
return Float(left_val * right_val)
|
|
156
|
+
elif operator == "/":
|
|
157
|
+
if right_val == 0:
|
|
158
|
+
return EvaluationError("Division by zero")
|
|
159
|
+
return Float(left_val / right_val)
|
|
160
|
+
elif operator == "<":
|
|
161
|
+
return TRUE if left_val < right_val else FALSE
|
|
162
|
+
elif operator == ">":
|
|
163
|
+
return TRUE if left_val > right_val else FALSE
|
|
164
|
+
elif operator == "<=":
|
|
165
|
+
return TRUE if left_val <= right_val else FALSE
|
|
166
|
+
elif operator == ">=":
|
|
167
|
+
return TRUE if left_val >= right_val else FALSE
|
|
168
|
+
elif operator == "==":
|
|
169
|
+
return TRUE if left_val == right_val else FALSE
|
|
170
|
+
elif operator == "!=":
|
|
171
|
+
return TRUE if left_val != right_val else FALSE
|
|
172
|
+
|
|
173
|
+
return EvaluationError(f"Unknown float operator: {operator}")
|
|
174
|
+
|
|
175
|
+
def eval_string_infix(self, operator, left, right):
|
|
176
|
+
if operator == "+":
|
|
177
|
+
return String(left.value + right.value)
|
|
178
|
+
elif operator == "==":
|
|
179
|
+
return TRUE if left.value == right.value else FALSE
|
|
180
|
+
elif operator == "!=":
|
|
181
|
+
return TRUE if left.value != right.value else FALSE
|
|
182
|
+
elif operator == "*":
|
|
183
|
+
# String repetition: "x" * 3 = "xxx"
|
|
184
|
+
# Only works with String * Integer, not String * String
|
|
185
|
+
return EvaluationError(f"Type mismatch: STRING * STRING (use STRING * INTEGER for repetition)")
|
|
186
|
+
return EvaluationError(f"Unknown string operator: {operator}")
|
|
187
|
+
|
|
188
|
+
def eval_infix_expression(self, node, env, stack_trace):
|
|
189
|
+
debug_log("eval_infix_expression", f"{node.left} {node.operator} {node.right}")
|
|
190
|
+
|
|
191
|
+
left = self.eval_node(node.left, env, stack_trace)
|
|
192
|
+
if is_error(left):
|
|
193
|
+
return left
|
|
194
|
+
|
|
195
|
+
right = self.eval_node(node.right, env, stack_trace)
|
|
196
|
+
if is_error(right):
|
|
197
|
+
return right
|
|
198
|
+
|
|
199
|
+
# (removed debug instrumentation)
|
|
200
|
+
|
|
201
|
+
operator = node.operator
|
|
202
|
+
|
|
203
|
+
# Check for operator overloading in left operand (for dataclasses)
|
|
204
|
+
if isinstance(left, Map) and hasattr(left, 'pairs'):
|
|
205
|
+
operator_key = String(f"__operator_{operator}__")
|
|
206
|
+
if operator_key in left.pairs:
|
|
207
|
+
operator_method = left.pairs[operator_key]
|
|
208
|
+
if isinstance(operator_method, Builtin):
|
|
209
|
+
# Call the operator method with right operand
|
|
210
|
+
result = operator_method.fn(right)
|
|
211
|
+
debug_log(" Operator overload called", f"{operator} on {left}")
|
|
212
|
+
return result
|
|
213
|
+
|
|
214
|
+
# Logical Operators (short-circuiting)
|
|
215
|
+
if operator == "&&":
|
|
216
|
+
return TRUE if is_truthy(left) and is_truthy(right) else FALSE
|
|
217
|
+
elif operator == "||":
|
|
218
|
+
return TRUE if is_truthy(left) or is_truthy(right) else FALSE
|
|
219
|
+
|
|
220
|
+
# Equality operators
|
|
221
|
+
elif operator == "==":
|
|
222
|
+
if hasattr(left, 'value') and hasattr(right, 'value'):
|
|
223
|
+
return TRUE if left.value == right.value else FALSE
|
|
224
|
+
return TRUE if left == right else FALSE
|
|
225
|
+
elif operator == "!=":
|
|
226
|
+
if hasattr(left, 'value') and hasattr(right, 'value'):
|
|
227
|
+
return TRUE if left.value != right.value else FALSE
|
|
228
|
+
return TRUE if left != right else FALSE
|
|
229
|
+
|
|
230
|
+
# Type-specific dispatch
|
|
231
|
+
if isinstance(left, Integer) and isinstance(right, Integer):
|
|
232
|
+
return self.eval_integer_infix(operator, left, right)
|
|
233
|
+
elif isinstance(left, Float) and isinstance(right, Float):
|
|
234
|
+
return self.eval_float_infix(operator, left, right)
|
|
235
|
+
elif isinstance(left, String) and isinstance(right, String):
|
|
236
|
+
return self.eval_string_infix(operator, left, right)
|
|
237
|
+
|
|
238
|
+
# String repetition: "x" * 100 or 100 * "x"
|
|
239
|
+
elif operator == "*":
|
|
240
|
+
if isinstance(left, String) and isinstance(right, Integer):
|
|
241
|
+
# "x" * 100
|
|
242
|
+
return String(left.value * right.value)
|
|
243
|
+
elif isinstance(left, Integer) and isinstance(right, String):
|
|
244
|
+
# 100 * "x"
|
|
245
|
+
return String(right.value * left.value)
|
|
246
|
+
|
|
247
|
+
# Array Concatenation
|
|
248
|
+
elif operator == "+" and isinstance(left, List) and isinstance(right, List):
|
|
249
|
+
# Concatenate two arrays: [1, 2] + [3, 4] = [1, 2, 3, 4]
|
|
250
|
+
new_elements = left.elements[:] + right.elements[:]
|
|
251
|
+
return List(new_elements)
|
|
252
|
+
|
|
253
|
+
# DateTime arithmetic
|
|
254
|
+
elif isinstance(left, DateTime) and isinstance(right, DateTime):
|
|
255
|
+
# DateTime - DateTime = time difference in seconds (as Float)
|
|
256
|
+
if operator == "-":
|
|
257
|
+
diff = left.timestamp - right.timestamp
|
|
258
|
+
# Return the difference as a Float in seconds
|
|
259
|
+
return Float(diff)
|
|
260
|
+
else:
|
|
261
|
+
return EvaluationError(f"Unsupported operation: DATETIME {operator} DATETIME")
|
|
262
|
+
elif isinstance(left, DateTime) and isinstance(right, (Integer, Float)):
|
|
263
|
+
# DateTime + Number or DateTime - Number (add/subtract seconds)
|
|
264
|
+
if operator == "+":
|
|
265
|
+
new_timestamp = left.timestamp + float(right.value)
|
|
266
|
+
return DateTime(new_timestamp)
|
|
267
|
+
elif operator == "-":
|
|
268
|
+
new_timestamp = left.timestamp - float(right.value)
|
|
269
|
+
return DateTime(new_timestamp)
|
|
270
|
+
else:
|
|
271
|
+
return EvaluationError(f"Unsupported operation: DATETIME {operator} {right.type()}")
|
|
272
|
+
elif isinstance(left, (Integer, Float)) and isinstance(right, DateTime):
|
|
273
|
+
# Number + DateTime (add seconds to datetime)
|
|
274
|
+
if operator == "+":
|
|
275
|
+
new_timestamp = right.timestamp + float(left.value)
|
|
276
|
+
return DateTime(new_timestamp)
|
|
277
|
+
else:
|
|
278
|
+
return EvaluationError(f"Unsupported operation: {left.type()} {operator} DATETIME")
|
|
279
|
+
|
|
280
|
+
# Mixed String Concatenation
|
|
281
|
+
elif operator == "+":
|
|
282
|
+
if isinstance(left, String):
|
|
283
|
+
right_str = right.inspect() if not isinstance(right, String) else right.value
|
|
284
|
+
return String(left.value + str(right_str))
|
|
285
|
+
elif isinstance(right, String):
|
|
286
|
+
left_str = left.inspect() if not isinstance(left, String) else left.value
|
|
287
|
+
return String(str(left_str) + right.value)
|
|
288
|
+
# Mixed Numeric
|
|
289
|
+
elif isinstance(left, (Integer, Float)) and isinstance(right, (Integer, Float)):
|
|
290
|
+
l_val = float(left.value)
|
|
291
|
+
r_val = float(right.value)
|
|
292
|
+
return Float(l_val + r_val)
|
|
293
|
+
|
|
294
|
+
# Mixed arithmetic operations (String coerced to number for *, -, /, %)
|
|
295
|
+
elif operator in ("*", "-", "/", "%"):
|
|
296
|
+
# Try to coerce strings to numbers for arithmetic
|
|
297
|
+
l_val = None
|
|
298
|
+
r_val = None
|
|
299
|
+
|
|
300
|
+
# Get left value
|
|
301
|
+
if isinstance(left, (Integer, Float)):
|
|
302
|
+
l_val = float(left.value)
|
|
303
|
+
elif isinstance(left, String):
|
|
304
|
+
try:
|
|
305
|
+
l_val = float(left.value)
|
|
306
|
+
except ValueError:
|
|
307
|
+
pass
|
|
308
|
+
|
|
309
|
+
# Get right value
|
|
310
|
+
if isinstance(right, (Integer, Float)):
|
|
311
|
+
r_val = float(right.value)
|
|
312
|
+
elif isinstance(right, String):
|
|
313
|
+
try:
|
|
314
|
+
r_val = float(right.value)
|
|
315
|
+
except ValueError:
|
|
316
|
+
pass
|
|
317
|
+
|
|
318
|
+
# Perform operation if both values could be coerced
|
|
319
|
+
if l_val is not None and r_val is not None:
|
|
320
|
+
try:
|
|
321
|
+
if operator == "*":
|
|
322
|
+
result = l_val * r_val
|
|
323
|
+
elif operator == "-":
|
|
324
|
+
result = l_val - r_val
|
|
325
|
+
elif operator == "/":
|
|
326
|
+
if r_val == 0:
|
|
327
|
+
return EvaluationError("Division by zero")
|
|
328
|
+
result = l_val / r_val
|
|
329
|
+
elif operator == "%":
|
|
330
|
+
if r_val == 0:
|
|
331
|
+
return EvaluationError("Modulo by zero")
|
|
332
|
+
result = l_val % r_val
|
|
333
|
+
|
|
334
|
+
# Return Integer if result is whole number, Float otherwise
|
|
335
|
+
if result == int(result):
|
|
336
|
+
return Integer(int(result))
|
|
337
|
+
return Float(result)
|
|
338
|
+
except Exception as e:
|
|
339
|
+
return EvaluationError(f"Arithmetic error: {str(e)}")
|
|
340
|
+
|
|
341
|
+
# Comparison with mixed numeric types
|
|
342
|
+
elif operator in ("<", ">", "<=", ">="):
|
|
343
|
+
if isinstance(left, (Integer, Float)) and isinstance(right, (Integer, Float)):
|
|
344
|
+
l_val = float(left.value)
|
|
345
|
+
r_val = float(right.value)
|
|
346
|
+
if operator == "<": return TRUE if l_val < r_val else FALSE
|
|
347
|
+
elif operator == ">": return TRUE if l_val > r_val else FALSE
|
|
348
|
+
elif operator == "<=": return TRUE if l_val <= r_val else FALSE
|
|
349
|
+
elif operator == ">=": return TRUE if l_val >= r_val else FALSE
|
|
350
|
+
|
|
351
|
+
# Mixed String/Number comparison (Coerce to float)
|
|
352
|
+
elif (isinstance(left, (Integer, Float)) and isinstance(right, String)) or \
|
|
353
|
+
(isinstance(left, String) and isinstance(right, (Integer, Float))):
|
|
354
|
+
try:
|
|
355
|
+
l_val = float(left.value)
|
|
356
|
+
r_val = float(right.value)
|
|
357
|
+
if operator == "<": return TRUE if l_val < r_val else FALSE
|
|
358
|
+
elif operator == ">": return TRUE if l_val > r_val else FALSE
|
|
359
|
+
elif operator == "<=": return TRUE if l_val <= r_val else FALSE
|
|
360
|
+
elif operator == ">=": return TRUE if l_val >= r_val else FALSE
|
|
361
|
+
except ValueError:
|
|
362
|
+
# If conversion fails, return FALSE (NaN comparison behavior)
|
|
363
|
+
return FALSE
|
|
364
|
+
|
|
365
|
+
return EvaluationError(f"Type mismatch: {left.type()} {operator} {right.type()}")
|
|
366
|
+
|
|
367
|
+
def eval_prefix_expression(self, node, env, stack_trace):
|
|
368
|
+
debug_log("eval_prefix_expression", f"{node.operator} {node.right}")
|
|
369
|
+
|
|
370
|
+
right = self.eval_node(node.right, env, stack_trace)
|
|
371
|
+
if is_error(right):
|
|
372
|
+
return right
|
|
373
|
+
|
|
374
|
+
operator = node.operator
|
|
375
|
+
|
|
376
|
+
if operator == "!":
|
|
377
|
+
# !true = false, !false = true, !null = true, !anything_else = false
|
|
378
|
+
if right == TRUE:
|
|
379
|
+
return FALSE
|
|
380
|
+
elif right == FALSE or right == NULL:
|
|
381
|
+
return TRUE
|
|
382
|
+
else:
|
|
383
|
+
return FALSE
|
|
384
|
+
elif operator == "-":
|
|
385
|
+
if isinstance(right, Integer):
|
|
386
|
+
return Integer(-right.value)
|
|
387
|
+
elif isinstance(right, Float):
|
|
388
|
+
return Float(-right.value)
|
|
389
|
+
return EvaluationError(f"Unknown operator: -{right.type()}")
|
|
390
|
+
|
|
391
|
+
return EvaluationError(f"Unknown operator: {operator}{right.type()}")
|
|
392
|
+
|
|
393
|
+
def eval_if_expression(self, node, env, stack_trace):
|
|
394
|
+
debug_log("eval_if_expression", "Evaluating condition")
|
|
395
|
+
|
|
396
|
+
condition = self.eval_node(node.condition, env, stack_trace)
|
|
397
|
+
if is_error(condition):
|
|
398
|
+
return condition
|
|
399
|
+
|
|
400
|
+
if is_truthy(condition):
|
|
401
|
+
debug_log(" Condition true, evaluating consequence")
|
|
402
|
+
return self.eval_node(node.consequence, env, stack_trace)
|
|
403
|
+
elif node.alternative:
|
|
404
|
+
debug_log(" Condition false, evaluating alternative")
|
|
405
|
+
return self.eval_node(node.alternative, env, stack_trace)
|
|
406
|
+
|
|
407
|
+
debug_log(" Condition false, no alternative")
|
|
408
|
+
return NULL
|
|
409
|
+
|
|
410
|
+
def eval_expressions(self, exps, env):
|
|
411
|
+
results = []
|
|
412
|
+
for e in exps:
|
|
413
|
+
val = self.eval_node(e, env)
|
|
414
|
+
if is_error(val):
|
|
415
|
+
return val
|
|
416
|
+
results.append(val)
|
|
417
|
+
return results
|
|
418
|
+
|
|
419
|
+
def eval_ternary_expression(self, node, env, stack_trace):
|
|
420
|
+
"""Evaluate ternary expression: condition ? true_value : false_value"""
|
|
421
|
+
from .utils import is_truthy
|
|
422
|
+
|
|
423
|
+
condition = self.eval_node(node.condition, env, stack_trace)
|
|
424
|
+
if is_error(condition):
|
|
425
|
+
return condition
|
|
426
|
+
|
|
427
|
+
if is_truthy(condition):
|
|
428
|
+
return self.eval_node(node.true_value, env, stack_trace)
|
|
429
|
+
else:
|
|
430
|
+
return self.eval_node(node.false_value, env, stack_trace)
|
|
431
|
+
|
|
432
|
+
def eval_nullish_expression(self, node, env, stack_trace):
|
|
433
|
+
"""Evaluate nullish coalescing: value ?? default
|
|
434
|
+
Returns default if value is null/undefined, otherwise returns value"""
|
|
435
|
+
left = self.eval_node(node.left, env, stack_trace)
|
|
436
|
+
|
|
437
|
+
# If left is an error, return the error
|
|
438
|
+
if is_error(left):
|
|
439
|
+
return left
|
|
440
|
+
|
|
441
|
+
# Check if left is null or undefined (NULL)
|
|
442
|
+
if left is NULL or left is None or (hasattr(left, 'type') and left.type() == 'NULL'):
|
|
443
|
+
return self.eval_node(node.right, env, stack_trace)
|
|
444
|
+
|
|
445
|
+
return left
|
|
446
|
+
|
|
447
|
+
def eval_await_expression(self, node, env, stack_trace):
|
|
448
|
+
"""Evaluate await expression: await <expression>
|
|
449
|
+
|
|
450
|
+
Await can handle:
|
|
451
|
+
1. Promise objects - waits for resolution
|
|
452
|
+
2. Coroutine objects - resumes until complete
|
|
453
|
+
3. Async action calls - wraps in Promise
|
|
454
|
+
4. Regular values - returns immediately
|
|
455
|
+
"""
|
|
456
|
+
from ..object import Promise, Coroutine, EvaluationError
|
|
457
|
+
|
|
458
|
+
# Evaluate the expression to await
|
|
459
|
+
awaitable = self.eval_node(node.expression, env, stack_trace)
|
|
460
|
+
|
|
461
|
+
# Check for errors
|
|
462
|
+
if is_error(awaitable):
|
|
463
|
+
return awaitable
|
|
464
|
+
|
|
465
|
+
# Handle different awaitable types
|
|
466
|
+
if hasattr(awaitable, 'type'):
|
|
467
|
+
obj_type = awaitable.type()
|
|
468
|
+
|
|
469
|
+
# Await a Promise
|
|
470
|
+
if obj_type == "PROMISE":
|
|
471
|
+
# Since promises execute immediately in executor, they should be resolved
|
|
472
|
+
if awaitable.is_resolved():
|
|
473
|
+
try:
|
|
474
|
+
result = awaitable.get_value()
|
|
475
|
+
return result if result is not None else NULL
|
|
476
|
+
except Exception as e:
|
|
477
|
+
# Propagate error with stack trace context
|
|
478
|
+
error_msg = f"Promise rejected: {e}"
|
|
479
|
+
if hasattr(awaitable, 'stack_trace') and awaitable.stack_trace:
|
|
480
|
+
error_msg += f"\n Promise created at: {awaitable.stack_trace}"
|
|
481
|
+
return EvaluationError(error_msg)
|
|
482
|
+
else:
|
|
483
|
+
# Promise is still pending - this shouldn't happen with current implementation
|
|
484
|
+
# but we can spin-wait briefly
|
|
485
|
+
import time
|
|
486
|
+
max_wait = 1.0 # 1 second timeout
|
|
487
|
+
waited = 0.0
|
|
488
|
+
while not awaitable.is_resolved() and waited < max_wait:
|
|
489
|
+
time.sleep(0.001) # 1ms
|
|
490
|
+
waited += 0.001
|
|
491
|
+
|
|
492
|
+
if awaitable.is_resolved():
|
|
493
|
+
try:
|
|
494
|
+
result = awaitable.get_value()
|
|
495
|
+
return result if result is not None else NULL
|
|
496
|
+
except Exception as e:
|
|
497
|
+
return EvaluationError(f"Promise rejected: {e}")
|
|
498
|
+
else:
|
|
499
|
+
return EvaluationError("Await timeout: promise did not resolve")
|
|
500
|
+
|
|
501
|
+
# Await a Coroutine
|
|
502
|
+
elif obj_type == "COROUTINE":
|
|
503
|
+
# Resume coroutine until complete
|
|
504
|
+
while not awaitable.is_complete:
|
|
505
|
+
is_done, value = awaitable.resume()
|
|
506
|
+
if is_done:
|
|
507
|
+
# Check if there was an error
|
|
508
|
+
if awaitable.error:
|
|
509
|
+
return EvaluationError(f"Coroutine error: {awaitable.error}")
|
|
510
|
+
return value if value is not None else NULL
|
|
511
|
+
|
|
512
|
+
# If coroutine yielded a value, it might be another awaitable
|
|
513
|
+
if hasattr(value, 'type') and value.type() == "PROMISE":
|
|
514
|
+
# Wait for the promise
|
|
515
|
+
if value.is_resolved():
|
|
516
|
+
try:
|
|
517
|
+
resume_value = value.get_value()
|
|
518
|
+
# Send the value back to the coroutine
|
|
519
|
+
is_done, result = awaitable.resume(resume_value)
|
|
520
|
+
if is_done:
|
|
521
|
+
return result if result is not None else NULL
|
|
522
|
+
except Exception as e:
|
|
523
|
+
return EvaluationError(f"Promise error in coroutine: {e}")
|
|
524
|
+
|
|
525
|
+
return awaitable.result if awaitable.result is not None else NULL
|
|
526
|
+
|
|
527
|
+
# Regular value - return immediately
|
|
528
|
+
else:
|
|
529
|
+
return awaitable
|
|
530
|
+
|
|
531
|
+
# No type method - return as-is
|
|
532
|
+
return awaitable
|
|
533
|
+
|
|
534
|
+
def eval_file_import_expression(self, node, env, stack_trace):
|
|
535
|
+
"""Evaluate file import expression: let code << "filename.ext"
|
|
536
|
+
|
|
537
|
+
Reads the file contents and returns as a String object.
|
|
538
|
+
Supports any file extension - returns raw file content.
|
|
539
|
+
"""
|
|
540
|
+
import os
|
|
541
|
+
|
|
542
|
+
# 1. Evaluate the filepath expression
|
|
543
|
+
filepath_obj = self.eval_node(node.filepath, env, stack_trace)
|
|
544
|
+
if is_error(filepath_obj):
|
|
545
|
+
return filepath_obj
|
|
546
|
+
|
|
547
|
+
# 2. Convert to string
|
|
548
|
+
if hasattr(filepath_obj, 'value'):
|
|
549
|
+
filepath = str(filepath_obj.value)
|
|
550
|
+
else:
|
|
551
|
+
filepath = str(filepath_obj)
|
|
552
|
+
|
|
553
|
+
# 3. Normalize path (handle relative paths relative to CWD)
|
|
554
|
+
if not os.path.isabs(filepath):
|
|
555
|
+
filepath = os.path.join(os.getcwd(), filepath)
|
|
556
|
+
|
|
557
|
+
# 4. Check if file exists
|
|
558
|
+
if not os.path.exists(filepath):
|
|
559
|
+
return new_error(f"Cannot import file '{filepath}': File not found", stack_trace)
|
|
560
|
+
|
|
561
|
+
# 5. Read file contents
|
|
562
|
+
try:
|
|
563
|
+
with open(filepath, 'r', encoding='utf-8') as f:
|
|
564
|
+
content = f.read()
|
|
565
|
+
|
|
566
|
+
return String(content)
|
|
567
|
+
except UnicodeDecodeError:
|
|
568
|
+
# Try binary mode for non-text files
|
|
569
|
+
try:
|
|
570
|
+
with open(filepath, 'rb') as f:
|
|
571
|
+
content = f.read()
|
|
572
|
+
# Return as string representation of bytes
|
|
573
|
+
return String(str(content))
|
|
574
|
+
except Exception as e:
|
|
575
|
+
return new_error(f"Error reading file '{filepath}': {e}", stack_trace)
|
|
576
|
+
except Exception as e:
|
|
577
|
+
return new_error(f"Error importing file '{filepath}': {e}", stack_trace)
|
|
578
|
+
def eval_match_expression(self, node, env, stack_trace):
|
|
579
|
+
"""Evaluate match expression with pattern matching
|
|
580
|
+
|
|
581
|
+
match value {
|
|
582
|
+
Point(x, y) => x + y,
|
|
583
|
+
User(name, _) => name,
|
|
584
|
+
42 => "the answer",
|
|
585
|
+
_ => "default"
|
|
586
|
+
}
|
|
587
|
+
"""
|
|
588
|
+
from .. import zexus_ast
|
|
589
|
+
from ..object import Map, String, Integer, Boolean as BooleanObj, Environment
|
|
590
|
+
|
|
591
|
+
debug_log("eval_match_expression", "Evaluating match expression")
|
|
592
|
+
|
|
593
|
+
# Evaluate the value to match against
|
|
594
|
+
match_value = self.eval_node(node.value, env, stack_trace)
|
|
595
|
+
if is_error(match_value):
|
|
596
|
+
return match_value
|
|
597
|
+
|
|
598
|
+
debug_log(" Match value", str(match_value))
|
|
599
|
+
|
|
600
|
+
# Try each case in order
|
|
601
|
+
for case in node.cases:
|
|
602
|
+
pattern = case.pattern
|
|
603
|
+
result_expr = case.result
|
|
604
|
+
|
|
605
|
+
# Try to match the pattern
|
|
606
|
+
match_result = self._match_pattern(pattern, match_value, env)
|
|
607
|
+
|
|
608
|
+
if match_result is not None:
|
|
609
|
+
# Pattern matched! Create new environment with bindings
|
|
610
|
+
new_env = Environment(outer=env)
|
|
611
|
+
|
|
612
|
+
# Add all bindings to the new environment
|
|
613
|
+
for var_name, var_value in match_result.items():
|
|
614
|
+
new_env.set(var_name, var_value)
|
|
615
|
+
|
|
616
|
+
# Evaluate and return the result expression
|
|
617
|
+
result = self.eval_node(result_expr, new_env, stack_trace)
|
|
618
|
+
debug_log(" ✅ Pattern matched", f"Result: {result}")
|
|
619
|
+
return result
|
|
620
|
+
|
|
621
|
+
# No pattern matched
|
|
622
|
+
return EvaluationError("Match expression: no pattern matched")
|
|
623
|
+
|
|
624
|
+
def _match_pattern(self, pattern, value, env):
|
|
625
|
+
"""Try to match a pattern against a value
|
|
626
|
+
|
|
627
|
+
Returns:
|
|
628
|
+
dict: Bindings if matched (variable name -> value)
|
|
629
|
+
None: If pattern doesn't match
|
|
630
|
+
"""
|
|
631
|
+
from .. import zexus_ast
|
|
632
|
+
from ..object import Map, String, Integer, Float, Boolean as BooleanObj
|
|
633
|
+
|
|
634
|
+
# Wildcard pattern: always matches, no bindings
|
|
635
|
+
if isinstance(pattern, zexus_ast.WildcardPattern):
|
|
636
|
+
debug_log(" 🎯 Wildcard pattern matched", "_")
|
|
637
|
+
return {}
|
|
638
|
+
|
|
639
|
+
# Variable pattern: always matches, bind variable
|
|
640
|
+
if isinstance(pattern, zexus_ast.VariablePattern):
|
|
641
|
+
debug_log(" 🎯 Variable pattern matched", f"{pattern.name} = {value}")
|
|
642
|
+
return {pattern.name: value}
|
|
643
|
+
|
|
644
|
+
# Literal pattern: check equality
|
|
645
|
+
if isinstance(pattern, zexus_ast.LiteralPattern):
|
|
646
|
+
pattern_value = self.eval_node(pattern.value, env, [])
|
|
647
|
+
|
|
648
|
+
# Compare values
|
|
649
|
+
matches = False
|
|
650
|
+
if isinstance(value, Integer) and isinstance(pattern_value, Integer):
|
|
651
|
+
matches = value.value == pattern_value.value
|
|
652
|
+
elif isinstance(value, Float) and isinstance(pattern_value, Float):
|
|
653
|
+
matches = value.value == pattern_value.value
|
|
654
|
+
elif isinstance(value, String) and isinstance(pattern_value, String):
|
|
655
|
+
matches = value.value == pattern_value.value
|
|
656
|
+
elif isinstance(value, BooleanObj) and isinstance(pattern_value, BooleanObj):
|
|
657
|
+
matches = value.value == pattern_value.value
|
|
658
|
+
|
|
659
|
+
if matches:
|
|
660
|
+
debug_log(" 🎯 Literal pattern matched", str(pattern_value))
|
|
661
|
+
return {}
|
|
662
|
+
else:
|
|
663
|
+
debug_log(" ❌ Literal pattern didn't match", f"{pattern_value} != {value}")
|
|
664
|
+
return None
|
|
665
|
+
|
|
666
|
+
# Constructor pattern: match dataclass instances
|
|
667
|
+
if isinstance(pattern, zexus_ast.ConstructorPattern):
|
|
668
|
+
# Check if value is a Map (dataclass instance)
|
|
669
|
+
if not isinstance(value, Map):
|
|
670
|
+
debug_log(" ❌ Constructor pattern: value is not a dataclass", type(value).__name__)
|
|
671
|
+
return None
|
|
672
|
+
|
|
673
|
+
# Check if value has __type__ field matching constructor name
|
|
674
|
+
type_key = String("__type__")
|
|
675
|
+
if type_key not in value.pairs:
|
|
676
|
+
debug_log(" ❌ Constructor pattern: no __type__ field", "")
|
|
677
|
+
return None
|
|
678
|
+
|
|
679
|
+
type_value = value.pairs[type_key]
|
|
680
|
+
if not isinstance(type_value, String):
|
|
681
|
+
debug_log(" ❌ Constructor pattern: __type__ is not a string", type(type_value).__name__)
|
|
682
|
+
return None
|
|
683
|
+
|
|
684
|
+
# Extract actual type name (handle specialized generics like "Point<number>")
|
|
685
|
+
actual_type = type_value.value
|
|
686
|
+
if '<' in actual_type:
|
|
687
|
+
# Strip generic parameters for matching
|
|
688
|
+
actual_type = actual_type.split('<')[0]
|
|
689
|
+
|
|
690
|
+
if actual_type != pattern.constructor_name:
|
|
691
|
+
debug_log(" ❌ Constructor pattern: type mismatch", f"{actual_type} != {pattern.constructor_name}")
|
|
692
|
+
return None
|
|
693
|
+
|
|
694
|
+
debug_log(" ✅ Constructor type matched", pattern.constructor_name)
|
|
695
|
+
|
|
696
|
+
# Extract field values and match against bindings
|
|
697
|
+
bindings = {}
|
|
698
|
+
|
|
699
|
+
# Get all non-internal, non-method fields from the dataclass
|
|
700
|
+
# Maintain original field order from the dataclass definition
|
|
701
|
+
fields = []
|
|
702
|
+
field_dict = {}
|
|
703
|
+
|
|
704
|
+
for key, val in value.pairs.items():
|
|
705
|
+
if isinstance(key, String):
|
|
706
|
+
field_name = key.value
|
|
707
|
+
# Skip internal fields (__type__, __immutable__, etc.)
|
|
708
|
+
if field_name.startswith("__"):
|
|
709
|
+
continue
|
|
710
|
+
# Skip auto-generated methods (toString, toJSON, clone, equals, hash, verify, fromJSON)
|
|
711
|
+
if field_name in {"toString", "toJSON", "clone", "equals", "hash", "verify", "fromJSON"}:
|
|
712
|
+
continue
|
|
713
|
+
field_dict[field_name] = val
|
|
714
|
+
|
|
715
|
+
# Try to get field order from __field_order__ metadata if available
|
|
716
|
+
# Otherwise, use the order they appear in the Map (which should be insertion order in Python 3.7+)
|
|
717
|
+
field_order_key = String("__field_order__")
|
|
718
|
+
if field_order_key in value.pairs:
|
|
719
|
+
# Use explicit field order if available
|
|
720
|
+
field_order = value.pairs[field_order_key]
|
|
721
|
+
if isinstance(field_order, List):
|
|
722
|
+
for field_name_obj in field_order.elements:
|
|
723
|
+
if isinstance(field_name_obj, String):
|
|
724
|
+
field_name = field_name_obj.value
|
|
725
|
+
if field_name in field_dict:
|
|
726
|
+
fields.append((field_name, field_dict[field_name]))
|
|
727
|
+
else:
|
|
728
|
+
# Use insertion order (dict maintains order in Python 3.7+)
|
|
729
|
+
fields = [(k, v) for k, v in field_dict.items()]
|
|
730
|
+
|
|
731
|
+
# Match each binding pattern against corresponding field value
|
|
732
|
+
if len(pattern.bindings) != len(fields):
|
|
733
|
+
debug_log(" ❌ Constructor pattern: binding count mismatch", f"{len(pattern.bindings)} != {len(fields)}")
|
|
734
|
+
return None
|
|
735
|
+
|
|
736
|
+
for i, (field_name, field_value) in enumerate(fields):
|
|
737
|
+
binding_pattern = pattern.bindings[i]
|
|
738
|
+
|
|
739
|
+
# Recursively match the binding pattern
|
|
740
|
+
binding_result = self._match_pattern(binding_pattern, field_value, env)
|
|
741
|
+
|
|
742
|
+
if binding_result is None:
|
|
743
|
+
debug_log(" ❌ Constructor pattern: binding didn't match", f"field {field_name}")
|
|
744
|
+
return None
|
|
745
|
+
|
|
746
|
+
# Merge bindings
|
|
747
|
+
bindings.update(binding_result)
|
|
748
|
+
|
|
749
|
+
debug_log(" 🎯 Constructor pattern fully matched", f"{pattern.constructor_name} with {len(bindings)} bindings")
|
|
750
|
+
return bindings
|
|
751
|
+
|
|
752
|
+
# Unknown pattern type
|
|
753
|
+
debug_log(" ❌ Unknown pattern type", type(pattern).__name__)
|
|
754
|
+
return None
|
|
755
|
+
def eval_async_expression(self, node, env, stack_trace):
|
|
756
|
+
"""Evaluate async expression: async <expression>
|
|
757
|
+
|
|
758
|
+
Executes the expression in a background thread.
|
|
759
|
+
Example: async producer()
|
|
760
|
+
"""
|
|
761
|
+
import threading
|
|
762
|
+
import sys
|
|
763
|
+
|
|
764
|
+
# For call expressions, we need to defer evaluation to the thread
|
|
765
|
+
# Otherwise evaluating here will execute the action in the main thread
|
|
766
|
+
if type(node.expression).__name__ == 'CallExpression':
|
|
767
|
+
def run_in_thread():
|
|
768
|
+
try:
|
|
769
|
+
result = self.eval_node(node.expression, env, stack_trace)
|
|
770
|
+
|
|
771
|
+
# If it's a Coroutine (from async action), execute it
|
|
772
|
+
if hasattr(result, '__class__') and result.__class__.__name__ == 'Coroutine':
|
|
773
|
+
try:
|
|
774
|
+
# Prime the generator
|
|
775
|
+
next(result.generator)
|
|
776
|
+
# Execute until completion
|
|
777
|
+
while True:
|
|
778
|
+
next(result.generator)
|
|
779
|
+
except StopIteration:
|
|
780
|
+
pass # Coroutine completed
|
|
781
|
+
|
|
782
|
+
except StopIteration:
|
|
783
|
+
pass # Normal coroutine completion
|
|
784
|
+
except Exception as e:
|
|
785
|
+
import sys
|
|
786
|
+
print(f"[ASYNC ERROR] {type(e).__name__}: {str(e)}", file=sys.stderr, flush=True)
|
|
787
|
+
import traceback
|
|
788
|
+
traceback.print_exc(file=sys.stderr)
|
|
789
|
+
|
|
790
|
+
thread = threading.Thread(target=run_in_thread, daemon=True)
|
|
791
|
+
thread.start()
|
|
792
|
+
return NULL
|
|
793
|
+
|
|
794
|
+
# For other expressions, evaluate first then check if it's a Coroutine
|
|
795
|
+
result = self.eval_node(node.expression, env, stack_trace)
|
|
796
|
+
|
|
797
|
+
if is_error(result):
|
|
798
|
+
return result
|
|
799
|
+
|
|
800
|
+
# print(f"[ASYNC EXPR] Expression evaluated to: {type(result).__name__}", file=sys.stderr)
|
|
801
|
+
|
|
802
|
+
# If it's a Coroutine (from calling an async action), execute it in a thread
|
|
803
|
+
if hasattr(result, '__class__') and result.__class__.__name__ == 'Coroutine':
|
|
804
|
+
def run_coroutine():
|
|
805
|
+
try:
|
|
806
|
+
# Prime the generator
|
|
807
|
+
next(result.generator)
|
|
808
|
+
# Execute until completion
|
|
809
|
+
while True:
|
|
810
|
+
next(result.generator)
|
|
811
|
+
except StopIteration:
|
|
812
|
+
pass # Coroutine completed normally
|
|
813
|
+
except Exception as e:
|
|
814
|
+
import sys
|
|
815
|
+
print(f"[ASYNC ERROR] {type(e).__name__}: {str(e)}", file=sys.stderr, flush=True)
|
|
816
|
+
import traceback
|
|
817
|
+
traceback.print_exc(file=sys.stderr)
|
|
818
|
+
|
|
819
|
+
thread = threading.Thread(target=run_coroutine, daemon=True)
|
|
820
|
+
thread.start()
|
|
821
|
+
return NULL
|
|
822
|
+
|
|
823
|
+
# For any other result (including NULL from regular actions),
|
|
824
|
+
# we can't execute it asynchronously since it already executed.
|
|
825
|
+
# Just return NULL to indicate "async operation initiated"
|
|
826
|
+
# print(f"[ASYNC EXPR] Result is not a coroutine, returning NULL", file=sys.stderr)
|
|
827
|
+
return NULL
|