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.
Files changed (50) hide show
  1. package/README.md +12 -5
  2. package/package.json +1 -1
  3. package/src/zexus/__init__.py +1 -1
  4. package/src/zexus/capability_system.py +184 -9
  5. package/src/zexus/cli/main.py +85 -16
  6. package/src/zexus/cli/zpm.py +1 -1
  7. package/src/zexus/compiler/bytecode.py +124 -7
  8. package/src/zexus/compiler/compat_runtime.py +6 -2
  9. package/src/zexus/compiler/lexer.py +6 -0
  10. package/src/zexus/compiler/parser.py +108 -7
  11. package/src/zexus/compiler/semantic.py +18 -19
  12. package/src/zexus/compiler/zexus_ast.py +26 -1
  13. package/src/zexus/environment.py +103 -9
  14. package/src/zexus/evaluator/bytecode_compiler.py +16 -0
  15. package/src/zexus/evaluator/core.py +85 -2
  16. package/src/zexus/evaluator/expressions.py +236 -13
  17. package/src/zexus/evaluator/functions.py +41 -4
  18. package/src/zexus/evaluator/resource_limiter.py +4 -4
  19. package/src/zexus/evaluator/statements.py +253 -4
  20. package/src/zexus/evaluator_original.py +1 -1
  21. package/src/zexus/lexer.py +30 -2
  22. package/src/zexus/module_cache.py +2 -0
  23. package/src/zexus/module_manager.py +129 -1
  24. package/src/zexus/object.py +12 -0
  25. package/src/zexus/parser/parser.py +582 -142
  26. package/src/zexus/parser/strategy_context.py +361 -5
  27. package/src/zexus/parser/strategy_structural.py +4 -2
  28. package/src/zexus/renderer/__init__.py +46 -0
  29. package/src/zexus/renderer/backend.py +261 -0
  30. package/src/zexus/renderer/canvas.py +78 -0
  31. package/src/zexus/renderer/color_system.py +201 -0
  32. package/src/zexus/renderer/graphics.py +31 -0
  33. package/src/zexus/renderer/layout.py +222 -0
  34. package/src/zexus/renderer/main_renderer.py +66 -0
  35. package/src/zexus/renderer/painter.py +30 -0
  36. package/src/zexus/runtime/__init__.py +10 -2
  37. package/src/zexus/runtime/load_manager.py +368 -0
  38. package/src/zexus/vm/async_optimizer.py +66 -5
  39. package/src/zexus/vm/bytecode.py +31 -10
  40. package/src/zexus/vm/cache.py +4 -0
  41. package/src/zexus/vm/compiler.py +115 -46
  42. package/src/zexus/vm/gas_metering.py +1 -1
  43. package/src/zexus/vm/jit.py +281 -18
  44. package/src/zexus/vm/parallel_vm.py +22 -3
  45. package/src/zexus/vm/vm.py +246 -34
  46. package/src/zexus/zexus_ast.py +74 -0
  47. package/src/zexus/zexus_token.py +3 -0
  48. package/src/zexus/zpm/package_manager.py +1 -1
  49. package/src/zexus.egg-info/PKG-INFO +13 -6
  50. 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
- ![Zexus Logo](https://img.shields.io/badge/Zexus-v1.6.8-FF6B35?style=for-the-badge)
5
+ ![Zexus Logo](https://img.shields.io/badge/Zexus-v1.7.1-FF6B35?style=for-the-badge)
6
6
  [![License](https://img.shields.io/badge/License-MIT-blue.svg?style=for-the-badge)](LICENSE)
7
7
  [![Python](https://img.shields.io/badge/Python-3.8+-3776AB?style=for-the-badge&logo=python)](https://python.org)
8
8
  [![GitHub](https://img.shields.io/badge/GitHub-Zaidux/zexus--interpreter-181717?style=for-the-badge&logo=github)](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-v150) • [Features](#-key-features) • [Installation](#-installation) • [Quick Start](#-quick-start) • [Keywords](#-complete-keyword-reference) • [Documentation](#-documentation) • [Examples](#-examples) • [Troubleshooting](#-getting-help--troubleshooting)
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-v150)
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.6.3
70
+ ## 🎉 What's New in v1.7.1
71
71
 
72
- ### Latest Features (v1.6.3)
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zexus",
3
- "version": "1.6.8",
3
+ "version": "1.7.1",
4
4
  "description": "A modern, security-first programming language with blockchain support",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -4,7 +4,7 @@ Zexus Programming Language
4
4
  A declarative, intent-based programming language for modern applications.
5
5
  """
6
6
 
7
- __version__ = "1.6.8"
7
+ __version__ = "1.7.1"
8
8
  __author__ = "Ziver Labs"
9
9
  __email__ = "ziverofficial567@gmail.com"
10
10
 
@@ -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
- # 1. Check if policy allows all (AllowAllPolicy)
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
- # 2. Check if capability is base capability (always available)
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
- # 3. Check if requester has been explicitly granted this capability
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
- # 4. Check if capability is allowed by policy
269
+ # 5. Check if capability is allowed by policy
234
270
  policy_level = self.policy.check(capability)
235
- if policy_level == CapabilityLevel.ALLOWED:
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
+ ]
@@ -3,10 +3,66 @@ import click
3
3
  import sys
4
4
  import os
5
5
  from pathlib import Path
6
- from rich.console import Console
7
- from rich.syntax import Syntax
8
- from rich.panel import Panel
9
- from rich.table import Table
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.6.8", prog_name="Zexus")
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(f" [red]failed[/red]")
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
- sys.exit(1)
248
-
249
- # Initialize VM
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
- sys.exit(1)
276
- else:
277
- # INTERPRETER EXECUTION PATH (Standard)
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':
@@ -18,7 +18,7 @@ console = Console()
18
18
 
19
19
 
20
20
  @click.group()
21
- @click.version_option(version="1.6.8", prog_name="ZPM")
21
+ @click.version_option(version="1.7.1", prog_name="ZPM")
22
22
  def cli():
23
23
  """ZPM - Zexus Package Manager
24
24
 
@@ -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
- for stmt in getattr(program, "statements", []):
51
- self._emit_statement(stmt, self.bytecode)
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
- # drop result (no-op) or keep for top-level
68
- bc.add_instruction("POP", None)
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
- # Event/emit/enum/import handled at higher-level generator earlier; treat as NOP here
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
- RENDER_REGISTRY = {'screens': {}, 'components': {}, 'themes': {}, 'canvases': {}, 'current_theme': None}
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: