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:
@@ -235,6 +235,21 @@ class PyEmitter:
|
||||
"map-dict": "map_dict",
|
||||
"eval-cond": "eval_cond",
|
||||
"process-bindings": "process_bindings",
|
||||
# deps.sx
|
||||
"scan-refs": "scan_refs",
|
||||
"scan-refs-walk": "scan_refs_walk",
|
||||
"transitive-deps": "transitive_deps",
|
||||
"compute-all-deps": "compute_all_deps",
|
||||
"scan-components-from-source": "scan_components_from_source",
|
||||
"components-needed": "components_needed",
|
||||
"page-component-bundle": "page_component_bundle",
|
||||
"page-css-classes": "page_css_classes",
|
||||
"component-deps": "component_deps",
|
||||
"component-set-deps!": "component_set_deps",
|
||||
"component-css-classes": "component_css_classes",
|
||||
"env-components": "env_components",
|
||||
"regex-find-all": "regex_find_all",
|
||||
"scan-css-classes": "scan_css_classes",
|
||||
}
|
||||
if name in RENAMES:
|
||||
return RENAMES[name]
|
||||
@@ -803,6 +818,11 @@ ADAPTER_FILES = {
|
||||
}
|
||||
|
||||
|
||||
SPEC_MODULES = {
|
||||
"deps": ("deps.sx", "deps (component dependency analysis)"),
|
||||
}
|
||||
|
||||
|
||||
EXTENSION_NAMES = {"continuations"}
|
||||
|
||||
# Extension-provided special forms (not in eval.sx core)
|
||||
@@ -889,6 +909,7 @@ def compile_ref_to_py(
|
||||
adapters: list[str] | None = None,
|
||||
modules: list[str] | None = None,
|
||||
extensions: list[str] | None = None,
|
||||
spec_modules: list[str] | None = None,
|
||||
) -> str:
|
||||
"""Read reference .sx files and emit Python.
|
||||
|
||||
@@ -902,6 +923,9 @@ def compile_ref_to_py(
|
||||
extensions: List of optional extensions to include.
|
||||
Valid names: continuations.
|
||||
None = no extensions.
|
||||
spec_modules: List of spec module names to include.
|
||||
Valid names: deps.
|
||||
None = no spec modules.
|
||||
"""
|
||||
# Determine which primitive modules to include
|
||||
prim_modules = None # None = all
|
||||
@@ -926,7 +950,16 @@ def compile_ref_to_py(
|
||||
raise ValueError(f"Unknown adapter: {a!r}. Valid: {', '.join(ADAPTER_FILES)}")
|
||||
adapter_set.add(a)
|
||||
|
||||
# Core files always included, then selected adapters
|
||||
# Resolve spec modules
|
||||
spec_mod_set = set()
|
||||
if spec_modules:
|
||||
for sm in spec_modules:
|
||||
if sm not in SPEC_MODULES:
|
||||
raise ValueError(f"Unknown spec module: {sm!r}. Valid: {', '.join(SPEC_MODULES)}")
|
||||
spec_mod_set.add(sm)
|
||||
has_deps = "deps" in spec_mod_set
|
||||
|
||||
# Core files always included, then selected adapters, then spec modules
|
||||
sx_files = [
|
||||
("eval.sx", "eval"),
|
||||
("forms.sx", "forms (server definition forms)"),
|
||||
@@ -935,6 +968,8 @@ def compile_ref_to_py(
|
||||
for name in ("html", "sx"):
|
||||
if name in adapter_set:
|
||||
sx_files.append(ADAPTER_FILES[name])
|
||||
for name in sorted(spec_mod_set):
|
||||
sx_files.append(SPEC_MODULES[name])
|
||||
|
||||
all_sections = []
|
||||
for filename, label in sx_files:
|
||||
@@ -969,6 +1004,9 @@ def compile_ref_to_py(
|
||||
parts.append(_assemble_primitives_py(prim_modules))
|
||||
parts.append(PRIMITIVES_PY_POST)
|
||||
|
||||
if has_deps:
|
||||
parts.append(PLATFORM_DEPS_PY)
|
||||
|
||||
for label, defines in all_sections:
|
||||
parts.append(f"\n# === Transpiled from {label} ===\n")
|
||||
for name, expr in defines:
|
||||
@@ -979,7 +1017,7 @@ def compile_ref_to_py(
|
||||
parts.append(FIXUPS_PY)
|
||||
if has_continuations:
|
||||
parts.append(CONTINUATIONS_PY)
|
||||
parts.append(public_api_py(has_html, has_sx))
|
||||
parts.append(public_api_py(has_html, has_sx, has_deps))
|
||||
return "\n".join(parts)
|
||||
|
||||
|
||||
@@ -1903,6 +1941,50 @@ assoc = PRIMITIVES["assoc"]
|
||||
concat = PRIMITIVES["concat"]
|
||||
'''
|
||||
|
||||
|
||||
PLATFORM_DEPS_PY = (
|
||||
'\n'
|
||||
'# =========================================================================\n'
|
||||
'# Platform: deps module — component dependency analysis\n'
|
||||
'# =========================================================================\n'
|
||||
'\n'
|
||||
'import re as _re\n'
|
||||
'\n'
|
||||
'def component_deps(c):\n'
|
||||
' """Return cached deps list for a component (may be empty)."""\n'
|
||||
' return list(c.deps) if hasattr(c, "deps") and c.deps else []\n'
|
||||
'\n'
|
||||
'def component_set_deps(c, deps):\n'
|
||||
' """Cache deps on a component."""\n'
|
||||
' c.deps = set(deps) if not isinstance(deps, set) else deps\n'
|
||||
'\n'
|
||||
'def component_css_classes(c):\n'
|
||||
' """Return pre-scanned CSS class list for a component."""\n'
|
||||
' return list(c.css_classes) if hasattr(c, "css_classes") and c.css_classes else []\n'
|
||||
'\n'
|
||||
'def env_components(env):\n'
|
||||
' """Return list of component/macro names in an environment."""\n'
|
||||
' return [k for k, v in env.items()\n'
|
||||
' if isinstance(v, (Component, Macro))]\n'
|
||||
'\n'
|
||||
'def regex_find_all(pattern, source):\n'
|
||||
' """Return list of capture group 1 matches."""\n'
|
||||
' return [m.group(1) for m in _re.finditer(pattern, source)]\n'
|
||||
'\n'
|
||||
'def scan_css_classes(source):\n'
|
||||
' """Extract CSS class strings from SX source."""\n'
|
||||
' classes = set()\n'
|
||||
' for m in _re.finditer(r\':class\\s+"([^"]*)"\', source):\n'
|
||||
' classes.update(m.group(1).split())\n'
|
||||
' for m in _re.finditer(r\':class\\s+\\(str\\s+((?:"[^"]*"\\s*)+)\\)\', source):\n'
|
||||
' for s in _re.findall(r\'"([^"]*)"\', m.group(1)):\n'
|
||||
' classes.update(s.split())\n'
|
||||
' for m in _re.finditer(r\';;\\s*@css\\s+(.+)\', source):\n'
|
||||
' classes.update(m.group(1).split())\n'
|
||||
' return list(classes)\n'
|
||||
)
|
||||
|
||||
|
||||
FIXUPS_PY = '''
|
||||
# =========================================================================
|
||||
# Fixups -- wire up render adapter dispatch
|
||||
@@ -1996,7 +2078,7 @@ aser_special = _aser_special_with_continuations
|
||||
'''
|
||||
|
||||
|
||||
def public_api_py(has_html: bool, has_sx: bool) -> str:
|
||||
def public_api_py(has_html: bool, has_sx: bool, has_deps: bool = False) -> str:
|
||||
lines = [
|
||||
'',
|
||||
'# =========================================================================',
|
||||
@@ -2059,11 +2141,17 @@ def main():
|
||||
default=None,
|
||||
help="Comma-separated extensions (continuations). Default: none.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--spec-modules",
|
||||
default=None,
|
||||
help="Comma-separated spec modules (deps). Default: none.",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
adapters = args.adapters.split(",") if args.adapters else None
|
||||
modules = args.modules.split(",") if args.modules else None
|
||||
extensions = args.extensions.split(",") if args.extensions else None
|
||||
print(compile_ref_to_py(adapters, modules, extensions))
|
||||
spec_modules = args.spec_modules.split(",") if args.spec_modules else None
|
||||
print(compile_ref_to_py(adapters, modules, extensions, spec_modules))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
Reference in New Issue
Block a user