;; lib/guest/match.sx — pure pattern-match + unification kit. ;; ;; Shipped for miniKanren / Datalog / future logic-flavoured guests that ;; want immutable unification without writing it from scratch. The two ;; existing prolog/haskell engines stay as-is — porting them in place ;; risks the 746 tests they currently pass; consumers can migrate ;; gradually via the converters in lib/guest/ast.sx. ;; ;; Term shapes (canonical wire format) ;; ----------------------------------- ;; var (:var NAME) NAME a string ;; constructor (:ctor HEAD ARGS) HEAD a string, ARGS a list of terms ;; literal number / string / boolean / nil ;; ;; Guests with their own shape pass adapter callbacks via the cfg arg — ;; see (unify-with cfg ...) and (match-pat-with cfg ...) below. The ;; default canonical entry points (unify / match-pat) use the wire shape. ;; ;; Substitution / env ;; ------------------ ;; A substitution is a SX dict mapping VAR-NAME → term. There are no ;; trails, no mutation: each step either returns an extended dict or nil. ;; ;; (empty-subst) → {} ;; (walk term s) → term with top-level vars resolved ;; (walk* term s) → term with all vars resolved (recursive) ;; (extend name term s) → s with NAME → term added ;; (occurs? name term s) → bool ;; ;; Unify (symmetric, miniKanren-flavour) ;; ------------------------------------- ;; (unify u v s) → extended subst or nil ;; (unify-with cfg u v s) → ditto, with adapter callbacks: ;; :var? :var-name :ctor? :ctor-head ;; :ctor-args :occurs-check? ;; ;; Match (asymmetric, haskell-flavour: pattern → value, vars only in pat) ;; --------------------------------------------------------------------- ;; (match-pat pat val env) → extended env or nil ;; (match-pat-with cfg pat val env) (define mk-var (fn (name) (list :var name))) (define mk-ctor (fn (head args) (list :ctor head args))) (define is-var? (fn (t) (and (list? t) (not (empty? t)) (= (first t) :var)))) (define is-ctor? (fn (t) (and (list? t) (not (empty? t)) (= (first t) :ctor)))) (define var-name (fn (t) (nth t 1))) (define ctor-head (fn (t) (nth t 1))) (define ctor-args (fn (t) (nth t 2))) (define empty-subst (fn () {})) (define walk (fn (t s) (if (and (is-var? t) (has-key? s (var-name t))) (walk (get s (var-name t)) s) t))) (define walk* (fn (t s) (let ((w (walk t s))) (cond ((is-ctor? w) (mk-ctor (ctor-head w) (map (fn (a) (walk* a s)) (ctor-args w)))) (:else w))))) (define extend (fn (name term s) (assoc s name term))) (define occurs? (fn (name term s) (let ((w (walk term s))) (cond ((is-var? w) (= (var-name w) name)) ((is-ctor? w) (some (fn (a) (occurs? name a s)) (ctor-args w))) (:else false))))) (define unify-with (fn (cfg u v s) (let ((var?-fn (get cfg :var?)) (var-name-fn (get cfg :var-name)) (ctor?-fn (get cfg :ctor?)) (ctor-head-fn (get cfg :ctor-head)) (ctor-args-fn (get cfg :ctor-args)) (occurs?-on (get cfg :occurs-check?))) (let ((wu (walk-with cfg u s)) (wv (walk-with cfg v s))) (cond ((and (var?-fn wu) (var?-fn wv) (= (var-name-fn wu) (var-name-fn wv))) s) ((var?-fn wu) (if (and occurs?-on (occurs-with cfg (var-name-fn wu) wv s)) nil (extend (var-name-fn wu) wv s))) ((var?-fn wv) (if (and occurs?-on (occurs-with cfg (var-name-fn wv) wu s)) nil (extend (var-name-fn wv) wu s))) ((and (ctor?-fn wu) (ctor?-fn wv)) (if (= (ctor-head-fn wu) (ctor-head-fn wv)) (unify-list-with cfg (ctor-args-fn wu) (ctor-args-fn wv) s) nil)) (:else (if (= wu wv) s nil))))))) (define walk-with (fn (cfg t s) (if (and ((get cfg :var?) t) (has-key? s ((get cfg :var-name) t))) (walk-with cfg (get s ((get cfg :var-name) t)) s) t))) (define occurs-with (fn (cfg name term s) (let ((w (walk-with cfg term s))) (cond (((get cfg :var?) w) (= ((get cfg :var-name) w) name)) (((get cfg :ctor?) w) (some (fn (a) (occurs-with cfg name a s)) ((get cfg :ctor-args) w))) (:else false))))) (define unify-list-with (fn (cfg xs ys s) (cond ((and (empty? xs) (empty? ys)) s) ((or (empty? xs) (empty? ys)) nil) (:else (let ((s2 (unify-with cfg (first xs) (first ys) s))) (if (= s2 nil) nil (unify-list-with cfg (rest xs) (rest ys) s2))))))) (define canonical-cfg {:var? is-var? :var-name var-name :ctor? is-ctor? :ctor-head ctor-head :ctor-args ctor-args :occurs-check? true}) (define unify (fn (u v s) (unify-with canonical-cfg u v s))) ;; Asymmetric pattern match (haskell-style): only patterns may contain vars; ;; values are concrete. On a var pattern, bind name to value. (define match-pat-with (fn (cfg pat val env) (let ((var?-fn (get cfg :var?)) (var-name-fn (get cfg :var-name)) (ctor?-fn (get cfg :ctor?)) (ctor-head-fn (get cfg :ctor-head)) (ctor-args-fn (get cfg :ctor-args))) (cond ((var?-fn pat) (extend (var-name-fn pat) val env)) ((and (ctor?-fn pat) (ctor?-fn val)) (if (= (ctor-head-fn pat) (ctor-head-fn val)) (match-list-pat-with cfg (ctor-args-fn pat) (ctor-args-fn val) env) nil)) ((ctor?-fn pat) nil) (:else (if (= pat val) env nil)))))) (define match-list-pat-with (fn (cfg pats vals env) (cond ((and (empty? pats) (empty? vals)) env) ((or (empty? pats) (empty? vals)) nil) (:else (let ((env2 (match-pat-with cfg (first pats) (first vals) env))) (if (= env2 nil) nil (match-list-pat-with cfg (rest pats) (rest vals) env2))))))) (define match-pat (fn (pat val env) (match-pat-with canonical-cfg pat val env)))