emacs-diffs
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

master 18491f48d97: Install cond*


From: Richard M. Stallman
Subject: master 18491f48d97: Install cond*
Date: Fri, 2 Aug 2024 12:04:21 -0400 (EDT)

branch: master
commit 18491f48d973c9cbc453d9f742ec7f73e83df3bb
Author: Richard Stallman <rms@gnu.org>
Commit: Richard Stallman <rms@gnu.org>

    Install cond*
    
    * oond-star.el: New file.
---
 lisp/emacs-lisp/cond-star.el | 707 +++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 707 insertions(+)

diff --git a/lisp/emacs-lisp/cond-star.el b/lisp/emacs-lisp/cond-star.el
new file mode 100644
index 00000000000..6309b0d1a15
--- /dev/null
+++ b/lisp/emacs-lisp/cond-star.el
@@ -0,0 +1,707 @@
+;;; -*-lexical-binding: t; -*-
+
+;; Copyright (C) 1985-2024 Free Software Foundation, Inc.
+
+;; Maintainer: rms@gnu.org
+;; Package: emacs
+
+;; This file is part of GNU Emacs.  It implements `cond*'.
+
+;; cond* is free software: you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; cond* is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.
+
+;; Here is the list of functions the generated code is known to call:
+;; car, cdr, car-safe, cdr-safe, nth, nthcdr, null, eq, equal, eql, =,
+;; vectorp, length.
+;; It also uses these control and binding promitives:
+;; and, or, if, progn, let, let*, setq.
+;; For regexp matching only, it can call string-match and match-string.
+
+;;; ??? If a clause starts with a keyword,
+;;; should the element after the kwyword be treated in the usual way
+;;; as a pattern?  Curently `cond*-non-exit-clause-substance' explicitly
+;;; prevents that by adding t at the front of its value.
+
+(defmacro cond* (&rest clauses)
+  "Extended form of traditional Lisp `cond' construct.
+A `cond*' construct is a series of clauses, and a clause
+normally has the form (CONDITION BDOY...).
+
+CONDITION can be a Lisp expression, as in `cond'.
+Or it can be `(bind* BINDINGS...)' or `(match* PATTERN DATUM)'.
+
+`(bind* BINDINGS...)' means to bind BINDINGS (as if they were in `let*')
+for the body of the clause.  As a condition, it counts as true
+if the first binding's value is non-nil.  All the bindings are made
+unconditionally for whatever scope they cover.
+
+`(match* PATTERN DATUM)' means to match DATUM against the pattern PATTERN
+The condition counts as true if PATTERN matches DATUM.
+
+When a clause's condition is true, and it exits the `cond*'
+or is the last clause, the value of the last expression
+in its body becomes the return value of the `cond*' construct.
+
+Mon-exit clause:
+
+If a clause has only one element, or if its first element is
+t, or if it ends with the keyword :non-exit, then
+this clause never exits the `cond*' construct.  Instead,
+control falls through to the next clause (if any).
+The bindings made in CONDITION for the BODY of the non-exit clause
+are passed along to the rest of the clauses in this `cond*' construct.
+
+\\[match*\\] for documentation of the patterns for use in `match*'."
+  (cond*-convert clauses))
+
+(defmacro match* (pattern datum)
+  "This specifies matching DATUM against PATTERN.
+It is not really a LIsp function, and it is meaningful
+only in the CONDITION of a `cond*' clause.
+
+`_' matches any value.
+KEYWORD matches that keyword.
+nil  matches nil.
+t    matches t.
+SYMBOL matches any value and binds SYMBOL to that value.
+  If SYMBOL has been matched and bound earlier in this pattern,
+  it matches here the same value that it matched before.
+REGEXP matches a string if REGEXP matches it.
+  The match must cover the entire string from its first char to its last.
+ATOM (meaning any other kind of non-list not described above)
+  matches anything `equal' to it.
+(rx REGEXP) uses a regexp specified in s-expression form,
+  as in the function `rx', and matches the data that way.
+(rx REGEXP SYM0 SYM1...) uses a regexp specified in s-expression form,
+  and binds the symbols SYM0, SYM1, and so on
+  to (match-string 0 DATUM), (match-string 1 DATUM), and so on.
+  You can use as many SYMs as regexp matching supports.
+
+`OBJECT  matches any value `equal' to OBJECT.
+(cons CARPAT CDRPAT)
+  matches a cons cell if CARPAT matches its car and CDRPAT matches its cdr.
+(list ELTPATS...)
+  matches a list if the ELTPATS match its elements.
+  The first ELTPAT should match the list's first element.
+  The second ELTPAT should match the list's second element.  And so on.
+(vector ELTPATS...)
+  matches a vector if the ELTPATS match its elements.
+  The first ELTPAT should match the vector's first element.
+  The second ELTPAT should match the vector's second element.  And so on.
+(cdr PATTERN)  matches PATTERN with strict checking of cdrs.
+  That means that `list' patterns verify that the final cdr is nil.
+  Strict checking is the default.
+(cdr-safe PATTERN)  matches PATTERN with lax checking of cdrs.
+  That means that `list' patterns do not examine the final cdr.
+(and CONJUNCTS...)  matches each of the CONJUNCTS against the same data.
+  If all of them match, this pattern succeeds.
+  If one CONJUNCT fails, this pattern fails and does not try more CONJUNCTS.
+(or DISJUNCTS...)  matches each of te DISJUNCTS against the same data.
+  If one DISJUNCT succeeds, this pattern succeeds
+  and does not try more DISJUNCTs.
+  If all of them fail, this pattern fails.
+(COND*-EXPANDER ...)
+  Here the car is a symbol that has a `cond*-expander' property
+  which defines how to handle it in a pattern.  The property value
+  is a function.  Trying to match such a pattern calls that
+  function with one argument, the pattern in question (including its car).
+  The function should return an equivalent pattern
+  to be matched inetead.
+(PREDICATE SYMBOL)
+  matches datum if (PREDICATE DATUM) is true,
+  then binds SYMBOL to DATUM.
+(PREDICATE SYMBOL MORE-ARGS...)
+  matches datum if (PREDICATE DATUM MORE-ARGS...) is true,
+  then binds SYMBOL to DATUM.
+  MORE-ARGS... can refer to symbols bound earlier in the pattern.
+(constrain SYMBOL EXP)
+  matches datum if the form EXP is true.
+  EXP can refer to symbols bound earlier in the pattern."
+  (ignore datum)
+  (byte-compile-warn-x pattern "`match*' used other than as a `cond*' 
condition"))
+
+(defun cond*-non-exit-clause-p (clause)
+  "If CLAUSE, a cond* clause, is a non-exit clause, return t."
+  (or (null (cdr-safe clause))   ;; clause has only one element.
+      (and (cdr-safe clause)
+           ;; Starts with t.
+           (or (eq (car clause) t)
+               ;; Begins with keyword.
+               (keywordp (car clause))))
+      ;; Ends with keyword.
+      (keywordp (car (last clause)))))
+
+(defun cond*-non-exit-clause-substance (clause)
+  "For a non-exit cond* clause CLAUSE, return its substance.
+This removes a final keyword if that's what makes CLAUSE non-exit."
+  (cond ((null (cdr-safe clause))   ;; clause has only one element.
+         clause)
+        ;; Starts with t or a keyword.
+        ;; Include t as the first element of the substancea
+        ;; so that the following element is not treated as a pattern.
+        ((and (cdr-safe clause)
+              (or (eq (car clause) t)
+                  (keywordp (car clause))))
+         ;; Standardize on t as the first element.
+         (cons t (cdr clause)))
+
+        ;; Ends with keyword.
+        ((keywordp (car (last clause)))
+         ;; Do NOT include the final keyword.
+         (butlast clause))))
+
+(defun cond*-convert (clauses)
+  "Process a list of cond* clauses, CLAUSES.
+Returns the equivalent Lisp expression."
+  (if clauses
+      (cond*-convert-clause (car-safe clauses) (cdr-safe clauses))))
+
+(defun cond*-convert-clause (clause rest)
+  "Process one `cond*' clause, CLAUSE.
+REST is the rest of the clauses of this cond* expression."
+  (if (cond*-non-exit-clause-p clause)
+      ;; Handle a non-exit clause.  Make its bindings active
+      ;; around the whole rest of this cond*, treating it as
+      ;; a condition whose value is always t, around the rest
+      ;; of this cond*.
+      (let ((substance (cond*-non-exit-clause-substance clause)))
+        (cond*-convert-condition
+         ;; Handle the first substantial element in the non-exit clause
+         ;; as a matching condition.
+         (car substance)
+         ;; Any following elements in the
+         ;; non-exit clause are just expressions.
+         (cdr substance)
+         ;; Remaining clauses will be UNCONDIT-CLAUSES:
+         ;; run unconditionally and handled as a cond* body.
+         rest
+         nil nil))
+    ;; Handle a normal (conditional exit) clauss.
+    (cond*-convert-condition (car-safe clause) (cdr-safe clause) nil
+                             rest (cond*-convert rest))))
+
+(defun cond*-convert-condition (condition true-exps uncondit-clauses rest 
iffalse)
+  "Process the condition part of one cond* clause.
+TRUE-EXPS is a list of Lisp expressions to be executed if this
+condition is true, and inside its bindings.
+UNCONDIT-CLAUSES is a list of cond*-clauses to be executed if this
+condition is true, and inside its bindings.
+This is used for non-exit clauses; it is nil for conditional-exit clauses.
+
+REST and IFFALSE are non-nil for conditional-exit clauses that are not final.
+REST is a list of clauses to process after this one if
+this one could have exited but does not exit.
+This is used for conditional exit clauses.
+IFFALSE is the value to compute after this one if
+this one could have exited but does not exit.
+This is used for conditional exit clauses."
+  (if (and uncondit-clauses rest)
+      (error "Clause is both exiting and non-exit"))
+  (let ((pat-type (car-safe condition)))
+    (cond ((eq pat-type 'bind*)
+           (let* ((bindings (cdr condition))
+                  (first-binding (car bindings))
+                  (first-variable (if (symbolp first-binding) first-binding
+                                   (car first-binding)))
+                  (first-value (if (symbolp first-binding) nil
+                                 (cadr first-binding)))
+                  (init-gensym (gensym "init"))
+                  ;; BINDINGS with the initial value of the first binding
+                  ;; replaced by INIT-GENSYM.
+                  (mod-bindings
+                   (cons (list first-variable init-gensym) (cdr bindings))))
+             ;;; ??? Here pull out all nontrivial initial values
+             ;;; ??? to compute them earlier.
+             (if rest
+                 ;; bind* starts an exiting clause which is not final.
+                 ;; Therefore, must run IFFALSE.
+                 `(let ((,init-gensym ,first-value))
+                    (if ,init-gensym
+                        (let* ,mod-bindings
+                          . ,true-exps)
+                      ;; Always calculate all bindings' initial values,
+                      ;; but the bindings must not cover IFFALSE.
+                      (let* ,mod-bindings nil)
+                      ,iffalse))
+               (if uncondit-clauses
+                   ;; bind* starts a non-exit clause which is not final.
+                   ;; Run the TRUE-EXPS if condition value is true.
+                   ;; Then always go on to run the UNCONDIT-CLAUSES.
+                   (if true-exps
+                       `(let ((,init-gensym ,first-value))
+;;; ??? Should we make the bindings a second time for the UNCONDIT-CLAUSES.
+;;; as the doc string says, for uniformity with match*?
+                          (let* ,mod-bindings
+                            (when ,init-gensym
+                              . ,true-exps)
+                            ,(cond*-convert uncondit-clauses)))
+                     `(let* ,bindings
+                        ,(cond*-convert uncondit-clauses)))
+                 ;; bind* starts a final clause.
+                 ;; If there are TRUE-EXPS, run them if condition succeeded.
+                 ;; Always make the bindings, in case the
+                 ;; initial values have side effects.
+                 `(let ((,init-gensym ,first-value))
+                    ;; Calculate all binding values unconditionally.
+                    (let* ,mod-bindings
+                      (when ,init-gensym
+                        . ,true-exps)))))))
+          ((eq pat-type 'match*)
+           (cond*-match condition true-exps uncondit-clauses iffalse))
+          (t
+           ;; Ordinary Lixp expression is the condition 
+           (if rest
+               ;; A nonfinal exiting clause.
+               ;; If condition succeeds, run the TRUE-EXPS.
+               ;; There are following clauses, so run IFFALSE
+               ;; if the condition fails.
+               `(if ,condition
+                    (progn . ,true-exps)
+                  ,iffalse)
+             (if uncondit-clauses
+                 ;; A non-exit clause.
+                 ;; If condition succeeds, run the TRUE-EXPS.
+                 ;; Then always go on to run the UNCONDIT-CLAUSES.
+                 `(progn (if ,condition
+                             (progn . ,true-exps))
+                         ,(cond*-convert uncondit-clauses))
+               ;; An exiting clause which is also final.
+               ;; If there are TRUE-EXPS, run them if CONDITION succeeds.
+               (if true-exps
+                   `(if ,condition (progn . ,true-exps))
+                 ;; Run and return CONDITION.
+                 condition)))))))
+
+(defun cond*-match (matchexp true-exps uncondit-clauses iffalse)
+  "Generate code to match a match* pattern PATTERN.
+Match it against data represented by the expression DATA.
+TRUE-EXPS, UNCONDIT-CLAUSES and IFFALSE have the same meanings
+as in `cond*-condition'."
+  (when (or (null matchexp) (null (cdr-safe matchexp))
+            (null (cdr-safe (cdr matchexp)))
+            (cdr-safe (cdr (cdr matchexp))))
+    (byte-compile-warn-x matchexp "Malformed (match* ...) expression"))
+  (let* (raw-result
+         (pattern (nth 1 matchexp))
+         (data (nth 2 matchexp))
+         expression
+         (inner-data data)
+         ;; Add backtrack aliases for or-subpatterns to cdr of this.
+         (backtrack-aliases (list nil))
+         run-true-exps
+         store-value-swap-outs retrieve-value-swap-outs
+         gensym)
+    ;; For now, always bind a gensym to the data to be matched.
+    (setq gensym (gensym "d") inner-data gensym)
+    ;; Process the whole pattern as a subpattern.
+    (setq raw-result (cond*-subpat pattern nil nil nil backtrack-aliases 
inner-data))
+    (setq expression (cdr raw-result))
+    ;; If there are conditional expressions and some
+    ;; unconditional clauses to follow,
+    ;; and the pattern bound some variables,
+    ;; copy their values into special aliases
+    ;; to be copied back at the start of the unonditional clauses.
+    (when (and uncondit-clauses true-exps 
+               (car raw-result))
+      (dolist (bound-var (car raw-result))
+        (push `(setq ,(gensym "ua") ,(car bound-var)) store-value-swap-outs)
+        (push `(,(car bound-var) ,(gensym "ua")) retrieve-value-swap-outs)))
+
+    ;; Make an expression to run the TRUE-EXPS inside our bindings.
+    (if store-value-swap-outs
+        ;; If we have to store those bindings' values in aliases
+        ;; for the UNCONDIT-CLAUSES, ;; do so inside these bindigs.
+        (setq run-true-exps
+              (cond*-bind-pattern-syms
+               (car raw-result)
+               `(prog1 (progn . ,true-exps) . ,store-value-swap-outs)))
+      (setq run-true-exps
+            (cond*-bind-pattern-syms
+             (car raw-result)
+             `(progn . ,true-exps))))
+    ;; Run TRUE-EXPS if match succeeded.  Bind our bindings around it.
+    (setq expression
+          (if (and (null run-true-exps) (null iffalse))
+              ;; We MUST compute the expression, even when no decision
+              ;; depends on its value, because it may call functions with
+              ;; side effects.
+              expression
+            `(if ,expression
+                 ,run-true-exps
+               ;; For a non-final exiting clause, run IFFALSE if match failed.
+               ;; Don't bind the bindings around it, since
+               ;; an exiting clause's bindings don't affect later clauses.
+               ,iffalse)))
+    ;; For a non-final non-exiting clause,
+    ;; always run the UNCONDIT-CLAUSES.
+    (if uncondit-clauses
+        (setq expression
+              `(progn ,expression 
+                      (cond*-bind-pattern-syms
+                       ,(if retrieve-value-swap-outs
+                            ;; If we saved the bindings' values after the
+                            ;; true-clauses, bind the same variables
+                            ;; here to the values we saved then.
+                            retrieve-value-swap-outs
+                          ;; Otherwise bind them to the values
+                          ;; they matched in the pattern.
+                          (car raw-result))
+                       (cond*-convert uncondit-clauses)))))
+    ;; Bind the backtrack-aliases if any.
+    ;; We need them bound for the TRUE-EXPS.
+    ;; It is harmless to bind them around IFFALSE
+    ;; because they are all gensyms anyway.
+    (if (cdr backtrack-aliases)
+        (setq expression
+              `(let ,(mapcar 'cdr (cdr backtrack-aliases))
+                 ,expression)))
+    (if retrieve-value-swap-outs
+        (setq expression
+              `(let ,(mapcar 'cadr retrieve-value-swap-outs)
+                 ,expression)))
+    ;; If we used a gensym, wrap on code to bind it.
+    (if gensym
+        (if (and (listp expression) (eq (car expression) 'progn))
+            `(let ((,gensym ,data)) . ,(cdr expression))
+          `(let ((,gensym ,data)) ,expression))
+      expression)))
+
+(defun cond*-bind-pattern-syms (bindings expr)
+  "Wrap EXPR in code to bind the BINDINGS.
+This is used for the bindings specified explicitly in match* patterns."
+  ;; They can't have side effects.   Skip them
+  ;; if we don't actually need them.
+  (if (equal expr '(progn))
+      nil
+    (if bindings
+        (if (eq (car expr) 'progn)
+            `(let* ,bindings . ,(cdr expr))
+          `(let* ,bindings ,expr))
+      expr)))
+
+(defvar cond*-debug-pattern nil)
+
+;;; ??? Structure type patterns not implemented yet.
+;;; ??? Probably should optimize the `nth' calls in handling `list'.
+
+(defun cond*-subpat (subpat cdr-ignore bindings inside-or backtrack-aliases 
data)
+  "Generate code to match the subpattern within `match*'.
+SUBPAT is the subpattern to handle.
+CDR-IGNORE if true means don't verify there are no extra elts in a list.
+BINDINGS is the list of bindings made by
+the containing and previous subpatterns of this pattern.
+Each element of BINDINGS must have the form (VAR VALUE).
+BACKTRACK-ALIASES is used to pass data upward.  Initial call should
+pass (list).  The cdr of this collects backtracking aliases made for
+variables bound within (or...) patterns so that the caller
+can bind them etc.  Each of them has the form (USER-SYMBOL . GENSYM).
+DATA is the expression for the data that this subpattern is
+supposed to match against.
+
+Return Value has the form (BINDINGS . CONDITION), where
+BINDINGS is the list of bindings to be made for SUBPAT
+plus the subpatterns that contain/precede it.
+Each element of BINDINGS has the form (VAR VALUE).
+CONDITION is the condition to be tested to decide
+whether SUBPAT (as well as the subpatterns that contain/precede it) matches,"
+  (if (equal cond*-debug-pattern subpat)
+      (debug))
+;;;  (push subpat subpat-log)
+  (cond ((eq subpat '_)
+         ;; _ as pattern makes no bindings and matches any data.
+         (cons bindings t))
+        ((memq subpat '(nil t))
+         (cons bindings `(eq ,subpat ,data)))
+        ((keywordp subpat)
+         (cons bindings `(eq ,subpat ,data)))
+        ((symbolp subpat)
+         (let ((this-binding (assq subpat bindings))
+               (this-alias (assq subpat (cdr backtrack-aliases))))
+           (if this-binding
+               ;; Variable already bound.
+               ;; Compare what this variable should be bound to
+               ;; to the data it is supposed to match.
+               ;; That is because we don't actually bind these bindings
+               ;; around the condition-testing expression.
+               (cons bindings `(equal ,(cadr this-binding) ,data))
+             (if inside-or
+                 (let (alias-gensym)
+                   (if this-alias
+                       ;; Inside `or' subpattern, if this symbol already 
+                       ;; has an alias for backtracking, just use that.
+                       ;; This means the symbol was matched
+                       ;; in a previous arm of the `or'.
+                       (setq alias-gensym (cdr this-alias))
+                     ;; Inside `or' subpattern but this symbol has no alias,
+                     ;; make an alias for it.
+                     (setq alias-gensym (gensym "ba"))
+                     (push (cons subpat alias-gensym) (cdr backtrack-aliases)))
+                   ;; Make a binding for the symbol, to its backtrack-alias,
+                   ;; and set the alias (a gensym) to nil.
+                   (cons `((,subpat ,alias-gensym) . ,bindings)
+                         `(setq ,alias-gensym ,data)))
+               ;; Not inside `or' subpattern: ask for a binding for this symbol
+               ;; and say it does match whatever datum.
+               (cons `((,subpat ,data) . ,bindings)
+                     t)))))
+        ;; Various constants.
+        ((numberp subpat)
+         (cons bindings `(eql ,subpat ,data)))
+        ;; Regular expressions as strings.
+        ((stringp subpat)
+         (cons bindings `(string-match ,(concat subpat "\\'") ,data)))
+        ;; All other atoms match with `equal'.
+        ((not (consp subpat))
+         (cons bindings `(equal ,subpat ,data)))
+        ((not (consp (cdr subpat)))
+         (byte-compile-warn-x subpat "%s subpattern with malformed or missing 
arguments" (car subpat)))
+        ;; Regular expressions specified as list structure.
+        ;; (rx REGEXP VARS...)
+        ((eq (car subpat) 'rx)
+         (let* ((rxpat (concat (rx-to-string (cadr subpat) t) "\\'"))
+                (vars (cddr subpat)) setqs (varnum 0)
+                (match-exp `(string-match ,rxpat ,data)))
+           (if (null vars)
+               (cons bindings match-exp)
+             ;; There are variables to bind to the matched substrings.
+             (if (> (length vars) 10)
+                 (byte-compile-warn-x vars "Too many variables specified for 
matched substrings"))
+             (dolist (elt vars)
+               (unless (symbolp elt)
+                 (byte-compile-warn-x vars "Non-symbol %s given as name for 
matched substring" elt)))
+             ;; Bind these variables to nil, before the pattern.
+             (setq bindings (nconc (mapcar 'list vars) bindings))
+             ;; Make the expressions to set the variables.
+             (setq setqs (mapcar
+                          (lambda (var)
+                            (prog1 `(setq ,var (match-string ,varnum ,data))
+                              (setq varnum (1+ varnum))))
+                          vars))
+             (cons bindings `(if ,match-exp
+                                 (progn ,@setqs t))))))
+        ;; Quoted object as constant to match with `eq' or `equal'.
+        ((eq (car subpat) 'quote)
+         (if (symbolp (car-safe (cdr-safe subpat)))
+             (cons bindings `(eq ,subpat ,data))
+           (cons bindings `(equal ,subpat ,data))))
+        ;; Match a call to `cons' by destructuring.
+        ((eq (car subpat) 'cons)
+         (let (car-result cdr-result car-exp cdr-exp)
+           (setq car-result
+                 (cond*-subpat (nth 1 subpat) cdr-ignore bindings inside-or 
backtrack-aliases `(car ,data)))
+           (setq bindings (car car-result)
+                 car-exp (cdr car-result))
+           (setq cdr-result
+                 (cond*-subpat (nth 2 subpat) cdr-ignore bindings inside-or 
backtrack-aliases `(cdr ,data)))
+           (setq bindings (car cdr-result)
+                 cdr-exp (cdr cdr-result))
+           (cons bindings
+                 (cond*-and `((consp ,data) ,car-exp ,cdr-exp)))))
+        ;; Match a call to `list' by destructuring.
+        ((eq (car subpat) 'list)
+         (let ((i 0) expressions)
+           ;; Check for bad structure of SUBPAT here?
+           (dolist (this-elt (cdr subpat))
+             (let ((result 
+                    (cond*-subpat this-elt cdr-ignore bindings inside-or 
backtrack-aliases `(nth ,i ,data))))
+               (setq bindings (car result))
+               (push `(consp ,(if (zerop i) data `(nthcdr ,i ,data)))
+                     expressions)
+               (setq i (1+ i))
+               (push (cdr result) expressions)))
+           ;; Verify that list ends here, if we are supposed to check that.
+           (unless cdr-ignore
+             (push `(null (nthcdr ,i ,data)) expressions))
+           (cons bindings (cond*-and (nreverse expressions)))))
+        ;; Match (apply 'vector (backquote-list* LIST...)), destructuring.
+        ((eq (car subpat) 'apply)
+         ;; We only try to handle the case generated by backquote.
+         ;; Convert it to a call to `vector' and handle that.
+         (let ((cleaned-up
+                `(vector . ,(cond*-un-backquote-list* (cdr (nth 2 subpat))))))
+           ;; (cdr (nth 2 subpat)) gets LIST as above.
+           (cond*-subpat cleaned-up
+                         cdr-ignore bindings inside-or backtrack-aliases 
data)))
+        ;; Match a call to `vector' by destructuring.
+        ((eq (car subpat) 'vector)
+         (let* ((elts (cdr subpat))
+                (length (length elts))
+                expressions (i 0))
+           (dolist (elt elts)
+             (let* ((result 
+                     (cond*-subpat elt cdr-ignore
+                                   bindings inside-or backtrack-aliases `(aref 
,i ,data))))
+               (setq i (1+ i))
+               (setq bindings (car result))
+               (push (cdr result) expressions)))
+           (cons bindings
+                 (cond*-and `((vectorp ,data) (= (length ,data) ,length)
+                              . ,(nreverse expressions))))))
+        ;; Subpattern to set the cdr-ignore flag
+        ((eq (car subpat) 'cdr-ignore)
+         (cond*-subpat (cadr subpat) t bindings inside-or backtrack-aliases 
data))
+        ;; Subpattern to clear the cdr-ignore flag
+        ((eq (car subpat) 'cdr)
+         (cond*-subpat (cadr subpat) nil bindings inside-or backtrack-aliases 
data))
+        ;; Handle conjunction subpatterns.
+        ((eq (car subpat) 'and)
+         (let (expressions)
+           ;; Check for bad structure of SUBPAT here?
+           (dolist (this-elt (cdr subpat))
+             (let ((result 
+                    (cond*-subpat this-elt cdr-ignore bindings inside-or 
backtrack-aliases data)))
+               (setq bindings (car result))
+               (push (cdr result) expressions)))
+           (cons bindings (cond*-and (nreverse expressions)))))
+        ;; Handle disjunction subpatterns.
+        ((eq (car subpat) 'or)
+         ;; The main complexity is unsetting the pattern variables
+         ;; that tentatively matche in an or-branch that later failed.
+         (let (expressions
+               (bindings-before-or bindings)
+               (aliases-before-or (cdr backtrack-aliases)))
+           ;; Check for bad structure of SUBPAT here?
+           (dolist (this-elt (cdr subpat))
+             (let* ((bindings bindings-before-or)
+                    bindings-to-clear expression
+                    result)
+               (setq result 
+                     (cond*-subpat this-elt cdr-ignore bindings t 
backtrack-aliases data))
+               (setq bindings (car result))
+               (setq expression (cdr result))
+               ;; Were any bindings made by this arm of the disjunction?
+               (when (not (eq bindings bindings-before-or))
+                 ;; Ok, arrange to clear their backtrack aliases
+                 ;; if this arm does not match.
+                 (setq bindings-to-clear bindings)
+                 (let (clearing)
+                   ;; For each of those bindings,
+                   (while (not (eq bindings-to-clear bindings-before-or))
+                     ;; Make an expression to set it to nil, in CLEARING.
+                     (let* ((this-variable (caar bindings-to-clear))
+                            (this-backtrack (assq this-variable
+                                                  (cdr backtrack-aliases))))
+                       (push `(setq ,(cdr this-backtrack) nil) clearing))
+                     (setq bindings-to-clear (cdr bindings-to-clear)))
+                   ;; Wrap EXPRESSION to clear those backtrack aliases
+                   ;; if EXPRESSION is false.
+                   (setq expression
+                         (if (null clearing)
+                             expression
+                           (if (null (cdr clearing))
+                               `(or ,expression
+                                    ,(car clearing))
+                             `(progn ,@clearing))))))
+               (push expression expressions)))
+           ;; At end of (or...), EACH variable bound by any arm
+           ;; has a backtrack alias gensym.  At run time, that gensym's value
+           ;; will be what was bound in the successful arm, or nil.
+           ;; Now make a binding for each variable from its alias gensym.
+           (let ((aliases (cdr backtrack-aliases)))
+             (while (not (eq aliases aliases-before-or))
+               (push `(,(caar aliases) ,(cdar aliases)) bindings)
+               (pop aliases)))
+           (cons bindings `(or . ,(nreverse expressions)))))
+        ;; Expand cond*-macro call, treat result as a subpattern.
+        ((get (car subpat) 'cond*-expander)
+         ;; Treat result as a subpattern.
+         (cond*-subpat (funcall (get (car subpat) 'cond*-expander) subpat)
+                       cdr-ignore bindings inside-or backtrack-aliases data))
+        ((macrop (car subpat))
+         (cond*-subpat (macroexpand subpat) cdr-ignore bindings inside-or 
backtrack-aliases data))
+        ;; Simple constrained variable, as in (symbolp x).
+        ((functionp (car subpat))
+         ;; Without this, nested constrained variables just work.
+         (unless (symbolp (cadr subpat))
+           (byte-compile-warn-x subpat "Complex pattern nested in constrained 
variable pattern"))
+         (let* ((rest-args (cddr subpat))
+                ;; Process VAR to get a binding for it.
+                (result (cond*-subpat (cadr subpat) cdr-ignore bindings 
inside-or backtrack-aliases data))
+                (new-bindings (car result))
+                (expression (cdr result))
+                (combined-exp
+                 (cond*-and (list `(,(car subpat) ,data . ,rest-args) 
expression))))
+
+           (cons new-bindings
+                 (cond*-bind-around new-bindings combined-exp))))
+        ;; Generalized constrained variable: (constrain VAR EXP)
+        ((eq (car subpat) 'constrain)
+         ;; Without this, nested constrained variables just work.
+         (unless (symbolp (cadr subpat))
+           (byte-compile-warn-x subpat "Complex pattern nested in constrained 
variable pattern"))
+         ;; Process VAR to get a binding for it.
+         (let ((result (cond*-subpat (cadr subpat) cdr-ignore bindings 
inside-or backtrack-aliases data)))
+           (cons (car result)
+                 ;; This is the test condition.
+                 (cond*-bind-around (car result) (nth 2 subpat)))))
+        (t 
+         (byte-compile-warn-x subpat "Undefined pattern type `%s' in `cond*'" 
(car subpat)))))
+
+;;; Subroutines of cond*-subpat.
+
+(defun cond*-bind-around (bindings exp)
+  "Wrap a `let*' around EXP, to bind those of BINDINGS used in EXP."
+  (let ((what-to-bind (cond*-used-within bindings exp)))
+    (if what-to-bind
+        `(let* ,(nreverse what-to-bind) ,exp)
+      exp)))
+
+(defun cond*-used-within (bindings exp)
+  "Return the list of those bindings in BINDINGS which EXP refers to.
+This operates naively and errs on the side of overinclusion,
+and does not distinguish function names from variable names.
+That is safe for the purpose this is used for."
+  (cond ((symbolp exp) 
+         (let ((which (assq exp bindings)))
+           (if which (list which))))
+        ((listp exp)
+         (let (combined (rest exp))
+           ;; Find the bindings used in each element of EXP
+           ;; and merge them together in COMBINED.
+           ;; It would be simpler to use dolist at each level,
+           ;; but this avoids errors from improper lists.
+           (while rest
+             (let ((in-this-elt (cond*-used-within bindings (car rest))))
+               (while in-this-elt
+                 ;; Don't insert the same binding twice.
+                 (unless (memq (car-safe in-this-elt) combined)
+                   (push (car-safe in-this-elt) combined))
+                 (pop in-this-elt)))
+             (pop rest))
+           combined))))
+
+;; Construct a simplified equivalent to `(and . ,CONJUNCTS),
+;; assuming that it will be used only as a truth value.
+;; We don't bother checking for nil in CONJUNCTS
+;; because that would not normally happen.
+(defun cond*-and (conjuncts)
+  (setq conjuncts (remq t conjuncts))
+  (if (null conjuncts)
+      t
+    (if (null (cdr conjuncts))
+        (car conjuncts)
+      `(and . ,conjuncts))))
+
+;; Convert the arguments in a form that calls `backquote-list*'
+;; into equivalent args to pass to `list'.
+;; We assume the last argument has the form 'LIST.
+;; That means quotify each of that list's elements,
+;; and preserve the other arguments in front of them.
+(defun cond*-un-backquote-list* (args)
+  (if (cdr args)
+      (cons (car args)
+            (cond*-un-backquote-list* (cdr args)))
+    (mapcar (lambda (x) (list 'quote x)) (cadr (car args)))))
+
+
+



reply via email to

[Prev in Thread] Current Thread [Next in Thread]