;; -*- comment-column: 32 -*- (eval-when-compile (require 'cl)) (put 'mcase 'lisp-indent-function 1) (put 'pmatch 'lisp-indent-function 2) (put 'mlet 'lisp-indent-function 2) (defmacro mcase (object &rest clauses) "Pattern-matching case expression. The syntax is like the normal `case': (mcase EXPR (PATTERN . BODY) ...) The body of the first matching pattern is executed, with pattern variables bound to their matching values. If no patterns match, an error is signaled. See `mlet' for a description of pattern syntax." `(mcase* ,object ,(mcase-parse-clauses clauses))) (eval-and-compile (defun mcase-parse-clauses (clauses) `(list ,@(mapcar #'(lambda (clause) `(list ',(car clause) (lambda () ,@(cdr clause)))) clauses)))) (defmacro pmatch (&rest args) "Deprecated; see `mlet'." `(mlet ,@args)) (defmacro mlet (pattern object &rest body) "Match PATTERN with OBJECT, and execute BODY with all bindings. The pattern syntax is: Trivial: t, nil, 42 Testing with `equal' Pattern variable: x, my-variable Variable that the pattern should bind. If the same variable appears several times in a pattern, then all of its bindings must match. Within the body of a successful pattern match, lisp variables are bound for all pattern variables. Constant: 'symbol, '(1 2 3), ... Quoted constant, matched with `equal'. Bound variable: ,var Pre-bound Lisp variable, matched by value. Wild card: _ (underscore) Matches anything, with no binding. Sequence: (pat1 ...), [pat1 ...] Matches the \"shape\" of the pattern, as well as each individual subpattern." (let ((var (make-symbol "var"))) `(let ((,var ,object)) ; so that we just eval `object' once (mcase ,var (,pattern ,@body) (_ (signal 'erl-exit-signal (list (tuple 'badmatch ',pattern ,var)))))))) (defun mcase* (object clauses) (let ((clause (mcase-choose object clauses))) (if clause (funcall clause) (signal 'erl-exit-signal '(case-clause))))) (defun mcase-choose (object clauses) (if (null clauses) nil (let* ((clause (car clauses)) (pattern (car clause)) (action (cadr clause)) (result (patmatch pattern object))) (if (eq result 'fail) (mcase-choose object (cdr clauses)) `(lambda () (let ,(alist-to-letlist result) (funcall ,action))))))) (defun alist-to-letlist (alist) "Convert an alist into `let' binding syntax, eg: ((A . B)) => ((A 'B))" (mapcar (lambda (cell) (list (car cell) (list 'quote (cdr cell)))) alist)) (defun pmatch-tail (seq) (if (consp seq) (cdr seq) (let ((new (make-vector (1- (length seq)) nil))) (dotimes (i (length new)) (aset new i (aref seq (1+ i)))) new))) (defun patmatch (pattern object &optional bindings) "Match OBJECT with PATTERN, and return an alist of bindings." (if (eq bindings 'fail) 'fail (cond ((pmatch-wildcard-p pattern) bindings) ((pmatch-constant-p pattern) ; '(x) (pmatch-constant pattern object bindings)) ((pmatch-bound-var-p pattern) ; ,foo (pmatch-match-var pattern object bindings)) ((pmatch-unbound-var-p pattern) ; foo (pmatch-bind-var pattern object bindings)) ((pmatch-trivial-p pattern) ; nil, t, any-symbol (if (equal pattern object) bindings 'fail)) ((consp pattern) (if (consp object) (patmatch (cdr pattern) (cdr object) (patmatch (car pattern) (car object) bindings)) 'fail)) ((vectorp pattern) (if (and (vectorp object) (= (length pattern) (length object))) (patmatch (coerce pattern 'list) (coerce object 'list) bindings) 'fail)) (t 'fail)))) (defun pmatch-wildcard-p (pat) (eq pat '_)) (defun pmatch-trivial-p (pat) "Test for patterns which can always be matched literally with `equal'." (or (numberp pat) (equal pat []) (equal pat nil) (equal pat t))) (defun pmatch-constant-p (pat) "Test for (quoted) constant patterns. Example: (QUOTE QUOTE)" (and (consp pat) (= (length pat) 2) (eq (car pat) 'quote))) (defun pmatch-constant-value (pat) "The value of a constant pattern. (QUOTE X) => X" (cadr pat)) (defun pmatch-constant (pat object bindings) "Match OBJECT with the constant pattern PAT." (if (equal (pmatch-constant-value pat) object) bindings 'fail)) (defun pmatch-unbound-var-p (obj) "Unbound variable is any symbol except nil or t." (and (symbolp obj) (not (eq obj nil)) (not (eq obj t)))) (defun pmatch-unbound-var-symbol (sym) sym) (defun pmatch-bind-var (pat object bindings) "Add a binding of pattern variable VAR to OBJECT in BINDINGS." (if (eq object erl-tag) ;; `erl-tag' cannot bind to a variable; this is to prevent pids ;; or ports from matching tuple patterns. 'fail (let* ((var (pmatch-unbound-var-symbol pat)) (binding (assoc var bindings))) (cond ((null binding) (acons var object bindings)) ((equal (cdr binding) object) bindings) (t 'fail))))) (eval-when-compile (defvar pattern)) ; dynamic (defun pmatch-match-var (var object bindings) "Match the value of the Lisp variable VAR with OBJECT." (if (equal (symbol-value (pmatch-bound-var-name pattern)) object) bindings 'fail)) (defun pmatch-bound-var-p (obj) (and (symbolp obj) (eq (elt (symbol-name obj) 0) ?,))) (defun pmatch-bound-var-name (sym) (intern (substring (symbol-name sym) 1))) (defun pmatch-alist-keysort (alist) (sort alist (lambda (a b) (string< (symbol-name (car a)) (symbol-name (car b)))))) ;;; Test suite (defun pmatch-expect (pattern object expected) "Assert that matching PATTERN with OBJECT yields EXPECTED. EXPECTED is either 'fail or a list of bindings (in any order)." (let ((actual (patmatch pattern object))) (if (or (and (eq actual 'fail) (eq actual expected)) (and (listp expected) (listp actual) (equal (pmatch-alist-keysort actual) (pmatch-alist-keysort expected)))) t (error "Patmatch: %S %S => %S, expected %S" pattern object actual expected)))) (defun pmatch-test () "Test the pattern matcher." (interactive) (pmatch-expect t t ()) (pmatch-expect '(t nil 1) '(t nil 1) ()) (let ((foo 'foo)) (pmatch-expect '(FOO ,foo 'foo [FOO]) '(foo foo foo [foo]) '((FOO . foo)))) (pmatch-expect 1 2 'fail) (pmatch-expect '(x x) '(1 2) 'fail) (pmatch-expect '_ '(1 2) 'nil) (assert (equal 'yes (mcase '(call 42 lists length ((1 2 3))) (t 'no) (1 'no) ((call Ref 'lists 'length (_)) 'yes) (_ 'no)))) (message "Smooth sailing")) (provide 'patmatch)