Bug: Readtable-Case :INVERT does not work properly.

Thomas Russ <[email protected]> Wed, 16 Jul 2003 18:23:03 -0700
Newsgroups gmane.lisp.openmcl.bugs
Message-ID <[email protected]>
SUMMARY:
The problem is that readtable-case of :INVERT does not properly 
preserve the case of mixed case identifiers.

PRODUCTS AFFECTED:

    MCL 5.0;  MCL 5.0b;  OpenMCL 0.13
    MCL 4.3.5

    Possibly other older versions as well.

EXAMPLE:

(setf (readtable-case *readtable*) :invert)
(symbol-name (read-from-string "foo"))   =>  "FOO"      ; Correct
(symbol-name (read-from-string "FOO"))   =>  "foo"      ; Correct
(symbol-name (read-from-string "Foo"))   =>  "foo"      ; BUG

ANALYSIS:

The problem lies in the function %casify-token in the file 
ccl:l1;ppc;l1-reader.lisp which just gets the decision about whether to 
upcase or downcase wrong.  Below I give a corrected function that will 
fix the problem.  It changes the logic used to determine whether a 
mixed case identifier is seen or not so that it now works properly.

I note that packaging this as a patch is a little bit tricky because of 
the use of accessors and macros that were only defined in the 
compile-time environment when %casify-token was compiled.

CORRECTION:

A corrected version of %casify-token is shown below.  I am also 
attaching a patch file which sets up the proper compilation environment 
so that it can be compiled and then loaded.  The part of the function 
that I changed is the body of the
     (WHEN (EQ CASE :INVERT) ...)


(defun %casify-token (token escapes)
   (let* ((case (readtable-case *readtable*))
          (opos (token.opos token))
          (string (token.string token)))
     (declare (fixnum opos))
     (if (and (null escapes) (eq case :upcase))          ; Most common 
case, pardon the pun
       ; %strup is faster - boot probs tho
       (dotimes (i opos)
         (setf (%schar string i) (char-upcase (%schar string i))))
       (unless (eq case :preserve)
         (when (eq case :invert)
           (let* ((lower-seen nil)
                  (upper-seen nil))
             (do* ((i (1- opos) (1- i))
                   (esclist escapes)
                   (nextesc (if esclist (pop esclist) -1)))
                  ((< i 0) (if upper-seen (unless lower-seen (setq case 
:downcase))
                                          (when lower-seen (setq case 
:upcase))))
               (declare (fixnum i nextesc))
               (if (= nextesc i)
                 (setq nextesc (if esclist (pop esclist) -1))
                 (let* ((ch (%schar string i)))
                   (if (upper-case-p ch)
                     (setq upper-seen t)
                     (if (lower-case-p ch)
                       (setq lower-seen t))))))))
         (if (eq case :upcase)
           (do* ((i (1- opos) (1- i))
                   (nextesc (if escapes (pop escapes) -1)))
                ((< i 0))
             (declare (fixnum i nextesc))
             (if (= nextesc i)
                 (setq nextesc (if escapes (pop escapes) -1))
                 (setf (%schar string i) (char-upcase (%schar string 
i)))))
           (if (eq case :downcase)
             (do* ((i (1- opos) (1- i))
                   (nextesc (if escapes (pop escapes) -1)))
                ((< i 0))
             (declare (fixnum i nextesc))
             (if (= nextesc i)
                 (setq nextesc (if escapes (pop escapes) -1))
                 (setf (%schar string i) (char-downcase (%schar string 
i)))))))))))

It is also possible to code an earlier exit from the DO loop if a mixed 
case identifier was encountered.  It isn't clear that this is 
necessarily worthwhile, though, since it involves less work for mixed 
case identifiers, but more for single case identifiers.  It would allow 
a simplification of the termination clause of the DO* loop.  In any 
case, the code below could replace the LET* above if you want to have 
this particular enhancement.

(let* ((ch (%schar string i)))
   (cond ((upper-case-p ch)
          (if lower-seen
            (return)
            (setq upper-seen t)))
         ((lower-case-p ch)
          (if upper-seen
            (return)
            (setq lower-seen t)))))


--
Thomas A. Russ, Ph.D.  Senior Research Scientist             [email protected]
USC/Information Sciences Institute              WWW:  http://www.isi.edu
4676 Admiralty Way, Marina del Rey, CA 90292              (310) 448-8775

_______________________________________________
bug-openmcl mailing list
[email protected]
http://clozure.com/cgi-bin/mailman/listinfo/bug-openmcl
readtable-invert-fix.lisp (application/applefile, 538 B) - not displayed
readtable-invert-fix.lisp (text/plain, 2.2 KB)
(in-package "CCL")

;; Patch to fix readtable-case :INVERT

(let ((*WARN-IF-REDEFINE* NIL)
      (*WARN-IF-REDEFINE-KERNEL* NIL))

(eval-when (:compile-toplevel :execute)
(def-accessors %svref
  token.string
  token.ipos
  token.opos
  token.len
  token.extendp                       ; Is token.string extended ?
)
)

(defun %casify-token (token escapes)
  (let* ((case (readtable-case *readtable*))
         (opos (token.opos token))
         (string (token.string token)))
    (declare (fixnum opos))
    (if (and (null escapes) (eq case :upcase))          ; Most common case, pardon the pun
      ; %strup is faster - boot probs tho
      (dotimes (i opos)
        (setf (%schar string i) (char-upcase (%schar string i))))
      (unless (eq case :preserve)
        (when (eq case :invert)
          (let* ((lower-seen nil)
                 (upper-seen nil))
            (do* ((i (1- opos) (1- i))
                  (esclist escapes)
                  (nextesc (if esclist (pop esclist) -1)))
                 ((< i 0) (if upper-seen (unless lower-seen (setq case :downcase))
                                         (when lower-seen (setq case :upcase))))
              (declare (fixnum i nextesc))
              (if (= nextesc i)
                (setq nextesc (if esclist (pop esclist) -1))
                (let* ((ch (%schar string i)))
                  (if (upper-case-p ch)
                    (setq upper-seen t)
                    (if (lower-case-p ch)
                      (setq lower-seen t))))))))
        (if (eq case :upcase)
          (do* ((i (1- opos) (1- i))
                  (nextesc (if escapes (pop escapes) -1)))
               ((< i 0))
            (declare (fixnum i nextesc))
            (if (= nextesc i)
                (setq nextesc (if escapes (pop escapes) -1))
                (setf (%schar string i) (char-upcase (%schar string i)))))
          (if (eq case :downcase)
            (do* ((i (1- opos) (1- i))
                  (nextesc (if escapes (pop escapes) -1)))
               ((< i 0))
            (declare (fixnum i nextesc))
            (if (= nextesc i)
                (setq nextesc (if escapes (pop escapes) -1))
                (setf (%schar string i) (char-downcase (%schar string i)))))))))))

)  ; End LET