diff --git a/sx/sx/geography/capabilities.sx b/sx/sx/geography/capabilities.sx new file mode 100644 index 00000000..49bafb0a --- /dev/null +++ b/sx/sx/geography/capabilities.sx @@ -0,0 +1,52 @@ +(defcomp ~geography/capabilities-content () + (~docs/page :title "Capabilities" + (p :class "text-stone-500 text-sm italic mb-8" + "Abstract evaluation contexts — what an expression is allowed to do, without prescribing where it runs.") + + (~docs/section :title "The model" :id "model" + (p "SX expressions evaluate in contexts that provide named capabilities. A capability is a permission to perform a class of side effect: " (code "io") " for network/filesystem, " (code "mutation") " for mutable state, " (code "dom") " for browser DOM, " (code "render") " for rendering operations.") + (p "The key insight: capabilities are abstract. " (code "io") " doesn't mean 'server' — it means 'this context can perform input/output.' A server, an edge worker, and a browser tab can all provide " (code "io") ". The language doesn't care where the code runs, only what it's allowed to do.") + (~docs/code :src (str "(with-capabilities (list \"pure\" \"mutation\")\n (fn ()\n (has-capability? \"io\") ;; false\n (has-capability? \"pure\") ;; true\n (require-capability! \"io\") ;; ERROR: Capability 'io' not available\n ))"))) + + (~docs/section :title "Capability primitives" :id "primitives" + (table :class "min-w-full text-sm mb-6" + (thead + (tr + (th :class "text-left pr-4 pb-2 font-semibold text-stone-700" "Primitive") + (th :class "text-left pb-2 font-semibold text-stone-700" "Purpose"))) + (tbody :class "text-stone-600" + (tr + (td :class "pr-4 py-1 font-mono text-xs" "with-capabilities") + (td :class "py-1" "Restrict capabilities for a body. Takes a list of capability names and a thunk.")) + (tr + (td :class "pr-4 py-1 font-mono text-xs" "has-capability?") + (td :class "py-1" "Check if a capability is available. Returns true in unrestricted contexts.")) + (tr + (td :class "pr-4 py-1 font-mono text-xs" "require-capability!") + (td :class "py-1" "Assert a capability. Error with a clear message if not available.")) + (tr + (td :class "pr-4 py-1 font-mono text-xs" "current-capabilities") + (td :class "py-1" "Return the current capability set, or nil if unrestricted."))))) + + (~docs/section :title "Effect annotations" :id "effects" + (p "Every function can declare its effect requirements:") + (~docs/code :src (str "(define fetch-user :effects (io)\n (fn (id) (http-get (str \"/users/\" id))))\n\n(define format-name :effects ()\n (fn (user) (str (get user \"first\") \" \" (get user \"last\"))))")) + (p (code "format-name") " is pure — it can run anywhere. " (code "fetch-user") " requires " (code "io") " — it can only run in a context that provides that capability. The " (em "where") " is decided by the host, not the language.")) + + (~docs/section :title "Standard capabilities" :id "standard" + (table :class "min-w-full text-sm mb-6" + (thead + (tr + (th :class "text-left pr-4 pb-2 font-semibold text-stone-700" "Capability") + (th :class "text-left pb-2 font-semibold text-stone-700" "What it permits"))) + (tbody :class "text-stone-600" + (tr (td :class "pr-4 py-1 font-mono text-xs" "pure") (td :class "py-1" "No side effects. Deterministic. Cacheable. Runnable anywhere.")) + (tr (td :class "pr-4 py-1 font-mono text-xs" "mutation") (td :class "py-1" "Mutable state: set!, append!, dict-set!, signal mutation.")) + (tr (td :class "pr-4 py-1 font-mono text-xs" "io") (td :class "py-1" "External I/O: network, filesystem, timers, promises.")) + (tr (td :class "pr-4 py-1 font-mono text-xs" "dom") (td :class "py-1" "Browser DOM operations: create elements, set attributes, event listeners.")) + (tr (td :class "pr-4 py-1 font-mono text-xs" "render") (td :class "py-1" "Rendering operations: component expansion, HTML generation, DOM patching."))))) + + (~docs/section :title "Why not phases?" :id "why-not-phases" + (p "Many frameworks hard-code evaluation phases: 'server' vs 'client', 'build time' vs 'runtime'. This bakes in assumptions about deployment topology.") + (p "SX might run on a server, in a browser, at an edge node, in a WebAssembly sandbox, on another peer's machine, or in a context that doesn't exist yet. Capabilities are the right abstraction because they describe " (em "what") " an expression needs, not " (em "where") " it runs.") + (p "A 'server' is just a context that provides " (code "(pure mutation io)") ". A 'browser' provides " (code "(pure mutation io dom render)") ". An edge worker might provide " (code "(pure io)") " but not " (code "mutation") ". The evaluator doesn't need to know — it just checks capabilities.")))) diff --git a/sx/sx/geography/eval-rules.sx b/sx/sx/geography/eval-rules.sx new file mode 100644 index 00000000..ae9d0916 --- /dev/null +++ b/sx/sx/geography/eval-rules.sx @@ -0,0 +1,36 @@ +(defcomp ~geography/eval-rules-content () + (~docs/page :title "Evaluation Rules" + (p :class "text-stone-500 text-sm italic mb-8" + "Machine-readable SX semantics — a non-circular reference for how the language evaluates expressions.") + + (~docs/section :title "Why rules as data" :id "why" + (p "The SX spec is self-hosting: SX defines SX. This is elegant for bootstrapping but circular for understanding — you need to know SX to read the spec that defines SX.") + (p "The evaluation rules break this circularity. Each rule is a data structure describing one dispatch case in the CEK evaluator: name, pattern, semantics, effects, and examples. Tools and LLMs can query these rules without reading the evaluator source.") + (~docs/code :src (str ";; Ask the MCP server to explain a form\nsx_explain name=\"let\"\n\nlet\n Category: special-form\n Pattern: (let ((name val) ...) body ...)\n Effects: (none)\n\nCreate new scope. Evaluate each val sequentially,\nbind name. Last body form is in tail position.\n\nExamples:\n (let ((x 1) (y 2)) (+ x y)) → 3"))) + + (~docs/section :title "Rule categories" :id "categories" + (table :class "min-w-full text-sm mb-6" + (thead + (tr + (th :class "text-left pr-4 pb-2 font-semibold text-stone-700" "Category") + (th :class "text-left pr-4 pb-2 font-semibold text-stone-700" "Count") + (th :class "text-left pb-2 font-semibold text-stone-700" "What it covers"))) + (tbody :class "text-stone-600" + (tr (td :class "pr-4 py-1 font-mono text-xs" "literal") (td :class "pr-4 py-1" "6") (td :class "py-1" "Numbers, strings, booleans, nil, keywords, dicts")) + (tr (td :class "pr-4 py-1 font-mono text-xs" "lookup") (td :class "pr-4 py-1" "1") (td :class "py-1" "Symbol resolution: env → primitives → error")) + (tr (td :class "pr-4 py-1 font-mono text-xs" "special-form") (td :class "pr-4 py-1" "13") (td :class "py-1" "if, when, cond, case, let, letrec, lambda, define, set!, begin, quote, quasiquote, ->")) + (tr (td :class "pr-4 py-1 font-mono text-xs" "definition") (td :class "pr-4 py-1" "3") (td :class "py-1" "defcomp, defisland, defmacro")) + (tr (td :class "pr-4 py-1 font-mono text-xs" "higher-order") (td :class "pr-4 py-1" "6") (td :class "py-1" "map, filter, reduce, some, every?, for-each")) + (tr (td :class "pr-4 py-1 font-mono text-xs" "scope") (td :class "pr-4 py-1" "5") (td :class "py-1" "scope, provide, context, emit!, emitted")) + (tr (td :class "pr-4 py-1 font-mono text-xs" "continuation") (td :class "pr-4 py-1" "2") (td :class "py-1" "reset, shift (delimited continuations)")) + (tr (td :class "pr-4 py-1 font-mono text-xs" "reactive") (td :class "pr-4 py-1" "1") (td :class "py-1" "deref (signal reading)")) + (tr (td :class "pr-4 py-1 font-mono text-xs" "call") (td :class "pr-4 py-1" "1") (td :class "py-1" "General function call dispatch"))))) + + (~docs/section :title "The sx_explain tool" :id "tool" + (p "Query rules by name or category:") + (~docs/code :src (str ";; Explain a specific form\nsx_explain name=\"if\"\n\n;; List all forms in a category\nsx_explain name=\"higher-order\"\n\n;; The rules live in spec/eval-rules.sx as SX data")) + (p "The rules file is loaded by the MCP server at startup. It's plain SX — you can extend it with new rules for custom forms.")) + + (~docs/section :title "Rule structure" :id "structure" + (~docs/code :src (str ";; Each rule is a dict with:\n{:name \"let\" ;; form name\n :category \"special-form\" ;; dispatch category\n :pattern \"(let ...)\" ;; syntax pattern\n :rule \"description\" ;; evaluation semantics\n :effects () ;; required capabilities\n :examples \"...\"} ;; input → output")) + (p "The " (code ":effects") " field connects rules to the capability system. A rule with " (code ":effects \"mutation\"") " (like " (code "set!") ") can only be evaluated in contexts that provide the " (code "mutation") " capability.")))) diff --git a/sx/sx/geography/modules.sx b/sx/sx/geography/modules.sx new file mode 100644 index 00000000..7eafe141 --- /dev/null +++ b/sx/sx/geography/modules.sx @@ -0,0 +1,30 @@ +(defcomp ~geography/modules-content () + (~docs/page :title "Modules" + (p :class "text-stone-500 text-sm italic mb-8" + "Declaring what a file needs — for documentation, static analysis, and tooling.") + + (~docs/section :title "The use form" :id "use-form" + (p "A " (code "(use module-name)") " declaration at the top of an " (code ".sx") " file says: this file depends on definitions from that module.") + (~docs/code :src (str "(use signals) ;; needs signal, deref, reset!, swap!, computed\n(use web-signals) ;; needs resource, emit-event, on-event\n\n(defisland ~demo ()\n (let ((data (resource (fn () (promise-delayed 1000 42)))))\n (div (deref data))))")) + (p (code "use") " is purely declarative — it doesn't load anything. The glob loader still loads all " (code ".sx") " files. The declaration documents intent and enables static checking.")) + + (~docs/section :title "What use enables" :id "what-it-enables" + (h4 :class "font-semibold text-stone-700 mt-6 mb-2" "Dependency visibility") + (p "The " (code "sx_deps") " tool reports both referenced symbols and declared modules. If a file references " (code "resource") " but doesn't " (code "(use web-signals)") ", the tool flags the gap.") + (h4 :class "font-semibold text-stone-700 mt-6 mb-2" "Build pipeline validation") + (p "The " (code "sx_build_manifest") " tool shows which modules are in the current build. Cross-referencing with " (code "use") " declarations catches missing build modules — exactly the bug that caused the " (code "resource") " island hydration failure.") + (h4 :class "font-semibold text-stone-700 mt-6 mb-2" "Documentation") + (p "For humans and LLMs reading a file: " (code "use") " declarations immediately show what the file depends on, without grep.")) + + (~docs/section :title "Semantics" :id "semantics" + (p (code "(use name)") " is a no-op at evaluation time. It does not:") + (ul :class "space-y-1 text-stone-600 ml-4" + (li "Load any files") + (li "Modify the environment") + (li "Affect evaluation order") + (li "Create any runtime cost")) + (p "It " (em "does") ":") + (ul :class "space-y-1 text-stone-600 ml-4" + (li "Appear in the parsed tree for static analysis") + (li "Get reported by " (code "sx_deps")) + (li "Enable future tooling (unused-import warnings, auto-import suggestions)"))))) diff --git a/sx/sx/nav-geography.sx b/sx/sx/nav-geography.sx index 3d22c1c2..36ac3f8b 100644 --- a/sx/sx/nav-geography.sx +++ b/sx/sx/nav-geography.sx @@ -14,3 +14,13 @@ (define marshes-examples-nav-items (list {:href "/sx/(geography.(marshes.hypermedia-feeds))" :label "Hypermedia Feeds State"} {:href "/sx/(geography.(marshes.server-signals))" :label "Server Writes to Signals"} {:href "/sx/(geography.(marshes.on-settle))" :label "sx-on-settle"} {:href "/sx/(geography.(marshes.signal-triggers))" :label "Signal-Bound Triggers"} {:href "/sx/(geography.(marshes.view-transform))" :label "Reactive View Transform"})) + +(define semantics-nav-items + (list + (dict :label "Capabilities" :href "/sx/(geography.(capabilities))" + :summary "Abstract evaluation contexts — what an expression can do, not where it runs.") + (dict :label "Modules" :href "/sx/(geography.(modules))" + :summary "The (use) form — declaring dependencies for documentation and static analysis.") + (dict :label "Eval Rules" :href "/sx/(geography.(eval-rules))" + :summary "Machine-readable SX semantics — 35 rules as queryable data."))) + diff --git a/sx/sx/nav-tree.sx b/sx/sx/nav-tree.sx index e8eb56cc..7ffdaa88 100644 --- a/sx/sx/nav-tree.sx +++ b/sx/sx/nav-tree.sx @@ -4,7 +4,7 @@ (defcomp ~nav-data/section-nav (&key items current) (<> (map (fn (item) (~shared:layout/nav-link :href (get item "href") :label (get item "label") :is-selected (when (= (get item "label") current) "true") :select-colours "aria-selected:bg-violet-200 aria-selected:text-violet-900")) items))) -(define sx-nav-tree {:href "/sx/" :children (list {:href "/sx/(geography)" :children (list {:href "/sx/(geography.(reactive))" :children reactive-islands-nav-items :label "Reactive Islands"} {:href "/sx/(geography.(hypermedia))" :children (list {:href "/sx/(geography.(hypermedia.(reference)))" :children reference-nav-items :label "Reference"} {:href "/sx/(geography.(hypermedia.(example)))" :children examples-nav-items :label "Examples"}) :label "Hypermedia Lakes"} {:href "/sx/(geography.(scopes))" :summary "The unified primitive beneath provide, collect!, spreads, and islands. Named scope with downward value, upward accumulation, and a dedup flag." :label "Scopes"} {:href "/sx/(geography.(provide))" :summary "Sugar for scope-with-value. Render-time dynamic scope — the substrate beneath spreads, CSSX, and script collection." :label "Provide / Emit!"} {:href "/sx/(geography.(spreads))" :summary "Child-to-parent communication across render boundaries — spread, collect!, reactive-spread, built on scopes." :label "Spreads"} {:href "/sx/(geography.(marshes))" :children marshes-examples-nav-items :summary "Where reactivity and hypermedia interpenetrate — server writes to signals, reactive transforms reshape server content, client state modifies how hypermedia is interpreted." :label "Marshes"} {:href "/sx/(geography.(isomorphism))" :children isomorphism-nav-items :label "Isomorphism"} {:href "/sx/(geography.(cek))" :children cek-nav-items :label "CEK Machine"}) :label "Geography"} {:href "/sx/(language)" :children (list {:href "/sx/(language.(doc))" :children docs-nav-items :label "Docs"} {:href "/sx/(language.(spec))" :children specs-nav-items :label "Specs"} {:href "/sx/(language.(spec.(explore.evaluator)))" :label "Spec Explorer"} {:href "/sx/(language.(bootstrapper))" :children bootstrappers-nav-items :label "Bootstrappers"} {:href "/sx/(language.(test))" :children testing-nav-items :label "Testing"}) :label "Language"} {:href "/sx/(applications)" :children (list {:href "/sx/(applications.(sx-urls))" :label "SX URLs"} {:href "/sx/(applications.(cssx))" :children cssx-nav-items :label "CSSX"} {:href "/sx/(applications.(protocol))" :children protocols-nav-items :label "Protocols"} {:href "/sx/(applications.(sx-pub))" :label "sx-pub"} {:href "/sx/(applications.(reactive-runtime))" :children reactive-runtime-nav-items :label "Reactive Runtime"}) :label "Applications"} {:href "/sx/(tools)" :children tools-nav-items :label "Tools"} {:href "/sx/(etc)" :children (list {:href "/sx/(etc.(essay))" :children essays-nav-items :label "Essays"} {:href "/sx/(etc.(philosophy))" :children philosophy-nav-items :label "Philosophy"} {:href "/sx/(etc.(plan))" :children plans-nav-items :label "Plans"}) :label "Etc"}) :label "sx"}) +(define sx-nav-tree {:href "/sx/" :children (list {:href "/sx/(geography)" :children (list {:href "/sx/(geography.(reactive))" :children reactive-islands-nav-items :label "Reactive Islands"} {:href "/sx/(geography.(hypermedia))" :children (list {:href "/sx/(geography.(hypermedia.(reference)))" :children reference-nav-items :label "Reference"} {:href "/sx/(geography.(hypermedia.(example)))" :children examples-nav-items :label "Examples"}) :label "Hypermedia Lakes"} {:href "/sx/(geography.(scopes))" :summary "The unified primitive beneath provide, collect!, spreads, and islands. Named scope with downward value, upward accumulation, and a dedup flag." :label "Scopes"} {:href "/sx/(geography.(provide))" :summary "Sugar for scope-with-value. Render-time dynamic scope — the substrate beneath spreads, CSSX, and script collection." :label "Provide / Emit!"} {:href "/sx/(geography.(spreads))" :summary "Child-to-parent communication across render boundaries — spread, collect!, reactive-spread, built on scopes." :label "Spreads"} {:href "/sx/(geography.(marshes))" :children marshes-examples-nav-items :summary "Where reactivity and hypermedia interpenetrate — server writes to signals, reactive transforms reshape server content, client state modifies how hypermedia is interpreted." :label "Marshes"} {:href "/sx/(geography.(isomorphism))" :children isomorphism-nav-items :label "Isomorphism"} {:href "/sx/(geography.(cek))" :children cek-nav-items :label "CEK Machine"} {:href "/sx/(geography.(capabilities))" :children semantics-nav-items :label "Semantics"}) :label "Geography"} {:href "/sx/(language)" :children (list {:href "/sx/(language.(doc))" :children docs-nav-items :label "Docs"} {:href "/sx/(language.(spec))" :children specs-nav-items :label "Specs"} {:href "/sx/(language.(spec.(explore.evaluator)))" :label "Spec Explorer"} {:href "/sx/(language.(bootstrapper))" :children bootstrappers-nav-items :label "Bootstrappers"} {:href "/sx/(language.(test))" :children testing-nav-items :label "Testing"}) :label "Language"} {:href "/sx/(applications)" :children (list {:href "/sx/(applications.(sx-urls))" :label "SX URLs"} {:href "/sx/(applications.(cssx))" :children cssx-nav-items :label "CSSX"} {:href "/sx/(applications.(protocol))" :children protocols-nav-items :label "Protocols"} {:href "/sx/(applications.(sx-pub))" :label "sx-pub"} {:href "/sx/(applications.(reactive-runtime))" :children reactive-runtime-nav-items :label "Reactive Runtime"}) :label "Applications"} {:href "/sx/(tools)" :children tools-nav-items :label "Tools"} {:href "/sx/(etc)" :children (list {:href "/sx/(etc.(essay))" :children essays-nav-items :label "Essays"} {:href "/sx/(etc.(philosophy))" :children philosophy-nav-items :label "Philosophy"} {:href "/sx/(etc.(plan))" :children plans-nav-items :label "Plans"}) :label "Etc"}) :label "sx"}) (define has-descendant-href? (fn (node path) (let ((children (get node "children"))) (when children (some (fn (child) (or (= (get child "href") path) (has-descendant-href? child path))) children))))) diff --git a/sx/sx/page-functions.sx b/sx/sx/page-functions.sx index ace86a75..0e9fb43c 100644 --- a/sx/sx/page-functions.sx +++ b/sx/sx/page-functions.sx @@ -69,3 +69,9 @@ (define philosophy (fn (slug) (if (nil? slug) (quote (~essays/philosophy-index/content)) (case slug "sx-manifesto" (quote (~essay-sx-manifesto)) "godel-escher-bach" (quote (~essays/godel-escher-bach/essay-godel-escher-bach)) "wittgenstein" (quote (~essays/sx-and-wittgenstein/essay-sx-and-wittgenstein)) "dennett" (quote (~essays/sx-and-dennett/essay-sx-and-dennett)) "existentialism" (quote (~essays/s-existentialism/essay-s-existentialism)) "platonic-sx" (quote (~essays/platonic-sx/essay-platonic-sx)) :else (quote (~essays/philosophy-index/content)))))) (define plan (make-page-fn "~plans/index/plans-index-content" "~plans/" "/plan-" "-content")) + +(define capabilities (fn (&key title &rest args) (quasiquote (~geography/capabilities-content)))) + +(define modules (fn (&key title &rest args) (quasiquote (~geography/modules-content)))) + +(define eval-rules (fn (&key title &rest args) (quasiquote (~geography/eval-rules-content))))