Source code for crosstl.translator.codegen.registry
"""Backend registry and alias resolution for CrossGL code generators."""
from __future__ import annotations
from dataclasses import dataclass
from typing import Dict, Iterable, Optional, Sequence, Type, Any
def _normalize_backend_name(name: str) -> str:
if not isinstance(name, str):
raise TypeError(f"Backend name must be a string, got {type(name)}")
return name.strip().lower()
[docs]
@dataclass(frozen=True)
class BackendSpec:
"""Descriptor for a target code generator backend."""
name: str
codegen_class: Type[Any]
aliases: Sequence[str] = ()
file_extensions: Sequence[str] = ()
format_backend: Optional[str] = None
[docs]
class BackendRegistry:
"""Lookup table for target code generators by backend name and alias."""
def __init__(self) -> None:
self._by_name: Dict[str, BackendSpec] = {}
self._by_alias: Dict[str, str] = {}
[docs]
def register(self, spec: BackendSpec, *, overwrite: bool = False) -> BackendSpec:
"""Register a backend spec and all of its aliases."""
name = _normalize_backend_name(spec.name)
if name in self._by_name and not overwrite:
existing = self._by_name[name]
if existing.codegen_class is spec.codegen_class:
return existing
raise ValueError(f"Backend '{name}' already registered")
self._by_name[name] = spec
for alias in spec.aliases:
alias_key = _normalize_backend_name(alias)
if alias_key in self._by_alias and not overwrite:
if self._by_alias[alias_key] == name:
continue
raise ValueError(f"Backend alias '{alias_key}' already registered")
self._by_alias[alias_key] = name
return spec
[docs]
def resolve_name(self, name: str) -> Optional[str]:
"""Resolve a backend name or alias to its canonical registry name."""
if not name:
return None
key = _normalize_backend_name(name)
if key in self._by_name:
return key
return self._by_alias.get(key)
[docs]
def get(self, name: str) -> Optional[BackendSpec]:
"""Return the backend spec registered for a name or alias."""
resolved = self.resolve_name(name)
if not resolved:
return None
return self._by_name.get(resolved)
[docs]
def all(self) -> Iterable[BackendSpec]:
"""Return all registered backend specs."""
return list(self._by_name.values())
[docs]
def names(self) -> Sequence[str]:
"""Return registered canonical backend names in sorted order."""
return sorted(self._by_name.keys())
[docs]
def aliases(self) -> Dict[str, str]:
"""Return a copy of the alias-to-backend mapping."""
return dict(self._by_alias)
BACKEND_REGISTRY = BackendRegistry()
[docs]
def register_backend(spec: BackendSpec, *, overwrite: bool = False) -> BackendSpec:
"""Register a target backend in the global backend registry."""
return BACKEND_REGISTRY.register(spec, overwrite=overwrite)
[docs]
def normalize_backend_name(name: str) -> Optional[str]:
"""Resolve a backend name or alias through the global registry."""
return BACKEND_REGISTRY.resolve_name(name)
[docs]
def get_backend(name: str) -> Optional[BackendSpec]:
"""Return a backend spec from the global registry."""
return BACKEND_REGISTRY.get(name)
[docs]
def backend_names() -> Sequence[str]:
"""Return registered backend names from the global registry."""
return BACKEND_REGISTRY.names()
[docs]
def get_backend_extension(name: str) -> Optional[str]:
"""Return the preferred output extension for a backend, if known."""
spec = BACKEND_REGISTRY.get(name)
if not spec or not spec.file_extensions:
return None
return spec.file_extensions[0]
[docs]
def get_codegen(name: str):
"""Instantiate the code generator class for a registered backend."""
spec = BACKEND_REGISTRY.get(name)
if not spec:
supported = ", ".join(backend_names())
raise ValueError(
f"Unsupported backend '{name}'. Supported backends: {supported}"
)
return spec.codegen_class()