Rebrand sexp → sx across web platform (173 files)
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 11m37s
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 11m37s
Rename all sexp directories, files, identifiers, and references to sx. artdag/ excluded (separate media processing DSL). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
176
shared/sx/types.py
Normal file
176
shared/sx/types.py
Normal file
@@ -0,0 +1,176 @@
|
||||
"""
|
||||
Core types for the s-expression language.
|
||||
|
||||
Symbol — unquoted identifier (e.g. div, ~card, map)
|
||||
Keyword — colon-prefixed key (e.g. :class, :id)
|
||||
Lambda — callable closure created by (lambda ...) or (fn ...)
|
||||
Nil — singleton null value
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Any
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Nil
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class _Nil:
|
||||
"""Singleton nil value — falsy, serialises as 'nil'."""
|
||||
_instance: _Nil | None = None
|
||||
|
||||
def __new__(cls):
|
||||
if cls._instance is None:
|
||||
cls._instance = super().__new__(cls)
|
||||
return cls._instance
|
||||
|
||||
def __bool__(self):
|
||||
return False
|
||||
|
||||
def __repr__(self):
|
||||
return "nil"
|
||||
|
||||
def __eq__(self, other):
|
||||
return other is None or isinstance(other, _Nil)
|
||||
|
||||
def __hash__(self):
|
||||
return hash(None)
|
||||
|
||||
|
||||
NIL = _Nil()
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Symbol
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Symbol:
|
||||
"""An unquoted symbol/identifier."""
|
||||
name: str
|
||||
|
||||
def __repr__(self):
|
||||
return f"Symbol({self.name!r})"
|
||||
|
||||
def __eq__(self, other):
|
||||
if isinstance(other, Symbol):
|
||||
return self.name == other.name
|
||||
if isinstance(other, str):
|
||||
return self.name == other
|
||||
return False
|
||||
|
||||
def __hash__(self):
|
||||
return hash(self.name)
|
||||
|
||||
@property
|
||||
def is_component(self) -> bool:
|
||||
"""True if this symbol names a component (~prefix)."""
|
||||
return self.name.startswith("~")
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Keyword
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Keyword:
|
||||
"""A keyword starting with colon (e.g. :class, :id)."""
|
||||
name: str
|
||||
|
||||
def __repr__(self):
|
||||
return f"Keyword({self.name!r})"
|
||||
|
||||
def __eq__(self, other):
|
||||
if isinstance(other, Keyword):
|
||||
return self.name == other.name
|
||||
return False
|
||||
|
||||
def __hash__(self):
|
||||
return hash((":", self.name))
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Lambda
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
@dataclass
|
||||
class Lambda:
|
||||
"""A callable closure.
|
||||
|
||||
Created by ``(lambda (x) body)`` or ``(fn (x) body)``.
|
||||
Captures the defining environment so free variables resolve correctly.
|
||||
"""
|
||||
params: list[str]
|
||||
body: Any
|
||||
closure: dict[str, Any] = field(default_factory=dict)
|
||||
name: str | None = None # optional, set by (define name (fn ...))
|
||||
|
||||
def __repr__(self):
|
||||
tag = self.name or "lambda"
|
||||
return f"<{tag}({', '.join(self.params)})>"
|
||||
|
||||
def __call__(self, *args: Any, evaluator: Any = None, caller_env: dict | None = None) -> Any:
|
||||
"""Invoke the lambda. Requires *evaluator* — the evaluate() function."""
|
||||
if evaluator is None:
|
||||
raise RuntimeError("Lambda requires evaluator to be called")
|
||||
if len(args) != len(self.params):
|
||||
raise RuntimeError(
|
||||
f"{self!r} expects {len(self.params)} args, got {len(args)}"
|
||||
)
|
||||
local = dict(self.closure)
|
||||
if caller_env:
|
||||
local.update(caller_env)
|
||||
for p, v in zip(self.params, args):
|
||||
local[p] = v
|
||||
return evaluator(self.body, local)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Component
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
@dataclass
|
||||
class Component:
|
||||
"""A reusable UI component defined via ``(defcomp ~name (&key ...) body)``.
|
||||
|
||||
Components are like lambdas but accept keyword arguments and support
|
||||
a ``children`` rest parameter.
|
||||
"""
|
||||
name: str
|
||||
params: list[str] # keyword parameter names (without &key prefix)
|
||||
has_children: bool # True if &rest children declared
|
||||
body: Any # unevaluated s-expression body
|
||||
closure: dict[str, Any] = field(default_factory=dict)
|
||||
|
||||
def __repr__(self):
|
||||
return f"<Component ~{self.name}({', '.join(self.params)})>"
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# RelationDef
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class RelationDef:
|
||||
"""A declared relation between two entity types.
|
||||
|
||||
Created by ``(defrelation :name ...)`` s-expressions.
|
||||
"""
|
||||
name: str # "page->market"
|
||||
from_type: str # "page"
|
||||
to_type: str # "market"
|
||||
cardinality: str # "one-to-one" | "one-to-many" | "many-to-many"
|
||||
inverse: str | None # "market->page"
|
||||
nav: str # "submenu" | "tab" | "badge" | "inline" | "hidden"
|
||||
nav_icon: str | None # "fa fa-shopping-bag"
|
||||
nav_label: str | None # "markets"
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Type alias
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
# An s-expression value after evaluation
|
||||
SExp = int | float | str | bool | Symbol | Keyword | Lambda | Component | RelationDef | list | dict | _Nil | None
|
||||
Reference in New Issue
Block a user