Files
ritz/compiler.py
2025-11-15 08:07:09 +00:00

345 lines
12 KiB
Python

#!/usr/bin/env python3
"""
Ritz Compilation Pipeline
From Python source to debuggable WASM modules
"""
import ast
import hashlib
import subprocess
from pathlib import Path
from typing import Dict, List, Optional
from dataclasses import dataclass
@dataclass
class CompilationUnit:
"""A unit of compilation (module)"""
name: str
components: List[str]
dependencies: List[str]
source_files: List[Path]
output_path: Path
requires_auth: bool = False
class RitzCompiler:
"""Complete compilation pipeline"""
def __init__(self, project_dir: Path):
self.project_dir = project_dir
self.build_dir = project_dir / 'build'
self.wasm_dir = project_dir / 'dist' / 'wasm'
self.units: List[CompilationUnit] = []
def compile_project(self):
"""Full compilation pipeline"""
print("=== Ritz Compilation Pipeline ===\n")
# 1. Discover and parse components
print("1. Parsing Python components...")
components = self.discover_components()
# 2. Split into modules
print("\n2. Splitting into modules...")
self.split_into_modules(components)
# 3. Generate IR for each module
print("\n3. Generating intermediate representation...")
for unit in self.units:
self.generate_ir(unit)
# 4. Compile to WASM with debug symbols
print("\n4. Compiling to WASM...")
for unit in self.units:
self.compile_to_wasm(unit)
# 5. Generate debug artifacts
print("\n5. Generating debug artifacts...")
self.generate_debug_artifacts()
# 6. Generate module manifest
print("\n6. Creating module manifest...")
self.generate_manifest()
print("\n✅ Compilation complete!")
def discover_components(self) -> Dict[str, ast.AST]:
"""Discover all @component decorated functions"""
components = {}
for py_file in self.project_dir.glob('**/*.py'):
if 'build' in py_file.parts or '__pycache__' in py_file.parts:
continue
with open(py_file) as f:
source = f.read()
try:
tree = ast.parse(source)
for node in ast.walk(tree):
if isinstance(node, ast.FunctionDef):
if self.has_component_decorator(node):
components[node.name] = {
'ast': node,
'file': py_file,
'source': source
}
print(f" Found: {node.name} in {py_file.name}")
except SyntaxError as e:
print(f" Skipping {py_file}: {e}")
return components
def has_component_decorator(self, node: ast.FunctionDef) -> bool:
"""Check if function has @component decorator"""
for decorator in node.decorator_list:
if isinstance(decorator, ast.Name) and decorator.id == 'component':
return True
return False
def split_into_modules(self, components: Dict):
"""Split components into compilation units"""
# Bootstrap module - login only
bootstrap_components = [
name for name in components
if 'Login' in name or 'Auth' in name
]
if bootstrap_components:
self.units.append(CompilationUnit(
name='bootstrap',
components=bootstrap_components,
dependencies=[],
source_files=[components[c]['file'] for c in bootstrap_components],
output_path=self.wasm_dir / 'bootstrap.wasm',
requires_auth=False
))
print(f" bootstrap.wasm: {len(bootstrap_components)} components")
# Core module - main app
core_components = [
name for name in components
if name not in bootstrap_components
and not name.startswith('Admin')
and not name.startswith('Analytics')
]
if core_components:
self.units.append(CompilationUnit(
name='app-core',
components=core_components,
dependencies=['bootstrap'],
source_files=[components[c]['file'] for c in core_components],
output_path=self.wasm_dir / 'app-core.wasm',
requires_auth=True
))
print(f" app-core.wasm: {len(core_components)} components")
# Feature modules
admin_components = [
name for name in components if name.startswith('Admin')
]
if admin_components:
self.units.append(CompilationUnit(
name='admin',
components=admin_components,
dependencies=['app-core'],
source_files=[components[c]['file'] for c in admin_components],
output_path=self.wasm_dir / 'features' / 'admin.wasm',
requires_auth=True
))
print(f" admin.wasm: {len(admin_components)} components")
def generate_ir(self, unit: CompilationUnit):
"""Generate Ritz IR from Python AST"""
print(f" Generating IR for {unit.name}...")
# Use our AST parser
from ast_parser import RitzASTTransformer
ir_module = {
'name': unit.name,
'components': [],
'dependencies': unit.dependencies
}
for component_name in unit.components:
# Parse component
# In real implementation, would use the stored AST
print(f" Processing {component_name}")
# Generate IR for component
ir = {
'type': 'component',
'name': component_name,
# ... IR details
}
ir_module['components'].append(ir)
# Save IR for next stage
ir_path = self.build_dir / f'{unit.name}.ir.json'
ir_path.parent.mkdir(parents=True, exist_ok=True)
import json
with open(ir_path, 'w') as f:
json.dump(ir_module, f, indent=2)
def compile_to_wasm(self, unit: CompilationUnit):
"""Compile IR to WASM using LLVM"""
print(f" Compiling {unit.name} to WASM...")
# This would use LLVM in reality
# For demonstration, show the command that would be run
llvm_commands = [
# 1. Generate LLVM IR
f"ritz-codegen {self.build_dir}/{unit.name}.ir.json -o {self.build_dir}/{unit.name}.ll",
# 2. Compile to WASM with debug symbols
f"clang --target=wasm32-unknown-unknown "
f"-O2 -g " # Optimization + debug symbols
f"-fdebug-prefix-map={self.project_dir}=/src " # Remap paths for debugging
f"-o {self.build_dir}/{unit.name}.wasm "
f"{self.build_dir}/{unit.name}.ll",
# 3. Optimize with Binaryen
f"wasm-opt "
f"-O2 " # Optimize
f"--debuginfo " # Preserve debug info
f"--source-map {unit.output_path}.map " # Generate source map
f"-o {unit.output_path} "
f"{self.build_dir}/{unit.name}.wasm"
]
for cmd in llvm_commands:
print(f" $ {cmd}")
# Create output file (mock)
unit.output_path.parent.mkdir(parents=True, exist_ok=True)
unit.output_path.write_bytes(b'\x00asm\x01\x00\x00\x00...')
def generate_debug_artifacts(self):
"""Generate debugging support files"""
print(" Generating source maps...")
for unit in self.units:
source_map = {
"version": 3,
"file": str(unit.output_path),
"sources": [str(f) for f in unit.source_files],
"sourcesContent": [
f.read_text() for f in unit.source_files
],
"names": unit.components,
"mappings": "...", # Would be actual VLQ encodings
# Ritz extensions for Python debugging
"x_ritz": {
"python_version": "3.11",
"module_name": unit.name,
"components": unit.components,
"debug_symbols": f"{unit.output_path}.dwarf"
}
}
map_path = unit.output_path.with_suffix('.wasm.map')
print(f" {map_path.name}")
def generate_manifest(self):
"""Generate module manifest for loader"""
manifest = {
"version": "1.0",
"modules": {}
}
for unit in self.units:
# Calculate hash for cache busting
if unit.output_path.exists():
content = unit.output_path.read_bytes()
hash_val = hashlib.sha256(content).hexdigest()[:12]
else:
hash_val = "mock"
manifest["modules"][unit.name] = {
"path": f"/wasm/{unit.output_path.name}",
"size": unit.output_path.stat().st_size if unit.output_path.exists() else 0,
"hash": hash_val,
"dependencies": unit.dependencies,
"requires_auth": unit.requires_auth,
"components": unit.components
}
manifest_path = self.wasm_dir / 'manifest.json'
print(f" Writing {manifest_path}")
import json
manifest_path.parent.mkdir(parents=True, exist_ok=True)
with open(manifest_path, 'w') as f:
json.dump(manifest, f, indent=2)
class DebugServer:
"""Development server with debugging support"""
def __init__(self, compiler: RitzCompiler):
self.compiler = compiler
def start(self, port: int = 8080):
"""Start dev server with hot reload and debugging"""
print(f"\n🚀 Starting Ritz dev server on http://localhost:{port}")
print("\nFeatures:")
print(" ✓ Python source debugging in browser")
print(" ✓ Hot module reloading")
print(" ✓ Source maps")
print(" ✓ Progressive loading")
print("\nDevTools Integration:")
print(" 1. Open Chrome DevTools")
print(" 2. Go to Sources tab")
print(" 3. Find 'wasm://' in file tree")
print(" 4. Set breakpoints in Python files!")
print("\nCommands:")
print(" r - Recompile all")
print(" m <module> - Recompile specific module")
print(" d - Open debugger")
print(" q - Quit")
# Example usage
if __name__ == '__main__':
import sys
from pathlib import Path
# Compile the project
project_dir = Path.cwd()
compiler = RitzCompiler(project_dir)
# Mock some components for demo
print("=== Ritz Compiler Demo ===\n")
# Show what the compiler would do
print("Project structure:")
print(" ritz/")
print(" ├── components/")
print(" │ ├── login.py (@component LoginForm)")
print(" │ ├── dashboard.py (@component Dashboard)")
print(" │ ├── admin.py (@component AdminPanel)")
print(" │ └── analytics.py (@component AnalyticsView)")
print(" ├── build/ (intermediate files)")
print(" └── dist/")
print(" └── wasm/")
print(" ├── bootstrap.wasm (50KB - no auth)")
print(" ├── app-core.wasm (200KB - auth required)")
print(" ├── features/")
print(" │ ├── admin.wasm (150KB)")
print(" │ └── analytics.wasm (120KB)")
print(" └── manifest.json")
print("\nCompilation would:")
print("1. Parse Python with ast module")
print("2. Generate Ritz IR")
print("3. Compile to LLVM IR")
print("4. Generate WASM with debug symbols")
print("5. Create source maps")
print("6. Split into secure modules")
print("\n🎉 The dream is real: Python → WASM with debugging!")