(define (new-goal prop) (list '() '() prop))
(define (goal-bindings g) (car g))
(define (goal-hyps g) (cadr g))
(define (goal-prop g) (caddr g))
(define (prop->expr p)
(cond ((and (pair? p) (equal? (car p) 'forall))
(error "prover: intro the forall first (intros)"))
((and (pair? p) (equal? (car p) 'implies))
(list 'or (list 'not (prop->expr (cadr p))) (prop->expr (caddr p))))
((and (pair? p) (or (equal? (car p) 'and) (equal? (car p) 'or)))
(cons (car p) (map prop->expr (cdr p))))
((and (pair? p) (equal? (car p) 'not))
(list 'not (prop->expr (cadr p))))
(else p)))
(define (goal->expr g)
(let ((hyps (goal-hyps g)) (prop (goal-prop g)))
(prop->expr (if (null? hyps) prop (list 'implies (cons 'and hyps) prop)))))
(define (intros g) (let ((p (goal-prop g)))
(if (and (pair? p) (equal? (car p) 'forall))
(list 'goals (list (append (goal-bindings g) (cadr p))
(goal-hyps g)
(caddr p)))
(error "intros: goal is not a forall"))))
(define (intro g) (let ((p (goal-prop g)))
(if (and (pair? p) (equal? (car p) 'implies))
(list 'goals (list (goal-bindings g)
(append (goal-hyps g) (list (cadr p)))
(caddr p)))
(error "intro: goal is not an implication"))))
(define (split g) (let ((p (goal-prop g)))
(if (and (pair? p) (equal? (car p) 'and))
(cons 'goals (map (lambda (c) (list (goal-bindings g) (goal-hyps g) c))
(cdr p)))
(error "split: goal is not a conjunction"))))
(define (exhaust g) (let ((bs (goal-bindings g)))
(if (null? bs)
(if (eval (goal->expr g)) 'discharged
(error (format "exhaust: proposition is false: ~a" (goal->expr g))))
(let ((f (eval (list 'lambda (map car bs) (goal->expr g)))))
(let ((r (check-exhaustive f (map cadr bs))))
(if (equal? r 'verified) 'discharged
(error (format "exhaust: counterexamples ~a" r))))))))
(define *theorems* '())
(define (register-theorem name prop)
(set! *theorems* (cons (list name prop) *theorems*)))
(define (theorem name)
(let ((hit (assoc name *theorems*)))
(if hit (cadr hit) (error (format "no theorem named ~a" name)))))
(define (lemma name) (lambda (g)
(if (equal? (goal-prop g) (theorem name))
'discharged
(error (format "lemma ~a does not match the goal" name)))))
(define (find-counterexample prop)
(let ((g (new-goal prop)))
(let ((g2 (if (and (pair? prop) (equal? (car prop) 'forall))
(cadr (intros g)) g)))
(let ((bs (goal-bindings g2)))
(if (null? bs)
(if (eval (goal->expr g2)) 'no-counterexample (list 'false prop))
(let ((f (eval (list 'lambda (map car bs) (goal->expr g2)))))
(let ((r (check-exhaustive f (map cadr bs))))
(if (equal? r 'verified) 'no-counterexample
(list 'counterexamples r)))))))))
(define (apply-tactic tac state) (if (null? state)
state
(let ((r (tac (car state))))
(cond ((equal? r 'discharged) (cdr state))
((and (pair? r) (equal? (car r) 'goals))
(append (cdr r) (cdr state)))
(else (error (format "tactic returned ~a" r)))))))
(define (step tac) (lambda (state) (apply-tactic tac state)))
(defmacro tactic-seq (s1 . more)
`(lambda (st) (foldl (lambda (s acc) (s acc)) st (list ,s1 ,@more))))
(defmacro tactic-repeat (s)
`(lambda (st)
(let rep ((cur st))
(let ((nxt (try-catch (,s cur) (e) cur)))
(if (equal? nxt cur) cur (rep nxt))))))
(defmacro tactic-or (a b)
`(lambda (st) (try-catch (,a st) (e) (,b st))))
(define (auto-step g) (let ((p (goal-prop g)))
(cond ((and (pair? p) (equal? (car p) 'forall)) (intros g))
((and (pair? p) (equal? (car p) 'implies)) (intro g))
((and (pair? p) (equal? (car p) 'and)) (split g))
(else (exhaust g)))))
(defmacro auto ()
`(tactic-repeat (step auto-step)))
(defmacro defproof (name prop . script)
`(let ((final ((tactic-seq ,@script) (list (new-goal (quote ,prop))))))
(if (null? final)
(begin (register-theorem (quote ,name) (quote ,prop))
(list 'proved (quote ,name)))
(list 'unproved (quote ,name) 'open-goals (length final)))))
(define *proof-state* '())
(define *proof-goal-name* #f)
(define *proof-goal-prop* #f)
(define (prove name prop)
(set! *proof-state* (list (new-goal prop)))
(set! *proof-goal-name* name)
(set! *proof-goal-prop* prop)
(list 'proving name 'goals 1))
(define (tac! tac)
(set! *proof-state* (apply-tactic tac *proof-state*))
(list 'open-goals (length *proof-state*)))
(define (goals) *proof-state*)
(define (qed)
(if (null? *proof-state*)
(begin (register-theorem *proof-goal-name* *proof-goal-prop*)
(list 'qed *proof-goal-name*))
(error (format "qed: ~a goal(s) still open" (length *proof-state*)))))