;;
|
|
;; SELECT-MATCH macro (and IN macro)
|
|
;;
|
|
;; Copyright 1990 Stephen Adams
|
|
;;
|
|
;; You are free to copy, distribute and make derivative works of this
|
|
;; source provided that this copyright notice is displayed near the
|
|
;; beginning of the file. No liability is accepted for the
|
|
;; correctness or performance of the code. If you modify the code
|
|
;; please indicate this fact both at the place of modification and in
|
|
;; this copyright message.
|
|
;;
|
|
;; Stephen Adams
|
|
;; Department of Electronics and Computer Science
|
|
;; University of Southampton
|
|
;; SO9 5NH, UK
|
|
;;
|
|
;; sra@ecs.soton.ac.uk
|
|
;;
|
|
|
|
;;
|
|
;; Synopsis:
|
|
;;
|
|
;; (select-match expression
|
|
;; (pattern action+)*)
|
|
;;
|
|
;; --- or ---
|
|
;;
|
|
;; (select-match expression
|
|
;; pattern => expression
|
|
;; pattern => expression
|
|
;; ...)
|
|
;;
|
|
;; pattern -> constant ;egs 1, #\x, #c(1.0 1.1)
|
|
;; | symbol ;matches anything
|
|
;; | 'anything ;must be EQUAL
|
|
;; | (pattern = pattern) ;both patterns must match
|
|
;; | (#'function pattern) ;predicate test
|
|
;; | (pattern . pattern) ;cons cell
|
|
;;
|
|
|
|
;; Example
|
|
;;
|
|
;; (select-match item
|
|
;; (('if e1 e2 e3) 'if-then-else) ;(1)
|
|
;; ((#'oddp k) 'an-odd-integer) ;(2)
|
|
;; (((#'treep tree) = (hd . tl)) 'something-else) ;(3)
|
|
;; (other 'anything-else)) ;(4)
|
|
;;
|
|
;; Notes
|
|
;;
|
|
;; . Each pattern is tested in turn. The first match is taken.
|
|
;;
|
|
;; . If no pattern matches, an error is signalled.
|
|
;;
|
|
;; . Constant patterns (things X for which (CONSTANTP X) is true, i.e.
|
|
;; numbers, strings, characters, etc.) match things which are EQUAL.
|
|
;;
|
|
;; . Quoted patterns (which are CONSTANTP) are constants.
|
|
;;
|
|
;; . Symbols match anything. The symbol is bound to the matched item
|
|
;; for the execution of the actions.
|
|
;; For example, (SELECT-MATCH '(1 2 3)
|
|
;; (1 . X) => X)
|
|
;; returns (2 3) because X is bound to the cdr of the candidate.
|
|
;;
|
|
;; . The two pattern match (p1 = p2) can be used to name parts
|
|
;; of the matched structure. For example, (ALL = (HD . TL))
|
|
;; matches a cons cell. ALL is bound to the cons cell, HD to its car
|
|
;; and TL to its tail.
|
|
;;
|
|
;; . A predicate test applies the predicate to the item being matched.
|
|
;; If the predicate returns NIL then the match fails.
|
|
;; If it returns truth, then the nested pattern is matched. This is
|
|
;; often just a symbol like K in the example.
|
|
;;
|
|
;; . Care should be taken with the domain values for predicate matches.
|
|
;; If, in the above eg, item is not an integer, an error would occur
|
|
;; during the test. A safer pattern would be
|
|
;; (#'integerp (#'oddp k))
|
|
;; This would only test for oddness of the item was an integer.
|
|
;;
|
|
;; . A single symbol will match anything so it can be used as a default
|
|
;; case, like OTHER above.
|
|
;;
|
|
|
|
(in-package swank/match)
|
|
|
|
(defmacro match (expression &body patterns)
|
|
`(select-match ,expression ,@patterns))
|
|
|
|
(defmacro select-match (expression &rest patterns)
|
|
(let* ((do-let (not (atom expression)))
|
|
(key (if do-let (gensym) expression))
|
|
(cbody (expand-select-patterns key patterns))
|
|
(cform `(cond . ,cbody)))
|
|
(if do-let
|
|
`(let ((,key ,expression)) ,cform)
|
|
cform)))
|
|
|
|
(defun expand-select-patterns (key patterns)
|
|
(if (eq (second patterns) '=>)
|
|
(expand-select-patterns-style-2 key patterns)
|
|
(expand-select-patterns-style-1 key patterns)))
|
|
|
|
(defun expand-select-patterns-style-1 (key patterns)
|
|
(if (null patterns)
|
|
`((t (error "Case select pattern match failure on ~S" ,key)))
|
|
(let* ((pattern (caar patterns))
|
|
(actions (cdar patterns))
|
|
(rest (cdr patterns))
|
|
(test (compile-select-test key pattern))
|
|
(bindings (compile-select-bindings key pattern actions)))
|
|
`(,(if bindings `(,test (let ,bindings . ,actions))
|
|
`(,test . ,actions))
|
|
. ,(unless (eq test t)
|
|
(expand-select-patterns-style-1 key rest))))))
|
|
|
|
(defun expand-select-patterns-style-2 (key patterns)
|
|
(cond ((null patterns)
|
|
`((t (error "Case select pattern match failure on ~S" ,key))))
|
|
(t (when (or (< (length patterns) 3)
|
|
(not (eq (second patterns) '=>)))
|
|
(error "Illegal patterns: ~S" patterns))
|
|
(let* ((pattern (first patterns))
|
|
(actions (list (third patterns)))
|
|
(rest (cdddr patterns))
|
|
(test (compile-select-test key pattern))
|
|
(bindings (compile-select-bindings key pattern actions)))
|
|
`(,(if bindings `(,test (let ,bindings . ,actions))
|
|
`(,test . ,actions))
|
|
. ,(unless (eq test t)
|
|
(expand-select-patterns-style-2 key rest)))))))
|
|
|
|
(defun compile-select-test (key pattern)
|
|
(let ((tests (remove t (compile-select-tests key pattern))))
|
|
(cond
|
|
;; note AND does this anyway, but this allows us to tell if
|
|
;; the pattern will always match.
|
|
((null tests) t)
|
|
((= (length tests) 1) (car tests))
|
|
(t `(and . ,tests)))))
|
|
|
|
(defun compile-select-tests (key pattern)
|
|
(cond ((constantp pattern) `((,(cond ((numberp pattern) 'eql)
|
|
((symbolp pattern) 'eq)
|
|
(t 'equal))
|
|
,key ,pattern)))
|
|
((symbolp pattern) '(t))
|
|
((select-double-match? pattern)
|
|
(append
|
|
(compile-select-tests key (first pattern))
|
|
(compile-select-tests key (third pattern))))
|
|
((select-predicate? pattern)
|
|
(append
|
|
`((,(second (first pattern)) ,key))
|
|
(compile-select-tests key (second pattern))))
|
|
((consp pattern)
|
|
(append
|
|
`((consp ,key))
|
|
(compile-select-tests (cs-car key) (car
|
|
pattern))
|
|
(compile-select-tests (cs-cdr key) (cdr
|
|
pattern))))
|
|
(t (error "Illegal select pattern: ~S" pattern))))
|
|
|
|
|
|
(defun compile-select-bindings (key pattern action)
|
|
(cond ((constantp pattern) '())
|
|
((symbolp pattern)
|
|
(if (select-in-tree pattern action)
|
|
`((,pattern ,key))
|
|
'()))
|
|
((select-double-match? pattern)
|
|
(append
|
|
(compile-select-bindings key (first pattern) action)
|
|
(compile-select-bindings key (third pattern) action)))
|
|
((select-predicate? pattern)
|
|
(compile-select-bindings key (second pattern) action))
|
|
((consp pattern)
|
|
(append
|
|
(compile-select-bindings (cs-car key) (car pattern)
|
|
action)
|
|
(compile-select-bindings (cs-cdr key) (cdr pattern)
|
|
action)))))
|
|
|
|
(defun select-in-tree (atom tree)
|
|
(or (eq atom tree)
|
|
(if (consp tree)
|
|
(or (select-in-tree atom (car tree))
|
|
(select-in-tree atom (cdr tree))))))
|
|
|
|
(defun select-double-match? (pattern)
|
|
;; (<pattern> = <pattern>)
|
|
(and (consp pattern) (consp (cdr pattern)) (consp (cddr pattern))
|
|
(null (cdddr pattern))
|
|
(eq (second pattern) '=)))
|
|
|
|
(defun select-predicate? (pattern)
|
|
;; ((function <f>) <pattern>)
|
|
(and (consp pattern)
|
|
(consp (cdr pattern))
|
|
(null (cddr pattern))
|
|
(consp (first pattern))
|
|
(consp (cdr (first pattern)))
|
|
(null (cddr (first pattern)))
|
|
(eq (caar pattern) 'function)))
|
|
|
|
(defun cs-car (exp)
|
|
(cs-car/cdr 'car exp
|
|
'((car . caar) (cdr . cadr) (caar . caaar) (cadr . caadr)
|
|
(cdar . cadar) (cddr . caddr)
|
|
(caaar . caaaar) (caadr . caaadr) (cadar . caadar)
|
|
(caddr . caaddr) (cdaar . cadaar) (cdadr . cadadr)
|
|
(cddar . caddar) (cdddr . cadddr))))
|
|
|
|
(defun cs-cdr (exp)
|
|
(cs-car/cdr 'cdr exp
|
|
'((car . cdar) (cdr . cddr) (caar . cdaar) (cadr . cdadr)
|
|
(cdar . cddar) (cddr . cdddr)
|
|
(caaar . cdaaar) (caadr . cdaadr) (cadar . cdadar)
|
|
(caddr . cdaddr) (cdaar . cddaar) (cdadr . cddadr)
|
|
(cddar . cdddar) (cdddr . cddddr))))
|
|
|
|
(defun cs-car/cdr (op exp table)
|
|
(if (and (consp exp) (= (length exp) 2))
|
|
(let ((replacement (assoc (car exp) table)))
|
|
(if replacement
|
|
`(,(cdr replacement) ,(second exp))
|
|
`(,op ,exp)))
|
|
`(,op ,exp)))
|
|
|
|
;; (setf c1 '(select-match x (a 1) (b 2 3 4)))
|
|
;; (setf c2 '(select-match (car y)
|
|
;; (1 (print 100) 101) (2 200) ("hello" 5) (:x 20) (else (1+
|
|
;; else))))
|
|
;; (setf c3 '(select-match (caddr y)
|
|
;; ((all = (x y)) (list x y all))
|
|
;; ((a '= b) (list 'assign a b))
|
|
;; ((#'oddp k) (1+ k)))))
|
|
|
|
|