MATCH.LSP
(Newer syntax version)
#|
The final feature added in the series of Match functions is the ability
to match any sequence of elements of S. This form is written as (* X) and
is called a wild sequence construct. Unlike (? X), which can only
match one element of S and assign it to X, the element (* X) can match
zero or more elements of S and assign a list of the matched elements to X.
The * construct works as follows:
Subcase 1 allows the construct to match exactly one element of S, as if
the construct were the ? construct.
Subcase 2 handles the situation in which the * construct should match
zero elements of S, and so the overall match depends on whether the
(REST P) matches S.
Subcase 3 handles the case when the * construct should match more than
one element of S. It does this by taking one element of S (calling
recursively on its REST), while not taking the * construct itself,
which is implemented by calling recursively with P rather than
(REST P).
An example:
> (match '((* x) wild (? y) (* z))
returns: ((X * SPECIFIES A) (Y . CARD) (Z SEQUENCE ELEMENT) (:YES . :YES))
|#
;;; MATCH2000.LSP -- 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)) )