From 4ce4762237b2fbaaed5e4ff79f5599baf2545a01 Mon Sep 17 00:00:00 2001 From: giles Date: Mon, 16 Mar 2026 08:55:57 +0000 Subject: [PATCH] Add spec/stdlib.sx: 46 primitives become library functions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The irreducible primitive set drops from 79 to 33. Everything that can be expressed in SX is now a library function in stdlib.sx, loaded after evaluator.sx and before render.sx. Moved to stdlib.sx (pure SX, no host dependency): - Logic: not - Comparison: != <= >= eq? eqv? equal? - Predicates: nil? boolean? number? string? list? dict? continuation? empty? odd? even? zero? contains? - Arithmetic: inc dec abs ceil round min max clamp - Collections: first last rest nth cons append reverse flatten range chunk-every zip-pairs vals has-key? merge assoc dissoc into - Strings: upcase downcase string-length substring string-contains? starts-with? ends-with? split join replace - Text: pluralize escape assert parse-datetime Remaining irreducible primitives (33): + - * / mod floor pow sqrt = < > type-of symbol-name keyword-name str slice index-of upper lower trim char-from-code list dict concat get len keys dict-set! append! random-int json-encode format-date parse-int format-decimal strip-tags sx-parse error apply All hosts: JS 957+1080, Python 744, OCaml 952 — zero regressions. Co-Authored-By: Claude Opus 4.6 (1M context) --- hosts/javascript/bootstrap.py | 1 + hosts/ocaml/bootstrap.py | 1 + hosts/python/platform.py | 4 +- spec/primitives.sx | 48 ++--- spec/stdlib.sx | 367 ++++++++++++++++++++++++++++++++++ 5 files changed, 397 insertions(+), 24 deletions(-) create mode 100644 spec/stdlib.sx diff --git a/hosts/javascript/bootstrap.py b/hosts/javascript/bootstrap.py index 5206da4..57b5a8f 100644 --- a/hosts/javascript/bootstrap.py +++ b/hosts/javascript/bootstrap.py @@ -131,6 +131,7 @@ def compile_ref_to_js( # evaluator.sx = merged frames + eval utilities + CEK machine sx_files = [ ("evaluator.sx", "evaluator (frames + eval + CEK)"), + ("stdlib.sx", "stdlib (library functions from former primitives)"), ("freeze.sx", "freeze (serializable state boundaries)"), ("content.sx", "content (content-addressed computation)"), ("render.sx", "render (core)"), diff --git a/hosts/ocaml/bootstrap.py b/hosts/ocaml/bootstrap.py index c65968a..530b47f 100644 --- a/hosts/ocaml/bootstrap.py +++ b/hosts/ocaml/bootstrap.py @@ -96,6 +96,7 @@ def compile_spec_to_ml(spec_dir: str | None = None) -> str: # Spec files to transpile (in dependency order) sx_files = [ ("evaluator.sx", "evaluator (frames + eval + CEK)"), + ("stdlib.sx", "stdlib (library functions from former primitives)"), ] parts = [PREAMBLE] diff --git a/hosts/python/platform.py b/hosts/python/platform.py index fcf660a..95df497 100644 --- a/hosts/python/platform.py +++ b/hosts/python/platform.py @@ -1640,6 +1640,7 @@ SPEC_MODULES = { "engine": ("engine.sx", "engine (fetch/swap/trigger pure logic)"), "signals": ("signals.sx", "signals (reactive signal runtime)"), "page-helpers": ("page-helpers.sx", "page-helpers (pure data transformation helpers)"), + "stdlib": ("stdlib.sx", "stdlib (library functions from former primitives)"), "types": ("types.sx", "types (gradual type system)"), "freeze": ("freeze.sx", "freeze (serializable state boundaries)"), "content": ("content.sx", "content (content-addressed computation)"), @@ -1647,9 +1648,10 @@ SPEC_MODULES = { # Note: frames and cek are now part of evaluator.sx (always loaded as core) # Explicit ordering for spec modules with dependencies. +# stdlib must come first — other modules use its functions. # freeze depends on signals; content depends on freeze. SPEC_MODULE_ORDER = [ - "deps", "engine", "page-helpers", "router", "signals", "types", "freeze", "content", + "stdlib", "deps", "engine", "page-helpers", "router", "signals", "types", "freeze", "content", ] EXTENSION_NAMES = {"continuations"} diff --git a/spec/primitives.sx b/spec/primitives.sx index ec5a7cb..bc93830 100644 --- a/spec/primitives.sx +++ b/spec/primitives.sx @@ -1,36 +1,38 @@ ;; ========================================================================== -;; primitives.sx — Specification of all SX built-in pure functions +;; primitives.sx — Irreducible primitive set ;; -;; Each entry declares: name, parameter signature, and semantics. -;; Bootstrap compilers implement these natively per target. +;; These are the functions that CANNOT be written in SX because they +;; require host-native capabilities: native arithmetic, type inspection, +;; host string library, host math, host I/O, host data structures. ;; -;; This file is a SPECIFICATION, not executable code. The define-primitive -;; form is a declarative macro that bootstrap compilers consume to generate -;; native primitive registrations. +;; Everything else lives in spec/stdlib.sx as library functions. +;; +;; The primitive set is the out-of-band floor. The fewer primitives, +;; the tighter the strange loop and the more of the system is auditable, +;; verifiable, portable SX. ;; ;; Format: ;; (define-primitive "name" ;; :params (param1 param2 &rest rest) ;; :returns "type" -;; :doc "description" -;; :body (reference-implementation ...)) +;; :doc "description") ;; -;; Typed params use (name :as type) syntax: -;; (define-primitive "+" -;; :params (&rest (args :as number)) -;; :returns "number" -;; :doc "Sum all arguments.") +;; Typed params use (name :as type) syntax. +;; Modules: (define-module :name) scopes subsequent entries. ;; -;; Untyped params default to `any`. Typed params enable the gradual -;; type checker (types.sx) to catch mistyped primitive calls. -;; -;; The :body is optional — when provided, it gives a reference -;; implementation in SX that bootstrap compilers MAY use for testing -;; or as a fallback. Most targets will implement natively for performance. -;; -;; Modules: (define-module :name) scopes subsequent define-primitive -;; entries until the next define-module. Bootstrappers use this to -;; selectively include primitive groups. +;; Functions moved to stdlib.sx (no longer primitives): +;; Comparison: != <= >= eq? eqv? equal? +;; Predicates: nil? boolean? number? string? list? dict? +;; continuation? empty? odd? even? zero? contains? +;; Arithmetic: inc dec abs ceil round min max clamp +;; Collections: first last rest nth cons append reverse flatten +;; range chunk-every zip-pairs vals has-key? merge +;; assoc dissoc into +;; Strings: upcase downcase string-length substring +;; string-contains? starts-with? ends-with? +;; split join replace +;; Logic: not +;; Text: pluralize escape assert parse-datetime ;; ========================================================================== diff --git a/spec/stdlib.sx b/spec/stdlib.sx new file mode 100644 index 0000000..dd7495d --- /dev/null +++ b/spec/stdlib.sx @@ -0,0 +1,367 @@ +;; ========================================================================== +;; stdlib.sx — Standard library functions +;; +;; Every function here CAN be expressed in SX using the irreducible +;; primitive set. They are library functions, not primitives. +;; +;; These were previously platform-provided primitives. Moving them to +;; SX tightens the strange loop — less out-of-band, more auditable, +;; portable, and verifiable. +;; +;; Depends on: evaluator.sx (special forms) +;; Must load before: render.sx, freeze.sx, types.sx, user code +;; +;; Irreducible primitives (the ones that CANNOT be written in SX): +;; Arithmetic: + - * / mod floor pow sqrt +;; Comparison: = < > +;; Types: type-of symbol-name keyword-name +;; Strings: str slice index-of upper lower trim char-from-code +;; Collections: list dict concat get len keys dict-set! append! +;; I/O & host: random-int json-encode format-date parse-int +;; format-decimal strip-tags sx-parse error apply +;; ========================================================================== + + +;; -------------------------------------------------------------------------- +;; Logic +;; -------------------------------------------------------------------------- + +(define not + (fn (x) (if x false true))) + + +;; -------------------------------------------------------------------------- +;; Comparison +;; -------------------------------------------------------------------------- + +(define != + (fn (a b) (not (= a b)))) + +(define <= + (fn (a b) (or (< a b) (= a b)))) + +(define >= + (fn (a b) (or (> a b) (= a b)))) + +;; Aliases — SX uses structural equality for all three +(define eq? =) +(define eqv? =) +(define equal? =) + + +;; -------------------------------------------------------------------------- +;; Type predicates +;; -------------------------------------------------------------------------- + +(define nil? + (fn (x) (= (type-of x) "nil"))) + +(define boolean? + (fn (x) (= (type-of x) "boolean"))) + +(define number? + (fn (x) (= (type-of x) "number"))) + +(define string? + (fn (x) (= (type-of x) "string"))) + +(define list? + (fn (x) (= (type-of x) "list"))) + +(define dict? + (fn (x) (= (type-of x) "dict"))) + +(define continuation? + (fn (x) (= (type-of x) "continuation"))) + + +;; -------------------------------------------------------------------------- +;; Numeric predicates +;; -------------------------------------------------------------------------- + +(define zero? + (fn (n) (= n 0))) + +(define odd? + (fn (n) (= (mod n 2) 1))) + +(define even? + (fn (n) (= (mod n 2) 0))) + + +;; -------------------------------------------------------------------------- +;; Arithmetic +;; -------------------------------------------------------------------------- + +(define inc + (fn (n) (+ n 1))) + +(define dec + (fn (n) (- n 1))) + +(define abs + (fn (x) (if (< x 0) (- x) x))) + +(define ceil + (fn (x) + (let ((f (floor x))) + (if (= x f) f (+ f 1))))) + +(define round + (fn (x ndigits) + (if (nil? ndigits) + (floor (+ x 0.5)) + (let ((f (pow 10 ndigits))) + (/ (floor (+ (* x f) 0.5)) f))))) + +(define min + (fn (a b) (if (< a b) a b))) + +(define max + (fn (a b) (if (> a b) a b))) + +(define clamp + (fn (x lo hi) (max lo (min hi x)))) + + +;; -------------------------------------------------------------------------- +;; Collection accessors +;; -------------------------------------------------------------------------- + +(define first + (fn (coll) + (if (and coll (> (len coll) 0)) (get coll 0) nil))) + +(define last + (fn (coll) + (if (and coll (> (len coll) 0)) + (get coll (- (len coll) 1)) + nil))) + +(define rest + (fn (coll) (if coll (slice coll 1) (list)))) + +(define nth + (fn (coll n) + (if (and coll (>= n 0) (< n (len coll))) + (get coll n) + nil))) + +(define empty? + (fn (coll) (or (nil? coll) (= (len coll) 0)))) + +(define cons + (fn (x coll) (concat (list x) (or coll (list))))) + +(define append + (fn (coll x) + (if (list? x) (concat coll x) (concat coll (list x))))) + + +;; -------------------------------------------------------------------------- +;; Collection transforms +;; -------------------------------------------------------------------------- + +(define reverse + (fn (coll) + (let ((result (list)) + (i (- (len coll) 1))) + (let loop ((i i)) + (when (>= i 0) + (append! result (get coll i)) + (loop (- i 1)))) + result))) + +(define flatten + (fn (coll) + (let ((result (list))) + (for-each + (fn (x) + (if (list? x) + (for-each (fn (y) (append! result y)) x) + (append! result x))) + coll) + result))) + +(define range + (fn (start end step) + (let ((s (if (nil? step) 1 step)) + (result (list))) + (let loop ((i start)) + (when (< i end) + (append! result i) + (loop (+ i s)))) + result))) + +(define chunk-every + (fn (coll n) + (let ((result (list)) + (clen (len coll))) + (let loop ((i 0)) + (when (< i clen) + (append! result (slice coll i (min (+ i n) clen))) + (loop (+ i n)))) + result))) + +(define zip-pairs + (fn (coll) + (let ((result (list)) + (clen (len coll))) + (let loop ((i 0)) + (when (< i (- clen 1)) + (append! result (list (get coll i) (get coll (+ i 1)))) + (loop (+ i 1)))) + result))) + + +;; -------------------------------------------------------------------------- +;; Dict operations +;; -------------------------------------------------------------------------- + +(define vals + (fn (d) + (let ((result (list))) + (for-each (fn (k) (append! result (get d k))) (keys d)) + result))) + +(define has-key? + (fn (d key) + (some (fn (k) (= k key)) (keys d)))) + +(define merge + (fn (a b) + (let ((result (dict))) + (when a + (for-each (fn (k) (dict-set! result k (get a k))) (keys a))) + (when b + (for-each (fn (k) (dict-set! result k (get b k))) (keys b))) + result))) + +(define assoc + (fn (d key val) + (let ((result (dict))) + (when d + (for-each (fn (k) (dict-set! result k (get d k))) (keys d))) + (dict-set! result key val) + result))) + +(define dissoc + (fn (d key) + (let ((result (dict))) + (for-each + (fn (k) + (when (!= k key) + (dict-set! result k (get d k)))) + (keys d)) + result))) + +(define into + (fn (target coll) + (cond + (list? target) + (if (list? coll) + (concat coll (list)) + (let ((result (list))) + (for-each (fn (k) (append! result (list k (get coll k)))) (keys coll)) + result)) + (dict? target) + (let ((result (dict))) + (for-each + (fn (pair) + (when (and (list? pair) (>= (len pair) 2)) + (dict-set! result (get pair 0) (get pair 1)))) + coll) + result) + :else target))) + + +;; -------------------------------------------------------------------------- +;; String operations +;; -------------------------------------------------------------------------- + +(define upcase upper) +(define downcase lower) + +(define string-length + (fn (s) (len s))) + +(define substring + (fn (s start end) (slice s start end))) + +(define string-contains? + (fn (s needle) (!= (index-of s needle) -1))) + +(define starts-with? + (fn (s prefix) (= (index-of s prefix) 0))) + +(define ends-with? + (fn (s suffix) + (let ((slen (len s)) + (plen (len suffix))) + (if (< slen plen) false + (= (slice s (- slen plen)) suffix))))) + +(define split + (fn (s sep) + (let ((separator (if (nil? sep) " " sep)) + (result (list)) + (slen (len s)) + (seplen (len separator))) + (let loop ((start 0)) + (let ((idx (index-of s separator start))) + (if (= idx -1) + (do (append! result (slice s start)) result) + (do (append! result (slice s start idx)) + (loop (+ idx seplen))))))))) + +(define join + (fn (sep coll) + (let ((result "")) + (for-each + (fn (x) + (set! result (if (= result "") + (str x) + (str result sep x)))) + coll) + result))) + +(define replace + (fn (s old new) + (join new (split s old)))) + +(define contains? + (fn (coll key) + (cond + (string? coll) (!= (index-of coll (str key)) -1) + (dict? coll) (has-key? coll key) + (list? coll) (some (fn (x) (= x key)) coll) + :else false))) + + +;; -------------------------------------------------------------------------- +;; Text utilities +;; -------------------------------------------------------------------------- + +(define pluralize + (fn (count singular plural) + (if (= count 1) + (or singular "") + (or plural "s")))) + +(define escape + (fn (s) + (-> (str s) + (replace "&" "&") + (replace "<" "<") + (replace ">" ">") + (replace "\"" """) + (replace "'" "'")))) + +(define parse-datetime + (fn (s) (if s (str s) nil))) + +(define assert + (fn (condition message) + (when (not condition) + (error (or message "Assertion failed"))) + true))