zexus 1.7.1 → 1.7.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (159) hide show
  1. package/README.md +3 -3
  2. package/package.json +1 -1
  3. package/src/__init__.py +7 -0
  4. package/src/zexus/__init__.py +1 -1
  5. package/src/zexus/__pycache__/__init__.cpython-312.pyc +0 -0
  6. package/src/zexus/__pycache__/capability_system.cpython-312.pyc +0 -0
  7. package/src/zexus/__pycache__/debug_sanitizer.cpython-312.pyc +0 -0
  8. package/src/zexus/__pycache__/environment.cpython-312.pyc +0 -0
  9. package/src/zexus/__pycache__/error_reporter.cpython-312.pyc +0 -0
  10. package/src/zexus/__pycache__/input_validation.cpython-312.pyc +0 -0
  11. package/src/zexus/__pycache__/lexer.cpython-312.pyc +0 -0
  12. package/src/zexus/__pycache__/module_cache.cpython-312.pyc +0 -0
  13. package/src/zexus/__pycache__/module_manager.cpython-312.pyc +0 -0
  14. package/src/zexus/__pycache__/object.cpython-312.pyc +0 -0
  15. package/src/zexus/__pycache__/security.cpython-312.pyc +0 -0
  16. package/src/zexus/__pycache__/security_enforcement.cpython-312.pyc +0 -0
  17. package/src/zexus/__pycache__/syntax_validator.cpython-312.pyc +0 -0
  18. package/src/zexus/__pycache__/zexus_ast.cpython-312.pyc +0 -0
  19. package/src/zexus/__pycache__/zexus_token.cpython-312.pyc +0 -0
  20. package/src/zexus/access_control_system/__pycache__/__init__.cpython-312.pyc +0 -0
  21. package/src/zexus/access_control_system/__pycache__/access_control.cpython-312.pyc +0 -0
  22. package/src/zexus/advanced_types.py +17 -2
  23. package/src/zexus/blockchain/__init__.py +411 -0
  24. package/src/zexus/blockchain/accelerator.py +1160 -0
  25. package/src/zexus/blockchain/chain.py +660 -0
  26. package/src/zexus/blockchain/consensus.py +821 -0
  27. package/src/zexus/blockchain/contract_vm.py +1019 -0
  28. package/src/zexus/blockchain/crypto.py +79 -14
  29. package/src/zexus/blockchain/events.py +526 -0
  30. package/src/zexus/blockchain/loadtest.py +721 -0
  31. package/src/zexus/blockchain/monitoring.py +350 -0
  32. package/src/zexus/blockchain/mpt.py +716 -0
  33. package/src/zexus/blockchain/multichain.py +951 -0
  34. package/src/zexus/blockchain/multiprocess_executor.py +338 -0
  35. package/src/zexus/blockchain/network.py +886 -0
  36. package/src/zexus/blockchain/node.py +666 -0
  37. package/src/zexus/blockchain/rpc.py +1203 -0
  38. package/src/zexus/blockchain/rust_bridge.py +421 -0
  39. package/src/zexus/blockchain/storage.py +423 -0
  40. package/src/zexus/blockchain/tokens.py +750 -0
  41. package/src/zexus/blockchain/upgradeable.py +1004 -0
  42. package/src/zexus/blockchain/verification.py +1602 -0
  43. package/src/zexus/blockchain/wallet.py +621 -0
  44. package/src/zexus/cli/__pycache__/main.cpython-312.pyc +0 -0
  45. package/src/zexus/cli/main.py +300 -20
  46. package/src/zexus/cli/zpm.py +1 -1
  47. package/src/zexus/compiler/__pycache__/bytecode.cpython-312.pyc +0 -0
  48. package/src/zexus/compiler/__pycache__/lexer.cpython-312.pyc +0 -0
  49. package/src/zexus/compiler/__pycache__/parser.cpython-312.pyc +0 -0
  50. package/src/zexus/compiler/__pycache__/semantic.cpython-312.pyc +0 -0
  51. package/src/zexus/compiler/__pycache__/zexus_ast.cpython-312.pyc +0 -0
  52. package/src/zexus/compiler/lexer.py +10 -5
  53. package/src/zexus/concurrency_system.py +79 -0
  54. package/src/zexus/config.py +54 -0
  55. package/src/zexus/crypto_bridge.py +244 -8
  56. package/src/zexus/dap/__init__.py +10 -0
  57. package/src/zexus/dap/__main__.py +4 -0
  58. package/src/zexus/dap/dap_server.py +391 -0
  59. package/src/zexus/dap/debug_engine.py +298 -0
  60. package/src/zexus/environment.py +10 -1
  61. package/src/zexus/evaluator/__pycache__/bytecode_compiler.cpython-312.pyc +0 -0
  62. package/src/zexus/evaluator/__pycache__/core.cpython-312.pyc +0 -0
  63. package/src/zexus/evaluator/__pycache__/expressions.cpython-312.pyc +0 -0
  64. package/src/zexus/evaluator/__pycache__/functions.cpython-312.pyc +0 -0
  65. package/src/zexus/evaluator/__pycache__/resource_limiter.cpython-312.pyc +0 -0
  66. package/src/zexus/evaluator/__pycache__/statements.cpython-312.pyc +0 -0
  67. package/src/zexus/evaluator/__pycache__/unified_execution.cpython-312.pyc +0 -0
  68. package/src/zexus/evaluator/__pycache__/utils.cpython-312.pyc +0 -0
  69. package/src/zexus/evaluator/bytecode_compiler.py +441 -37
  70. package/src/zexus/evaluator/core.py +560 -49
  71. package/src/zexus/evaluator/expressions.py +122 -49
  72. package/src/zexus/evaluator/functions.py +417 -16
  73. package/src/zexus/evaluator/statements.py +521 -118
  74. package/src/zexus/evaluator/unified_execution.py +573 -72
  75. package/src/zexus/evaluator/utils.py +14 -2
  76. package/src/zexus/event_loop.py +186 -0
  77. package/src/zexus/lexer.py +742 -486
  78. package/src/zexus/lsp/__init__.py +1 -1
  79. package/src/zexus/lsp/definition_provider.py +163 -9
  80. package/src/zexus/lsp/server.py +22 -8
  81. package/src/zexus/lsp/symbol_provider.py +182 -9
  82. package/src/zexus/module_cache.py +237 -9
  83. package/src/zexus/object.py +64 -6
  84. package/src/zexus/parser/__pycache__/parser.cpython-312.pyc +0 -0
  85. package/src/zexus/parser/__pycache__/strategy_context.cpython-312.pyc +0 -0
  86. package/src/zexus/parser/__pycache__/strategy_structural.cpython-312.pyc +0 -0
  87. package/src/zexus/parser/parser.py +786 -285
  88. package/src/zexus/parser/strategy_context.py +407 -66
  89. package/src/zexus/parser/strategy_structural.py +117 -19
  90. package/src/zexus/persistence.py +15 -1
  91. package/src/zexus/renderer/__init__.py +15 -0
  92. package/src/zexus/renderer/__pycache__/__init__.cpython-312.pyc +0 -0
  93. package/src/zexus/renderer/__pycache__/backend.cpython-312.pyc +0 -0
  94. package/src/zexus/renderer/__pycache__/canvas.cpython-312.pyc +0 -0
  95. package/src/zexus/renderer/__pycache__/color_system.cpython-312.pyc +0 -0
  96. package/src/zexus/renderer/__pycache__/layout.cpython-312.pyc +0 -0
  97. package/src/zexus/renderer/__pycache__/main_renderer.cpython-312.pyc +0 -0
  98. package/src/zexus/renderer/__pycache__/painter.cpython-312.pyc +0 -0
  99. package/src/zexus/renderer/tk_backend.py +208 -0
  100. package/src/zexus/renderer/web_backend.py +260 -0
  101. package/src/zexus/runtime/__pycache__/__init__.cpython-312.pyc +0 -0
  102. package/src/zexus/runtime/__pycache__/async_runtime.cpython-312.pyc +0 -0
  103. package/src/zexus/runtime/__pycache__/load_manager.cpython-312.pyc +0 -0
  104. package/src/zexus/runtime/file_flags.py +137 -0
  105. package/src/zexus/safety/__pycache__/__init__.cpython-312.pyc +0 -0
  106. package/src/zexus/safety/__pycache__/memory_safety.cpython-312.pyc +0 -0
  107. package/src/zexus/security.py +424 -34
  108. package/src/zexus/stdlib/fs.py +23 -18
  109. package/src/zexus/stdlib/http.py +289 -186
  110. package/src/zexus/stdlib/sockets.py +207 -163
  111. package/src/zexus/stdlib/websockets.py +282 -0
  112. package/src/zexus/stdlib_integration.py +369 -2
  113. package/src/zexus/strategy_recovery.py +6 -3
  114. package/src/zexus/type_checker.py +423 -0
  115. package/src/zexus/virtual_filesystem.py +189 -2
  116. package/src/zexus/vm/__init__.py +113 -3
  117. package/src/zexus/vm/__pycache__/async_optimizer.cpython-312.pyc +0 -0
  118. package/src/zexus/vm/__pycache__/bytecode.cpython-312.pyc +0 -0
  119. package/src/zexus/vm/__pycache__/bytecode_converter.cpython-312.pyc +0 -0
  120. package/src/zexus/vm/__pycache__/cache.cpython-312.pyc +0 -0
  121. package/src/zexus/vm/__pycache__/compiler.cpython-312.pyc +0 -0
  122. package/src/zexus/vm/__pycache__/gas_metering.cpython-312.pyc +0 -0
  123. package/src/zexus/vm/__pycache__/jit.cpython-312.pyc +0 -0
  124. package/src/zexus/vm/__pycache__/parallel_vm.cpython-312.pyc +0 -0
  125. package/src/zexus/vm/__pycache__/vm.cpython-312.pyc +0 -0
  126. package/src/zexus/vm/async_optimizer.py +14 -1
  127. package/src/zexus/vm/binary_bytecode.py +659 -0
  128. package/src/zexus/vm/bytecode.py +28 -1
  129. package/src/zexus/vm/bytecode_converter.py +26 -12
  130. package/src/zexus/vm/cabi.c +1985 -0
  131. package/src/zexus/vm/cabi.cpython-312-x86_64-linux-gnu.so +0 -0
  132. package/src/zexus/vm/cabi.h +127 -0
  133. package/src/zexus/vm/cache.py +557 -17
  134. package/src/zexus/vm/compiler.py +703 -5
  135. package/src/zexus/vm/fastops.c +15743 -0
  136. package/src/zexus/vm/fastops.cpython-312-x86_64-linux-gnu.so +0 -0
  137. package/src/zexus/vm/fastops.pyx +288 -0
  138. package/src/zexus/vm/gas_metering.py +50 -9
  139. package/src/zexus/vm/jit.py +83 -2
  140. package/src/zexus/vm/native_jit_backend.py +1816 -0
  141. package/src/zexus/vm/native_runtime.cpp +1388 -0
  142. package/src/zexus/vm/native_runtime.cpython-312-x86_64-linux-gnu.so +0 -0
  143. package/src/zexus/vm/optimizer.py +161 -11
  144. package/src/zexus/vm/parallel_vm.py +118 -42
  145. package/src/zexus/vm/peephole_optimizer.py +82 -4
  146. package/src/zexus/vm/profiler.py +38 -18
  147. package/src/zexus/vm/register_allocator.py +16 -5
  148. package/src/zexus/vm/register_vm.py +8 -5
  149. package/src/zexus/vm/vm.py +3411 -573
  150. package/src/zexus/vm/wasm_compiler.py +658 -0
  151. package/src/zexus/zexus_ast.py +63 -11
  152. package/src/zexus/zexus_token.py +13 -5
  153. package/src/zexus/zpm/installer.py +55 -15
  154. package/src/zexus/zpm/package_manager.py +1 -1
  155. package/src/zexus/zpm/registry.py +257 -28
  156. package/src/zexus.egg-info/PKG-INFO +7 -4
  157. package/src/zexus.egg-info/SOURCES.txt +116 -9
  158. package/src/zexus.egg-info/entry_points.txt +1 -0
  159. package/src/zexus.egg-info/requires.txt +4 -0
@@ -1,19 +1,255 @@
1
+ """
2
+ Zexus Post-Quantum Cryptography Bridge
3
+
4
+ Provides post-quantum digital signature support. When the ``pqcrypto``
5
+ or ``oqs`` library is available, real SPHINCS+-SHA2-128f is used.
6
+ Otherwise, a pure-Python **WOTS+ (Winternitz One-Time Signature Plus)**
7
+ hash-based scheme is provided as a cryptographically real fallback.
8
+
9
+ WOTS+ is provably secure under the assumption that the underlying hash
10
+ function (SHA-256) is a pseudorandom function. It is one of the building
11
+ blocks of XMSS (RFC 8391) and SPHINCS+.
12
+
13
+ Limitations of the WOTS+ fallback:
14
+ - Each private key should only sign ONE message safely. Re-using a
15
+ WOTS+ key for a second message leaks partial private key information.
16
+ - Signatures are ~2 KB (67 × 32 bytes).
17
+
18
+ For production deployments involving many signatures per key, install a
19
+ real post-quantum library:
20
+ pip install pqcrypto # or: pip install oqs
21
+ """
22
+
1
23
  import hashlib
24
+ import secrets
25
+ from typing import Dict, Tuple
26
+
27
+ # ── Try real post-quantum libraries first ────────────────────────────
28
+
29
+ _PQ_BACKEND = "wots" # default fallback
30
+
31
+ try:
32
+ import oqs # liboqs-python
33
+ _PQ_BACKEND = "oqs"
34
+ except ImportError:
35
+ pass
36
+
37
+ if _PQ_BACKEND == "wots":
38
+ try:
39
+ from pqcrypto.sign.sphincs_sha2_128f import (
40
+ generate_keypair as _sphincs_keygen,
41
+ sign as _sphincs_sign_raw,
42
+ verify as _sphincs_verify_raw,
43
+ )
44
+ _PQ_BACKEND = "pqcrypto"
45
+ except ImportError:
46
+ pass
47
+
48
+
49
+ # ═══════════════════════════════════════════════════════════════════════
50
+ # WOTS+ (Winternitz One-Time Signature Plus) — Pure Python Fallback
51
+ # ═══════════════════════════════════════════════════════════════════════
52
+
53
+ _WOTS_N = 32 # security parameter (hash output bytes = 256 bits)
54
+ _WOTS_W = 16 # Winternitz parameter
55
+ _WOTS_LEN1 = 64 # ceil(8*N / log2(W)) = ceil(256/4) = 64
56
+ _WOTS_LEN2 = 3 # floor(log2(LEN1 * (W-1)) / log2(W)) + 1 = 3
57
+ _WOTS_LEN = _WOTS_LEN1 + _WOTS_LEN2 # 67 chains total
58
+
59
+ # Fixed public seed for domain separation (safe — only provides randomization)
60
+ _WOTS_PUB_SEED = hashlib.sha256(b"zexus-wots-public-seed-v1").digest()
61
+
62
+
63
+ def _hash(data: bytes) -> bytes:
64
+ return hashlib.sha256(data).digest()
65
+
66
+
67
+ def _wots_chain(value: bytes, start: int, steps: int, addr: int) -> bytes:
68
+ """Iterate the hash chain ``steps`` times starting at index ``start``.
69
+
70
+ Uses bitmask indices start, start+1, ..., start+steps-1 so that
71
+ chain(x, 0, a, addr) followed by chain(result, a, b, addr) equals
72
+ chain(x, 0, a+b, addr). This composability is essential for WOTS+.
73
+ """
74
+ result = value
75
+ for i in range(start, start + steps):
76
+ bitmask = _hash(_WOTS_PUB_SEED + addr.to_bytes(4, "big") + i.to_bytes(4, "big"))
77
+ result = _hash(bytes(a ^ b for a, b in zip(result, bitmask)))
78
+ return result
79
+
80
+
81
+ def _base_w(msg_hash: bytes, w: int = _WOTS_W) -> list:
82
+ """Convert hash to base-w representation (4-bit nibbles for w=16)."""
83
+ out = []
84
+ for byte in msg_hash:
85
+ out.append(byte >> 4)
86
+ out.append(byte & 0x0F)
87
+ return out
88
+
89
+
90
+ def _checksum(msg_base_w: list) -> list:
91
+ """Compute WOTS+ checksum digits."""
92
+ total = sum(_WOTS_W - 1 - v for v in msg_base_w)
93
+ cs = []
94
+ for _ in range(_WOTS_LEN2):
95
+ cs.append(total % _WOTS_W)
96
+ total //= _WOTS_W
97
+ cs.reverse()
98
+ return cs
99
+
100
+
101
+ def wots_keygen(seed: bytes = b"") -> Tuple[bytes, bytes]:
102
+ """Generate a WOTS+ keypair.
103
+
104
+ Returns:
105
+ (private_key, public_key)
106
+ private_key = 32-byte seed
107
+ public_key = 67 × 32 = 2144 bytes
108
+ """
109
+ if not seed:
110
+ seed = secrets.token_bytes(_WOTS_N)
111
+
112
+ pk_parts = []
113
+ for i in range(_WOTS_LEN):
114
+ sk_i = _hash(seed + i.to_bytes(4, "big"))
115
+ pk_i = _wots_chain(sk_i, 0, _WOTS_W - 1, i)
116
+ pk_parts.append(pk_i)
117
+
118
+ return seed, b"".join(pk_parts)
119
+
120
+
121
+ def wots_sign(message: bytes, private_key: bytes) -> bytes:
122
+ """Sign a message using WOTS+.
123
+
124
+ Returns signature bytes (67 × 32 = 2144 bytes).
125
+ """
126
+ seed = private_key[:_WOTS_N]
127
+ msg_hash = _hash(message)
128
+ msg_bw = _base_w(msg_hash)
129
+ msg_bw += _checksum(msg_bw)
130
+
131
+ sig_parts = []
132
+ for i in range(_WOTS_LEN):
133
+ sk_i = _hash(seed + i.to_bytes(4, "big"))
134
+ sig_i = _wots_chain(sk_i, 0, msg_bw[i], i)
135
+ sig_parts.append(sig_i)
136
+
137
+ return b"".join(sig_parts)
138
+
139
+
140
+ def wots_verify(message: bytes, signature: bytes, public_key: bytes) -> bool:
141
+ """Verify a WOTS+ signature.
142
+
143
+ Returns True only if the signature is cryptographically valid.
144
+ """
145
+ if len(signature) != _WOTS_LEN * _WOTS_N:
146
+ return False
147
+ if len(public_key) != _WOTS_LEN * _WOTS_N:
148
+ return False
149
+
150
+ msg_hash = _hash(message)
151
+ msg_bw = _base_w(msg_hash)
152
+ msg_bw += _checksum(msg_bw)
153
+
154
+ for i in range(_WOTS_LEN):
155
+ sig_i = signature[i * _WOTS_N: (i + 1) * _WOTS_N]
156
+ pk_i = public_key[i * _WOTS_N: (i + 1) * _WOTS_N]
157
+ remaining = _WOTS_W - 1 - msg_bw[i]
158
+ computed = _wots_chain(sig_i, msg_bw[i], remaining, i)
159
+ if computed != pk_i:
160
+ return False
161
+
162
+ return True
163
+
164
+
165
+ # ═══════════════════════════════════════════════════════════════════════
166
+ # Public API — auto-selects the best available backend
167
+ # ═══════════════════════════════════════════════════════════════════════
2
168
 
3
169
  def sha256_hash(data):
170
+ """SHA-256 hash (hex)."""
4
171
  return hashlib.sha256(data.encode()).hexdigest()
5
172
 
6
- def generate_sphincs_keypair():
7
- # Placeholder - will implement real SPHINCS+ later
173
+
174
+ def generate_sphincs_keypair() -> Dict[str, str]:
175
+ """Generate a post-quantum keypair.
176
+
177
+ Returns dict with ``public_key`` and ``private_key`` (hex-encoded),
178
+ plus ``algorithm`` indicating which backend was used.
179
+ """
180
+ if _PQ_BACKEND == "oqs":
181
+ signer = oqs.Signature("SPHINCS+-SHA2-128f-simple")
182
+ pk = signer.generate_keypair()
183
+ sk = signer.export_secret_key()
184
+ return {
185
+ "public_key": pk.hex(),
186
+ "private_key": sk.hex(),
187
+ "algorithm": "SPHINCS+-SHA2-128f (liboqs)",
188
+ }
189
+
190
+ if _PQ_BACKEND == "pqcrypto":
191
+ pk, sk = _sphincs_keygen()
192
+ return {
193
+ "public_key": pk.hex(),
194
+ "private_key": sk.hex(),
195
+ "algorithm": "SPHINCS+-SHA2-128f (pqcrypto)",
196
+ }
197
+
198
+ # WOTS+ fallback
199
+ sk, pk = wots_keygen()
8
200
  return {
9
- "public_key": "sphincs_public_key_placeholder",
10
- "private_key": "sphincs_private_key_placeholder"
201
+ "public_key": pk.hex(),
202
+ "private_key": sk.hex(),
203
+ "algorithm": "WOTS+ (hash-based OTS, pure Python)",
11
204
  }
12
205
 
206
+
13
207
  def sphincs_sign(message, private_key):
14
- # Placeholder
15
- return "sphincs_signature_placeholder"
208
+ """Sign a message with the post-quantum private key.
209
+
210
+ Returns hex-encoded signature.
211
+ """
212
+ msg_bytes = message.encode("utf-8") if isinstance(message, str) else message
213
+ sk_bytes = bytes.fromhex(private_key) if isinstance(private_key, str) else private_key
214
+
215
+ if _PQ_BACKEND == "oqs":
216
+ signer = oqs.Signature("SPHINCS+-SHA2-128f-simple", sk_bytes)
217
+ sig = signer.sign(msg_bytes)
218
+ return sig.hex()
219
+
220
+ if _PQ_BACKEND == "pqcrypto":
221
+ sig = _sphincs_sign_raw(sk_bytes, msg_bytes)
222
+ return sig.hex()
223
+
224
+ # WOTS+ fallback
225
+ sig = wots_sign(msg_bytes, sk_bytes)
226
+ return sig.hex()
227
+
16
228
 
17
229
  def sphincs_verify(message, signature, public_key):
18
- # Placeholder
19
- return True
230
+ """Verify a post-quantum signature.
231
+
232
+ Returns True ONLY if the signature is cryptographically valid.
233
+ No longer returns True unconditionally.
234
+ """
235
+ msg_bytes = message.encode("utf-8") if isinstance(message, str) else message
236
+
237
+ try:
238
+ sig_bytes = bytes.fromhex(signature) if isinstance(signature, str) else signature
239
+ pk_bytes = bytes.fromhex(public_key) if isinstance(public_key, str) else public_key
240
+ except (ValueError, AttributeError):
241
+ return False
242
+
243
+ if _PQ_BACKEND == "oqs":
244
+ verifier = oqs.Signature("SPHINCS+-SHA2-128f-simple")
245
+ return verifier.verify(msg_bytes, sig_bytes, pk_bytes)
246
+
247
+ if _PQ_BACKEND == "pqcrypto":
248
+ try:
249
+ _sphincs_verify_raw(pk_bytes, msg_bytes, sig_bytes)
250
+ return True
251
+ except Exception:
252
+ return False
253
+
254
+ # WOTS+ fallback
255
+ return wots_verify(msg_bytes, sig_bytes, pk_bytes)
@@ -0,0 +1,10 @@
1
+ """
2
+ Debug Adapter Protocol (DAP) support for Zexus.
3
+
4
+ Provides step-through debugging with breakpoints, variable inspection,
5
+ call stack display, and expression evaluation.
6
+ """
7
+
8
+ from .debug_engine import DebugEngine, StopReason
9
+
10
+ __all__ = ["DebugEngine", "StopReason"]
@@ -0,0 +1,4 @@
1
+ """Allow running the DAP server via ``python -m zexus.dap``."""
2
+ from .dap_server import main
3
+
4
+ main()
@@ -0,0 +1,391 @@
1
+ """
2
+ DAP (Debug Adapter Protocol) Server for Zexus.
3
+
4
+ Implements the DAP JSON wire protocol over stdin/stdout so that VS Code
5
+ (or any DAP client) can drive step-through debugging of Zexus programs.
6
+
7
+ The server spawns the Zexus evaluator on a worker thread and controls it
8
+ via the ``DebugEngine``.
9
+
10
+ Reference: https://microsoft.github.io/debug-adapter-protocol/specification
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ import json
16
+ import os
17
+ import sys
18
+ import threading
19
+ import traceback
20
+ from typing import Any, Dict, List, Optional
21
+
22
+ from .debug_engine import DebugEngine, StopReason
23
+
24
+
25
+ # ---------------------------------------------------------------------------
26
+ # Helpers
27
+ # ---------------------------------------------------------------------------
28
+
29
+ def _read_message(stream) -> Optional[Dict]:
30
+ """Read one DAP message (Content-Length header + JSON body)."""
31
+ headers: Dict[str, str] = {}
32
+ while True:
33
+ line = stream.readline()
34
+ if not line:
35
+ return None # EOF
36
+ if isinstance(line, bytes):
37
+ line = line.decode("utf-8")
38
+ line = line.strip()
39
+ if line == "":
40
+ break
41
+ if ":" in line:
42
+ key, val = line.split(":", 1)
43
+ headers[key.strip()] = val.strip()
44
+ length = int(headers.get("Content-Length", 0))
45
+ if length == 0:
46
+ return None
47
+ body = stream.read(length)
48
+ if isinstance(body, bytes):
49
+ body = body.decode("utf-8")
50
+ return json.loads(body)
51
+
52
+
53
+ def _send_message(stream, msg: Dict):
54
+ """Write one DAP message."""
55
+ body = json.dumps(msg)
56
+ header = f"Content-Length: {len(body)}\r\n\r\n"
57
+ data = header + body
58
+ if hasattr(stream, "buffer"):
59
+ stream.buffer.write(data.encode("utf-8"))
60
+ stream.buffer.flush()
61
+ else:
62
+ stream.write(data.encode("utf-8") if isinstance(data, str) else data)
63
+ stream.flush()
64
+
65
+
66
+ # ---------------------------------------------------------------------------
67
+ # DAP Server
68
+ # ---------------------------------------------------------------------------
69
+
70
+ _SEQ = 0
71
+
72
+
73
+ def _next_seq() -> int:
74
+ global _SEQ
75
+ _SEQ += 1
76
+ return _SEQ
77
+
78
+
79
+ class DAPServer:
80
+ """Minimal DAP server that wraps the Zexus evaluator."""
81
+
82
+ def __init__(self, input_stream=None, output_stream=None):
83
+ self._in = input_stream or sys.stdin
84
+ self._out = output_stream or sys.stdout
85
+ self.engine = DebugEngine()
86
+ self.engine.on_stopped = self._on_stopped
87
+ self.engine.on_terminated = self._on_terminated
88
+ self._program_path: Optional[str] = None
89
+ self._worker: Optional[threading.Thread] = None
90
+ self._launched = False
91
+ self._variable_refs: Dict[int, Dict[str, Any]] = {}
92
+ self._var_ref_counter = 0
93
+
94
+ # -- Main loop ---------------------------------------------------------
95
+
96
+ def run(self):
97
+ """Read DAP messages in a loop and dispatch them."""
98
+ while True:
99
+ try:
100
+ msg = _read_message(self._in)
101
+ except Exception:
102
+ break
103
+ if msg is None:
104
+ break
105
+ msg_type = msg.get("type")
106
+ if msg_type == "request":
107
+ self._handle_request(msg)
108
+ # (events and responses from client are ignored)
109
+
110
+ # -- Response / event helpers ------------------------------------------
111
+
112
+ def _respond(self, request: Dict, body: Optional[Dict] = None,
113
+ success: bool = True, message: str = ""):
114
+ resp: Dict[str, Any] = {
115
+ "seq": _next_seq(),
116
+ "type": "response",
117
+ "request_seq": request["seq"],
118
+ "command": request["command"],
119
+ "success": success,
120
+ }
121
+ if body:
122
+ resp["body"] = body
123
+ if message:
124
+ resp["message"] = message
125
+ _send_message(self._out, resp)
126
+
127
+ def _event(self, event: str, body: Optional[Dict] = None):
128
+ msg: Dict[str, Any] = {
129
+ "seq": _next_seq(),
130
+ "type": "event",
131
+ "event": event,
132
+ }
133
+ if body:
134
+ msg["body"] = body
135
+ _send_message(self._out, msg)
136
+
137
+ # -- Request dispatch --------------------------------------------------
138
+
139
+ def _handle_request(self, req: Dict):
140
+ cmd = req.get("command", "")
141
+ handler = getattr(self, f"_cmd_{cmd}", None)
142
+ if handler:
143
+ try:
144
+ handler(req)
145
+ except Exception as exc:
146
+ self._respond(req, success=False, message=str(exc))
147
+ else:
148
+ # Unknown command — respond with success (DAP spec recommends this)
149
+ self._respond(req)
150
+
151
+ # -- DAP commands ------------------------------------------------------
152
+
153
+ def _cmd_initialize(self, req: Dict):
154
+ capabilities = {
155
+ "supportsConfigurationDoneRequest": True,
156
+ "supportsFunctionBreakpoints": False,
157
+ "supportsConditionalBreakpoints": False,
158
+ "supportsEvaluateForHovers": True,
159
+ "supportsStepBack": False,
160
+ "supportsSetVariable": False,
161
+ "supportsTerminateRequest": True,
162
+ "supportsSingleThreadExecutionRequests": False,
163
+ }
164
+ self._respond(req, body=capabilities)
165
+ self._event("initialized")
166
+
167
+ def _cmd_configurationDone(self, req: Dict):
168
+ self._respond(req)
169
+
170
+ def _cmd_launch(self, req: Dict):
171
+ args = req.get("arguments", {})
172
+ self._program_path = args.get("program", "")
173
+ stop_on_entry = args.get("stopOnEntry", False)
174
+ self.engine.set_stop_on_entry(stop_on_entry)
175
+ self._respond(req)
176
+ # Start execution on worker thread
177
+ self._worker = threading.Thread(
178
+ target=self._run_program, daemon=True
179
+ )
180
+ self._launched = True
181
+ self._worker.start()
182
+
183
+ def _cmd_disconnect(self, req: Dict):
184
+ self.engine.terminate()
185
+ self._respond(req)
186
+
187
+ def _cmd_terminate(self, req: Dict):
188
+ self.engine.terminate()
189
+ self._respond(req)
190
+
191
+ def _cmd_setBreakpoints(self, req: Dict):
192
+ args = req.get("arguments", {})
193
+ source = args.get("source", {})
194
+ path = source.get("path", "")
195
+ bp_args = args.get("breakpoints", [])
196
+ lines = [b["line"] for b in bp_args]
197
+ bps = self.engine.set_breakpoints(path, lines)
198
+ body = {
199
+ "breakpoints": [
200
+ {"id": bp.id, "verified": True, "line": bp.line}
201
+ for bp in bps
202
+ ]
203
+ }
204
+ self._respond(req, body=body)
205
+
206
+ def _cmd_threads(self, req: Dict):
207
+ self._respond(req, body={
208
+ "threads": [{"id": 1, "name": "Main Thread"}]
209
+ })
210
+
211
+ def _cmd_stackTrace(self, req: Dict):
212
+ frames = self.engine.get_stack_trace()
213
+ dap_frames = []
214
+ for f in frames:
215
+ self._var_ref_counter += 1
216
+ ref = self._var_ref_counter
217
+ self._variable_refs[ref] = f.variables
218
+ dap_frames.append({
219
+ "id": f.id,
220
+ "name": f.name,
221
+ "source": {"name": os.path.basename(f.file), "path": f.file},
222
+ "line": f.line,
223
+ "column": f.column,
224
+ "scopes": ref, # we'll use this in scopes request
225
+ })
226
+ self._respond(req, body={
227
+ "stackFrames": dap_frames,
228
+ "totalFrames": len(dap_frames),
229
+ })
230
+
231
+ def _cmd_scopes(self, req: Dict):
232
+ args = req.get("arguments", {})
233
+ frame_id = args.get("frameId", 0)
234
+ variables = self.engine.get_variables(frame_id)
235
+ self._var_ref_counter += 1
236
+ ref = self._var_ref_counter
237
+ self._variable_refs[ref] = variables
238
+ self._respond(req, body={
239
+ "scopes": [{
240
+ "name": "Locals",
241
+ "variablesReference": ref,
242
+ "expensive": False,
243
+ }]
244
+ })
245
+
246
+ def _cmd_variables(self, req: Dict):
247
+ args = req.get("arguments", {})
248
+ ref = args.get("variablesReference", 0)
249
+ variables = self._variable_refs.get(ref, {})
250
+ dap_vars = []
251
+ for name, val in variables.items():
252
+ if name.startswith("__"):
253
+ continue # hide internal vars
254
+ dap_vars.append({
255
+ "name": name,
256
+ "value": str(val),
257
+ "variablesReference": 0,
258
+ })
259
+ self._respond(req, body={"variables": dap_vars})
260
+
261
+ def _cmd_continue(self, req: Dict):
262
+ self.engine.continue_execution()
263
+ self._respond(req, body={"allThreadsContinued": True})
264
+
265
+ def _cmd_next(self, req: Dict):
266
+ self.engine.step_over()
267
+ self._respond(req)
268
+
269
+ def _cmd_stepIn(self, req: Dict):
270
+ self.engine.step_into()
271
+ self._respond(req)
272
+
273
+ def _cmd_stepOut(self, req: Dict):
274
+ self.engine.step_out()
275
+ self._respond(req)
276
+
277
+ def _cmd_pause(self, req: Dict):
278
+ self.engine.pause()
279
+ self._respond(req)
280
+
281
+ def _cmd_evaluate(self, req: Dict):
282
+ args = req.get("arguments", {})
283
+ expression = args.get("expression", "")
284
+ # Simple variable lookup from current frame
285
+ frames = self.engine.get_stack_trace()
286
+ result = "undefined"
287
+ if frames:
288
+ vs = frames[0].variables
289
+ if expression in vs:
290
+ result = str(vs[expression])
291
+ self._respond(req, body={"result": result, "variablesReference": 0})
292
+
293
+ # -- Execution ---------------------------------------------------------
294
+
295
+ def _run_program(self):
296
+ """Execute the Zexus program on this thread."""
297
+ try:
298
+ if not self._program_path:
299
+ self._event("output", {
300
+ "category": "stderr",
301
+ "output": "No program specified\n",
302
+ })
303
+ return
304
+
305
+ # Import Zexus machinery
306
+ from ..lexer import Lexer
307
+ from ..parser.parser import UltimateParser as Parser
308
+ from ..evaluator import evaluate, Evaluator
309
+ from ..environment import Environment
310
+ from ..object import String
311
+
312
+ with open(self._program_path, "r") as f:
313
+ source = f.read()
314
+
315
+ # Parse
316
+ lexer = Lexer(source, filename=self._program_path)
317
+ parser = Parser(lexer, "auto", enable_advanced_strategies=True)
318
+ program = parser.parse_program()
319
+
320
+ if parser.errors:
321
+ for err in parser.errors:
322
+ self._event("output", {
323
+ "category": "stderr",
324
+ "output": f"Parse error: {err}\n",
325
+ })
326
+ return
327
+
328
+ # Set up environment
329
+ env = Environment()
330
+ abs_path = os.path.abspath(self._program_path)
331
+ env.set("__file__", String(abs_path))
332
+ env.set("__FILE__", String(abs_path))
333
+ env.set("__MODULE__", String("__main__"))
334
+ env.set("__DIR__", String(os.path.dirname(abs_path)))
335
+
336
+ # Push initial frame
337
+ self.engine.notify_call(
338
+ "<module>", abs_path, 1, env
339
+ )
340
+
341
+ # Evaluate with debug engine attached
342
+ evaluator = Evaluator(use_vm=False)
343
+ evaluator._debug_engine = self.engine
344
+ evaluator._debug_file = abs_path
345
+
346
+ # Merge builtins
347
+ for name, val in evaluator.builtins.items():
348
+ env.set(name, val)
349
+
350
+ evaluator.eval_node(program, env)
351
+
352
+ except Exception as exc:
353
+ self._event("output", {
354
+ "category": "stderr",
355
+ "output": f"Runtime error: {exc}\n{traceback.format_exc()}",
356
+ })
357
+ finally:
358
+ self.engine.notify_terminated()
359
+
360
+ # -- Engine callbacks --------------------------------------------------
361
+
362
+ def _on_stopped(self, reason: StopReason, description: str):
363
+ reason_map = {
364
+ StopReason.BREAKPOINT: "breakpoint",
365
+ StopReason.STEP: "step",
366
+ StopReason.PAUSE: "pause",
367
+ StopReason.ENTRY: "entry",
368
+ StopReason.EXCEPTION: "exception",
369
+ }
370
+ self._event("stopped", {
371
+ "reason": reason_map.get(reason, "unknown"),
372
+ "threadId": 1,
373
+ "description": description,
374
+ "allThreadsStopped": True,
375
+ })
376
+
377
+ def _on_terminated(self):
378
+ self._event("terminated")
379
+
380
+
381
+ # ---------------------------------------------------------------------------
382
+ # Entry point (python -m zexus.dap)
383
+ # ---------------------------------------------------------------------------
384
+
385
+ def main():
386
+ server = DAPServer()
387
+ server.run()
388
+
389
+
390
+ if __name__ == "__main__":
391
+ main()