From 6aa2f3f6bd5b7b04db51cf02c248879d76ff2bdd Mon Sep 17 00:00:00 2001 From: giles Date: Fri, 6 Mar 2026 01:59:47 +0000 Subject: [PATCH] Add Special Forms docs page at /docs/special-forms Parses special-forms.sx spec into categorized form cards with syntax, description, tail-position info, and highlighted examples. Follows the same pattern as the Primitives page: Python helper returns structured data, .sx components render it. Co-Authored-By: Claude Opus 4.6 --- shared/sx/ref/boundary.sx | 5 +++ sx/sx/docs-content.sx | 9 +++++ sx/sx/docs.sx | 37 +++++++++++++++++ sx/sx/nav-data.sx | 1 + sx/sxc/pages/docs.sx | 2 + sx/sxc/pages/helpers.py | 84 +++++++++++++++++++++++++++++++++++++++ 6 files changed, 138 insertions(+) diff --git a/shared/sx/ref/boundary.sx b/shared/sx/ref/boundary.sx index 308ac1f..e86eead 100644 --- a/shared/sx/ref/boundary.sx +++ b/shared/sx/ref/boundary.sx @@ -293,6 +293,11 @@ :returns "dict" :service "sx") +(define-page-helper "special-forms-data" + :params () + :returns "dict" + :service "sx") + (define-page-helper "reference-data" :params (slug) :returns "dict" diff --git a/sx/sx/docs-content.sx b/sx/sx/docs-content.sx index d13a518..f3038e0 100644 --- a/sx/sx/docs-content.sx +++ b/sx/sx/docs-content.sx @@ -76,6 +76,15 @@ "sx provides ~80 built-in pure functions. They work identically on server (Python) and client (JavaScript).") (div :class "space-y-6" prims)))) +(defcomp ~docs-special-forms-content (&key forms) + (~doc-page :title "Special Forms" + (~doc-section :title "Syntactic constructs" :id "special-forms" + (p :class "text-stone-600" + "Special forms are syntactic constructs whose arguments are NOT evaluated before dispatch. Each form has its own evaluation rules — unlike primitives, which receive pre-evaluated values. Together with primitives, special forms define the complete language surface.") + (p :class "text-stone-600" + "Forms marked with a tail position enable " (a :href "/essays/tco" :class "text-violet-600 hover:underline" "tail-call optimization") " — recursive calls in tail position use constant stack space.") + (div :class "space-y-10" forms)))) + (defcomp ~docs-css-content () (~doc-page :title "On-Demand CSS" (~doc-section :title "How it works" :id "how" diff --git a/sx/sx/docs.sx b/sx/sx/docs.sx index f52ec72..7d0ac83 100644 --- a/sx/sx/docs.sx +++ b/sx/sx/docs.sx @@ -123,3 +123,40 @@ :category cat :primitives (get primitives cat))) (keys primitives)))) + +;; Build all special form category sections from a {category: [form, ...]} dict. +(defcomp ~doc-special-forms-tables (&key forms) + (<> (map (fn (cat) + (~doc-special-forms-category + :category cat + :forms (get forms cat))) + (keys forms)))) + +(defcomp ~doc-special-forms-category (&key category forms) + (div :class "space-y-4" + (h3 :class "text-xl font-semibold text-stone-800 border-b border-stone-200 pb-2" category) + (div :class "space-y-4" + (map (fn (f) + (~doc-special-form-card + :name (get f "name") + :syntax (get f "syntax") + :doc (get f "doc") + :tail-position (get f "tail-position") + :example (get f "example"))) + forms)))) + +(defcomp ~doc-special-form-card (&key name syntax doc tail-position example) + (div :class "border border-stone-200 rounded-lg p-4 space-y-3" + (div :class "flex items-baseline gap-3" + (code :class "text-lg font-bold text-violet-700" name) + (when (not (= tail-position "none")) + (span :class "text-xs px-2 py-0.5 rounded-full bg-green-100 text-green-700" "TCO"))) + (when (not (= syntax "")) + (pre :class "bg-stone-50 rounded px-3 py-2 text-sm font-mono text-stone-700 overflow-x-auto" + syntax)) + (p :class "text-stone-600 text-sm whitespace-pre-line" doc) + (when (not (= tail-position "")) + (p :class "text-xs text-stone-500" + (span :class "font-semibold" "Tail position: ") tail-position)) + (when (not (= example "")) + (~doc-code :code (highlight example "lisp"))))) diff --git a/sx/sx/nav-data.sx b/sx/sx/nav-data.sx index 070ea67..6120186 100644 --- a/sx/sx/nav-data.sx +++ b/sx/sx/nav-data.sx @@ -8,6 +8,7 @@ (dict :label "Components" :href "/docs/components") (dict :label "Evaluator" :href "/docs/evaluator") (dict :label "Primitives" :href "/docs/primitives") + (dict :label "Special Forms" :href "/docs/special-forms") (dict :label "CSS" :href "/docs/css") (dict :label "Server Rendering" :href "/docs/server-rendering"))) diff --git a/sx/sxc/pages/docs.sx b/sx/sxc/pages/docs.sx index 8027429..1c7d7f9 100644 --- a/sx/sxc/pages/docs.sx +++ b/sx/sxc/pages/docs.sx @@ -44,6 +44,8 @@ "evaluator" (~docs-evaluator-content) "primitives" (~docs-primitives-content :prims (~doc-primitives-tables :primitives (primitives-data))) + "special-forms" (~docs-special-forms-content + :forms (~doc-special-forms-tables :forms (special-forms-data))) "css" (~docs-css-content) "server-rendering" (~docs-server-rendering-content) :else (~docs-introduction-content))) diff --git a/sx/sxc/pages/helpers.py b/sx/sxc/pages/helpers.py index 997abe6..0aaf364 100644 --- a/sx/sxc/pages/helpers.py +++ b/sx/sxc/pages/helpers.py @@ -14,6 +14,7 @@ def _register_sx_helpers() -> None: register_page_helpers("sx", { "highlight": _highlight, "primitives-data": _primitives_data, + "special-forms-data": _special_forms_data, "reference-data": _reference_data, "attr-detail-data": _attr_detail_data, "header-detail-data": _header_detail_data, @@ -29,6 +30,89 @@ def _primitives_data() -> dict: return PRIMITIVES +def _special_forms_data() -> dict: + """Parse special-forms.sx and return categorized form data. + + Returns a dict of category → list of form dicts, each with: + name, syntax, doc, tail_position, example + """ + import os + from shared.sx.parser import parse_all, serialize + from shared.sx.types import Symbol, Keyword + + spec_path = os.path.join( + os.path.dirname(os.path.abspath(__file__)), + "..", "..", "..", "shared", "sx", "ref", "special-forms.sx", + ) + with open(spec_path) as f: + exprs = parse_all(f.read()) + + # Categories inferred from comment sections in the file. + # We assign forms to categories based on their order in the spec. + categories: dict[str, list[dict]] = {} + current_category = "Other" + + # Map form names to categories + category_map = { + "if": "Control Flow", "when": "Control Flow", "cond": "Control Flow", + "case": "Control Flow", "and": "Control Flow", "or": "Control Flow", + "let": "Binding", "let*": "Binding", "letrec": "Binding", + "define": "Binding", "set!": "Binding", + "lambda": "Functions & Components", "fn": "Functions & Components", + "defcomp": "Functions & Components", "defmacro": "Functions & Components", + "begin": "Sequencing & Threading", "do": "Sequencing & Threading", + "->": "Sequencing & Threading", + "quote": "Quoting", "quasiquote": "Quoting", + "reset": "Continuations", "shift": "Continuations", + "dynamic-wind": "Guards", + "map": "Higher-Order Forms", "map-indexed": "Higher-Order Forms", + "filter": "Higher-Order Forms", "reduce": "Higher-Order Forms", + "some": "Higher-Order Forms", "every?": "Higher-Order Forms", + "for-each": "Higher-Order Forms", + "defstyle": "Domain Definitions", "defkeyframes": "Domain Definitions", + "defhandler": "Domain Definitions", "defpage": "Domain Definitions", + "defquery": "Domain Definitions", "defaction": "Domain Definitions", + } + + for expr in exprs: + if not isinstance(expr, list) or len(expr) < 2: + continue + head = expr[0] + if not isinstance(head, Symbol) or head.name != "define-special-form": + continue + + name = expr[1] + # Extract keyword args + kwargs: dict[str, str] = {} + i = 2 + while i < len(expr) - 1: + if isinstance(expr[i], Keyword): + key = expr[i].name + val = expr[i + 1] + if isinstance(val, list): + # For :syntax, avoid quote sugar (quasiquote → `x) + items = [serialize(item) for item in val] + kwargs[key] = "(" + " ".join(items) + ")" + else: + kwargs[key] = str(val) + i += 2 + else: + i += 1 + + category = category_map.get(name, "Other") + if category not in categories: + categories[category] = [] + categories[category].append({ + "name": name, + "syntax": kwargs.get("syntax", ""), + "doc": kwargs.get("doc", ""), + "tail-position": kwargs.get("tail-position", ""), + "example": kwargs.get("example", ""), + }) + + return categories + + def _reference_data(slug: str) -> dict: """Return reference table data for a given slug.