Wire deps.sx into both bootstrappers, rebootstrap Python + JS
deps.sx is now a spec module that both bootstrap_py.py and bootstrap_js.py can include via --spec-modules deps. Platform functions (component-deps, component-set-deps!, component-css-classes, env-components, regex-find-all, scan-css-classes) implemented natively in both Python and JS. - Fix deps.sx: env-get-or → env-get, extract nested define to top-level - bootstrap_py.py: SPEC_MODULES, PLATFORM_DEPS_PY, mangle entries, CLI arg - bootstrap_js.py: SPEC_MODULES, PLATFORM_DEPS_JS, mangle entries, CLI arg - Regenerate sx_ref.py and sx-ref.js with deps module - deps.py: thin dispatcher (SX_USE_REF=1 → bootstrapped, else fallback) - scan_components_from_sx now returns ~prefixed names (consistent with spec) Verified: 541 Python tests pass, JS deps tested with Node.js, both code paths (fallback + bootstrapped) produce identical results. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,56 +1,48 @@
|
||||
"""
|
||||
Component dependency analysis.
|
||||
|
||||
Walks component AST bodies to compute transitive dependency sets.
|
||||
A component's deps are all other components (~name references) it
|
||||
can potentially render, including through control flow branches.
|
||||
Thin host wrapper over bootstrapped deps module from shared/sx/ref/deps.sx.
|
||||
The canonical logic lives in the spec; this module provides Python-typed
|
||||
entry points for the rest of the codebase.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
from typing import Any
|
||||
|
||||
from .types import Component, Macro, Symbol
|
||||
|
||||
|
||||
def _scan_ast(node: Any) -> set[str]:
|
||||
"""Scan an AST node for ~component references.
|
||||
def _use_ref() -> bool:
|
||||
return os.environ.get("SX_USE_REF") == "1"
|
||||
|
||||
Walks all branches of control flow (if/when/cond/case) to find
|
||||
every component that *could* be rendered. Returns a set of
|
||||
component names (with ~ prefix).
|
||||
"""
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Hand-written fallback (used when SX_USE_REF != 1)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def _scan_ast(node: Any) -> set[str]:
|
||||
refs: set[str] = set()
|
||||
_walk(node, refs)
|
||||
return refs
|
||||
|
||||
|
||||
def _walk(node: Any, refs: set[str]) -> None:
|
||||
"""Recursively walk an AST node collecting ~name references."""
|
||||
if isinstance(node, Symbol):
|
||||
if node.name.startswith("~"):
|
||||
refs.add(node.name)
|
||||
return
|
||||
|
||||
if isinstance(node, list):
|
||||
for item in node:
|
||||
_walk(item, refs)
|
||||
return
|
||||
|
||||
if isinstance(node, dict):
|
||||
for v in node.values():
|
||||
_walk(v, refs)
|
||||
return
|
||||
|
||||
# Literals (str, int, float, bool, None, Keyword) — no refs
|
||||
return
|
||||
|
||||
|
||||
def transitive_deps(name: str, env: dict[str, Any]) -> set[str]:
|
||||
"""Compute transitive component dependencies for *name*.
|
||||
|
||||
Returns the set of all component names (with ~ prefix) that
|
||||
*name* can transitively render, NOT including *name* itself.
|
||||
"""
|
||||
def _transitive_deps_fallback(name: str, env: dict[str, Any]) -> set[str]:
|
||||
seen: set[str] = set()
|
||||
|
||||
def walk(n: str) -> None:
|
||||
@@ -70,37 +62,19 @@ def transitive_deps(name: str, env: dict[str, Any]) -> set[str]:
|
||||
return seen - {key}
|
||||
|
||||
|
||||
def compute_all_deps(env: dict[str, Any]) -> None:
|
||||
"""Compute and cache deps for all Component entries in *env*.
|
||||
|
||||
Mutates each Component's ``deps`` field in place.
|
||||
"""
|
||||
def _compute_all_deps_fallback(env: dict[str, Any]) -> None:
|
||||
for key, val in env.items():
|
||||
if isinstance(val, Component):
|
||||
val.deps = transitive_deps(key, env)
|
||||
val.deps = _transitive_deps_fallback(key, env)
|
||||
|
||||
|
||||
def scan_components_from_sx(source: str) -> set[str]:
|
||||
"""Extract component names referenced in SX source text.
|
||||
|
||||
Uses regex to find (~name patterns in serialized SX wire format.
|
||||
Returns names with ~ prefix, e.g. {"~card", "~nav-link"}.
|
||||
"""
|
||||
def _scan_components_from_sx_fallback(source: str) -> set[str]:
|
||||
import re
|
||||
return set(re.findall(r'\(~([a-zA-Z_][a-zA-Z0-9_\-]*)', source))
|
||||
return {f"~{m}" for m in re.findall(r'\(~([a-zA-Z_][a-zA-Z0-9_\-]*)', source)}
|
||||
|
||||
|
||||
def components_needed(page_sx: str, env: dict[str, Any]) -> set[str]:
|
||||
"""Compute the full set of component names needed for a page.
|
||||
|
||||
Scans *page_sx* for direct component references, then computes
|
||||
the transitive closure over the component dependency graph.
|
||||
Returns names with ~ prefix.
|
||||
"""
|
||||
# Direct refs from the page source
|
||||
direct = {f"~{n}" for n in scan_components_from_sx(page_sx)}
|
||||
|
||||
# Transitive closure
|
||||
def _components_needed_fallback(page_sx: str, env: dict[str, Any]) -> set[str]:
|
||||
direct = _scan_components_from_sx_fallback(page_sx)
|
||||
all_needed: set[str] = set()
|
||||
for name in direct:
|
||||
all_needed.add(name)
|
||||
@@ -108,7 +82,52 @@ def components_needed(page_sx: str, env: dict[str, Any]) -> set[str]:
|
||||
if isinstance(val, Component) and val.deps:
|
||||
all_needed.update(val.deps)
|
||||
else:
|
||||
# deps not cached yet — compute on the fly
|
||||
all_needed.update(transitive_deps(name, env))
|
||||
|
||||
all_needed.update(_transitive_deps_fallback(name, env))
|
||||
return all_needed
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Public API — dispatches to bootstrapped or fallback
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def transitive_deps(name: str, env: dict[str, Any]) -> set[str]:
|
||||
"""Compute transitive component dependencies for *name*.
|
||||
|
||||
Returns the set of all component names (with ~ prefix) that
|
||||
*name* can transitively render, NOT including *name* itself.
|
||||
"""
|
||||
if _use_ref():
|
||||
from .ref.sx_ref import transitive_deps as _ref_td
|
||||
return set(_ref_td(name, env))
|
||||
return _transitive_deps_fallback(name, env)
|
||||
|
||||
|
||||
def compute_all_deps(env: dict[str, Any]) -> None:
|
||||
"""Compute and cache deps for all Component entries in *env*."""
|
||||
if _use_ref():
|
||||
from .ref.sx_ref import compute_all_deps as _ref_cad
|
||||
_ref_cad(env)
|
||||
return
|
||||
_compute_all_deps_fallback(env)
|
||||
|
||||
|
||||
def scan_components_from_sx(source: str) -> set[str]:
|
||||
"""Extract component names referenced in SX source text.
|
||||
|
||||
Returns names with ~ prefix, e.g. {"~card", "~nav-link"}.
|
||||
"""
|
||||
if _use_ref():
|
||||
from .ref.sx_ref import scan_components_from_source as _ref_sc
|
||||
return set(_ref_sc(source))
|
||||
return _scan_components_from_sx_fallback(source)
|
||||
|
||||
|
||||
def components_needed(page_sx: str, env: dict[str, Any]) -> set[str]:
|
||||
"""Compute the full set of component names needed for a page.
|
||||
|
||||
Returns names with ~ prefix.
|
||||
"""
|
||||
if _use_ref():
|
||||
from .ref.sx_ref import components_needed as _ref_cn
|
||||
return set(_ref_cn(page_sx, env))
|
||||
return _components_needed_fallback(page_sx, env)
|
||||
|
||||
Reference in New Issue
Block a user