""" Parse boundary.sx and primitives.sx to extract declared names. Shared by both bootstrap_py.py and bootstrap_js.py, and used at runtime by the validation module. """ from __future__ import annotations import os from typing import Any # Allow standalone use (from bootstrappers) or in-project imports try: from shared.sx.parser import parse_all from shared.sx.types import Symbol, Keyword, NIL as SX_NIL except ImportError: import sys _HERE = os.path.dirname(os.path.abspath(__file__)) _PROJECT = os.path.abspath(os.path.join(_HERE, "..", "..", "..")) sys.path.insert(0, _PROJECT) from shared.sx.parser import parse_all from shared.sx.types import Symbol, Keyword, NIL as SX_NIL def _ref_dir() -> str: return os.path.dirname(os.path.abspath(__file__)) def _read_file(filename: str) -> str: filepath = os.path.join(_ref_dir(), filename) with open(filepath, encoding="utf-8") as f: return f.read() def _extract_keyword_arg(expr: list, key: str) -> Any: """Extract :key value from a flat keyword-arg list.""" for i, item in enumerate(expr): if isinstance(item, Keyword) and item.name == key and i + 1 < len(expr): return expr[i + 1] return None def parse_primitives_sx() -> frozenset[str]: """Parse primitives.sx and return frozenset of declared pure primitive names.""" by_module = parse_primitives_by_module() all_names: set[str] = set() for names in by_module.values(): all_names.update(names) return frozenset(all_names) def parse_primitives_by_module() -> dict[str, frozenset[str]]: """Parse primitives.sx and return primitives grouped by module. Returns: Dict mapping module name (e.g. "core.arithmetic") to frozenset of primitive names declared under that module. """ source = _read_file("primitives.sx") exprs = parse_all(source) modules: dict[str, set[str]] = {} current_module = "_unscoped" for expr in exprs: if not isinstance(expr, list) or len(expr) < 2: continue if not isinstance(expr[0], Symbol): continue if expr[0].name == "define-module": mod_name = expr[1] if isinstance(mod_name, Keyword): current_module = mod_name.name elif isinstance(mod_name, str): current_module = mod_name elif expr[0].name == "define-primitive": name = expr[1] if isinstance(name, str): modules.setdefault(current_module, set()).add(name) return {mod: frozenset(names) for mod, names in modules.items()} def parse_boundary_sx() -> tuple[frozenset[str], dict[str, frozenset[str]]]: """Parse boundary.sx and return (io_names, {service: helper_names}). Returns: io_names: frozenset of declared I/O primitive names helpers: dict mapping service name to frozenset of helper names """ source = _read_file("boundary.sx") exprs = parse_all(source) io_names: set[str] = set() helpers: dict[str, set[str]] = {} for expr in exprs: if not isinstance(expr, list) or not expr: continue head = expr[0] if not isinstance(head, Symbol): continue if head.name == "define-io-primitive": name = expr[1] if isinstance(name, str): io_names.add(name) elif head.name == "define-page-helper": name = expr[1] service = _extract_keyword_arg(expr, "service") if isinstance(name, str) and isinstance(service, str): helpers.setdefault(service, set()).add(name) frozen_helpers = {svc: frozenset(names) for svc, names in helpers.items()} return frozenset(io_names), frozen_helpers def parse_boundary_types() -> frozenset[str]: """Parse boundary.sx and return the declared boundary type names.""" source = _read_file("boundary.sx") exprs = parse_all(source) for expr in exprs: if (isinstance(expr, list) and len(expr) >= 2 and isinstance(expr[0], Symbol) and expr[0].name == "define-boundary-types"): type_list = expr[1] if isinstance(type_list, list): # (list "number" "string" ...) return frozenset( item for item in type_list if isinstance(item, str) ) return frozenset()