diff --git a/shared/sx/ref/bootstrap_py.py b/shared/sx/ref/bootstrap_py.py index 34924f7..53cdf3d 100644 --- a/shared/sx/ref/bootstrap_py.py +++ b/shared/sx/ref/bootstrap_py.py @@ -1051,10 +1051,13 @@ def compile_ref_to_py( if sm not in SPEC_MODULES: raise ValueError(f"Unknown spec module: {sm!r}. Valid: {', '.join(SPEC_MODULES)}") spec_mod_set.add(sm) + # html adapter needs deps (component analysis) and signals (island rendering) + if "html" in adapter_set: + if "deps" in SPEC_MODULES: + spec_mod_set.add("deps") + if "signals" in SPEC_MODULES: + spec_mod_set.add("signals") has_deps = "deps" in spec_mod_set - # html adapter uses signal runtime for server-side island rendering - if "html" in adapter_set and "signals" in SPEC_MODULES: - spec_mod_set.add("signals") # Core files always included, then selected adapters, then spec modules sx_files = [ diff --git a/shared/sx/ref/sx_ref.py b/shared/sx/ref/sx_ref.py index 3f3e719..ea4645e 100644 --- a/shared/sx/ref/sx_ref.py +++ b/shared/sx/ref/sx_ref.py @@ -1012,6 +1012,54 @@ has_key_p = PRIMITIVES["has-key?"] dissoc = PRIMITIVES["dissoc"] +# ========================================================================= +# Platform: deps module — component dependency analysis +# ========================================================================= + +import re as _re + +def component_deps(c): + """Return cached deps list for a component (may be empty).""" + return list(c.deps) if hasattr(c, "deps") and c.deps else [] + +def component_set_deps(c, deps): + """Cache deps on a component.""" + c.deps = set(deps) if not isinstance(deps, set) else deps + +def component_css_classes(c): + """Return pre-scanned CSS class list for a component.""" + return list(c.css_classes) if hasattr(c, "css_classes") and c.css_classes else [] + +def env_components(env): + """Return list of component/macro names in an environment.""" + return [k for k, v in env.items() + if isinstance(v, (Component, Macro))] + +def regex_find_all(pattern, source): + """Return list of capture group 1 matches.""" + return [m.group(1) for m in _re.finditer(pattern, source)] + +def scan_css_classes(source): + """Extract CSS class strings from SX source.""" + classes = set() + for m in _re.finditer(r':class\s+"([^"]*)"', source): + classes.update(m.group(1).split()) + for m in _re.finditer(r':class\s+\(str\s+((?:"[^"]*"\s*)+)\)', source): + for s in _re.findall(r'"([^"]*)"', m.group(1)): + classes.update(s.split()) + for m in _re.finditer(r';;\s*@css\s+(.+)', source): + classes.update(m.group(1).split()) + return list(classes) + +def component_io_refs(c): + """Return cached IO refs list for a component (may be empty).""" + return list(c.io_refs) if hasattr(c, "io_refs") and c.io_refs else [] + +def component_set_io_refs(c, refs): + """Cache IO refs on a component.""" + c.io_refs = set(refs) if not isinstance(refs, set) else refs + + # === Transpiled from eval === # trampoline @@ -1316,6 +1364,63 @@ aser_fragment = lambda children, env: (lambda parts: ('' if sx_truthy(empty_p(pa aser_call = lambda name, args, env: (lambda parts: _sx_begin(reduce(lambda state, arg: (lambda skip: (assoc(state, 'skip', False, 'i', (get(state, 'i') + 1)) if sx_truthy(skip) else ((lambda val: _sx_begin((_sx_begin(_sx_append(parts, sx_str(':', keyword_name(arg))), _sx_append(parts, serialize(val))) if sx_truthy((not sx_truthy(is_nil(val)))) else NIL), assoc(state, 'skip', True, 'i', (get(state, 'i') + 1))))(aser(nth(args, (get(state, 'i') + 1)), env)) if sx_truthy(((type_of(arg) == 'keyword') if not sx_truthy((type_of(arg) == 'keyword')) else ((get(state, 'i') + 1) < len(args)))) else (lambda val: _sx_begin((_sx_append(parts, serialize(val)) if sx_truthy((not sx_truthy(is_nil(val)))) else NIL), assoc(state, 'i', (get(state, 'i') + 1))))(aser(arg, env)))))(get(state, 'skip')), {'i': 0, 'skip': False}, args), sx_str('(', join(' ', parts), ')')))([name]) +# === Transpiled from deps (component dependency analysis) === + +# scan-refs +scan_refs = lambda node: (lambda refs: _sx_begin(scan_refs_walk(node, refs), refs))([]) + +# scan-refs-walk +scan_refs_walk = lambda node, refs: ((lambda name: ((_sx_append(refs, name) if sx_truthy((not sx_truthy(contains_p(refs, name)))) else NIL) if sx_truthy(starts_with_p(name, '~')) else NIL))(symbol_name(node)) if sx_truthy((type_of(node) == 'symbol')) else (for_each(lambda item: scan_refs_walk(item, refs), node) if sx_truthy((type_of(node) == 'list')) else (for_each(lambda key: scan_refs_walk(dict_get(node, key), refs), keys(node)) if sx_truthy((type_of(node) == 'dict')) else NIL))) + +# transitive-deps-walk +transitive_deps_walk = lambda n, seen, env: (_sx_begin(_sx_append(seen, n), (lambda val: (for_each(lambda ref: transitive_deps_walk(ref, seen, env), scan_refs(component_body(val))) if sx_truthy((type_of(val) == 'component')) else (for_each(lambda ref: transitive_deps_walk(ref, seen, env), scan_refs(macro_body(val))) if sx_truthy((type_of(val) == 'macro')) else NIL)))(env_get(env, n))) if sx_truthy((not sx_truthy(contains_p(seen, n)))) else NIL) + +# transitive-deps +transitive_deps = lambda name, env: (lambda seen: (lambda key: _sx_begin(transitive_deps_walk(key, seen, env), filter(lambda x: (not sx_truthy((x == key))), seen)))((name if sx_truthy(starts_with_p(name, '~')) else sx_str('~', name))))([]) + +# compute-all-deps +compute_all_deps = lambda env: for_each(lambda name: (lambda val: (component_set_deps(val, transitive_deps(name, env)) if sx_truthy((type_of(val) == 'component')) else NIL))(env_get(env, name)), env_components(env)) + +# scan-components-from-source +scan_components_from_source = lambda source: (lambda matches: map(lambda m: sx_str('~', m), matches))(regex_find_all('\\(~([a-zA-Z_][a-zA-Z0-9_\\-]*)', source)) + +# components-needed +components_needed = lambda page_source, env: (lambda direct: (lambda all_needed: _sx_begin(for_each(_sx_fn(lambda name: ( + (_sx_append(all_needed, name) if sx_truthy((not sx_truthy(contains_p(all_needed, name)))) else NIL), + (lambda val: (lambda deps: for_each(lambda dep: (_sx_append(all_needed, dep) if sx_truthy((not sx_truthy(contains_p(all_needed, dep)))) else NIL), deps))((component_deps(val) if sx_truthy(((type_of(val) == 'component') if not sx_truthy((type_of(val) == 'component')) else (not sx_truthy(empty_p(component_deps(val)))))) else transitive_deps(name, env))))(env_get(env, name)) +)[-1]), direct), all_needed))([]))(scan_components_from_source(page_source)) + +# page-component-bundle +page_component_bundle = lambda page_source, env: components_needed(page_source, env) + +# page-css-classes +page_css_classes = lambda page_source, env: (lambda needed: (lambda classes: _sx_begin(for_each(lambda name: (lambda val: (for_each(lambda cls: (_sx_append(classes, cls) if sx_truthy((not sx_truthy(contains_p(classes, cls)))) else NIL), component_css_classes(val)) if sx_truthy((type_of(val) == 'component')) else NIL))(env_get(env, name)), needed), for_each(lambda cls: (_sx_append(classes, cls) if sx_truthy((not sx_truthy(contains_p(classes, cls)))) else NIL), scan_css_classes(page_source)), classes))([]))(components_needed(page_source, env)) + +# scan-io-refs-walk +scan_io_refs_walk = lambda node, io_names, refs: ((lambda name: ((_sx_append(refs, name) if sx_truthy((not sx_truthy(contains_p(refs, name)))) else NIL) if sx_truthy(contains_p(io_names, name)) else NIL))(symbol_name(node)) if sx_truthy((type_of(node) == 'symbol')) else (for_each(lambda item: scan_io_refs_walk(item, io_names, refs), node) if sx_truthy((type_of(node) == 'list')) else (for_each(lambda key: scan_io_refs_walk(dict_get(node, key), io_names, refs), keys(node)) if sx_truthy((type_of(node) == 'dict')) else NIL))) + +# scan-io-refs +scan_io_refs = lambda node, io_names: (lambda refs: _sx_begin(scan_io_refs_walk(node, io_names, refs), refs))([]) + +# transitive-io-refs-walk +transitive_io_refs_walk = lambda n, seen, all_refs, env, io_names: (_sx_begin(_sx_append(seen, n), (lambda val: (_sx_begin(for_each(lambda ref: (_sx_append(all_refs, ref) if sx_truthy((not sx_truthy(contains_p(all_refs, ref)))) else NIL), scan_io_refs(component_body(val), io_names)), for_each(lambda dep: transitive_io_refs_walk(dep, seen, all_refs, env, io_names), scan_refs(component_body(val)))) if sx_truthy((type_of(val) == 'component')) else (_sx_begin(for_each(lambda ref: (_sx_append(all_refs, ref) if sx_truthy((not sx_truthy(contains_p(all_refs, ref)))) else NIL), scan_io_refs(macro_body(val), io_names)), for_each(lambda dep: transitive_io_refs_walk(dep, seen, all_refs, env, io_names), scan_refs(macro_body(val)))) if sx_truthy((type_of(val) == 'macro')) else NIL)))(env_get(env, n))) if sx_truthy((not sx_truthy(contains_p(seen, n)))) else NIL) + +# transitive-io-refs +transitive_io_refs = lambda name, env, io_names: (lambda all_refs: (lambda seen: (lambda key: _sx_begin(transitive_io_refs_walk(key, seen, all_refs, env, io_names), all_refs))((name if sx_truthy(starts_with_p(name, '~')) else sx_str('~', name))))([]))([]) + +# compute-all-io-refs +compute_all_io_refs = lambda env, io_names: for_each(lambda name: (lambda val: (component_set_io_refs(val, transitive_io_refs(name, env, io_names)) if sx_truthy((type_of(val) == 'component')) else NIL))(env_get(env, name)), env_components(env)) + +# component-pure? +component_pure_p = lambda name, env, io_names: empty_p(transitive_io_refs(name, env, io_names)) + +# render-target +render_target = lambda name, env, io_names: (lambda key: (lambda val: ('server' if sx_truthy((not sx_truthy((type_of(val) == 'component')))) else (lambda affinity: ('server' if sx_truthy((affinity == 'server')) else ('client' if sx_truthy((affinity == 'client')) else ('server' if sx_truthy((not sx_truthy(component_pure_p(name, env, io_names)))) else 'client'))))(component_affinity(val))))(env_get(env, key)))((name if sx_truthy(starts_with_p(name, '~')) else sx_str('~', name))) + +# page-render-plan +page_render_plan = lambda page_source, env, io_names: (lambda needed: (lambda comp_targets: (lambda server_list: (lambda client_list: (lambda io_deps: _sx_begin(for_each(lambda name: (lambda target: _sx_begin(_sx_dict_set(comp_targets, name, target), (_sx_begin(_sx_append(server_list, name), for_each(lambda io_ref: (_sx_append(io_deps, io_ref) if sx_truthy((not sx_truthy(contains_p(io_deps, io_ref)))) else NIL), transitive_io_refs(name, env, io_names))) if sx_truthy((target == 'server')) else _sx_append(client_list, name))))(render_target(name, env, io_names)), needed), {'components': comp_targets, 'server': server_list, 'client': client_list, 'io-deps': io_deps}))([]))([]))([]))({}))(components_needed(page_source, env)) + + # === Transpiled from signals (reactive signal runtime) === # signal