Rather than a new UNIFY** function, make UNIFY* work that way
Annotate for file match-block.lisp
2007-11-09 mantoniotti 1 ;;;; -*- Mode: Lisp -*-
13:43:20 ' 2
' 3 ;;;; match-block.lisp --
' 4 ;;;; Various macros built on top of the unifier: MATCH, MATCHING and MATCH-CASE.
2004-11-17 mantoniotti 5
22:19:54 ' 6 (in-package "UNIFY")
' 7
' 8 (defun clean-unify-var-name (v)
' 9 (assert (variablep v))
' 10 (intern (subseq (symbol-name v) 1)
' 11 (symbol-package v)))
' 12
2010-02-04 pix 13 (defun %template-for-match (template)
07:32:18 ' 14 (if (variablep template)
' 15 `',template ; Logical variables are special-cased.
' 16 template))
2004-11-17 mantoniotti 17
2010-02-04 pix 18 (defun %wrap-var-bindings (template environment-var forms)
07:32:18 ' 19 (let* ((template-vars (collect-template-vars template))
' 20 (bindings (loop for v in template-vars
' 21 nconc (list `(,v (find-variable-value ',v
' 22 ,environment-var))
' 23 `(,(clean-unify-var-name v) ,v)))))
' 24 `(let* ,bindings
' 25 (declare (ignorable ,@(mapcar #'first bindings)))
' 26 ,@forms)))
2010-01-12 pix 27
2004-11-17 mantoniotti 28 (defmacro match ((template object
22:19:54 ' 29 &key
2010-02-04 pix 30 (match-named nil)
07:32:18 ' 31 (substitution '(make-empty-environment))
2004-11-17 mantoniotti 32 (errorp t)
22:19:54 ' 33 (error-value nil))
' 34 &body forms)
2010-02-05 pix 35 "Sets up a lexical environment to evaluate FORMS after an unification.
03:21:40 ' 36
' 37 MATCH unifies a TEMPLATE and an OBJECT and then sets up a lexical
' 38 environment where the variables present in the template are bound
' 39 lexically. Note that both variable names '?FOO' and 'FOO' are bound
' 40 for convenience.
' 41
' 42 The MATCH form returns the values returned by the evaluation of the
' 43 last of the FORMS.
' 44
' 45 If ERRORP is non-NIL (the default) then the form raises a
' 46 UNIFICATION-FAILURE, otherwise the result of evaluating ERROR-VALUE,
' 47 whose default is NIL is returned. (Note that UNIFICATION-FAILUREs
' 48 raising from the evaluation of FORMS will also be caught and handled
' 49 according to ERRORP settings.)
' 50
' 51 If MATCH-NAMED is not NIL, then a surrounding BLOCK named MATCH-NAMED
' 52 is set up around the matching code.
' 53 "
2010-02-04 pix 54 (let ((env-var (gensym "UNIFICATION-ENV-"))
07:32:18 ' 55 (template (%template-for-match template))
' 56 )
' 57 `(block ,match-named
' 58 (handler-case
' 59 (let* ((,env-var (unify ,template ,object ,substitution))
' 60 )
' 61 ,(%wrap-var-bindings template env-var forms))
' 62
' 63 ;; Yes. The above is sligthly wasteful.
' 64
' 65 (unification-failure (uf)
' 66 (if ,errorp
' 67 (error uf)
' 68 ,error-value))
' 69 ))))
' 70
2009-04-15 mantoniotti 71
10:16:24 ' 72 (defmacro matchf ((template object
' 73 &key
2010-02-04 pix 74 (match-named nil)
07:32:18 ' 75 (substitution '(make-empty-environment))
2009-04-15 mantoniotti 76 (errorp t)
10:16:24 ' 77 (error-value nil))
' 78 &body forms)
2010-02-05 pix 79 "Sets up a lexical environment to evaluate FORMS after an unification.
2009-04-15 mantoniotti 80
2010-02-05 pix 81 MATCHF unifies a TEMPLATE and an OBJECT and then sets up a lexical
2009-04-15 mantoniotti 82 environment where the variables present in the template are bound
10:16:24 ' 83 lexically. Note that both variable names '?FOO' and 'FOO' are bound
' 84 for convenience.
' 85
' 86 MATCHF does not 'evaluate' TEMPLATE (note that using the #T syntax will
' 87 generate a template at read-time).
' 88
2010-02-05 pix 89 The MATCHF form returns the values returned by the evaluation of the
2009-04-15 mantoniotti 90 last of the FORMS.
10:16:24 ' 91
' 92 If ERRORP is non-NIL (the default) then the form raises a
' 93 UNIFICATION-FAILURE, otherwise the result of evaluating ERROR-VALUE,
' 94 whose default is NIL is returned. (Note that UNIFICATION-FAILUREs
2010-02-05 pix 95 raising from the evaluation of FORMS will also be caught and handled
2009-04-15 mantoniotti 96 according to ERRORP settings.)
10:16:24 ' 97
2010-02-05 pix 98 If MATCH-NAMED is not NIL, then a surrounding BLOCK named MATCH-NAMED
03:21:40 ' 99 is set up around the matching code.
' 100 "
2010-02-04 pix 101 (let ((env-var (gensym "UNIFICATION-ENV-"))
07:32:18 ' 102 (template (cond ((variablep template)
' 103 `',template) ; Logical variables are special-cased.
' 104 ((listp template) ; Same for lists.
' 105 (make-instance 'list-template
' 106 :spec (cons 'list template)))
' 107 ;`',template)
' 108 (t
' 109 template)))
' 110 )
' 111 ;; Logical variables and lists are special cased for convenience.
' 112 ;; Lists are especially inteded as abbreviation for destructuring.
' 113 `(block ,match-named
' 114 (handler-case
' 115 (let* ((,env-var (unify ,template ,object ,substitution))
' 116 )
' 117 ,(%wrap-var-bindings template env-var forms))
' 118
' 119 ;; Yes. The above is sligthly wasteful.
' 120
' 121 (unification-failure (uf)
' 122 (if ,errorp
' 123 (error uf)
' 124 ,error-value))
' 125 ))))
2004-11-17 mantoniotti 126
22:19:54 ' 127
' 128
' 129 (define-condition unification-non-exhaustive (unification-failure)
2010-02-04 pix 130 ())
2004-11-17 mantoniotti 131
22:19:54 ' 132
' 133 (defmacro matching ((&key errorp
2010-02-04 pix 134 (default-substitution
07:32:18 ' 135 (make-empty-environment))
' 136 (matching-named nil))
2010-01-15 pix 137 &body match-clauses)
2010-02-05 pix 138 "MATCHING sets up a COND-like environment for multiple template matching clauses.
03:21:40 ' 139
' 140 The syntax of MATCHING comprises a number of clauses of the form
' 141
' 142 <clause> ::= <regular-clause> | <default-clause>
' 143 <regular-clause> ::= ((<template> <form>) &body <forms>)
' 144 <default-clause> ::= (t &body <forms>)
' 145 | (otherwise &body <forms>)
' 146 <form> and <forms> are regular Common Lisp forms.
' 147 <template> is a unification template.
' 148
' 149 The full syntax of MATCHING is
' 150
' 151 matching (&key errorp default-substitution) <clauses>
' 152
' 153 Each clause evaluates its forms in an environment where the variables
' 154 present in the template are bound lexically. Note that both variable
' 155 names '?FOO' and 'FOO' are bound for convenience.
' 156
' 157 The values returned by the MATCHING form are those of the last form in
' 158 the first clause that satisfies the match test.
' 159
' 160 If ERRORP is non-NIL then if none of the regular clauses matches, then
' 161 an error of type UNIFICATION-NON-EXAUSTIVE is signalled, regardless of
' 162 any default clause. Otherwise, the default clause behaves as a
' 163 standard COND default clause. The default value of ERRORP is NIL.
' 164 "
2010-02-04 pix 165 (declare (ignore default-substitution)) ; For the time being.
07:32:18 ' 166 (labels ((%%match%% (clause-var template object forms substitution)
' 167 (let ((template (%template-for-match template))
' 168 )
' 169 `((setf ,clause-var
2010-01-21 pix 170 (unify* ,template ,object ,substitution))
2010-02-04 pix 171 ,(%wrap-var-bindings template clause-var forms))
07:32:18 ' 172 ))
' 173
' 174 (build-match-clause (match-clause match-env-var)
' 175 (destructuring-bind ((template object) &body forms)
' 176 match-clause
' 177 (%%match%% match-env-var
' 178 template
' 179 object
' 180 forms
' 181 '(make-empty-environment))))
' 182 )
' 183 (when (or (and (find t match-clauses :key #'first)
' 184 (find 'otherwise match-clauses :key #'first))
' 185 (> (count t match-clauses :key #'first) 1)
' 186 (> (count 'otherwise match-clauses :key #'first) 1))
' 187 (error 'program-error))
' 188 (let* ((default-clause (or (find t match-clauses
' 189 :key #'first)
' 190 (find 'otherwise match-clauses
' 191 :key #'first)))
' 192 (match-clauses (delete default-clause match-clauses)) ; EQL
' 193 ; test
' 194 ; suffices.
' 195 (env-var (gensym "UNIFICATION-ENV-"))
' 196 )
2004-11-17 mantoniotti 197
2010-02-04 pix 198 `(block ,matching-named
07:32:18 ' 199 (let (,env-var)
' 200 (declare (dynamic-extent ,env-var))
' 201 (cond ,@(mapcar (lambda (match-clause)
' 202 (build-match-clause match-clause
' 203 env-var))
' 204 match-clauses)
' 205 (,errorp
' 206 (error 'unification-non-exhaustive
' 207 :format-control "Non exhaustive matching."))
2010-01-25 pix 208 ,(when default-clause (cons t (cdr default-clause)))))))
2010-02-04 pix 209 ))
07:32:18 ' 210
' 211
' 212 (defmacro match-case ((object &key errorp default-substitution match-case-named)
2010-02-05 pix 213 &body clauses)
03:21:40 ' 214 "MATCH-CASE sets up a CASE-like environment for multiple template matching clauses.
' 215
' 216 The syntax of MATCH-CASE comprises a number of clauses of the form
2005-04-27 mantoniotti 217
20:41:56 ' 218 <clause> ::= <regular-clause> | <default-clause>
2010-02-05 pix 219 <regular-clause> ::= (<template> &body <forms>)
2005-04-27 mantoniotti 220 <default-clause> ::= (t &body <forms>)
20:41:56 ' 221 | (otherwise &body <forms>)
' 222 <form> and <forms> are regular Common Lisp forms.
' 223 <template> is a unification template.
' 224
2010-02-05 pix 225 The full syntax of MATCH-CASE is
03:21:40 ' 226
' 227 match-case <object> (&key errorp default-substitution) <clauses>
2005-04-27 mantoniotti 228
20:41:56 ' 229 Each clause evaluates its forms in an environment where the variables
' 230 present in the template are bound lexically. Note that both variable
' 231 names '?FOO' and 'FOO' are bound for convenience.
' 232
2010-02-05 pix 233 The values returned by the MATCH-CASE form are those of the last form in
2005-04-27 mantoniotti 234 the first clause that satisfies the match test.
20:41:56 ' 235
' 236 If ERRORP is non-NIL then if none of the regular clauses matches, then
' 237 an error of type UNIFICATION-NON-EXAUSTIVE is signalled, regardless of
' 238 any default clause. Otherwise, the default clause behaves as a
2010-02-05 pix 239 standard CASE default clause. The default value of ERRORP is NIL.
03:21:40 ' 240 "
2010-02-04 pix 241 (let ((object-var (gensym "OBJECT-VAR-")))
07:32:18 ' 242 `(let ((,object-var ,object))
' 243 (matching (:errorp ,errorp :default-substitution ,default-substitution :matching-named ,match-case-named)
' 244 ,@(mapcar
' 245 (lambda (clause)
' 246 `(,(if (member (first clause) '(t otherwise))
' 247 (first clause)
' 248 (list (first clause) object-var))
' 249 ,@(rest clause)))
' 250 clauses)))))
2005-04-27 mantoniotti 251
2009-12-17 mantoniotti 252
2010-02-04 pix 253 (defmacro matchf-case ((object &key errorp default-substitution match-case-named)
07:32:18 ' 254 &body clauses)
2010-02-05 pix 255 "MATCHF-CASE sets up a CASE-like environment for multiple template matching clauses.
03:21:40 ' 256
' 257 The syntax of MATCHF-CASE comprises a number of clauses of the form
' 258
' 259 <clause> ::= <regular-clause> | <default-clause>
' 260 <regular-clause> ::= (<template> &body <forms>)
' 261 <default-clause> ::= (t &body <forms>)
' 262 | (otherwise &body <forms>)
' 263 <form> and <forms> are regular Common Lisp forms.
' 264 <template> is a unification template.
' 265
' 266 The full syntax of MATCHF-CASE is
' 267
' 268 matchf-case <object> (&key errorp default-substitution) <clauses>
' 269
' 270 Each clause evaluates its forms in an environment where the variables
' 271 present in the template are bound lexically. Note that both variable
' 272 names '?FOO' and 'FOO' are bound for convenience.
' 273
' 274 The values returned by the MATCH-CASE form are those of the last form in
' 275 the first clause that satisfies the match test.
' 276
' 277 If ERRORP is non-NIL then if none of the regular clauses matches, then
' 278 an error of type UNIFICATION-NON-EXAUSTIVE is signalled, regardless of
' 279 any default clause. Otherwise, the default clause behaves as a
' 280 standard CASE default clause. The default value of ERRORP is NIL.
' 281
' 282 MATCHF-CASE behaves like MATCH-CASE, but the patterns are not
' 283 evaluated (i.e., it relies on MATCHF instead of MATCH to construct the
' 284 macro expansion.
' 285 "
2010-02-04 pix 286 (declare (ignore default-substitution)) ; For the time being.
07:32:18 ' 287 (let* ((object-var (gensym "OBJECT-VAR-"))
' 288 (otherwise-clause-present-p
' 289 (member (caar (last clauses)) '(t otherwise)))
' 290 (non-otherwise-clauses
' 291 (if otherwise-clause-present-p
' 292 (butlast clauses)
' 293 clauses))
' 294 (otherwise-clause
' 295 (if otherwise-clause-present-p
' 296 (first (last clauses))
' 297 (when errorp
' 298 `(t (error 'unification-non-exhaustive
' 299 :format-control "Non exhaustive matching.")))))
' 300 )
' 301 (labels ((generate-matchers (clauses)
' 302 (if (null clauses)
' 303 `(progn ,@(rest otherwise-clause))
' 304 (destructuring-bind (pattern &rest body)
' 305 (car clauses)
' 306 `(handler-case (matchf (,pattern ,object-var)
' 307 ,@body)
' 308 (unification-failure ()
' 309 ,(generate-matchers (cdr clauses))))))))
' 310 `(block ,match-case-named
' 311 (let ((,object-var ,object))
' 312 ,(generate-matchers non-otherwise-clauses))))))
2009-12-17 mantoniotti 313
2007-11-09 mantoniotti 314 ;;;;---------------------------------------------------------------------------
13:43:20 ' 315 ;;;; Testing.
' 316
' 317 #| Tests
' 318
' 319 (let ((n 42))
' 320 (matching ()
' 321 ((0 n) 1)
' 322 ((?x n) (* x (1- x)))))
' 323
' 324
' 325 (let ((n 42))
' 326 (match-case (n)
' 327 (0 1)
' 328 (?x (* x (1- x)))))
' 329
' 330
' 331 (let ((n 42))
' 332 (match-case (n)
' 333 (0 1)
' 334 (otherwise (* n (1- n)))))
' 335
' 336 (defun fatt (x)
' 337 (match-case (x :errorp t)
' 338 (0 1)
' 339 (#T(number ?n) (* ?n (fatt (1- n))))
' 340 ))
' 341
' 342 |#
2005-04-27 mantoniotti 343
2007-11-09 mantoniotti 344 ;;;; end of file -- math-blocks.lisp --