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.
Files changed (37) hide show
  1. package/README.md +89 -64
  2. package/package.json +1 -1
  3. package/rust_core/Cargo.lock +1 -1
  4. package/src/zexus/__init__.py +1 -1
  5. package/src/zexus/builtin_modules.py +50 -13
  6. package/src/zexus/cli/main.py +46 -1
  7. package/src/zexus/cli/zpm.py +1 -1
  8. package/src/zexus/evaluator/bytecode_compiler.py +11 -2
  9. package/src/zexus/evaluator/core.py +4 -1
  10. package/src/zexus/evaluator/expressions.py +11 -2
  11. package/src/zexus/evaluator/functions.py +72 -0
  12. package/src/zexus/evaluator/resource_limiter.py +1 -1
  13. package/src/zexus/evaluator/statements.py +44 -4
  14. package/src/zexus/kernel/__init__.py +34 -0
  15. package/src/zexus/kernel/hooks.py +276 -0
  16. package/src/zexus/kernel/registry.py +203 -0
  17. package/src/zexus/kernel/zir/__init__.py +145 -0
  18. package/src/zexus/lexer.py +7 -0
  19. package/src/zexus/object.py +28 -5
  20. package/src/zexus/parser/parser.py +53 -11
  21. package/src/zexus/parser/strategy_context.py +179 -10
  22. package/src/zexus/security.py +26 -2
  23. package/src/zexus/stdlib/blockchain.py +84 -0
  24. package/src/zexus/stdlib/http_server.py +2 -2
  25. package/src/zexus/stdlib/math.py +25 -17
  26. package/src/zexus/stdlib_integration.py +119 -2
  27. package/src/zexus/type_checker.py +17 -12
  28. package/src/zexus/vm/compiler.py +57 -6
  29. package/src/zexus/vm/fastops.c +4704 -1263
  30. package/src/zexus/vm/fastops.cpython-312-x86_64-linux-gnu.so +0 -0
  31. package/src/zexus/vm/fastops.pyx +81 -3
  32. package/src/zexus/vm/optimizer.py +65 -27
  33. package/src/zexus/vm/vm.py +871 -98
  34. package/src/zexus/zexus_ast.py +4 -1
  35. package/src/zexus/zpm/package_manager.py +1 -1
  36. package/src/zexus.egg-info/PKG-INFO +90 -65
  37. 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
@@ -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) * abs(x) ** (1/3)
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 round(x, ndigits)
153
+ return _builtin_round(x, ndigits)
146
154
 
147
155
  @staticmethod
148
156
  def abs(x: float) -> float:
149
157
  """Absolute value."""
150
- return abs(x)
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 abs(a * b) // math.gcd(a, b) if a and b else 0
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 sum(numbers)
220
+ return _builtin_sum(numbers)
213
221
 
214
222
  @staticmethod
215
223
  def mean(numbers: List[float]) -> float:
216
224
  """Arithmetic mean."""
217
- return sum(numbers) / len(numbers) if numbers else 0
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 sum((x - m) ** 2 for x in numbers) / len(numbers)
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 min(numbers) if numbers else 0
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 max(numbers) if numbers else 0
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 max(min_val, min(max_val, x))
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 random.random()
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 random.randint(a, b)
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 random.randrange(start)
288
- return random.randrange(start, stop, step)
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 random.choice(seq) if seq else None
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
- random.shuffle(result)
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 i, (pname, pts) in enumerate(params):
435
- if i >= len(args):
436
- break
437
- if pts is None:
438
- continue
439
- actual = _infer_expr_type(args[i], self._scope)
440
- if actual and not _is_compatible(pts, actual):
441
- self._error(
442
- f"Argument '{pname}' of '{fn_name}' expects "
443
- f"{pts.base_type.value} but got {actual.base_type.value}",
444
- call,
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."""
@@ -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: item = iterable[index] ---
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
- self._emit(Opcode.STORE_NAME, item_const) # store as loop variable
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
- self._compile_node(key)
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