Annotates ~500 defcomp params across 62 files: market (5), blog (7), cart (5), events (3), federation (4), account (3), orders (2), shared templates (11), sx docs (14), plus remaining spec fn params (z3, test-framework, adapter-dom, adapter-async, engine, eval). Total annotations in codebase: 1043. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
359 lines
13 KiB
Plaintext
359 lines
13 KiB
Plaintext
;; ==========================================================================
|
|
;; z3.sx — SX spec to SMT-LIB translator, written in SX
|
|
;;
|
|
;; Translates define-primitive, define-io-primitive, and define-special-form
|
|
;; declarations from the SX spec into SMT-LIB verification conditions for
|
|
;; Z3 and other theorem provers.
|
|
;;
|
|
;; This is the first self-hosted bootstrapper: the SX evaluator (itself
|
|
;; bootstrapped from eval.sx) executes this file against the spec to
|
|
;; produce output in a different language. Same pattern as bootstrap_js.py
|
|
;; and bootstrap_py.py, but written in SX instead of Python.
|
|
;;
|
|
;; Usage (from SX):
|
|
;; (z3-translate expr) — translate one define-* form
|
|
;; (z3-translate-file exprs) — translate a list of parsed expressions
|
|
;;
|
|
;; Usage (as reader macro):
|
|
;; #z3(define-primitive "inc" :params (n) :returns "number" :body (+ n 1))
|
|
;; → "; inc — ...\n(declare-fun inc (Int) Int)\n..."
|
|
;; ==========================================================================
|
|
|
|
|
|
;; --------------------------------------------------------------------------
|
|
;; Type mapping: SX type names → SMT-LIB sorts
|
|
;; --------------------------------------------------------------------------
|
|
|
|
(define z3-sort
|
|
(fn ((sx-type :as string))
|
|
(case sx-type
|
|
"number" "Int"
|
|
"boolean" "Bool"
|
|
"string" "String"
|
|
"list" "(List Value)"
|
|
"dict" "(Array String Value)"
|
|
:else "Value")))
|
|
|
|
|
|
;; --------------------------------------------------------------------------
|
|
;; Name translation: SX identifiers → SMT-LIB identifiers
|
|
;; --------------------------------------------------------------------------
|
|
|
|
(define z3-name
|
|
(fn ((name :as string))
|
|
(cond
|
|
(= name "!=") "neq"
|
|
(= name "+") "+"
|
|
(= name "-") "-"
|
|
(= name "*") "*"
|
|
(= name "/") "/"
|
|
(= name "=") "="
|
|
(= name "<") "<"
|
|
(= name ">") ">"
|
|
(= name "<=") "<="
|
|
(= name ">=") ">="
|
|
:else (replace (replace (replace name "-" "_") "?" "_p") "!" "_bang"))))
|
|
|
|
(define z3-sym
|
|
(fn (sym)
|
|
(let ((name (symbol-name sym)))
|
|
(cond
|
|
(ends-with? name "?")
|
|
(str "is_" (replace (slice name 0 (- (string-length name) 1)) "-" "_"))
|
|
:else
|
|
(replace (replace name "-" "_") "!" "_bang")))))
|
|
|
|
|
|
;; --------------------------------------------------------------------------
|
|
;; Expression translation: SX body expressions → SMT-LIB s-expressions
|
|
;; --------------------------------------------------------------------------
|
|
|
|
;; Operators that pass through unchanged
|
|
(define z3-identity-ops
|
|
(list "+" "-" "*" "/" "=" "!=" "<" ">" "<=" ">=" "and" "or" "not" "mod"))
|
|
|
|
;; Operators that get renamed
|
|
(define z3-rename-op
|
|
(fn ((op :as string))
|
|
(case op
|
|
"if" "ite"
|
|
"str" "str.++"
|
|
:else nil)))
|
|
|
|
(define z3-expr
|
|
(fn (expr)
|
|
(cond
|
|
;; Numbers
|
|
(number? expr)
|
|
(str expr)
|
|
|
|
;; Strings
|
|
(string? expr)
|
|
(str "\"" expr "\"")
|
|
|
|
;; Booleans
|
|
(= expr true) "true"
|
|
(= expr false) "false"
|
|
|
|
;; Nil
|
|
(nil? expr)
|
|
"nil_val"
|
|
|
|
;; Symbols
|
|
(= (type-of expr) "symbol")
|
|
(z3-sym expr)
|
|
|
|
;; Lists (function calls / special forms)
|
|
(list? expr)
|
|
(if (empty? expr)
|
|
"()"
|
|
(let ((head (first expr))
|
|
(args (rest expr)))
|
|
(if (not (= (type-of head) "symbol"))
|
|
(str expr)
|
|
(let ((op (symbol-name head)))
|
|
(cond
|
|
;; Identity ops: same syntax in both languages
|
|
(some (fn (x) (= x op)) z3-identity-ops)
|
|
(str "(" op " " (join " " (map z3-expr args)) ")")
|
|
|
|
;; Renamed ops
|
|
(not (nil? (z3-rename-op op)))
|
|
(str "(" (z3-rename-op op) " " (join " " (map z3-expr args)) ")")
|
|
|
|
;; max → ite
|
|
(and (= op "max") (= (len args) 2))
|
|
(let ((a (z3-expr (nth args 0)))
|
|
(b (z3-expr (nth args 1))))
|
|
(str "(ite (>= " a " " b ") " a " " b ")"))
|
|
|
|
;; min → ite
|
|
(and (= op "min") (= (len args) 2))
|
|
(let ((a (z3-expr (nth args 0)))
|
|
(b (z3-expr (nth args 1))))
|
|
(str "(ite (<= " a " " b ") " a " " b ")"))
|
|
|
|
;; empty? → length check
|
|
(= op "empty?")
|
|
(str "(= (len " (z3-expr (first args)) ") 0)")
|
|
|
|
;; first/rest → list ops
|
|
(= op "first")
|
|
(str "(head " (z3-expr (first args)) ")")
|
|
(= op "rest")
|
|
(str "(tail " (z3-expr (first args)) ")")
|
|
|
|
;; reduce with initial value
|
|
(and (= op "reduce") (>= (len args) 3))
|
|
(str "(reduce " (z3-expr (nth args 0)) " "
|
|
(z3-expr (nth args 2)) " "
|
|
(z3-expr (nth args 1)) ")")
|
|
|
|
;; fn (lambda)
|
|
(= op "fn")
|
|
(let ((params (first args))
|
|
(body (nth args 1)))
|
|
(str "(lambda (("
|
|
(join " " (map (fn (p) (str "(" (z3-sym p) " Int)")) params))
|
|
")) " (z3-expr body) ")"))
|
|
|
|
;; native-* → strip prefix
|
|
(starts-with? op "native-")
|
|
(str "(" (slice op 7 (string-length op)) " "
|
|
(join " " (map z3-expr args)) ")")
|
|
|
|
;; Generic function call
|
|
:else
|
|
(str "(" (z3-name op) " "
|
|
(join " " (map z3-expr args)) ")"))))))
|
|
|
|
;; Fallback
|
|
:else (str expr))))
|
|
|
|
|
|
;; --------------------------------------------------------------------------
|
|
;; Keyword argument extraction from define-* forms
|
|
;; --------------------------------------------------------------------------
|
|
|
|
(define z3-extract-kwargs
|
|
(fn ((expr :as list))
|
|
;; Returns a dict of keyword args from a define-* form
|
|
;; (define-primitive "name" :params (...) :returns "type" ...) → {:params ... :returns ...}
|
|
(let ((result {})
|
|
(items (rest (rest expr)))) ;; skip head and name
|
|
(z3-extract-kwargs-loop items result))))
|
|
|
|
(define z3-extract-kwargs-loop
|
|
(fn ((items :as list) (result :as dict))
|
|
(if (or (empty? items) (< (len items) 2))
|
|
result
|
|
(if (= (type-of (first items)) "keyword")
|
|
(z3-extract-kwargs-loop
|
|
(rest (rest items))
|
|
(assoc result (keyword-name (first items)) (nth items 1)))
|
|
(z3-extract-kwargs-loop (rest items) result)))))
|
|
|
|
|
|
;; --------------------------------------------------------------------------
|
|
;; Parameter processing
|
|
;; --------------------------------------------------------------------------
|
|
|
|
(define z3-params-to-sorts
|
|
(fn ((params :as list))
|
|
;; Convert SX param list to list of (name sort) pairs, skipping &rest/&key
|
|
(z3-params-loop params false (list))))
|
|
|
|
(define z3-params-loop
|
|
(fn ((params :as list) (skip-next :as boolean) (acc :as list))
|
|
(if (empty? params)
|
|
acc
|
|
(let ((p (first params))
|
|
(rest-p (rest params)))
|
|
(cond
|
|
;; &rest or &key marker — skip it and the next param
|
|
(and (= (type-of p) "symbol")
|
|
(or (= (symbol-name p) "&rest")
|
|
(= (symbol-name p) "&key")))
|
|
(z3-params-loop rest-p true acc)
|
|
;; Skipping the param after &rest/&key
|
|
skip-next
|
|
(z3-params-loop rest-p false acc)
|
|
;; Normal parameter
|
|
(= (type-of p) "symbol")
|
|
(z3-params-loop rest-p false
|
|
(append acc (list (list (symbol-name p) "Int"))))
|
|
;; Something else — skip
|
|
:else
|
|
(z3-params-loop rest-p false acc))))))
|
|
|
|
(define z3-has-rest?
|
|
(fn ((params :as list))
|
|
(some (fn (p) (and (= (type-of p) "symbol") (= (symbol-name p) "&rest")))
|
|
params)))
|
|
|
|
|
|
;; --------------------------------------------------------------------------
|
|
;; define-primitive → SMT-LIB
|
|
;; --------------------------------------------------------------------------
|
|
|
|
(define z3-translate-primitive
|
|
(fn ((expr :as list))
|
|
(let ((name (nth expr 1))
|
|
(kwargs (z3-extract-kwargs expr))
|
|
(params (or (get kwargs "params") (list)))
|
|
(returns (or (get kwargs "returns") "any"))
|
|
(doc (or (get kwargs "doc") ""))
|
|
(body (get kwargs "body"))
|
|
(pairs (z3-params-to-sorts params))
|
|
(has-rest (z3-has-rest? params))
|
|
(smt-name (z3-name name)))
|
|
|
|
(str
|
|
;; Comment header
|
|
"; " name " — " doc "\n"
|
|
|
|
;; Declaration
|
|
(if has-rest
|
|
(str "; (variadic — modeled as uninterpreted)\n"
|
|
"(declare-fun " smt-name " (Int Int) " (z3-sort returns) ")")
|
|
(str "(declare-fun " smt-name " ("
|
|
(join " " (map (fn (pair) (nth pair 1)) pairs))
|
|
") " (z3-sort returns) ")"))
|
|
"\n"
|
|
|
|
;; Assertion (if body exists and not variadic)
|
|
(if (and (not (nil? body)) (not has-rest))
|
|
(if (empty? pairs)
|
|
;; No params — simple assertion
|
|
(str "(assert (= (" smt-name ") " (z3-expr body) "))\n")
|
|
;; With params — forall
|
|
(let ((bindings (join " " (map (fn (pair) (str "(" (nth pair 0) " Int)")) pairs)))
|
|
(call-args (join " " (map (fn (pair) (nth pair 0)) pairs))))
|
|
(str "(assert (forall ((" bindings "))\n"
|
|
" (= (" smt-name " " call-args ") " (z3-expr body) ")))\n")))
|
|
"")
|
|
|
|
;; Check satisfiability
|
|
"(check-sat)"))))
|
|
|
|
|
|
;; --------------------------------------------------------------------------
|
|
;; define-io-primitive → SMT-LIB
|
|
;; --------------------------------------------------------------------------
|
|
|
|
(define z3-translate-io
|
|
(fn ((expr :as list))
|
|
(let ((name (nth expr 1))
|
|
(kwargs (z3-extract-kwargs expr))
|
|
(doc (or (get kwargs "doc") ""))
|
|
(smt-name (replace (replace name "-" "_") "?" "_p")))
|
|
(str "; IO primitive: " name " — " doc "\n"
|
|
"; (uninterpreted — IO cannot be verified statically)\n"
|
|
"(declare-fun " smt-name " () Value)"))))
|
|
|
|
|
|
;; --------------------------------------------------------------------------
|
|
;; define-special-form → SMT-LIB
|
|
;; --------------------------------------------------------------------------
|
|
|
|
(define z3-translate-special-form
|
|
(fn ((expr :as list))
|
|
(let ((name (nth expr 1))
|
|
(kwargs (z3-extract-kwargs expr))
|
|
(doc (or (get kwargs "doc") "")))
|
|
(case name
|
|
"if"
|
|
(str "; Special form: if — " doc "\n"
|
|
"(assert (forall ((c Bool) (t Value) (e Value))\n"
|
|
" (= (sx_if c t e) (ite c t e))))\n"
|
|
"(check-sat)")
|
|
"when"
|
|
(str "; Special form: when — " doc "\n"
|
|
"(assert (forall ((c Bool) (body Value))\n"
|
|
" (= (sx_when c body) (ite c body nil_val))))\n"
|
|
"(check-sat)")
|
|
:else
|
|
(str "; Special form: " name " — " doc "\n"
|
|
"; (not directly expressible in SMT-LIB)")))))
|
|
|
|
|
|
;; --------------------------------------------------------------------------
|
|
;; Top-level dispatch
|
|
;; --------------------------------------------------------------------------
|
|
|
|
(define z3-translate
|
|
(fn (expr)
|
|
(if (not (list? expr))
|
|
"; Cannot translate: not a list form"
|
|
(if (< (len expr) 2)
|
|
"; Cannot translate: too short"
|
|
(let ((head (first expr)))
|
|
(if (not (= (type-of head) "symbol"))
|
|
"; Cannot translate: head is not a symbol"
|
|
(case (symbol-name head)
|
|
"define-primitive" (z3-translate-primitive expr)
|
|
"define-io-primitive" (z3-translate-io expr)
|
|
"define-special-form" (z3-translate-special-form expr)
|
|
:else (z3-expr expr))))))))
|
|
|
|
|
|
;; --------------------------------------------------------------------------
|
|
;; Batch translation: process a list of parsed expressions
|
|
;; --------------------------------------------------------------------------
|
|
|
|
(define z3-translate-file
|
|
(fn ((exprs :as list))
|
|
;; Filter to translatable forms and translate each
|
|
(let ((translatable
|
|
(filter
|
|
(fn (expr)
|
|
(and (list? expr)
|
|
(>= (len expr) 2)
|
|
(= (type-of (first expr)) "symbol")
|
|
(let ((name (symbol-name (first expr))))
|
|
(or (= name "define-primitive")
|
|
(= name "define-io-primitive")
|
|
(= name "define-special-form")))))
|
|
exprs)))
|
|
(join "\n\n" (map z3-translate translatable)))))
|