zexus 1.6.8 → 1.7.1
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 +12 -5
- package/package.json +1 -1
- package/src/zexus/__init__.py +1 -1
- package/src/zexus/capability_system.py +184 -9
- package/src/zexus/cli/main.py +85 -16
- package/src/zexus/cli/zpm.py +1 -1
- package/src/zexus/compiler/bytecode.py +124 -7
- package/src/zexus/compiler/compat_runtime.py +6 -2
- package/src/zexus/compiler/lexer.py +6 -0
- package/src/zexus/compiler/parser.py +108 -7
- package/src/zexus/compiler/semantic.py +18 -19
- package/src/zexus/compiler/zexus_ast.py +26 -1
- package/src/zexus/environment.py +103 -9
- package/src/zexus/evaluator/bytecode_compiler.py +16 -0
- package/src/zexus/evaluator/core.py +85 -2
- package/src/zexus/evaluator/expressions.py +236 -13
- package/src/zexus/evaluator/functions.py +41 -4
- package/src/zexus/evaluator/resource_limiter.py +4 -4
- package/src/zexus/evaluator/statements.py +253 -4
- package/src/zexus/evaluator_original.py +1 -1
- package/src/zexus/lexer.py +30 -2
- package/src/zexus/module_cache.py +2 -0
- package/src/zexus/module_manager.py +129 -1
- package/src/zexus/object.py +12 -0
- package/src/zexus/parser/parser.py +582 -142
- package/src/zexus/parser/strategy_context.py +361 -5
- package/src/zexus/parser/strategy_structural.py +4 -2
- package/src/zexus/renderer/__init__.py +46 -0
- package/src/zexus/renderer/backend.py +261 -0
- package/src/zexus/renderer/canvas.py +78 -0
- package/src/zexus/renderer/color_system.py +201 -0
- package/src/zexus/renderer/graphics.py +31 -0
- package/src/zexus/renderer/layout.py +222 -0
- package/src/zexus/renderer/main_renderer.py +66 -0
- package/src/zexus/renderer/painter.py +30 -0
- package/src/zexus/runtime/__init__.py +10 -2
- package/src/zexus/runtime/load_manager.py +368 -0
- package/src/zexus/vm/async_optimizer.py +66 -5
- package/src/zexus/vm/bytecode.py +31 -10
- package/src/zexus/vm/cache.py +4 -0
- package/src/zexus/vm/compiler.py +115 -46
- package/src/zexus/vm/gas_metering.py +1 -1
- package/src/zexus/vm/jit.py +281 -18
- package/src/zexus/vm/parallel_vm.py +22 -3
- package/src/zexus/vm/vm.py +246 -34
- package/src/zexus/zexus_ast.py +74 -0
- package/src/zexus/zexus_token.py +3 -0
- package/src/zexus/zpm/package_manager.py +1 -1
- package/src/zexus.egg-info/PKG-INFO +13 -6
- package/src/zexus.egg-info/SOURCES.txt +13 -8
package/README.md
CHANGED
|
@@ -2,14 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
<div align="center">
|
|
4
4
|
|
|
5
|
-

|
|
6
6
|
[](LICENSE)
|
|
7
7
|
[](https://python.org)
|
|
8
8
|
[](https://github.com/Zaidux/zexus-interpreter)
|
|
9
9
|
|
|
10
10
|
**A modern, security-first programming language with built-in blockchain support, VM-accelerated execution, advanced memory management, and policy-as-code**
|
|
11
11
|
|
|
12
|
-
[What's New](#-whats-new-in-
|
|
12
|
+
[What's New](#-whats-new-in-v171) • [Features](#-key-features) • [Installation](#-installation) • [Quick Start](#-quick-start) • [Keywords](#-complete-keyword-reference) • [Documentation](#-documentation) • [Examples](#-examples) • [Troubleshooting](#-getting-help--troubleshooting)
|
|
13
13
|
|
|
14
14
|
</div>
|
|
15
15
|
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
## 📋 Table of Contents
|
|
19
19
|
|
|
20
20
|
- [What is Zexus?](#-what-is-zexus)
|
|
21
|
-
- [What's New](#-whats-new-in-
|
|
21
|
+
- [What's New](#-whats-new-in-v171)
|
|
22
22
|
- [Key Features](#-key-features)
|
|
23
23
|
- [VM-Accelerated Performance](#-vm-accelerated-performance-new)
|
|
24
24
|
- [Security & Policy-as-Code](#-security--policy-as-code--verify-enhanced)
|
|
@@ -67,9 +67,16 @@ Zexus is a next-generation, general-purpose programming language designed for se
|
|
|
67
67
|
|
|
68
68
|
---
|
|
69
69
|
|
|
70
|
-
## 🎉 What's New in v1.
|
|
70
|
+
## 🎉 What's New in v1.7.1
|
|
71
71
|
|
|
72
|
-
### Latest Features (v1.
|
|
72
|
+
### Latest Features (v1.7.1)
|
|
73
|
+
|
|
74
|
+
✅ **FIND Keyword** - Declarative project search that resolves exact module paths with scope filtering and smart suggestions
|
|
75
|
+
✅ **LOAD Keyword & Manager** - Provider-aware configuration loader with built-in ENV, JSON, and YAML support plus caching
|
|
76
|
+
✅ **VM + Bytecode Support** - FIND/LOAD now compile to bytecode with helper bridges so scripts run identically in the VM and interpreter
|
|
77
|
+
✅ **Targeted Test Coverage** - Added regression tests that exercise both interpreter and VM paths for FIND/LOAD workflows
|
|
78
|
+
|
|
79
|
+
### Previous Features (v1.6.3)
|
|
73
80
|
|
|
74
81
|
✅ **Complete Database Ecosystem** - Production-ready database drivers
|
|
75
82
|
✅ **4 Database Drivers** - SQLite, PostgreSQL, MySQL, MongoDB fully tested
|
package/package.json
CHANGED
package/src/zexus/__init__.py
CHANGED
|
@@ -5,7 +5,8 @@ Implements fine-grained access control through capability tokens.
|
|
|
5
5
|
Plugins declare required capabilities, and the evaluator enforces access.
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
|
-
from typing import Set, Dict, List, Callable, Optional, Tuple
|
|
8
|
+
from typing import Set, Dict, List, Callable, Optional, Tuple, Any
|
|
9
|
+
import uuid
|
|
9
10
|
from dataclasses import dataclass, field
|
|
10
11
|
from enum import Enum
|
|
11
12
|
import time
|
|
@@ -173,6 +174,8 @@ class CapabilityManager:
|
|
|
173
174
|
self.audit_log = CapabilityAuditLog()
|
|
174
175
|
self.granted_capabilities: Dict[str, Set[str]] = {} # requester -> capabilities
|
|
175
176
|
self.required_capabilities: Dict[str, Set[str]] = {} # requester -> required caps
|
|
177
|
+
self._contexts: Dict[str, "CapabilityContext"] = {}
|
|
178
|
+
self._context_counter = 0
|
|
176
179
|
|
|
177
180
|
# Initialize with base capabilities
|
|
178
181
|
for cap in self.BASE_CAPABILITIES:
|
|
@@ -197,6 +200,19 @@ class CapabilityManager:
|
|
|
197
200
|
for cap in capabilities:
|
|
198
201
|
self.grant_capability(requester, cap)
|
|
199
202
|
|
|
203
|
+
def revoke_capability(self, requester: str, capability: str):
|
|
204
|
+
"""Revoke a specific capability from a requester."""
|
|
205
|
+
caps = self.granted_capabilities.get(requester)
|
|
206
|
+
if not caps:
|
|
207
|
+
return
|
|
208
|
+
caps.discard(capability)
|
|
209
|
+
if not caps:
|
|
210
|
+
self.granted_capabilities.pop(requester, None)
|
|
211
|
+
|
|
212
|
+
def revoke_all_capabilities(self, requester: str):
|
|
213
|
+
"""Remove all capabilities granted to a requester."""
|
|
214
|
+
self.granted_capabilities.pop(requester, None)
|
|
215
|
+
|
|
200
216
|
def check_capability(self, requester: str, capability: str,
|
|
201
217
|
context: Optional[Dict] = None) -> Tuple[bool, str]:
|
|
202
218
|
"""
|
|
@@ -210,29 +226,49 @@ class CapabilityManager:
|
|
|
210
226
|
requester=requester,
|
|
211
227
|
context=context or {}
|
|
212
228
|
)
|
|
213
|
-
|
|
214
|
-
|
|
229
|
+
|
|
230
|
+
context_obj = self._contexts.get(requester)
|
|
231
|
+
|
|
232
|
+
# 1. Context-specific policy enforcement (takes precedence)
|
|
233
|
+
if context_obj and context_obj.policy:
|
|
234
|
+
policy_level = context_obj.policy.check(capability)
|
|
235
|
+
if policy_level in (CapabilityLevel.ALLOWED, CapabilityLevel.UNRESTRICTED, CapabilityLevel.RESTRICTED):
|
|
236
|
+
reason = (
|
|
237
|
+
f"Capability {capability} allowed by context policy "
|
|
238
|
+
f"'{context_obj.policy.name}'"
|
|
239
|
+
)
|
|
240
|
+
self.audit_log.log_request(request, True, reason)
|
|
241
|
+
return True, reason
|
|
242
|
+
if policy_level == CapabilityLevel.DENY:
|
|
243
|
+
reason = (
|
|
244
|
+
f"Capability {capability} denied by context policy "
|
|
245
|
+
f"'{context_obj.policy.name}'"
|
|
246
|
+
)
|
|
247
|
+
self.audit_log.log_request(request, False, reason)
|
|
248
|
+
return False, reason
|
|
249
|
+
|
|
250
|
+
# 2. Check if policy allows all (AllowAllPolicy)
|
|
215
251
|
if isinstance(self.policy, AllowAllPolicy):
|
|
216
252
|
reason = f"Capability {capability} allowed by policy (allow-all)"
|
|
217
253
|
self.audit_log.log_request(request, True, reason)
|
|
218
254
|
return True, reason
|
|
219
|
-
|
|
220
|
-
#
|
|
255
|
+
|
|
256
|
+
# 3. Check if capability is base capability (always available)
|
|
221
257
|
if capability in self.BASE_CAPABILITIES:
|
|
222
258
|
reason = f"Base capability {capability} available"
|
|
223
259
|
self.audit_log.log_request(request, True, reason)
|
|
224
260
|
return True, reason
|
|
225
|
-
|
|
226
|
-
#
|
|
261
|
+
|
|
262
|
+
# 4. Check if requester has been explicitly granted this capability
|
|
227
263
|
if requester in self.granted_capabilities:
|
|
228
264
|
if capability in self.granted_capabilities[requester]:
|
|
229
265
|
reason = f"Capability {capability} granted to {requester}"
|
|
230
266
|
self.audit_log.log_request(request, True, reason)
|
|
231
267
|
return True, reason
|
|
232
268
|
|
|
233
|
-
#
|
|
269
|
+
# 5. Check if capability is allowed by policy
|
|
234
270
|
policy_level = self.policy.check(capability)
|
|
235
|
-
if policy_level
|
|
271
|
+
if policy_level in (CapabilityLevel.ALLOWED, CapabilityLevel.UNRESTRICTED, CapabilityLevel.RESTRICTED):
|
|
236
272
|
reason = f"Capability {capability} allowed by policy"
|
|
237
273
|
self.audit_log.log_request(request, True, reason)
|
|
238
274
|
return True, reason
|
|
@@ -291,6 +327,97 @@ class CapabilityManager:
|
|
|
291
327
|
"""Get audit statistics."""
|
|
292
328
|
return self.audit_log.get_statistics()
|
|
293
329
|
|
|
330
|
+
def create_context(
|
|
331
|
+
self,
|
|
332
|
+
*,
|
|
333
|
+
capabilities: Optional[List[str]] = None,
|
|
334
|
+
policy: Optional[CapabilityPolicy] = None,
|
|
335
|
+
name: Optional[str] = None,
|
|
336
|
+
inherit_base: bool = True,
|
|
337
|
+
) -> "CapabilityContext":
|
|
338
|
+
"""Create a scoped capability context."""
|
|
339
|
+
self._context_counter += 1
|
|
340
|
+
context_name = name or f"context::{self._context_counter}:{uuid.uuid4().hex[:8]}"
|
|
341
|
+
context = CapabilityContext(
|
|
342
|
+
manager=self,
|
|
343
|
+
name=context_name,
|
|
344
|
+
capabilities=capabilities or [],
|
|
345
|
+
policy=policy,
|
|
346
|
+
inherit_base=inherit_base,
|
|
347
|
+
)
|
|
348
|
+
self._contexts[context.name] = context
|
|
349
|
+
return context
|
|
350
|
+
|
|
351
|
+
def destroy_context(self, name: str) -> Optional["CapabilityContext"]:
|
|
352
|
+
"""Destroy a previously created context and revoke its grants."""
|
|
353
|
+
context = self._contexts.pop(name, None)
|
|
354
|
+
if context:
|
|
355
|
+
context.destroy()
|
|
356
|
+
return context
|
|
357
|
+
|
|
358
|
+
def get_context(self, name: str) -> Optional["CapabilityContext"]:
|
|
359
|
+
"""Retrieve a context by name, if it exists."""
|
|
360
|
+
return self._contexts.get(name)
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
class CapabilityContext:
|
|
364
|
+
"""Lightweight wrapper for scoped capability enforcement."""
|
|
365
|
+
|
|
366
|
+
def __init__(
|
|
367
|
+
self,
|
|
368
|
+
*,
|
|
369
|
+
manager: CapabilityManager,
|
|
370
|
+
name: str,
|
|
371
|
+
capabilities: Optional[List[str]] = None,
|
|
372
|
+
policy: Optional[CapabilityPolicy] = None,
|
|
373
|
+
inherit_base: bool = True,
|
|
374
|
+
) -> None:
|
|
375
|
+
self.manager = manager
|
|
376
|
+
self.name = name
|
|
377
|
+
self.policy = policy or SelectivePolicy(capabilities or [])
|
|
378
|
+
self.capabilities: Set[str] = set(capabilities or [])
|
|
379
|
+
self.inherit_base = inherit_base
|
|
380
|
+
self.created_at = time.time()
|
|
381
|
+
|
|
382
|
+
if self.capabilities:
|
|
383
|
+
self.manager.grant_capabilities(self.name, list(self.capabilities))
|
|
384
|
+
elif inherit_base:
|
|
385
|
+
# Ensure entry exists so future grants can be tracked cleanly
|
|
386
|
+
self.manager.granted_capabilities.setdefault(self.name, set())
|
|
387
|
+
|
|
388
|
+
def grant(self, capability: str) -> None:
|
|
389
|
+
"""Grant an additional capability to this context."""
|
|
390
|
+
self.manager.grant_capability(self.name, capability)
|
|
391
|
+
self.capabilities.add(capability)
|
|
392
|
+
|
|
393
|
+
def revoke(self, capability: str) -> None:
|
|
394
|
+
"""Revoke a capability from this context."""
|
|
395
|
+
self.manager.revoke_capability(self.name, capability)
|
|
396
|
+
self.capabilities.discard(capability)
|
|
397
|
+
|
|
398
|
+
def check(self, capability: str, *, context: Optional[Dict] = None) -> bool:
|
|
399
|
+
"""Check if the context can access a capability."""
|
|
400
|
+
allowed, _ = self.manager.check_capability(self.name, capability, context)
|
|
401
|
+
return allowed
|
|
402
|
+
|
|
403
|
+
def require(self, capability: str, *, context: Optional[Dict] = None) -> bool:
|
|
404
|
+
"""Require a capability, raising if unavailable."""
|
|
405
|
+
self.manager.require_capability(self.name, capability, context)
|
|
406
|
+
return True
|
|
407
|
+
|
|
408
|
+
def snapshot(self) -> Dict[str, Any]:
|
|
409
|
+
"""Return a serializable snapshot of the context state."""
|
|
410
|
+
return {
|
|
411
|
+
"name": self.name,
|
|
412
|
+
"capabilities": self.manager.get_granted_capabilities(self.name),
|
|
413
|
+
"policy": getattr(self.policy, "name", None),
|
|
414
|
+
"created_at": self.created_at,
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
def destroy(self) -> None:
|
|
418
|
+
"""Revoke all privileges associated with this context."""
|
|
419
|
+
self.manager.revoke_all_capabilities(self.name)
|
|
420
|
+
|
|
294
421
|
|
|
295
422
|
class CapabilityError(Exception):
|
|
296
423
|
"""Exception raised when capability check fails."""
|
|
@@ -370,3 +497,51 @@ CAPABILITY_SETS = {
|
|
|
370
497
|
"description": "Full system access (privileged code)"
|
|
371
498
|
}
|
|
372
499
|
}
|
|
500
|
+
|
|
501
|
+
|
|
502
|
+
_DEFAULT_MANAGER: CapabilityManager = CapabilityManager()
|
|
503
|
+
|
|
504
|
+
|
|
505
|
+
def get_capability_manager() -> CapabilityManager:
|
|
506
|
+
"""Return the global capability manager singleton."""
|
|
507
|
+
return _DEFAULT_MANAGER
|
|
508
|
+
|
|
509
|
+
|
|
510
|
+
def set_capability_manager(manager: CapabilityManager) -> None:
|
|
511
|
+
"""Replace the global capability manager (primarily for tests)."""
|
|
512
|
+
global _DEFAULT_MANAGER
|
|
513
|
+
_DEFAULT_MANAGER = manager
|
|
514
|
+
|
|
515
|
+
|
|
516
|
+
def reset_capability_manager(policy: Optional[CapabilityPolicy] = None) -> CapabilityManager:
|
|
517
|
+
"""Reset the global capability manager to a fresh instance."""
|
|
518
|
+
manager = CapabilityManager(default_policy=policy)
|
|
519
|
+
set_capability_manager(manager)
|
|
520
|
+
return manager
|
|
521
|
+
|
|
522
|
+
|
|
523
|
+
def check_capability(capability: str, requester: str, *, context: Optional[Dict] = None) -> bool:
|
|
524
|
+
"""Convenience wrapper around the global manager's check."""
|
|
525
|
+
manager = get_capability_manager()
|
|
526
|
+
allowed, _ = manager.check_capability(requester, capability, context)
|
|
527
|
+
return allowed
|
|
528
|
+
|
|
529
|
+
|
|
530
|
+
__all__ = [
|
|
531
|
+
"Capability",
|
|
532
|
+
"CapabilityLevel",
|
|
533
|
+
"CapabilityPolicy",
|
|
534
|
+
"CapabilityManager",
|
|
535
|
+
"CapabilityContext",
|
|
536
|
+
"CapabilityError",
|
|
537
|
+
"AllowAllPolicy",
|
|
538
|
+
"DenyAllPolicy",
|
|
539
|
+
"SelectivePolicy",
|
|
540
|
+
"CapabilityAuditLog",
|
|
541
|
+
"CapabilityRequest",
|
|
542
|
+
"CAPABILITY_SETS",
|
|
543
|
+
"get_capability_manager",
|
|
544
|
+
"set_capability_manager",
|
|
545
|
+
"reset_capability_manager",
|
|
546
|
+
"check_capability",
|
|
547
|
+
]
|
package/src/zexus/cli/main.py
CHANGED
|
@@ -3,10 +3,66 @@ import click
|
|
|
3
3
|
import sys
|
|
4
4
|
import os
|
|
5
5
|
from pathlib import Path
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
from rich.
|
|
9
|
-
from rich.
|
|
6
|
+
|
|
7
|
+
try: # Optional rich dependency for colorful CLI output
|
|
8
|
+
from rich.console import Console
|
|
9
|
+
from rich.syntax import Syntax
|
|
10
|
+
from rich.panel import Panel
|
|
11
|
+
from rich.table import Table
|
|
12
|
+
_RICH_AVAILABLE = True
|
|
13
|
+
except ModuleNotFoundError: # Fallback to minimal console utilities when rich is missing
|
|
14
|
+
_RICH_AVAILABLE = False
|
|
15
|
+
|
|
16
|
+
class Console: # pragma: no cover - straightforward fallback
|
|
17
|
+
def print(self, *args, **kwargs):
|
|
18
|
+
sep = kwargs.get("sep", " ")
|
|
19
|
+
end = kwargs.get("end", "\n")
|
|
20
|
+
text = sep.join(str(arg) for arg in args)
|
|
21
|
+
sys.stdout.write(text)
|
|
22
|
+
if end is not None:
|
|
23
|
+
sys.stdout.write(end)
|
|
24
|
+
|
|
25
|
+
def rule(self, title=""):
|
|
26
|
+
line = "-" * 40
|
|
27
|
+
if title:
|
|
28
|
+
line = f"{line} {title} {line}"
|
|
29
|
+
self.print(line)
|
|
30
|
+
|
|
31
|
+
class Syntax: # pragma: no cover - fallback keeps interface compatible
|
|
32
|
+
def __init__(self, code, *_args, **_kwargs):
|
|
33
|
+
self.code = code
|
|
34
|
+
|
|
35
|
+
def __str__(self):
|
|
36
|
+
return self.code
|
|
37
|
+
|
|
38
|
+
class Panel: # pragma: no cover
|
|
39
|
+
def __init__(self, renderable, title=None):
|
|
40
|
+
self.renderable = renderable
|
|
41
|
+
self.title = title
|
|
42
|
+
|
|
43
|
+
def __str__(self):
|
|
44
|
+
if self.title:
|
|
45
|
+
return f"[{self.title}] {self.renderable}"
|
|
46
|
+
return str(self.renderable)
|
|
47
|
+
|
|
48
|
+
class Table: # pragma: no cover
|
|
49
|
+
def __init__(self, **_kwargs):
|
|
50
|
+
self.columns = []
|
|
51
|
+
self.rows = []
|
|
52
|
+
|
|
53
|
+
def add_column(self, header, **_kwargs):
|
|
54
|
+
self.columns.append(header)
|
|
55
|
+
|
|
56
|
+
def add_row(self, *columns):
|
|
57
|
+
self.rows.append(columns)
|
|
58
|
+
|
|
59
|
+
def __str__(self):
|
|
60
|
+
lines = []
|
|
61
|
+
if self.columns:
|
|
62
|
+
lines.append(" | ".join(self.columns))
|
|
63
|
+
for row in self.rows:
|
|
64
|
+
lines.append(" | ".join(str(col) for col in row))
|
|
65
|
+
return "\n".join(lines)
|
|
10
66
|
|
|
11
67
|
# Import your existing modules - UPDATED IMPORTS
|
|
12
68
|
from ..lexer import Lexer
|
|
@@ -22,7 +78,7 @@ from ..config import config
|
|
|
22
78
|
from ..error_reporter import get_error_reporter, ZexusError, print_error
|
|
23
79
|
# VM and Compiler for high-performance execution
|
|
24
80
|
from ..vm.vm import VM, VMMode
|
|
25
|
-
from ..vm.compiler import compile_ast_to_bytecode
|
|
81
|
+
from ..vm.compiler import compile_ast_to_bytecode, UnsupportedNodeError
|
|
26
82
|
|
|
27
83
|
console = Console()
|
|
28
84
|
|
|
@@ -94,7 +150,7 @@ def show_all_commands():
|
|
|
94
150
|
console.print("\n[bold green]💡 Tip:[/bold green] Use 'zx <command> --help' for detailed command options\n")
|
|
95
151
|
|
|
96
152
|
@click.group(invoke_without_command=True)
|
|
97
|
-
@click.version_option(version="1.
|
|
153
|
+
@click.version_option(version="1.7.1", prog_name="Zexus")
|
|
98
154
|
@click.option('--syntax-style', type=click.Choice(['universal', 'tolerable', 'auto']),
|
|
99
155
|
default='auto', help='Syntax style to use (universal=strict, tolerable=flexible)')
|
|
100
156
|
@click.option('--advanced-parsing', is_flag=True, default=True,
|
|
@@ -231,6 +287,9 @@ def run(ctx, file, args, use_vm, vm_mode, no_optimize):
|
|
|
231
287
|
env.set("__PACKAGE__", package_name)
|
|
232
288
|
|
|
233
289
|
# Execute based on mode
|
|
290
|
+
bytecode = None
|
|
291
|
+
fallback_reason = None
|
|
292
|
+
|
|
234
293
|
if use_vm:
|
|
235
294
|
# VM EXECUTION PATH (High Performance)
|
|
236
295
|
console.print("[dim]Compiling to bytecode...[/dim]", end="")
|
|
@@ -238,22 +297,29 @@ def run(ctx, file, args, use_vm, vm_mode, no_optimize):
|
|
|
238
297
|
bytecode = compile_ast_to_bytecode(program, optimize=not no_optimize)
|
|
239
298
|
console.print(" [green]done[/green]")
|
|
240
299
|
console.print(f"[dim]Bytecode: {len(bytecode.instructions)} instructions, {len(bytecode.constants)} constants[/dim]")
|
|
300
|
+
except UnsupportedNodeError as e:
|
|
301
|
+
console.print(" [yellow]unsupported[/yellow]")
|
|
302
|
+
console.print(f"[bold yellow]⚠️ VM fallback:[/bold yellow] {e}")
|
|
303
|
+
if ctx.obj.get('DEBUG'):
|
|
304
|
+
import traceback
|
|
305
|
+
traceback.print_exc()
|
|
306
|
+
fallback_reason = str(e)
|
|
241
307
|
except Exception as e:
|
|
242
|
-
console.print(
|
|
308
|
+
console.print(" [red]failed[/red]")
|
|
243
309
|
console.print(f"[bold red]Bytecode compilation error:[/bold red] {str(e)}")
|
|
244
310
|
if ctx.obj.get('DEBUG'):
|
|
245
311
|
import traceback
|
|
246
312
|
traceback.print_exc()
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
313
|
+
fallback_reason = str(e)
|
|
314
|
+
|
|
315
|
+
if bytecode is not None and fallback_reason is None:
|
|
250
316
|
vm_mode_enum = {
|
|
251
317
|
'auto': VMMode.AUTO,
|
|
252
318
|
'stack': VMMode.STACK,
|
|
253
319
|
'register': VMMode.REGISTER,
|
|
254
320
|
'parallel': VMMode.PARALLEL
|
|
255
321
|
}[vm_mode]
|
|
256
|
-
|
|
322
|
+
|
|
257
323
|
console.print(f"[dim]Initializing VM ({vm_mode} mode)...[/dim]", end="")
|
|
258
324
|
vm = VM(
|
|
259
325
|
mode=vm_mode_enum,
|
|
@@ -262,8 +328,7 @@ def run(ctx, file, args, use_vm, vm_mode, no_optimize):
|
|
|
262
328
|
debug=ctx.obj.get('DEBUG', False)
|
|
263
329
|
)
|
|
264
330
|
console.print(" [green]done[/green]")
|
|
265
|
-
|
|
266
|
-
# Execute on VM
|
|
331
|
+
|
|
267
332
|
console.print("[dim]Executing on VM...[/dim]")
|
|
268
333
|
try:
|
|
269
334
|
result = vm.execute(bytecode, debug=ctx.obj.get('DEBUG', False))
|
|
@@ -272,9 +337,13 @@ def run(ctx, file, args, use_vm, vm_mode, no_optimize):
|
|
|
272
337
|
if ctx.obj.get('DEBUG'):
|
|
273
338
|
import traceback
|
|
274
339
|
traceback.print_exc()
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
340
|
+
fallback_reason = str(e)
|
|
341
|
+
|
|
342
|
+
if fallback_reason is not None:
|
|
343
|
+
console.print("[bold yellow]🔄 Falling back to interpreter execution...[/bold yellow]")
|
|
344
|
+
result = evaluate(program, env, debug_mode=ctx.obj['DEBUG'])
|
|
345
|
+
elif bytecode is None:
|
|
346
|
+
# No bytecode generated but VM not requested or compile skipped
|
|
278
347
|
result = evaluate(program, env, debug_mode=ctx.obj['DEBUG'])
|
|
279
348
|
|
|
280
349
|
if result and hasattr(result, 'inspect') and result.inspect() != 'null':
|
package/src/zexus/cli/zpm.py
CHANGED
|
@@ -23,7 +23,9 @@ from .zexus_ast import (
|
|
|
23
23
|
Program, LetStatement, ExpressionStatement, PrintStatement, ReturnStatement,
|
|
24
24
|
IfStatement, WhileStatement, Identifier, IntegerLiteral, StringLiteral,
|
|
25
25
|
Boolean as AST_Boolean, InfixExpression, PrefixExpression, CallExpression,
|
|
26
|
-
ActionStatement, BlockStatement, MapLiteral, ListLiteral, AwaitExpression
|
|
26
|
+
ActionStatement, BlockStatement, MapLiteral, ListLiteral, AwaitExpression,
|
|
27
|
+
EnumDeclaration, ImportStatement, EventDeclaration, ProtocolDeclaration,
|
|
28
|
+
EmitStatement
|
|
27
29
|
)
|
|
28
30
|
|
|
29
31
|
# --- Bytecode representation ---
|
|
@@ -47,12 +49,68 @@ class BytecodeGenerator:
|
|
|
47
49
|
|
|
48
50
|
def generate(self, program: Program) -> Bytecode:
|
|
49
51
|
self.bytecode = Bytecode()
|
|
50
|
-
|
|
51
|
-
|
|
52
|
+
statements = list(getattr(program, "statements", []) or [])
|
|
53
|
+
total = len(statements)
|
|
54
|
+
index = 0
|
|
55
|
+
while index < total:
|
|
56
|
+
stmt = statements[index]
|
|
57
|
+
|
|
58
|
+
# Pattern-match legacy import syntax: import "module" [as alias]
|
|
59
|
+
if (
|
|
60
|
+
isinstance(stmt, ExpressionStatement)
|
|
61
|
+
and isinstance(stmt.expression, Identifier)
|
|
62
|
+
and stmt.expression.value == "import"
|
|
63
|
+
):
|
|
64
|
+
module_value = None
|
|
65
|
+
alias_value = None
|
|
66
|
+
consumed = 1
|
|
67
|
+
|
|
68
|
+
if index + 1 < total:
|
|
69
|
+
module_stmt = statements[index + 1]
|
|
70
|
+
if isinstance(module_stmt, ExpressionStatement):
|
|
71
|
+
module_expr = getattr(module_stmt, "expression", None)
|
|
72
|
+
if isinstance(module_expr, StringLiteral):
|
|
73
|
+
module_value = module_expr.value
|
|
74
|
+
consumed = 2
|
|
75
|
+
elif isinstance(module_expr, Identifier):
|
|
76
|
+
module_value = module_expr.value
|
|
77
|
+
consumed = 2
|
|
78
|
+
|
|
79
|
+
if module_value is not None and index + consumed < total:
|
|
80
|
+
next_stmt = statements[index + consumed]
|
|
81
|
+
if (
|
|
82
|
+
isinstance(next_stmt, ExpressionStatement)
|
|
83
|
+
and isinstance(next_stmt.expression, Identifier)
|
|
84
|
+
and next_stmt.expression.value == "as"
|
|
85
|
+
and index + consumed + 1 < total
|
|
86
|
+
):
|
|
87
|
+
alias_stmt = statements[index + consumed + 1]
|
|
88
|
+
if isinstance(alias_stmt, ExpressionStatement) and isinstance(alias_stmt.expression, Identifier):
|
|
89
|
+
alias_value = alias_stmt.expression.value
|
|
90
|
+
consumed += 2
|
|
91
|
+
|
|
92
|
+
if module_value is not None:
|
|
93
|
+
import_node = ImportStatement(module_path=module_value, alias=alias_value)
|
|
94
|
+
self._emit_statement(
|
|
95
|
+
import_node,
|
|
96
|
+
self.bytecode,
|
|
97
|
+
is_top_level=True,
|
|
98
|
+
is_last=(index + consumed - 1 == total - 1),
|
|
99
|
+
)
|
|
100
|
+
index += consumed
|
|
101
|
+
continue
|
|
102
|
+
|
|
103
|
+
self._emit_statement(
|
|
104
|
+
stmt,
|
|
105
|
+
self.bytecode,
|
|
106
|
+
is_top_level=True,
|
|
107
|
+
is_last=(index == total - 1),
|
|
108
|
+
)
|
|
109
|
+
index += 1
|
|
52
110
|
return self.bytecode
|
|
53
111
|
|
|
54
112
|
# Statement lowering
|
|
55
|
-
def _emit_statement(self, stmt, bc: Bytecode):
|
|
113
|
+
def _emit_statement(self, stmt, bc: Bytecode, *, is_top_level: bool = False, is_last: bool = False):
|
|
56
114
|
t = type(stmt).__name__
|
|
57
115
|
|
|
58
116
|
if t == "LetStatement":
|
|
@@ -64,8 +122,8 @@ class BytecodeGenerator:
|
|
|
64
122
|
|
|
65
123
|
if t == "ExpressionStatement":
|
|
66
124
|
self._emit_expression(stmt.expression, bc)
|
|
67
|
-
|
|
68
|
-
|
|
125
|
+
if not (is_top_level and is_last):
|
|
126
|
+
bc.add_instruction("POP", None)
|
|
69
127
|
return
|
|
70
128
|
|
|
71
129
|
if t == "PrintStatement":
|
|
@@ -125,7 +183,61 @@ class BytecodeGenerator:
|
|
|
125
183
|
bc.instructions[jump_pos] = ("JUMP_IF_FALSE", end_pos)
|
|
126
184
|
return
|
|
127
185
|
|
|
128
|
-
|
|
186
|
+
if t == "EnumDeclaration":
|
|
187
|
+
enum_name = getattr(stmt.name, "value", stmt.name)
|
|
188
|
+
members = getattr(stmt, "members", {}) or {}
|
|
189
|
+
resolved_members = {}
|
|
190
|
+
next_auto = 0
|
|
191
|
+
for key, explicit in members.items():
|
|
192
|
+
value = explicit if explicit is not None else next_auto
|
|
193
|
+
resolved_members[key] = value
|
|
194
|
+
next_auto = (value + 1) if explicit is not None else (next_auto + 1)
|
|
195
|
+
|
|
196
|
+
name_idx = bc.add_constant(enum_name)
|
|
197
|
+
members_idx = bc.add_constant(resolved_members)
|
|
198
|
+
bc.add_instruction("DEFINE_ENUM", (name_idx, members_idx))
|
|
199
|
+
return
|
|
200
|
+
|
|
201
|
+
if t == "ImportStatement":
|
|
202
|
+
module_idx = bc.add_constant(getattr(stmt, "module_path", None))
|
|
203
|
+
alias = getattr(stmt, "alias", None)
|
|
204
|
+
if alias is not None:
|
|
205
|
+
alias_idx = bc.add_constant(alias)
|
|
206
|
+
bc.add_instruction("IMPORT", (module_idx, alias_idx))
|
|
207
|
+
else:
|
|
208
|
+
bc.add_instruction("IMPORT", (module_idx,))
|
|
209
|
+
return
|
|
210
|
+
|
|
211
|
+
if t == "EventDeclaration":
|
|
212
|
+
event_name = getattr(stmt.name, "value", stmt.name)
|
|
213
|
+
name_idx = bc.add_constant(event_name)
|
|
214
|
+
bc.add_instruction("REGISTER_EVENT", name_idx)
|
|
215
|
+
return
|
|
216
|
+
|
|
217
|
+
if t == "ProtocolDeclaration":
|
|
218
|
+
proto_name = getattr(stmt.name, "value", stmt.name)
|
|
219
|
+
spec = getattr(stmt, "spec", {}) or {}
|
|
220
|
+
name_idx = bc.add_constant(proto_name)
|
|
221
|
+
spec_idx = bc.add_constant(spec)
|
|
222
|
+
bc.add_instruction("DEFINE_PROTOCOL", (name_idx, spec_idx))
|
|
223
|
+
return
|
|
224
|
+
|
|
225
|
+
if t == "EmitStatement":
|
|
226
|
+
event_name = getattr(stmt.name, "value", stmt.name)
|
|
227
|
+
name_idx = bc.add_constant(event_name)
|
|
228
|
+
if getattr(stmt, "payload", None) is not None:
|
|
229
|
+
self._emit_expression(stmt.payload, bc)
|
|
230
|
+
bc.add_instruction("EMIT_EVENT", (name_idx,))
|
|
231
|
+
return
|
|
232
|
+
|
|
233
|
+
if t == "StreamStatement":
|
|
234
|
+
# Streams are currently handled at runtime only; compiler no-op.
|
|
235
|
+
return
|
|
236
|
+
|
|
237
|
+
if t == "WatchStatement":
|
|
238
|
+
# Watch statements are runtime constructs; compiler emits no bytecode.
|
|
239
|
+
return
|
|
240
|
+
|
|
129
241
|
return
|
|
130
242
|
|
|
131
243
|
# Expression lowering
|
|
@@ -141,6 +253,11 @@ class BytecodeGenerator:
|
|
|
141
253
|
bc.add_instruction("LOAD_CONST", const_idx)
|
|
142
254
|
return
|
|
143
255
|
|
|
256
|
+
if typ == "FloatLiteral":
|
|
257
|
+
const_idx = bc.add_constant(float(expr.value))
|
|
258
|
+
bc.add_instruction("LOAD_CONST", const_idx)
|
|
259
|
+
return
|
|
260
|
+
|
|
144
261
|
if typ == "StringLiteral":
|
|
145
262
|
const_idx = bc.add_constant(expr.value)
|
|
146
263
|
bc.add_instruction("LOAD_CONST", const_idx)
|
|
@@ -182,8 +182,12 @@ except Exception as e:
|
|
|
182
182
|
def unwrap_return_value(obj):
|
|
183
183
|
return obj
|
|
184
184
|
|
|
185
|
-
# Minimal renderer fallback
|
|
186
|
-
|
|
185
|
+
# Minimal renderer fallback (used when the real backend is unavailable)
|
|
186
|
+
try:
|
|
187
|
+
from ..renderer import backend as _BACKEND
|
|
188
|
+
RENDER_REGISTRY = _BACKEND.inspect_registry()
|
|
189
|
+
except Exception:
|
|
190
|
+
RENDER_REGISTRY = {'screens': {}, 'components': {}, 'themes': {}, 'canvases': {}, 'current_theme': None}
|
|
187
191
|
|
|
188
192
|
# Try to create small wrappers for builtin functions by reading from object.File when present
|
|
189
193
|
try:
|