diff options
| author | Stefan Monnier | 2011-02-26 21:50:38 -0500 |
|---|---|---|
| committer | Stefan Monnier | 2011-02-26 21:50:38 -0500 |
| commit | 1f0816b69dfdbda486bf0329bbfb2e8ccee63d39 (patch) | |
| tree | c6ed9cdc04df06eea3d23aa98d06c45f86647445 /lisp | |
| parent | 53f963cf73d93a7d1dca07d4c338acd5b6c8cb2f (diff) | |
| download | emacs-1f0816b69dfdbda486bf0329bbfb2e8ccee63d39.tar.gz emacs-1f0816b69dfdbda486bf0329bbfb2e8ccee63d39.zip | |
* lisp/emacs-lisp/pcase.el (pcase--if): Try to invert test to reduce depth.
(pcase-mutually-exclusive-predicates): New var.
(pcase--split-consp, pcase--split-pred): Use it.
(pcase--split-equal, pcase--split-member): When splitting against
a pure predicate, run it to know the outcome.
(pcase--u1): Mark vars that are actually used.
(pcase--q1): Avoid introducing unused vars.
Diffstat (limited to 'lisp')
| -rw-r--r-- | lisp/ChangeLog | 10 | ||||
| -rw-r--r-- | lisp/emacs-lisp/pcase.el | 101 |
2 files changed, 97 insertions, 14 deletions
diff --git a/lisp/ChangeLog b/lisp/ChangeLog index d9f4c3c3ea2..c2731530e57 100644 --- a/lisp/ChangeLog +++ b/lisp/ChangeLog | |||
| @@ -1,3 +1,13 @@ | |||
| 1 | 2011-02-27 Stefan Monnier <monnier@iro.umontreal.ca> | ||
| 2 | |||
| 3 | * emacs-lisp/pcase.el (pcase--if): Try to invert test to reduce depth. | ||
| 4 | (pcase-mutually-exclusive-predicates): New var. | ||
| 5 | (pcase--split-consp, pcase--split-pred): Use it. | ||
| 6 | (pcase--split-equal, pcase--split-member): When splitting against | ||
| 7 | a pure predicate, run it to know the outcome. | ||
| 8 | (pcase--u1): Mark vars that are actually used. | ||
| 9 | (pcase--q1): Avoid introducing unused vars. | ||
| 10 | |||
| 1 | 2011-02-27 Jay Belanger <jay.p.belanger@gmail.com> | 11 | 2011-02-27 Jay Belanger <jay.p.belanger@gmail.com> |
| 2 | 12 | ||
| 3 | * calc/calc-ext.el (calc-init-extensions): | 13 | * calc/calc-ext.el (calc-init-extensions): |
diff --git a/lisp/emacs-lisp/pcase.el b/lisp/emacs-lisp/pcase.el index 3179672a3ec..0d5fd99db5d 100644 --- a/lisp/emacs-lisp/pcase.el +++ b/lisp/emacs-lisp/pcase.el | |||
| @@ -32,6 +32,14 @@ | |||
| 32 | ;; could be defined this way, as a shorthand for (pred (lambda (_) BOOLEXP)). | 32 | ;; could be defined this way, as a shorthand for (pred (lambda (_) BOOLEXP)). |
| 33 | ;; But better would be if we could define new ways to match by having the | 33 | ;; But better would be if we could define new ways to match by having the |
| 34 | ;; extension provide its own `pcase--split-<foo>' thingy. | 34 | ;; extension provide its own `pcase--split-<foo>' thingy. |
| 35 | ;; - provide something like (setq VAR) so a var can be set rather than | ||
| 36 | ;; let-bound. | ||
| 37 | ;; - provide a way to fallthrough to other cases. | ||
| 38 | ;; - try and be more clever to reduce the size of the decision tree, and | ||
| 39 | ;; to reduce the number of leafs that need to be turned into function: | ||
| 40 | ;; - first, do the tests shared by all remaining branches (it will have | ||
| 41 | ;; to be performed anyway, so better so it first so it's shared). | ||
| 42 | ;; - then choose the test that discriminates more (?). | ||
| 35 | ;; - ideally we'd want (pcase s ((re RE1) E1) ((re RE2) E2)) to be able to | 43 | ;; - ideally we'd want (pcase s ((re RE1) E1) ((re RE2) E2)) to be able to |
| 36 | ;; generate a lex-style DFA to decide whether to run E1 or E2. | 44 | ;; generate a lex-style DFA to decide whether to run E1 or E2. |
| 37 | 45 | ||
| @@ -209,6 +217,7 @@ of the form (UPAT EXP)." | |||
| 209 | (defun pcase--if (test then else) | 217 | (defun pcase--if (test then else) |
| 210 | (cond | 218 | (cond |
| 211 | ((eq else :pcase--dontcare) then) | 219 | ((eq else :pcase--dontcare) then) |
| 220 | ((eq then :pcase--dontcare) (debug) else) ;Can/should this ever happen? | ||
| 212 | ((eq (car-safe else) 'if) | 221 | ((eq (car-safe else) 'if) |
| 213 | (if (equal test (nth 1 else)) | 222 | (if (equal test (nth 1 else)) |
| 214 | ;; Doing a test a second time: get rid of the redundancy. | 223 | ;; Doing a test a second time: get rid of the redundancy. |
| @@ -223,6 +232,8 @@ of the form (UPAT EXP)." | |||
| 223 | `(cond (,test ,then) | 232 | `(cond (,test ,then) |
| 224 | ;; Doing a test a second time: get rid of the redundancy, as above. | 233 | ;; Doing a test a second time: get rid of the redundancy, as above. |
| 225 | ,@(remove (assoc test else) (cdr else)))) | 234 | ,@(remove (assoc test else) (cdr else)))) |
| 235 | ;; Invert the test if that lets us reduce the depth of the tree. | ||
| 236 | ((memq (car-safe then) '(if cond)) (pcase--if `(not ,test) else then)) | ||
| 226 | (t `(if ,test ,then ,else)))) | 237 | (t `(if ,test ,then ,else)))) |
| 227 | 238 | ||
| 228 | (defun pcase--upat (qpattern) | 239 | (defun pcase--upat (qpattern) |
| @@ -264,6 +275,22 @@ MATCH is the pattern that needs to be matched, of the form: | |||
| 264 | (defun pcase--and (match matches) | 275 | (defun pcase--and (match matches) |
| 265 | (if matches `(and ,match ,@matches) match)) | 276 | (if matches `(and ,match ,@matches) match)) |
| 266 | 277 | ||
| 278 | (defconst pcase-mutually-exclusive-predicates | ||
| 279 | '((symbolp . integerp) | ||
| 280 | (symbolp . numberp) | ||
| 281 | (symbolp . consp) | ||
| 282 | (symbolp . arrayp) | ||
| 283 | (symbolp . stringp) | ||
| 284 | (integerp . consp) | ||
| 285 | (integerp . arrayp) | ||
| 286 | (integerp . stringp) | ||
| 287 | (numberp . consp) | ||
| 288 | (numberp . arrayp) | ||
| 289 | (numberp . stringp) | ||
| 290 | (consp . arrayp) | ||
| 291 | (consp . stringp) | ||
| 292 | (arrayp . stringp))) | ||
| 293 | |||
| 267 | (defun pcase--split-match (sym splitter match) | 294 | (defun pcase--split-match (sym splitter match) |
| 268 | (case (car match) | 295 | (case (car match) |
| 269 | ((match) | 296 | ((match) |
| @@ -324,8 +351,14 @@ MATCH is the pattern that needs to be matched, of the form: | |||
| 324 | (cons `(and (match ,syma . ,(pcase--upat (car qpat))) | 351 | (cons `(and (match ,syma . ,(pcase--upat (car qpat))) |
| 325 | (match ,symd . ,(pcase--upat (cdr qpat)))) | 352 | (match ,symd . ,(pcase--upat (cdr qpat)))) |
| 326 | :pcase--fail))) | 353 | :pcase--fail))) |
| 327 | ;; A QPattern but not for a cons, can only go the `else' side. | 354 | ;; A QPattern but not for a cons, can only go to the `else' side. |
| 328 | ((eq (car-safe pat) '\`) (cons :pcase--fail nil)))) | 355 | ((eq (car-safe pat) '\`) (cons :pcase--fail nil)) |
| 356 | ((and (eq (car-safe pat) 'pred) | ||
| 357 | (or (member (cons 'consp (cadr pat)) | ||
| 358 | pcase-mutually-exclusive-predicates) | ||
| 359 | (member (cons (cadr pat) 'consp) | ||
| 360 | pcase-mutually-exclusive-predicates))) | ||
| 361 | (cons :pcase--fail nil)))) | ||
| 329 | 362 | ||
| 330 | (defun pcase--split-equal (elem pat) | 363 | (defun pcase--split-equal (elem pat) |
| 331 | (cond | 364 | (cond |
| @@ -337,7 +370,12 @@ MATCH is the pattern that needs to be matched, of the form: | |||
| 337 | ;; (or (integerp (cadr pat)) (symbolp (cadr pat)) | 370 | ;; (or (integerp (cadr pat)) (symbolp (cadr pat)) |
| 338 | ;; (consp (cadr pat))) | 371 | ;; (consp (cadr pat))) |
| 339 | ) | 372 | ) |
| 340 | (cons :pcase--fail nil)))) | 373 | (cons :pcase--fail nil)) |
| 374 | ((and (eq (car-safe pat) 'pred) | ||
| 375 | (symbolp (cadr pat)) | ||
| 376 | (get (cadr pat) 'side-effect-free) | ||
| 377 | (funcall (cadr pat) elem)) | ||
| 378 | (cons :pcase--succeed nil)))) | ||
| 341 | 379 | ||
| 342 | (defun pcase--split-member (elems pat) | 380 | (defun pcase--split-member (elems pat) |
| 343 | ;; Based on pcase--split-equal. | 381 | ;; Based on pcase--split-equal. |
| @@ -354,13 +392,39 @@ MATCH is the pattern that needs to be matched, of the form: | |||
| 354 | ;; (or (integerp (cadr pat)) (symbolp (cadr pat)) | 392 | ;; (or (integerp (cadr pat)) (symbolp (cadr pat)) |
| 355 | ;; (consp (cadr pat))) | 393 | ;; (consp (cadr pat))) |
| 356 | ) | 394 | ) |
| 357 | (cons :pcase--fail nil)))) | 395 | (cons :pcase--fail nil)) |
| 396 | ((and (eq (car-safe pat) 'pred) | ||
| 397 | (symbolp (cadr pat)) | ||
| 398 | (get (cadr pat) 'side-effect-free) | ||
| 399 | (let ((p (cadr pat)) (all t)) | ||
| 400 | (dolist (elem elems) | ||
| 401 | (unless (funcall p elem) (setq all nil))) | ||
| 402 | all)) | ||
| 403 | (cons :pcase--succeed nil)))) | ||
| 358 | 404 | ||
| 359 | (defun pcase--split-pred (upat pat) | 405 | (defun pcase--split-pred (upat pat) |
| 360 | ;; FIXME: For predicates like (pred (> a)), two such predicates may | 406 | ;; FIXME: For predicates like (pred (> a)), two such predicates may |
| 361 | ;; actually refer to different variables `a'. | 407 | ;; actually refer to different variables `a'. |
| 362 | (if (equal upat pat) | 408 | (cond |
| 363 | (cons :pcase--succeed :pcase--fail))) | 409 | ((equal upat pat) (cons :pcase--succeed :pcase--fail)) |
| 410 | ((and (eq 'pred (car upat)) | ||
| 411 | (eq 'pred (car-safe pat)) | ||
| 412 | (or (member (cons (cadr upat) (cadr pat)) | ||
| 413 | pcase-mutually-exclusive-predicates) | ||
| 414 | (member (cons (cadr pat) (cadr upat)) | ||
| 415 | pcase-mutually-exclusive-predicates))) | ||
| 416 | (cons :pcase--fail nil)) | ||
| 417 | ;; ((and (eq 'pred (car upat)) | ||
| 418 | ;; (eq '\` (car-safe pat)) | ||
| 419 | ;; (symbolp (cadr upat)) | ||
| 420 | ;; (or (symbolp (cadr pat)) (stringp (cadr pat)) (numberp (cadr pat))) | ||
| 421 | ;; (get (cadr upat) 'side-effect-free) | ||
| 422 | ;; (progn (message "Trying predicate %S" (cadr upat)) | ||
| 423 | ;; (ignore-errors | ||
| 424 | ;; (funcall (cadr upat) (cadr pat))))) | ||
| 425 | ;; (message "Simplify pred %S against %S" upat pat) | ||
| 426 | ;; (cons nil :pcase--fail)) | ||
| 427 | )) | ||
| 364 | 428 | ||
| 365 | (defun pcase--fgrep (vars sexp) | 429 | (defun pcase--fgrep (vars sexp) |
| 366 | "Check which of the symbols VARS appear in SEXP." | 430 | "Check which of the symbols VARS appear in SEXP." |
| @@ -433,6 +497,7 @@ and otherwise defers to REST which is a list of branches of the form | |||
| 433 | ((eq upat 'dontcare) :pcase--dontcare) | 497 | ((eq upat 'dontcare) :pcase--dontcare) |
| 434 | ((functionp upat) (error "Feature removed, use (pred %s)" upat)) | 498 | ((functionp upat) (error "Feature removed, use (pred %s)" upat)) |
| 435 | ((memq (car-safe upat) '(guard pred)) | 499 | ((memq (car-safe upat) '(guard pred)) |
| 500 | (if (eq (car upat) 'pred) (put sym 'pcase-used t)) | ||
| 436 | (destructuring-bind (then-rest &rest else-rest) | 501 | (destructuring-bind (then-rest &rest else-rest) |
| 437 | (pcase--split-rest | 502 | (pcase--split-rest |
| 438 | sym (apply-partially #'pcase--split-pred upat) rest) | 503 | sym (apply-partially #'pcase--split-pred upat) rest) |
| @@ -459,6 +524,7 @@ and otherwise defers to REST which is a list of branches of the form | |||
| 459 | (pcase--u1 matches code vars then-rest) | 524 | (pcase--u1 matches code vars then-rest) |
| 460 | (pcase--u else-rest)))) | 525 | (pcase--u else-rest)))) |
| 461 | ((symbolp upat) | 526 | ((symbolp upat) |
| 527 | (put sym 'pcase-used t) | ||
| 462 | (if (not (assq upat vars)) | 528 | (if (not (assq upat vars)) |
| 463 | (pcase--u1 matches code (cons (cons upat sym) vars) rest) | 529 | (pcase--u1 matches code (cons (cons upat sym) vars) rest) |
| 464 | ;; Non-linear pattern. Turn it into an `eq' test. | 530 | ;; Non-linear pattern. Turn it into an `eq' test. |
| @@ -466,6 +532,7 @@ and otherwise defers to REST which is a list of branches of the form | |||
| 466 | matches) | 532 | matches) |
| 467 | code vars rest))) | 533 | code vars rest))) |
| 468 | ((eq (car-safe upat) '\`) | 534 | ((eq (car-safe upat) '\`) |
| 535 | (put sym 'pcase-used t) | ||
| 469 | (pcase--q1 sym (cadr upat) matches code vars rest)) | 536 | (pcase--q1 sym (cadr upat) matches code vars rest)) |
| 470 | ((eq (car-safe upat) 'or) | 537 | ((eq (car-safe upat) 'or) |
| 471 | (let ((all (> (length (cdr upat)) 1)) | 538 | (let ((all (> (length (cdr upat)) 1)) |
| @@ -539,14 +606,20 @@ and if not, defers to REST which is a list of branches of the form | |||
| 539 | (pcase--split-rest sym | 606 | (pcase--split-rest sym |
| 540 | (apply-partially #'pcase--split-consp syma symd) | 607 | (apply-partially #'pcase--split-consp syma symd) |
| 541 | rest) | 608 | rest) |
| 542 | (pcase--if `(consp ,sym) | 609 | (let ((then-body (pcase--u1 `((match ,syma . ,(pcase--upat (car qpat))) |
| 543 | `(let ((,syma (car ,sym)) | 610 | (match ,symd . ,(pcase--upat (cdr qpat))) |
| 544 | (,symd (cdr ,sym))) | 611 | ,@matches) |
| 545 | ,(pcase--u1 `((match ,syma . ,(pcase--upat (car qpat))) | 612 | code vars then-rest))) |
| 546 | (match ,symd . ,(pcase--upat (cdr qpat))) | 613 | (pcase--if |
| 547 | ,@matches) | 614 | `(consp ,sym) |
| 548 | code vars then-rest)) | 615 | ;; We want to be careful to only add bindings that are used. |
| 549 | (pcase--u else-rest))))) | 616 | ;; The byte-compiler could do that for us, but it would have to pay |
| 617 | ;; attention to the `consp' test in order to figure out that car/cdr | ||
| 618 | ;; can't signal errors and our byte-compiler is not that clever. | ||
| 619 | `(let (,@(if (get syma 'pcase-used) `((,syma (car ,sym)))) | ||
| 620 | ,@(if (get symd 'pcase-used) `((,symd (cdr ,sym))))) | ||
| 621 | ,then-body) | ||
| 622 | (pcase--u else-rest)))))) | ||
| 550 | ((or (integerp qpat) (symbolp qpat) (stringp qpat)) | 623 | ((or (integerp qpat) (symbolp qpat) (stringp qpat)) |
| 551 | (destructuring-bind (then-rest &rest else-rest) | 624 | (destructuring-bind (then-rest &rest else-rest) |
| 552 | (pcase--split-rest sym (apply-partially 'pcase--split-equal qpat) rest) | 625 | (pcase--split-rest sym (apply-partially 'pcase--split-equal qpat) rest) |