Newsgroups: comp.lang.scheme
Path: cantaloupe.srv.cs.cmu.edu!das-news2.harvard.edu!news2.near.net!news.mathworks.com!udel!gatech!howland.reston.ans.net!EU.net!sun4nl!phcoms4.seri.philips.nl!hpcssg!batelaan
From: batelaan@ukpsshp1.serigate.philips.nl (Wouter Batelaan)
Subject: A utility function to match lists and extract items from it.
Sender: news@ukpsshp1.serigate.philips.nl (CNews account)
Message-ID: <CzGnGz.J1G@ukpsshp1.serigate.philips.nl>
Date: Fri, 18 Nov 1994 11:19:47 GMT
Lines: 11
Organization: Philips Semiconductors, Southampton, UK
X-Newsreader: TIN [version 1.2 PL2]

Wrote this function recently, thought it may be useful to others. Let
me know. Perhaps someday it could be incorporated into SLIB.

--
 Wouter Batelaan, SDC, Philips Semiconductors, Southampton, U.K.
 email: batelaan@ukpsshp1.serigate.philips.nl (seri: batelaan@ukpsshp1)
 tel  : +44 703 316556; fax : +44 703 316303

;=============================================================================
; LMATCH - matching lists and extracting parts from it.
; The matcher is based on a pattern matcher found in the great language POP-11.
; (c) Wouter Batelaan (batelaan@ukpsshp1.serigate.philips.nl)
; $Id: lmatch.scm,v 1.2 1994/11/08 12:55:29 batelaan Exp batelaan $
; 
; Usage:
; - define some match variables, which will be used as place holders
;   in a format definition against which lists can be matched.
; - Call (lmatch format expr)
;   format = a list defining the structure of the expr.
;   It can be an any expression, usually containing one or more match variables.
;   Returns #t if matching is successful.
; - Use the match variables to obtain the part
;   of the matched list that you wanted to obtain.
; 
; There are 2 kinds of match variables:
; - variables that match exactly one item.
;   To define: (define <var> (lmatch-var <id>))
;   To obtain value: (lmatch-var-value <var>)
;   To initialise to unbound: (lmatch-unset-var! <var>)
;
; - variables which match one or more items.
;   To define: (define <lvar> (lmatch-lvar <id>))
;   To obtain value: (lmatch-var-value <lvar>)
;   To initialise to unbound: (lmatch-unset-var! <lvar>)
; 
; A match variable can appear more than once in a format spec:
; in this case a match is only found if all occurances of the variable 
; match the same item(s).
; 
; Examples:
; - We have expressions of the format (if <expr> then <expr> ... else <expr> ...)
;   Define some match variables:
;   (define condition (lmatch-var 'condition))
;   (define then-part (lmatch-lvar 'then-part))
;   (define else-part (lmatch-lvar 'else-part))
;   Call the matcher:
;   (match (list 'if condition 'then then-part 'else else-part) '(if (a) then (b) (c) else (d) (e)))
;   Returns #t,
;   (lmatch-var-value  condition) -> (a)
;   (lmatch-var-value then-part) -> ((b) (c))
;   (lmatch-var-value else-part) -> ((d) (e))
;   
; - A VHDL language example:
;   (define name (lmatch-var 'name))
;   (define ifce-spec (lmatch-lvar 'ifce-spec))
;   (define body (lmatch-lvar 'body))
;
;   (match (list 'entity name 'is ifce-spec 'begin body 'end name) 
;          '(entity X is port (P1 P1 : inout BIT) begin CheckTiming (P1 P2 4) end X))
;   -> #t
;
;   (lmatch-var-value name) -> X
;   (lmatch-var-value ifce-spec) -> (port (P1 P1 : inout BIT))
;   (lmatch-var-value body) -> (CheckTiming (P1 P2 4))
;   
;   (match `(entity ,name is ,ifce-spec begin ,body end ,name) 
;          '(entity X is port (P1 P1 : inout BIT) begin CheckTiming (P1 P2 4) end Y))
;   -> #f
;   Failure to match caused by last element being Y instead of X.
;   
;=============================================================================
; There are many ways to extend this type of matcher.
; If you implement one, please let me know, I can try to incorporate it,
; and create a powerful universal matcher.
;=============================================================================

;(display "Loading lmatch.s") (newline)

(define (lmatch-var name)
  (vector '*var* name '*unbound*)
  )

(define (lmatch-var? v)
  (and (vector? v) (eq? (vector-ref v 0) '*var*))
)

(define (lmatch-lvar name)
  (vector '*lvar* name '*unbound* '*unbound*)
)
   
(define (lmatch-lvar? v)
  (and (vector? v) (eq? (vector-ref v 0) '*lvar*))
)

(define (lmatch-var-name v)
  (vector-ref v 1)
)

(define (lmatch-var-value v)
  (vector-ref v 2)
)

(define (lmatch-unset-var! v)
  (vector-set! v 2 '*unbound*)
  (and (eq? (vector-ref v 0) '*lvar*) (vector-set! v 3 '*unbound*))
)

(define (lmatch-free? v)
  (eq? (vector-ref v 2) '*unbound*)
)

(define (lmatch-bound? v)
  (not (eq? (vector-ref v 2) '*unbound*))
)

(define (lmatch pattern expr)

  (define (set-var! v val)
    (vector-set! v 2 val)
  )
  
  (define (set-lvar! v val)
    (vector-set! v 2 val)
  )
  
  (define (init-lvar! v val)
    (let ((new-val (list val)))
      (vector-set! v 2 new-val)
      (vector-set! v 3 new-val)
    )
  )
  
  (define (append-lvar! v val)
    (let ((new-val (list val)))
      (set-cdr! (vector-ref v 3) new-val)
      (vector-set! v 3 new-val)
    )
  )
  
  (let ((var #f))
    (cond
     ((equal? pattern expr) #t)

     ((not (pair? pattern)) #f)

     ((lmatch-lvar? (car pattern))
      (set! var (car pattern))
      (if (lmatch-free? var)
	  (begin
	    (if (null? (cdr pattern))
		(begin
		  ; list var must match at least one element:
		  (if (null? expr)
		      #f
		      (begin
			(set-lvar! var expr)
			;(format #t "lvar ~a set to cdr of expr~%" var)
			#t
		      )
		  )
		)
		; else... there is something after this list var.
		(let ((rest-expr (cdr expr))
		      (result #f))
		  (init-lvar! var (car expr))
		  ;(format #t "lvar ~a set to elem 1 of expr~%" var)
		  (while (and (pair? rest-expr)
			      (not (lmatch (cdr pattern) rest-expr)))
			 (append-lvar! var (car rest-expr))
			 (set! rest-expr (cdr rest-expr))
			 ;(format #t "Appended 1 elem to lvar ~a~%" var)
		  )
		  ; Matched ok if, since there is some cdr pattern,
		  ; the rest-expr != nil:
		  (or (pair? rest-expr) 
		      (begin
			(lmatch-unset-var! var)
			#f
		      )
		  )
		)
	    )
	  )
	  ; else the var is bound
	  (let ((rest-expr expr)
		(value (var-value var))
		(lvar-matches #t))
	    ;(format #t "Var ~a is bound; expr = ~a~%" var expr)
	    (while
	     (and lvar-matches (pair? value) (pair? rest-expr))
	     (if (equal? (car value) (car rest-expr))
		 (begin
		   (set! value (cdr value))
		   (set! rest-expr  (cdr rest-expr))
		 ) 
		 ; else:
		 (set! lvar-matches #f)
	     )
	    )
	    (and lvar-matches (lmatch (cdr pattern) rest-expr))
	  )
      )
     )
     
     ; pattern not a list var, so expr can not be null:
     ((null? expr) #f)
     
     ; check if pattern starts with a var:
     ((lmatch-var? (car pattern))
      (set! var (car pattern))
      (if (lmatch-free? (car pattern))
          (begin
            ;(format #t "match: setting var ~a to value ~a~%" var (car expr))
            (set-var! var (car expr))
            (or (lmatch (cdr pattern) (cdr expr))
		(begin
		  (lmatch-unset-var! var)
		  #f
		)
	    )
	  )
	  ; else the var is bound:
	  (and (equal? (var-value (car pattern)) (car expr))
               (lmatch (cdr pattern) (cdr expr)))
      )
     )
     
     ; if both pattern and expr are pairs, we recurse:
     ((and (pair? pattern) (pair? expr))
      (and (lmatch (car pattern) (car expr))
	   (lmatch (cdr pattern) (cdr expr))
      )
     )
     ; No possible match!
     (#t #f)
    )
  )
) ; define lmatch

(provide 'lmatch)
 
