;;; This code is copyright 1993 by Paul Graham. ;;; Distribution under clocc.sourceforge.net ;;; Liam M. Healy (LMH), liam@users.sourceforge.net ;;; By permission of Paul Graham, this is distributed under the ;;; GNU General Public License (GPL), version 2. ;;; The license accompanies this file under the name "COPYING", or ;;; see http://www.gnu.org/copyleft/gpl.html for a copy. ;;; LMH changes are: ;;; Added ;;; - in-package and export defintions from this package ;;; - form seperators (by book section) ;;; - comments ;;; - some type declarations (originally to help CMUCL). ;;; Book Info: ;;; Paul Graham, On Lisp, Prentice-Hall 1994 ;;; ISBN: 0-13-030552-9 ;;; LC: QA 76.73.C28G73 1994 ;;; This file is advanced and "sketch" applications (Chapters 19-25). ;;; No elimination of duplicate definitions, ;;; which will cause many warnings when compiling. ;;; Anyone who wants to clean up this file should send the ;;; result to me at the email above. (in-package :pg) ;;;; ******************************************************************************** ;;;; Chapter 19, A Query Compiler ;;;; ******************************************************************************** (defun make-db (&optional (size 100)) (make-hash-table :size size)) (defvar *default-db* (make-db)) (defun clear-db (&optional (db *default-db*)) (clrhash db)) (defmacro db-query (key &optional (db '*default-db*)) `(gethash ,key ,db)) (defun db-push (key val &optional (db *default-db*)) (push val (db-query key db))) (defmacro fact (pred &rest args) `(progn (db-push ',pred ',args) ',args)) (defmacro with-answer (query &body body) (let ((binds (gensym))) `(dolist (,binds (interpret-query ',query)) (let ,(mapcar #'(lambda (v) `(,v (binding ',v ,binds))) (vars-in query #'atom)) ,@body)))) (defun interpret-query (expr &optional binds) (case (car expr) (and (interpret-and (reverse (cdr expr)) binds)) (or (interpret-or (cdr expr) binds)) (not (interpret-not (cadr expr) binds)) (t (lookup (car expr) (cdr expr) binds)))) (defun interpret-and (clauses binds) (if (null clauses) (list binds) (mapcan #'(lambda (b) (interpret-query (car clauses) b)) (interpret-and (cdr clauses) binds)))) (defun interpret-or (clauses binds) (mapcan #'(lambda (c) (interpret-query c binds)) clauses)) (defun interpret-not (clause binds) (if (interpret-query clause binds) nil (list binds))) (defun lookup (pred args &optional binds) (mapcan #'(lambda (x) (aif2 (match x args binds) (list it))) (db-query pred))) (defmacro with-answer (query &body body) `(with-gensyms ,(vars-in query #'simple?) ,(compile-query query `(progn ,@body)))) (defun compile-query (q body) (case (car q) (and (compile-and (cdr q) body)) (or (compile-or (cdr q) body)) (not (compile-not (cadr q) body)) (lisp `(if ,(cadr q) ,body)) (t (compile-simple q body)))) (defun compile-simple (q body) (let ((fact (gensym))) `(dolist (,fact (db-query ',(car q))) (pat-match ,(cdr q) ,fact ,body nil)))) (defun compile-and (clauses body) (if (null clauses) body (compile-query (car clauses) (compile-and (cdr clauses) body)))) (defun compile-or (clauses body) (if (null clauses) nil (let ((gbod (gensym)) (vars (vars-in body #'simple?))) `(labels ((,gbod ,vars ,body)) ,@(mapcar #'(lambda (cl) (compile-query cl `(,gbod ,@vars))) clauses))))) (defun compile-not (q body) (let ((tag (gensym))) `(if (block ,tag ,(compile-query q `(return-from ,tag nil)) t) ,body))) ;;;; ******************************************************************************** ;;;; Chapter 20, Continuations ;;;; ******************************************************************************** (defvar *cont* #'identity) (defmacro =lambda (parms &body body) `#'(lambda (*cont* ,@parms) ,@body)) (defmacro =defun (name parms &body body) (let ((f (intern (concatenate 'string "=" (symbol-name name))))) `(progn (defmacro ,name ,parms `(,',f *cont* ,,@parms)) (defun ,f (*cont* ,@parms) ,@body)))) (defmacro =bind (parms expr &body body) `(let ((*cont* #'(lambda ,parms ,@body))) ,expr)) (defmacro =values (&rest retvals) `(funcall *cont* ,@retvals)) (defmacro =funcall (fn &rest args) `(funcall ,fn *cont* ,@args)) (defmacro =apply (fn &rest args) `(apply ,fn *cont* ,@args)) (defparameter *paths* nil) (defconstant failsym '@) ;;;; ******************************************************************************** ;;;; Section 22.4, Nondeterminism: Common Lisp Implementation ;;;; ******************************************************************************** (defmacro choose (&rest choices) (if choices `(progn ,@(mapcar #'(lambda (c) `(push #'(lambda () ,c) *paths*)) (reverse (cdr choices))) ,(car choices)) '(fail))) (defmacro choose-bind (var choices &body body) `(cb #'(lambda (,var) ,@body) ,choices)) (defun cb (fn choices) (declare (function fn)) ; LMH (if choices (progn (if (cdr choices) (push #'(lambda () (cb fn (cdr choices))) *paths*)) (funcall fn (car choices))) (fail))) (defun fail () (if *paths* (funcall (the function (pop *paths*))) ; LMH failsym)) ;;;; ******************************************************************************** ;;;; Chapter 21, Multiple Processes ;;;; ******************************************************************************** (defstruct proc pri state wait) (proclaim '(special *procs* *proc*)) (defvar *halt* (gensym)) (defvar *default-proc* (make-proc :state #'(lambda (x) (declare (ignore x)) (format t "~%>> ") (princ (eval (read))) (pick-process)))) (defmacro fork (expr pri) `(prog1 ',expr (push (make-proc :state #'(lambda (,(gensym)) ,expr (pick-process)) :pri ,pri) *procs*))) (defmacro program (name args &body body) `(=defun ,name ,args (setq *procs* nil) ,@body (catch *halt* (loop (pick-process))))) (defun pick-process () (multiple-value-bind (p val) (most-urgent-process) (setq *proc* p *procs* (delete p *procs*)) (funcall (proc-state p) val))) (defun most-urgent-process () (let ((proc1 *default-proc*) (max -1) (val1 t)) (dolist (p *procs*) (let ((pri (proc-pri p))) (if (> pri max) (let ((val (or (not (proc-wait p)) (funcall (proc-wait p))))) (when val (setq proc1 p max pri val1 val)))))) (values proc1 val1))) (defun arbitrator (test cont) (setf (proc-state *proc*) cont (proc-wait *proc*) test) (push *proc* *procs*) (pick-process)) (defmacro wait (parm test &body body) `(arbitrator #'(lambda () ,test) #'(lambda (,parm) ,@body))) (defmacro yield (&body body) `(arbitrator nil #'(lambda (,(gensym)) ,@body))) (defun setpri (n) (setf (proc-pri *proc*) n)) (defun halt (&optional val) (throw *halt* val)) (defun kill (&optional obj &rest args) (if obj (setq *procs* (apply #'delete obj *procs* args)) (pick-process))) (defvar *open-doors* nil) (=defun pedestrian () (wait d (car *open-doors*) (format t "Entering ~A~%" d))) (program ped () (fork (pedestrian) 1)) (=defun capture (city) (take city) (setpri 1) (yield (fortify city))) (=defun plunder (city) (loot city) (ransom city)) (defun take (c) (format t "Liberating ~A.~%" c)) (defun fortify (c) (format t "Rebuilding ~A.~%" c)) (defun loot (c) (format t "Nationalizing ~A.~%" c)) (defun ransom (c) (format t "Refinancing ~A.~%" c)) (program barbarians () (fork (capture 'rome) 100) (fork (plunder 'rome) 98)) ;;;; ******************************************************************************** ;;;; Chapter 23, Parsing with ATNs ;;;; ******************************************************************************** (defmacro defnode (name &rest arcs) `(=defun ,name (pos regs) (choose ,@arcs))) (defmacro down (sub next &rest cmds) `(=bind (* pos regs) (,sub pos (cons nil regs)) (,next pos ,(compile-cmds cmds)))) (defvar *sent*) (defmacro cat (cat next &rest cmds) `(if (= (length *sent*) pos) (fail) (let ((* (nth pos *sent*))) (if (member ',cat (types *)) (,next (1+ pos) ,(compile-cmds cmds)) (fail))))) (defmacro jump (next &rest cmds) `(,next pos ,(compile-cmds cmds))) (eval-when (:compile-toplevel :load-toplevel :execute); LMH (defun compile-cmds (cmds) (if (null cmds) 'regs `(,@(car cmds) ,(compile-cmds (cdr cmds)))))) (defmacro up (expr) `(let ((* (nth pos *sent*))) (=values ,expr pos (cdr regs)))) (defmacro getr (key &optional (regs 'regs)) `(let ((result (cdr (assoc ',key (car ,regs))))) (if (cdr result) result (car result)))) (defmacro set-register (key val regs) `(cons (cons (cons ,key ,val) (car ,regs)) (cdr ,regs))) (defmacro setr (key val regs) `(set-register ',key (list ,val) ,regs)) (defmacro pushr (key val regs) `(set-register ',key (cons ,val (cdr (assoc ',key (car ,regs)))) ,regs)) (defmacro with-parses (node sent &body body) (with-gensyms (pos regs) `(progn (setq *sent* ,sent) (setq *paths* nil) (=bind (parse ,pos ,regs) (,node 0 '(nil)) (if (= ,pos (length *sent*)) (progn ,@body (fail)) (fail)))))) (defun types (word) (case word ((do does did) '(aux v)) ((time times) '(n v)) ((fly flies) '(n v)) ((like) '(v prep)) ((liked likes) '(v)) ((a an the) '(det)) ((arrow arrows) '(n)) ((i you he she him her it) '(pron)))) (defnode mods (cat n mods/n (setr mods *))) (defnode mods/n (cat n mods/n (pushr mods *)) (up `(n-group ,(getr mods)))) (defnode np (cat det np/det (setr det *)) (jump np/det (setr det nil)) (cat pron pron (setr n *))) (defnode pron (up `(np (pronoun ,(getr n))))) (defnode np/det (down mods np/mods (setr mods *)) (jump np/mods (setr mods nil))) (defnode np/mods (cat n np/n (setr n *))) (defnode np/n (up `(np (det ,(getr det)) (modifiers ,(getr mods)) (noun ,(getr n)))) (down pp np/pp (setr pp *))) (defnode np/pp (up `(np (det ,(getr det)) (modifiers ,(getr mods)) (noun ,(getr n)) ,(getr pp)))) (defnode pp (cat prep pp/prep (setr prep *))) (defnode pp/prep (down np pp/np (setr op *))) (defnode pp/np (up `(pp (prep ,(getr prep)) (obj ,(getr op))))) (defnode s (down np s/subj (setr mood 'decl) (setr subj *)) (cat v v (setr mood 'imp) (setr subj '(np (pron you))) (setr aux nil) (setr v *))) (defnode s/subj (cat v v (setr aux nil) (setr v *))) (defnode v (up `(s (mood ,(getr mood)) (subj ,(getr subj)) (vcl (aux ,(getr aux)) (v ,(getr v))))) (down np s/obj (setr obj *))) (defnode s/obj (up `(s (mood ,(getr mood)) (subj ,(getr subj)) (vcl (aux ,(getr aux)) (v ,(getr v))) (obj ,(getr obj))))) ;;;; ******************************************************************************** ;;;; Chapter 24, Prolog ;;;; ******************************************************************************** (defmacro with-inference (query &body body) `(progn (setq *paths* nil) (=bind (binds) (prove-query ',(rep_ query) nil) (let ,(mapcar #'(lambda (v) `(,v (fullbind ',v binds))) (vars-in query #'atom)) ,@body (fail))))) (defun rep_ (x) (if (atom x) (if (eq x '_) (gensym "?") x) (cons (rep_ (car x)) (rep_ (cdr x))))) (defun fullbind (x b) (cond ((varsym? x) (aif2 (binding x b) (fullbind it b) (gensym))) ((atom x) x) (t (cons (fullbind (car x) b) (fullbind (cdr x) b))))) (defun varsym? (x) (and (symbolp x) (eq (char (symbol-name x) 0) #\?))) (defmacro with-inference (query &rest body) (let ((vars (vars-in query #'simple?)) (gb (gensym))) `(with-gensyms ,vars (setq *paths* nil) (=bind (,gb) ,(gen-query (rep_ query)) (let ,(mapcar #'(lambda (v) `(,v (fullbind ,v ,gb))) vars) ,@body) (fail))))) (defun varsym? (x) (and (symbolp x) (not (symbol-package x)))) (defun gen-query (expr &optional binds) (case (car expr) (and (gen-and (cdr expr) binds)) (or (gen-or (cdr expr) binds)) (not (gen-not (cadr expr) binds)) (t `(prove (list ',(car expr) ,@(mapcar #'form (cdr expr))) ,binds)))) (defun gen-and (clauses binds) (if (null clauses) `(=values ,binds) (let ((gb (gensym))) `(=bind (,gb) ,(gen-query (car clauses) binds) ,(gen-and (cdr clauses) gb))))) (defun gen-or (clauses binds) `(choose ,@(mapcar #'(lambda (c) (gen-query c binds)) clauses))) (defun gen-not (expr binds) (let ((gpaths (gensym))) `(let ((,gpaths *paths*)) (setq *paths* nil) (choose (=bind (b) ,(gen-query expr binds) (setq *paths* ,gpaths) (fail)) (progn (setq *paths* ,gpaths) (=values ,binds)))))) (defvar *rules* nil) (=defun prove (query binds) (choose-bind r *rules* (=funcall r query binds))) (defun form (pat) (if (simple? pat) pat `(cons ,(form (car pat)) ,(form (cdr pat))))) (defmacro <- (con &rest ant) (let ((ant (if (= (length ant) 1) (car ant) `(and ,@ant)))) `(length (conc1f *rules* ,(rule-fn (rep_ ant) (rep_ con)))))) (defun rule-fn (ant con) (with-gensyms (val win fact binds) `(=lambda (,fact ,binds) (with-gensyms ,(vars-in (list ant con) #'simple?) (multiple-value-bind (,val ,win) (match ,fact (list ',(car con) ,@(mapcar #'form (cdr con))) ,binds) (if ,win ,(gen-query ant val) (fail))))))) (defun rule-fn (ant con) (with-gensyms (val win fact binds paths) ; `(=lambda (,fact ,binds ,paths) ; (with-gensyms ,(vars-in (list ant con) #'simple?) (multiple-value-bind (,val ,win) (match ,fact (list ',(car con) ,@(mapcar #'form (cdr con))) ,binds) (if ,win ,(gen-query ant val paths) ; (fail))))))) (defmacro with-inference (query &rest body) (let ((vars (vars-in query #'simple?)) (gb (gensym))) `(with-gensyms ,vars (setq *paths* nil) (=bind (,gb) ,(gen-query (rep_ query) nil '*paths*) ; (let ,(mapcar #'(lambda (v) `(,v (fullbind ,v ,gb))) vars) ,@body) (fail))))) (defun gen-query (expr binds paths) ; (case (car expr) (and (gen-and (cdr expr) binds paths)) ; (or (gen-or (cdr expr) binds paths)) ; (not (gen-not (cadr expr) binds paths)) ; (lisp (gen-lisp (cadr expr) binds)) ; (is (gen-is (cadr expr) (third expr) binds)) ; (cut `(progn (setq *paths* ,paths) ; (=values ,binds))) ; (t `(prove (list ',(car expr) ,@(mapcar #'form (cdr expr))) ,binds *paths*)))) ; (=defun prove (query binds paths) ; (choose-bind r *rules* (=funcall r query binds paths))) ; (defun gen-and (clauses binds paths) ; (if (null clauses) `(=values ,binds) (let ((gb (gensym))) `(=bind (,gb) ,(gen-query (car clauses) binds paths); ,(gen-and (cdr clauses) gb paths))))) ; (defun gen-or (clauses binds paths) ; `(choose ,@(mapcar #'(lambda (c) (gen-query c binds paths)) ; clauses))) (defun gen-not (expr binds paths) ; (let ((gpaths (gensym))) `(let ((,gpaths *paths*)) (setq *paths* nil) (choose (=bind (b) ,(gen-query expr binds paths) ; (setq *paths* ,gpaths) (fail)) (progn (setq *paths* ,gpaths) (=values ,binds)))))) (defmacro with-binds (binds expr) `(let ,(mapcar #'(lambda (v) `(,v (fullbind ,v ,binds))) (vars-in expr)) ,expr)) (defun gen-lisp (expr binds) `(if (with-binds ,binds ,expr) (=values ,binds) (fail))) (defun gen-is (expr1 expr2 binds) `(aif2 (match ,expr1 (with-binds ,binds ,expr2) ,binds) (=values it) (fail))) ;;;; ******************************************************************************** ;;;; Chapter 25, Object-Oriented LISP ;;;; ******************************************************************************** (defun rget (obj prop) (some2 #'(lambda (a) (gethash prop a)) (get-ancestors obj))) (defun get-ancestors (obj) (labels ((getall (x) (append (list x) (mapcan #'getall (gethash 'parents x))))) (stable-sort (delete-duplicates (getall obj)) #'(lambda (x y) (member y (gethash 'parents x)))))) (defun some2 (fn lst) (if (atom lst) nil (multiple-value-bind (val win) (funcall fn (car lst)) (if (or val win) (values val win) (some2 fn (cdr lst)))))) (defun obj (&rest parents) (let ((obj (make-hash-table))) (setf (gethash 'parents obj) parents) (ancestors obj) obj)) (defun ancestors (obj) (or (gethash 'ancestors obj) (setf (gethash 'ancestors obj) (get-ancestors obj)))) (defun rget (obj prop) (some2 #'(lambda (a) (gethash prop a)) (ancestors obj))) (defmacro defprop (name &optional meth?) `(progn (defun ,name (obj &rest args) ,(if meth? `(run-methods obj ',name args) `(rget obj ',name))) (defsetf ,name (obj) (val) `(setf (gethash ',',name ,obj) ,val)))) (defun run-methods (obj name args) (let ((meth (rget obj name))) (if meth (apply meth obj args) (error "No ~A method for ~A." name obj)))) (defstruct meth around before primary after) (defmacro meth- (field obj) (let ((gobj (gensym))) `(let ((,gobj ,obj)) (and (meth-p ,gobj) (,(symb 'meth- field) ,gobj))))) (defun run-methods (obj name args) (let ((pri (rget obj name :primary))) (if pri (let ((ar (rget obj name :around))) (if ar (apply ar obj args) (run-core-methods obj name args pri))) (error "No primary ~A method for ~A." name obj)))) (defun run-core-methods (obj name args &optional pri) (multiple-value-prog1 (progn (run-befores obj name args) (apply (or pri (rget obj name :primary)) obj args)) (run-afters obj name args))) (defun rget (obj prop &optional meth (skip 0)) (some2 #'(lambda (a) (multiple-value-bind (val win) (gethash prop a) (if win (case meth (:around (meth- around val)) (:primary (meth- primary val)) (t (values val win)))))) (nthcdr skip (ancestors obj)))) (defun run-befores (obj prop args) (dolist (a (ancestors obj)) (let ((bm (meth- before (gethash prop a)))) (if bm (apply bm obj args))))) (defun run-afters (obj prop args) (labels ((rec (lst) (when lst (rec (cdr lst)) (let ((am (meth- after (gethash prop (car lst))))) (if am (apply am (car lst) args)))))) (rec (ancestors obj)))) (defmacro defmeth ((name &optional (type :primary)) obj parms &body body) (let ((gobj (gensym))) `(let ((,gobj ,obj)) (defprop ,name t) (unless (meth-p (gethash ',name ,gobj)) (setf (gethash ',name ,gobj) (make-meth))) (setf (,(symb 'meth- type) (gethash ',name ,gobj)) ,(build-meth name type gobj parms body))))) (defun build-meth (name type gobj parms body) (let ((gargs (gensym))) `#'(lambda (&rest ,gargs) (labels ((call-next () ,(if (or (eq type :primary) (eq type :around)) `(cnm ,gobj ',name (cdr ,gargs) ,type) '(error "Illegal call-next."))) (next-p () ,(case type (:around `(or (rget ,gobj ',name :around 1) (rget ,gobj ',name :primary))) (:primary `(rget ,gobj ',name :primary 1)) (t nil)))) (apply #'(lambda ,parms ,@body) ,gargs))))) (defun cnm (obj name args type) (case type (:around (let ((ar (rget obj name :around 1))) (if ar (apply ar obj args) (run-core-methods obj name args)))) (:primary (let ((pri (rget obj name :primary 1))) (if pri (apply pri obj args) (error "No next method.")))))) (defmacro undefmeth ((name &optional (type :primary)) obj) `(setf (,(symb 'meth- type) (gethash ',name ,obj)) nil)) (defmacro children (obj) `(gethash 'children ,obj)) (defun parents (obj) (gethash 'parents obj)) (defun set-parents (obj pars) (dolist (p (parents obj)) (setf (children p) (delete obj (children p)))) (setf (gethash 'parents obj) pars) (dolist (p pars) (pushnew obj (children p))) (maphier #'(lambda (obj) (setf (gethash 'ancestors obj) (get-ancestors obj))) obj) pars) (defsetf parents set-parents) (defun maphier (fn obj) (funcall fn obj) (dolist (c (children obj)) (maphier fn c))) (defun obj (&rest parents) (let ((obj (make-hash-table))) (setf (parents obj) parents) obj)) (defmacro defcomb (name op) `(progn (defprop ,name t) (setf (get ',name 'mcombine) ,(case op (:standard nil) (:progn '#'(lambda (&rest args) (car (last args)))) (t op))))) (defun run-core-methods (obj name args &optional pri) (let ((comb (get name 'mcombine))) (if comb (if (symbolp comb) (funcall (case comb (:and #'comb-and) (:or #'comb-or)) obj name args (ancestors obj)) (comb-normal comb obj name args)) (multiple-value-prog1 (progn (run-befores obj name args) (apply (or pri (rget obj name :primary)) obj args)) (run-afters obj name args))))) (defun comb-normal (comb obj name args) (apply comb (mapcan #'(lambda (a) (let* ((pm (meth- primary (gethash name a))) (val (if pm (apply pm obj args)))) (if val (list val)))) (ancestors obj)))) (defun comb-and (obj name args ancs &optional (last t)) (if (null ancs) last (let ((pm (meth- primary (gethash name (car ancs))))) (if pm (let ((new (apply pm obj args))) (and new (comb-and obj name args (cdr ancs) new))) (comb-and obj name args (cdr ancs) last))))) (defun comb-or (obj name args ancs) (and ancs (let ((pm (meth- primary (gethash name (car ancs))))) (or (and pm (apply pm obj args)) (comb-or obj name args (cdr ancs)))))) (defmacro undefmethod (name &rest args) (if (consp (car args)) (udm name nil (car args)) (udm name (list (car args)) (cadr args)))) (defun udm (name qual specs) (let ((classes (mapcar #'(lambda (s) `(find-class ',s)) specs))) `(remove-method (symbol-function ',name) (find-method (symbol-function ',name) ',qual (list ,@classes))))) (defun compall () (do-symbols (s) (when (fboundp s) (unless (compiled-function-p (symbol-function s)) (print s) (compile s))))) (defmacro check (expr) `(block nil (with-inference ,expr (return t)))) ; This code is copyright 1993 by Paul Graham, but anyone who wants ; to use the code in any nonprofit activity, or distribute free ; verbatim copies (including this notice), is encouraged to do so.