zexus 1.8.2 → 1.8.3
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/README.md +89 -64
- package/package.json +1 -1
- package/rust_core/Cargo.lock +1 -1
- package/src/zexus/__init__.py +1 -1
- package/src/zexus/builtin_modules.py +50 -13
- package/src/zexus/cli/main.py +46 -1
- package/src/zexus/cli/zpm.py +1 -1
- package/src/zexus/evaluator/bytecode_compiler.py +11 -2
- package/src/zexus/evaluator/core.py +4 -1
- package/src/zexus/evaluator/expressions.py +11 -2
- package/src/zexus/evaluator/functions.py +72 -0
- package/src/zexus/evaluator/resource_limiter.py +1 -1
- package/src/zexus/evaluator/statements.py +44 -4
- package/src/zexus/kernel/__init__.py +34 -0
- package/src/zexus/kernel/hooks.py +276 -0
- package/src/zexus/kernel/registry.py +203 -0
- package/src/zexus/kernel/zir/__init__.py +145 -0
- package/src/zexus/lexer.py +7 -0
- package/src/zexus/object.py +28 -5
- package/src/zexus/parser/parser.py +53 -11
- package/src/zexus/parser/strategy_context.py +179 -10
- package/src/zexus/security.py +26 -2
- package/src/zexus/stdlib/blockchain.py +84 -0
- package/src/zexus/stdlib/http_server.py +2 -2
- package/src/zexus/stdlib/math.py +25 -17
- package/src/zexus/stdlib_integration.py +119 -2
- package/src/zexus/type_checker.py +17 -12
- package/src/zexus/vm/compiler.py +57 -6
- package/src/zexus/vm/fastops.c +4704 -1263
- package/src/zexus/vm/fastops.cpython-312-x86_64-linux-gnu.so +0 -0
- package/src/zexus/vm/fastops.pyx +81 -3
- package/src/zexus/vm/optimizer.py +65 -27
- package/src/zexus/vm/vm.py +871 -98
- package/src/zexus/zexus_ast.py +4 -1
- package/src/zexus/zpm/package_manager.py +1 -1
- package/src/zexus.egg-info/PKG-INFO +90 -65
- package/src/zexus.egg-info/SOURCES.txt +51 -0
|
@@ -319,6 +319,90 @@ class BlockchainModule:
|
|
|
319
319
|
|
|
320
320
|
return True
|
|
321
321
|
|
|
322
|
+
@staticmethod
|
|
323
|
+
def create_chain(name: str = "default", config: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
|
|
324
|
+
"""Create a new blockchain initialized with a genesis block.
|
|
325
|
+
|
|
326
|
+
Args:
|
|
327
|
+
name: Name/identifier for this chain (default: "default")
|
|
328
|
+
config: Optional configuration dict. Supported keys:
|
|
329
|
+
- difficulty (int): Mining difficulty (default: 4)
|
|
330
|
+
- mining_reward (float): Block reward (default: 50.0)
|
|
331
|
+
- max_supply (float): Maximum token supply (default: 21_000_000.0)
|
|
332
|
+
|
|
333
|
+
Returns:
|
|
334
|
+
A chain dict with keys: name, config, blocks (list starting with
|
|
335
|
+
genesis), pending_transactions, height, and created_at.
|
|
336
|
+
"""
|
|
337
|
+
cfg = {
|
|
338
|
+
"difficulty": 4,
|
|
339
|
+
"mining_reward": 50.0,
|
|
340
|
+
"max_supply": 21_000_000.0,
|
|
341
|
+
}
|
|
342
|
+
if config:
|
|
343
|
+
cfg.update(config)
|
|
344
|
+
|
|
345
|
+
genesis = BlockchainModule.create_genesis_block()
|
|
346
|
+
|
|
347
|
+
return {
|
|
348
|
+
"name": name,
|
|
349
|
+
"config": cfg,
|
|
350
|
+
"blocks": [genesis],
|
|
351
|
+
"pending_transactions": [],
|
|
352
|
+
"height": 1,
|
|
353
|
+
"created_at": genesis["timestamp"],
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
@staticmethod
|
|
357
|
+
def add_block(chain: Dict[str, Any], data: Any) -> Dict[str, Any]:
|
|
358
|
+
"""Add a new block to an existing chain.
|
|
359
|
+
|
|
360
|
+
Args:
|
|
361
|
+
chain: Chain dict returned by create_chain()
|
|
362
|
+
data: Data/transactions to include in the block
|
|
363
|
+
|
|
364
|
+
Returns:
|
|
365
|
+
The newly created block (also appended to chain["blocks"]).
|
|
366
|
+
"""
|
|
367
|
+
blocks = chain.get("blocks", [])
|
|
368
|
+
last_block = blocks[-1] if blocks else None
|
|
369
|
+
prev_hash = last_block["hash"] if last_block else "0" * 64
|
|
370
|
+
index = len(blocks)
|
|
371
|
+
|
|
372
|
+
new_block = BlockchainModule.create_block(
|
|
373
|
+
index=index,
|
|
374
|
+
timestamp=time.time(),
|
|
375
|
+
data=data,
|
|
376
|
+
previous_hash=prev_hash,
|
|
377
|
+
nonce=0,
|
|
378
|
+
)
|
|
379
|
+
blocks.append(new_block)
|
|
380
|
+
chain["blocks"] = blocks
|
|
381
|
+
chain["height"] = len(blocks)
|
|
382
|
+
return new_block
|
|
383
|
+
|
|
384
|
+
@staticmethod
|
|
385
|
+
def get_chain_info(chain: Dict[str, Any]) -> Dict[str, Any]:
|
|
386
|
+
"""Return summary information about a chain.
|
|
387
|
+
|
|
388
|
+
Args:
|
|
389
|
+
chain: Chain dict returned by create_chain()
|
|
390
|
+
|
|
391
|
+
Returns:
|
|
392
|
+
Dict with name, height, difficulty, mining_reward, created_at,
|
|
393
|
+
latest_hash.
|
|
394
|
+
"""
|
|
395
|
+
blocks = chain.get("blocks", [])
|
|
396
|
+
cfg = chain.get("config", {})
|
|
397
|
+
return {
|
|
398
|
+
"name": chain.get("name", "unknown"),
|
|
399
|
+
"height": chain.get("height", len(blocks)),
|
|
400
|
+
"difficulty": cfg.get("difficulty", 4),
|
|
401
|
+
"mining_reward": cfg.get("mining_reward", 50.0),
|
|
402
|
+
"created_at": chain.get("created_at", 0),
|
|
403
|
+
"latest_hash": blocks[-1]["hash"] if blocks else "",
|
|
404
|
+
}
|
|
405
|
+
|
|
322
406
|
|
|
323
407
|
# Export functions for easy access
|
|
324
408
|
create_address = BlockchainModule.create_address
|
|
@@ -201,7 +201,7 @@ class HTTPServer:
|
|
|
201
201
|
finally:
|
|
202
202
|
try:
|
|
203
203
|
client_socket.close()
|
|
204
|
-
except:
|
|
204
|
+
except OSError:
|
|
205
205
|
pass
|
|
206
206
|
|
|
207
207
|
def listen(self):
|
|
@@ -244,5 +244,5 @@ class HTTPServer:
|
|
|
244
244
|
if self.socket:
|
|
245
245
|
try:
|
|
246
246
|
self.socket.close()
|
|
247
|
-
except:
|
|
247
|
+
except OSError:
|
|
248
248
|
pass
|
package/src/zexus/stdlib/math.py
CHANGED
|
@@ -1,9 +1,17 @@
|
|
|
1
1
|
"""Math module for Zexus standard library."""
|
|
2
2
|
|
|
3
3
|
import math
|
|
4
|
-
import random
|
|
4
|
+
import random as _random_module
|
|
5
5
|
from typing import List
|
|
6
6
|
|
|
7
|
+
# Save builtin references before module-level exports shadow them
|
|
8
|
+
_builtin_min = min
|
|
9
|
+
_builtin_max = max
|
|
10
|
+
_builtin_sum = sum
|
|
11
|
+
_builtin_abs = abs
|
|
12
|
+
_builtin_round = round
|
|
13
|
+
_builtin_pow = pow
|
|
14
|
+
|
|
7
15
|
|
|
8
16
|
class MathModule:
|
|
9
17
|
"""Provides advanced mathematical operations."""
|
|
@@ -121,7 +129,7 @@ class MathModule:
|
|
|
121
129
|
@staticmethod
|
|
122
130
|
def cbrt(x: float) -> float:
|
|
123
131
|
"""Cube root."""
|
|
124
|
-
return math.copysign(1, x) *
|
|
132
|
+
return math.copysign(1, x) * _builtin_abs(x) ** (1/3)
|
|
125
133
|
|
|
126
134
|
# Rounding and absolute functions
|
|
127
135
|
@staticmethod
|
|
@@ -142,12 +150,12 @@ class MathModule:
|
|
|
142
150
|
@staticmethod
|
|
143
151
|
def round(x: float, ndigits: int = 0) -> float:
|
|
144
152
|
"""Round to n digits."""
|
|
145
|
-
return
|
|
153
|
+
return _builtin_round(x, ndigits)
|
|
146
154
|
|
|
147
155
|
@staticmethod
|
|
148
156
|
def abs(x: float) -> float:
|
|
149
157
|
"""Absolute value."""
|
|
150
|
-
return
|
|
158
|
+
return _builtin_abs(x)
|
|
151
159
|
|
|
152
160
|
# Other functions
|
|
153
161
|
@staticmethod
|
|
@@ -163,7 +171,7 @@ class MathModule:
|
|
|
163
171
|
@staticmethod
|
|
164
172
|
def lcm(a: int, b: int) -> int:
|
|
165
173
|
"""Least common multiple."""
|
|
166
|
-
return
|
|
174
|
+
return _builtin_abs(a * b) // math.gcd(a, b) if a and b else 0
|
|
167
175
|
|
|
168
176
|
@staticmethod
|
|
169
177
|
def degrees(x: float) -> float:
|
|
@@ -209,12 +217,12 @@ class MathModule:
|
|
|
209
217
|
@staticmethod
|
|
210
218
|
def sum(numbers: List[float]) -> float:
|
|
211
219
|
"""Sum of numbers."""
|
|
212
|
-
return
|
|
220
|
+
return _builtin_sum(numbers)
|
|
213
221
|
|
|
214
222
|
@staticmethod
|
|
215
223
|
def mean(numbers: List[float]) -> float:
|
|
216
224
|
"""Arithmetic mean."""
|
|
217
|
-
return
|
|
225
|
+
return _builtin_sum(numbers) / len(numbers) if numbers else 0
|
|
218
226
|
|
|
219
227
|
@staticmethod
|
|
220
228
|
def median(numbers: List[float]) -> float:
|
|
@@ -242,7 +250,7 @@ class MathModule:
|
|
|
242
250
|
if not numbers:
|
|
243
251
|
return 0
|
|
244
252
|
m = MathModule.mean(numbers)
|
|
245
|
-
return
|
|
253
|
+
return _builtin_sum((x - m) ** 2 for x in numbers) / len(numbers)
|
|
246
254
|
|
|
247
255
|
@staticmethod
|
|
248
256
|
def stdev(numbers: List[float]) -> float:
|
|
@@ -252,17 +260,17 @@ class MathModule:
|
|
|
252
260
|
@staticmethod
|
|
253
261
|
def min(numbers: List[float]) -> float:
|
|
254
262
|
"""Minimum value."""
|
|
255
|
-
return
|
|
263
|
+
return _builtin_min(numbers) if numbers else 0
|
|
256
264
|
|
|
257
265
|
@staticmethod
|
|
258
266
|
def max(numbers: List[float]) -> float:
|
|
259
267
|
"""Maximum value."""
|
|
260
|
-
return
|
|
268
|
+
return _builtin_max(numbers) if numbers else 0
|
|
261
269
|
|
|
262
270
|
@staticmethod
|
|
263
271
|
def clamp(x: float, min_val: float, max_val: float) -> float:
|
|
264
272
|
"""Clamp x between min and max."""
|
|
265
|
-
return
|
|
273
|
+
return _builtin_max(min_val, _builtin_min(max_val, x))
|
|
266
274
|
|
|
267
275
|
@staticmethod
|
|
268
276
|
def lerp(a: float, b: float, t: float) -> float:
|
|
@@ -273,30 +281,30 @@ class MathModule:
|
|
|
273
281
|
@staticmethod
|
|
274
282
|
def random() -> float:
|
|
275
283
|
"""Random float in [0, 1)."""
|
|
276
|
-
return
|
|
284
|
+
return _random_module.random()
|
|
277
285
|
|
|
278
286
|
@staticmethod
|
|
279
287
|
def randint(a: int, b: int) -> int:
|
|
280
288
|
"""Random integer in [a, b]."""
|
|
281
|
-
return
|
|
289
|
+
return _random_module.randint(a, b)
|
|
282
290
|
|
|
283
291
|
@staticmethod
|
|
284
292
|
def randrange(start: int, stop: int = None, step: int = 1) -> int:
|
|
285
293
|
"""Random integer from range."""
|
|
286
294
|
if stop is None:
|
|
287
|
-
return
|
|
288
|
-
return
|
|
295
|
+
return _random_module.randrange(start)
|
|
296
|
+
return _random_module.randrange(start, stop, step)
|
|
289
297
|
|
|
290
298
|
@staticmethod
|
|
291
299
|
def choice(seq: List) -> any:
|
|
292
300
|
"""Random element from sequence."""
|
|
293
|
-
return
|
|
301
|
+
return _random_module.choice(seq) if seq else None
|
|
294
302
|
|
|
295
303
|
@staticmethod
|
|
296
304
|
def shuffle(seq: List) -> List:
|
|
297
305
|
"""Shuffle sequence (returns new list)."""
|
|
298
306
|
result = seq.copy()
|
|
299
|
-
|
|
307
|
+
_random_module.shuffle(result)
|
|
300
308
|
return result
|
|
301
309
|
|
|
302
310
|
|
|
@@ -3,7 +3,7 @@ Standard Library Integration for Zexus
|
|
|
3
3
|
Provides integration between Python stdlib modules and Zexus evaluator.
|
|
4
4
|
"""
|
|
5
5
|
|
|
6
|
-
from .object import Environment, Builtin, String, Integer, Float, Boolean, Map, List as ListObj, EvaluationError
|
|
6
|
+
from .object import Environment, Builtin, String, Integer, Float, Boolean, Null, Map, List as ListObj, EvaluationError
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
def create_stdlib_module(module_name, evaluator=None):
|
|
@@ -595,6 +595,54 @@ def create_stdlib_module(module_name, evaluator=None):
|
|
|
595
595
|
except Exception as e:
|
|
596
596
|
return EvaluationError(f"validate_chain error: {str(e)}")
|
|
597
597
|
|
|
598
|
+
def _blockchain_create_chain(*args):
|
|
599
|
+
name = "default"
|
|
600
|
+
config = None
|
|
601
|
+
if len(args) >= 1 and hasattr(args[0], 'value'):
|
|
602
|
+
name = str(args[0].value)
|
|
603
|
+
if len(args) >= 2 and isinstance(args[1], Map):
|
|
604
|
+
config = {}
|
|
605
|
+
for k, v in args[1].pairs.items():
|
|
606
|
+
key = k.value if hasattr(k, 'value') else str(k)
|
|
607
|
+
val = v.value if hasattr(v, 'value') else v
|
|
608
|
+
config[key] = val
|
|
609
|
+
try:
|
|
610
|
+
result = BlockchainModule.create_chain(name, config)
|
|
611
|
+
return _python_to_zexus(result)
|
|
612
|
+
except Exception as e:
|
|
613
|
+
return EvaluationError(f"create_chain error: {str(e)}")
|
|
614
|
+
|
|
615
|
+
def _blockchain_add_block(*args):
|
|
616
|
+
if len(args) < 2:
|
|
617
|
+
return EvaluationError("add_block() requires 2 args: chain, data")
|
|
618
|
+
chain_obj = args[0]
|
|
619
|
+
if not isinstance(chain_obj, Map):
|
|
620
|
+
return EvaluationError("add_block() first argument must be a chain map")
|
|
621
|
+
chain = _zexus_to_python(chain_obj)
|
|
622
|
+
data = args[1].value if hasattr(args[1], 'value') else str(args[1])
|
|
623
|
+
try:
|
|
624
|
+
new_block = BlockchainModule.add_block(chain, data)
|
|
625
|
+
# Update the original map in-place so the caller sees changes
|
|
626
|
+
updated = _python_to_zexus(chain)
|
|
627
|
+
if isinstance(updated, Map):
|
|
628
|
+
chain_obj.pairs = updated.pairs
|
|
629
|
+
return _python_to_zexus(new_block)
|
|
630
|
+
except Exception as e:
|
|
631
|
+
return EvaluationError(f"add_block error: {str(e)}")
|
|
632
|
+
|
|
633
|
+
def _blockchain_get_chain_info(*args):
|
|
634
|
+
if len(args) < 1:
|
|
635
|
+
return EvaluationError("get_chain_info() requires 1 argument: chain")
|
|
636
|
+
chain_obj = args[0]
|
|
637
|
+
if not isinstance(chain_obj, Map):
|
|
638
|
+
return EvaluationError("get_chain_info() expects a chain map")
|
|
639
|
+
chain = _zexus_to_python(chain_obj)
|
|
640
|
+
try:
|
|
641
|
+
result = BlockchainModule.get_chain_info(chain)
|
|
642
|
+
return _python_to_zexus(result)
|
|
643
|
+
except Exception as e:
|
|
644
|
+
return EvaluationError(f"get_chain_info error: {str(e)}")
|
|
645
|
+
|
|
598
646
|
env.set("create_address", Builtin(_blockchain_create_address))
|
|
599
647
|
env.set("validate_address", Builtin(_blockchain_validate_address))
|
|
600
648
|
env.set("calculate_merkle_root", Builtin(_blockchain_calculate_merkle_root))
|
|
@@ -606,6 +654,24 @@ def create_stdlib_module(module_name, evaluator=None):
|
|
|
606
654
|
env.set("create_transaction", Builtin(_blockchain_create_transaction))
|
|
607
655
|
env.set("hash_transaction", Builtin(_blockchain_hash_transaction))
|
|
608
656
|
env.set("validate_chain", Builtin(_blockchain_validate_chain))
|
|
657
|
+
env.set("create_chain", Builtin(_blockchain_create_chain))
|
|
658
|
+
env.set("add_block", Builtin(_blockchain_add_block))
|
|
659
|
+
env.set("get_chain_info", Builtin(_blockchain_get_chain_info))
|
|
660
|
+
# camelCase aliases for blockchain functions
|
|
661
|
+
env.set("createChain", Builtin(_blockchain_create_chain))
|
|
662
|
+
env.set("addBlock", Builtin(_blockchain_add_block))
|
|
663
|
+
env.set("getChainInfo", Builtin(_blockchain_get_chain_info))
|
|
664
|
+
env.set("createAddress", Builtin(_blockchain_create_address))
|
|
665
|
+
env.set("validateAddress", Builtin(_blockchain_validate_address))
|
|
666
|
+
env.set("calculateMerkleRoot", Builtin(_blockchain_calculate_merkle_root))
|
|
667
|
+
env.set("createGenesisBlock", Builtin(_blockchain_create_genesis_block))
|
|
668
|
+
env.set("createBlock", Builtin(_blockchain_create_block))
|
|
669
|
+
env.set("hashBlock", Builtin(_blockchain_hash_block))
|
|
670
|
+
env.set("validateBlock", Builtin(_blockchain_validate_block))
|
|
671
|
+
env.set("proofOfWork", Builtin(_blockchain_proof_of_work))
|
|
672
|
+
env.set("createTransaction", Builtin(_blockchain_create_transaction))
|
|
673
|
+
env.set("hashTransaction", Builtin(_blockchain_hash_transaction))
|
|
674
|
+
env.set("validateChain", Builtin(_blockchain_validate_chain))
|
|
609
675
|
|
|
610
676
|
elif module_name == "websocket" or module_name == "stdlib/websocket":
|
|
611
677
|
try:
|
|
@@ -655,6 +721,57 @@ def create_stdlib_module(module_name, evaluator=None):
|
|
|
655
721
|
env.set("create_server", Builtin(_ws_create_server))
|
|
656
722
|
env.set("connect", Builtin(_ws_connect))
|
|
657
723
|
|
|
724
|
+
elif module_name == "kernel" or module_name == "stdlib/kernel":
|
|
725
|
+
# Expose the kernel extension layer to Zexus scripts
|
|
726
|
+
try:
|
|
727
|
+
from .kernel import get_kernel
|
|
728
|
+
|
|
729
|
+
kernel = get_kernel()
|
|
730
|
+
if not kernel.is_booted:
|
|
731
|
+
kernel.boot()
|
|
732
|
+
|
|
733
|
+
def _kernel_status(*args):
|
|
734
|
+
return _python_to_zexus(kernel.status())
|
|
735
|
+
|
|
736
|
+
def _kernel_domains(*args):
|
|
737
|
+
domains = kernel.registry.list_domains()
|
|
738
|
+
return _python_to_zexus([
|
|
739
|
+
{"name": d.name, "version": d.version, "opcodes": len(d.opcodes)}
|
|
740
|
+
for d in domains
|
|
741
|
+
])
|
|
742
|
+
|
|
743
|
+
def _kernel_get_domain(*args):
|
|
744
|
+
if len(args) < 1:
|
|
745
|
+
return EvaluationError("get_domain() requires 1 argument: name")
|
|
746
|
+
name = args[0].value if hasattr(args[0], 'value') else str(args[0])
|
|
747
|
+
d = kernel.registry.get_domain(name)
|
|
748
|
+
if d is None:
|
|
749
|
+
return Null()
|
|
750
|
+
return _python_to_zexus({
|
|
751
|
+
"name": d.name,
|
|
752
|
+
"version": d.version,
|
|
753
|
+
"description": d.description,
|
|
754
|
+
"opcodes": d.opcodes,
|
|
755
|
+
})
|
|
756
|
+
|
|
757
|
+
def _kernel_resolve_opcode(*args):
|
|
758
|
+
if len(args) < 1:
|
|
759
|
+
return EvaluationError("resolve_opcode() requires 1 argument: opcode (int)")
|
|
760
|
+
op = int(args[0].value) if hasattr(args[0], 'value') else int(args[0])
|
|
761
|
+
owner = kernel.resolve_opcode_domain(op)
|
|
762
|
+
return String(owner) if owner else Null()
|
|
763
|
+
|
|
764
|
+
def _kernel_is_booted(*args):
|
|
765
|
+
return Boolean(kernel.is_booted)
|
|
766
|
+
|
|
767
|
+
env.set("status", Builtin(_kernel_status))
|
|
768
|
+
env.set("domains", Builtin(_kernel_domains))
|
|
769
|
+
env.set("get_domain", Builtin(_kernel_get_domain))
|
|
770
|
+
env.set("resolve_opcode", Builtin(_kernel_resolve_opcode))
|
|
771
|
+
env.set("is_booted", Builtin(_kernel_is_booted))
|
|
772
|
+
except Exception:
|
|
773
|
+
pass # Kernel not available — module is empty but doesn't crash
|
|
774
|
+
|
|
658
775
|
return env
|
|
659
776
|
|
|
660
777
|
|
|
@@ -690,7 +807,7 @@ def _zexus_to_python(obj):
|
|
|
690
807
|
|
|
691
808
|
def is_stdlib_module(module_name):
|
|
692
809
|
"""Check if a module name refers to a stdlib module."""
|
|
693
|
-
stdlib_modules = ['fs', 'http', 'json', 'datetime', 'crypto', 'blockchain', 'perf', 'websocket']
|
|
810
|
+
stdlib_modules = ['fs', 'http', 'json', 'datetime', 'crypto', 'blockchain', 'perf', 'websocket', 'kernel']
|
|
694
811
|
|
|
695
812
|
# Handle both "fs" and "stdlib/fs" formats
|
|
696
813
|
if module_name in stdlib_modules:
|
|
@@ -431,18 +431,23 @@ class StaticTypeChecker:
|
|
|
431
431
|
)
|
|
432
432
|
|
|
433
433
|
# Per-argument type check
|
|
434
|
-
for
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
)
|
|
434
|
+
# Skip for entity constructors using {field: value} syntax —
|
|
435
|
+
# the single MapLiteral arg doesn't match positional param types.
|
|
436
|
+
if is_entity and len(args) == 1 and isinstance(args[0], ast.MapLiteral):
|
|
437
|
+
pass
|
|
438
|
+
else:
|
|
439
|
+
for i, (pname, pts) in enumerate(params):
|
|
440
|
+
if i >= len(args):
|
|
441
|
+
break
|
|
442
|
+
if pts is None:
|
|
443
|
+
continue
|
|
444
|
+
actual = _infer_expr_type(args[i], self._scope)
|
|
445
|
+
if actual and not _is_compatible(pts, actual):
|
|
446
|
+
self._error(
|
|
447
|
+
f"Argument '{pname}' of '{fn_name}' expects "
|
|
448
|
+
f"{pts.base_type.value} but got {actual.base_type.value}",
|
|
449
|
+
call,
|
|
450
|
+
)
|
|
446
451
|
|
|
447
452
|
def _check_infix_types(self, expr: ast.InfixExpression):
|
|
448
453
|
"""Warn on obviously wrong infix operations."""
|
package/src/zexus/vm/compiler.py
CHANGED
|
@@ -542,13 +542,27 @@ class BytecodeCompiler:
|
|
|
542
542
|
exit_jump_idx = len(self.instructions)
|
|
543
543
|
self._emit(Opcode.JUMP_IF_FALSE, None) # jump to end if false
|
|
544
544
|
|
|
545
|
-
# --- Extract item
|
|
546
|
-
self._emit(Opcode.LOAD_NAME, iter_const) # push iterable
|
|
547
|
-
self._emit(Opcode.LOAD_NAME, index_const) # push index
|
|
548
|
-
self._emit(Opcode.INDEX) # iterable[index]
|
|
545
|
+
# --- Extract item (and optional index/key) ---
|
|
549
546
|
item_name = node.item.value if hasattr(node.item, 'value') else str(node.item)
|
|
550
547
|
item_const = self._add_constant(item_name)
|
|
551
|
-
|
|
548
|
+
|
|
549
|
+
if node.index:
|
|
550
|
+
# Two-variable for-each: for key, value in map OR for i, item in list
|
|
551
|
+
# Use FOR_ITER 2: pushes (index_or_key, item_or_value) onto stack
|
|
552
|
+
user_index_name = node.index.value if hasattr(node.index, 'value') else str(node.index)
|
|
553
|
+
user_index_const = self._add_constant(user_index_name)
|
|
554
|
+
self._emit(Opcode.LOAD_NAME, iter_const) # push iterable
|
|
555
|
+
self._emit(Opcode.LOAD_NAME, index_const) # push counter
|
|
556
|
+
self._emit(Opcode.FOR_ITER, 2) # push key/index, then value/item
|
|
557
|
+
self._emit(Opcode.STORE_NAME, item_const) # store item/value (top)
|
|
558
|
+
self._emit(Opcode.STORE_NAME, user_index_const) # store index/key (below)
|
|
559
|
+
else:
|
|
560
|
+
# Single variable: for item in list OR for key in map
|
|
561
|
+
# Use FOR_ITER 1: pushes single value (element for list, key for dict)
|
|
562
|
+
self._emit(Opcode.LOAD_NAME, iter_const) # push iterable
|
|
563
|
+
self._emit(Opcode.LOAD_NAME, index_const) # push counter
|
|
564
|
+
self._emit(Opcode.FOR_ITER, 1) # push item or key
|
|
565
|
+
self._emit(Opcode.STORE_NAME, item_const) # store as loop variable
|
|
552
566
|
|
|
553
567
|
# --- Body ---
|
|
554
568
|
self._compile_node(node.body)
|
|
@@ -633,7 +647,16 @@ class BytecodeCompiler:
|
|
|
633
647
|
count = len(pairs)
|
|
634
648
|
|
|
635
649
|
for key, value in iterable:
|
|
636
|
-
|
|
650
|
+
# Map literal keys that are Identifiers should be treated as
|
|
651
|
+
# string constants (field names), NOT as variable lookups.
|
|
652
|
+
if hasattr(key, '__class__') and key.__class__.__name__ == 'Identifier':
|
|
653
|
+
const_idx = self._add_constant(key.value)
|
|
654
|
+
self._emit(Opcode.LOAD_CONST, const_idx)
|
|
655
|
+
elif isinstance(key, str):
|
|
656
|
+
const_idx = self._add_constant(key)
|
|
657
|
+
self._emit(Opcode.LOAD_CONST, const_idx)
|
|
658
|
+
else:
|
|
659
|
+
self._compile_node(key)
|
|
637
660
|
self._compile_node(value)
|
|
638
661
|
|
|
639
662
|
# Build map
|
|
@@ -1104,6 +1127,34 @@ class BytecodeCompiler:
|
|
|
1104
1127
|
self._emit(Opcode.DEFINE_ENTITY, member_count)
|
|
1105
1128
|
self._emit(Opcode.STORE_NAME, name_idx)
|
|
1106
1129
|
|
|
1130
|
+
def _compile_StateStatement(self, node):
|
|
1131
|
+
"""Compile a standalone state declaration.
|
|
1132
|
+
|
|
1133
|
+
Inside a contract, state vars are handled by _compile_ContractStatement
|
|
1134
|
+
which pushes (name, value) pairs for DEFINE_CONTRACT. When a
|
|
1135
|
+
StateStatement appears at the top level or outside contracts, we
|
|
1136
|
+
compile it as a STATE_WRITE + STORE_NAME so the variable is available
|
|
1137
|
+
in the local environment AND in the blockchain state dict.
|
|
1138
|
+
"""
|
|
1139
|
+
var_name = node.name.value if hasattr(node.name, 'value') else str(node.name)
|
|
1140
|
+
name_idx = self._add_constant(var_name)
|
|
1141
|
+
|
|
1142
|
+
# Compile the initial value expression (or None)
|
|
1143
|
+
if getattr(node, 'initial_value', None):
|
|
1144
|
+
self._compile_node(node.initial_value)
|
|
1145
|
+
else:
|
|
1146
|
+
self._emit(Opcode.LOAD_CONST, self._add_constant(None))
|
|
1147
|
+
|
|
1148
|
+
# Write to blockchain state dict
|
|
1149
|
+
self._emit(Opcode.STATE_WRITE, name_idx)
|
|
1150
|
+
|
|
1151
|
+
# Also store in local environment so it's accessible as a normal variable
|
|
1152
|
+
if getattr(node, 'initial_value', None):
|
|
1153
|
+
self._compile_node(node.initial_value)
|
|
1154
|
+
else:
|
|
1155
|
+
self._emit(Opcode.LOAD_CONST, self._add_constant(None))
|
|
1156
|
+
self._emit(Opcode.STORE_NAME, name_idx)
|
|
1157
|
+
|
|
1107
1158
|
def _compile_ExportStatement(self, node):
|
|
1108
1159
|
"""Compile export statement - marks names for module export.
|
|
1109
1160
|
|