;;;
MATCH.CL -- a recursive pattern-matching function
;;; for use in
production-systems programming.
;;; (C) Copyright 1995 by Steven L.
Tanimoto.
;;; This program is described in Chapter 3 ("Productions
Systems
;;; and Pattern Matching") of
;;; "The Elements of
Artificial Intelligence Using Common Lisp," 2nd ed.,
;;; published by
W. H. Freeman, 41 Madison Ave., New York, NY 10010.
;;; Permission is
granted for noncommercial use and modification of
;;; this program,
provided that this copyright notice is retained
;;; and followed by a
notice of any modifications made to the program.
;;; (MATCH P S)
returns an association list of bindings
;;; e.g., ((X . 5) (Y A SEQUENCE
OF ELTS) (:YES . :YES)),
;;; that represents the pairings of variables of
P with
;;; components of S that put P into correspondence with S.
;;;
The substitution list always ends with (:YES . :YES)
;;; which represents
an empty substitution. The presence of this
;;; empty substitution
indicates that the match was successful.
;;; If matching is unsuccessful,
NIL is returned.
(defun match (p s)
"Attempt to find a correspondence between P and S,
utilizing
any special constructs
appearing in P. Return an
association
list of bindings if
successful; NIL otherwise."
(cond
((handle-both-null
p s))
((handle-normal-recursion
p s))
((atom (first p))
nil)
((handle-? p s))
((handle-* p s))
((handle-restrict-pred p s))
(t nil) ) )
(defun
1st-pattern-op (p)
"Return
the *, ? or predicate in the first pattern
construct of P."
(first (first p)) ) ; same as (CAAR P)
(defun
1st-pattern-variable (p)
"Return the variable in the first pattern
construct of P."
(first (rest (first p))) ) ; same as (CADAR
P)
(defun handle-both-null (p s)
"Test for and handle case when both P and S
are null."
(if (and (null p)(null s))
'((:yes . :yes)) ) )
(defun
handle-normal-recursion (p s)
"Test for and handle case when the first
elements of P and S are EQL."
(if (atom (first p))
(if (eql (first p)(first s))
(match (rest p)(rest s)) ) ) )
(defun
handle-? (p s)
"Test for and
handle the case when (FIRST P) is of
the form (? X)."
(if
s ; S must not be null
(if
(eql (1st-pattern-op p) '?)
(let ((rest-match
(match (rest p)(rest s)) ))
(if rest-match
(acons
(1st-pattern-variable p)
(first s)
rest-match) ) ) ) ) )
(defun
handle-* (p s)
"Test for and
handle the case when (FIRST P) is of
the form (* X)."
(if
(eql (1st-pattern-op p) '*)
(let ((pattern-variable
(1st-pattern-variable p) )
(rest-match nil) )
(cond ; subcase 1 --match 1 element of
S:
((and s
(setf rest-match
(match (rest p)
(rest s) ) )
)
(acons
pattern-variable
(list (first s))
rest-match) )
; subcase 2 --match no elements
of S:
((setf
rest-match (match (rest p) s))
(acons pattern-variable
nil
rest-match) )
; subcase 3 --match more than 1
elt of S:
((and
s
(setf rest-match
(match p (rest s)) ) )
(acons pattern-variable
(cons (first s)
(val
pattern-variable
rest-match) )
(rest rest-match)) )
(t nil) ) ) ) )
(defun
handle-restrict-pred (p s)
"Handle case when (FIRST P) is of the form
(PREDICATE X)."
(if s ; S must not be null
(if (member (1st-pattern-op p)
'(? *) ) ; Don't apply '? or
'*.
nil
(if (apply (1st-pattern-op p)
(list (first s)) )
(let ((rest-match
(match (rest p) (rest s))
)
(pattern-variable
(1st-pattern-variable p) ) )
(if rest-match
(acons pattern-variable
(first s)
rest-match) ) ) ) ) )
)
;;; The function VAL provides convenient access to
;;;
something matched by a variable after matching
;;; with MATCH.
(defun
val (variable alist)
"Return
the value associated with VARIABLE
on ALIST."
(rest
(assoc variable alist)) )