:directory
'(:relative :up :up "output")
:name name :type type)
- sb!xc:*compile-file-truename*)))
- (let ((character-database
- (with-open-file (stream (file "ucd" "dat")
+ sb!xc:*compile-file-truename*))
+ (read-ub8-vector (pathname)
+ (with-open-file (stream pathname
:direction :input
:element-type '(unsigned-byte 8))
(let* ((length (file-length stream))
length :element-type '(unsigned-byte 8))))
(read-sequence array stream)
array))))
+ (let ((character-database (read-ub8-vector (file "ucd" "dat")))
+ (decompositions (read-ub8-vector (file "decomp" "dat")))
+ (long-decompositions (read-ub8-vector (file "ldecomp" "dat"))))
`(progn
- (declaim (type (simple-array (unsigned-byte 8) (*)) **character-database**))
+ (declaim (type (simple-array (unsigned-byte 8) (*)) **character-database** **character-decompositions** **character-long-decompositions**))
(defglobal **character-database** ,character-database)
+ (defglobal **character-decompositions** ,decompositions)
+ (defglobal **character-long-decompositions** ,long-decompositions)
(defun !character-database-cold-init ()
(setf **character-database** ,character-database))
,(with-open-file (stream (file "ucd-names" "lisp-expr")
\f
;;;; UCD accessor functions
-;;; The first (* 8 206) => 1648 entries in **CHARACTER-DATABASE**
+;;; The first (* 8 395) => 3160 entries in **CHARACTER-DATABASE**
;;; contain entries for the distinct character attributes:
;;; specifically, indexes into the GC kinds, Bidi kinds, CCC kinds,
;;; the decimal digit property, the digit property and the
;;; the next (ash #x110000 -8) entries contain single-byte indexes
;;; into a table of 256-element 4-byte-sized entries. These entries
;;; follow directly on, and are of the form
-;;; {attribute-index[1B],transformed-code-point[3B]}x256, where the
+;;; {attribute-index[11b],transformed-code-point[21b]}x256, where the
;;; attribute index is an index into the miscellaneous information
;;; table, and the transformed code point is the code point of the
;;; simple mapping of the character to its lowercase or uppercase
;;;
;;; To look up information about a character, take the high 13 bits of
;;; its code point, and index the character database with that and a
-;;; base of 1648 (going past the miscellaneous information[*], so
+;;; base of 3160 (going past the miscellaneous information[*], so
;;; treating (a) as the start of the array). This, labelled A, gives
;;; us another index into the detailed pages[-], which we can use to
;;; look up the details for the character in question: we add the low
;;; 8 bits of the character, shifted twice (because we have four-byte
-;;; table entries) to 1024 times the `page' index, with a base of 6000
+;;; table entries) to 1024 times the `page' index, with a base of 6088
;;; to skip over everything else. This gets us to point B. If we're
;;; after a transformed code point (i.e. an upcase or downcase
;;; operation), we can simply read it off now, beginning with an
-;;; offset of 1 byte from point B in some endianness; if we're looking
-;;; for miscellaneous information, we take the value at B, and index
-;;; the character database once more to get to the relevant
-;;; miscellaneous information.
+;;; offset of 11 bits from point B in some endianness; if we're
+;;; looking for miscellaneous information, we take the 11-bit value at
+;;; B, and index the character database once more to get to the
+;;; relevant miscellaneous information.
+;;;
+;;; As an optimization to the common case (pun intended) of looking up
+;;; case information for a character, the entries in C above are
+;;; sorted such that the characters which are UPPER-CASE-P in CL terms
+;;; have index values lower than all others, followed by those which
+;;; are LOWER-CASE-P in CL terms; this permits implementation of
+;;; character case tests without actually going to the trouble of
+;;; looking up the value associated with the index. (Actually, this
+;;; isn't just a speed optimization; the information about whether a
+;;; character is BOTH-CASE-P is used just in the ordering and not
+;;; explicitly recorded in the database).
;;;
;;; The moral of all this? Next time, don't just say "FIXME: document
;;; this"
(defun ucd-index (char)
(let* ((cp (char-code char))
(cp-high (ash cp -8))
- (page (aref **character-database** (+ 1648 cp-high))))
- (+ 6000 (ash page 10) (ash (ldb (byte 8 0) cp) 2))))
+ (page (aref **character-database** (+ 3160 cp-high))))
+ (+ 7512 (ash page 10) (ash (ldb (byte 8 0) cp) 2))))
-(declaim (ftype (sfunction (t) (unsigned-byte 8)) ucd-value-0))
+(declaim (ftype (sfunction (t) (unsigned-byte 11)) ucd-value-0))
(defun ucd-value-0 (char)
- (aref **character-database** (ucd-index char)))
+ (let ((index (ucd-index char))
+ (character-database **character-database**))
+ (dpb (aref character-database index)
+ (byte 8 3)
+ (ldb (byte 3 5) (aref character-database (+ index 1))))))
-(declaim (ftype (sfunction (t) (unsigned-byte 24)) ucd-value-1))
+(declaim (ftype (sfunction (t) (unsigned-byte 21)) ucd-value-1))
(defun ucd-value-1 (char)
(let ((index (ucd-index char))
(character-database **character-database**))
- (dpb (aref character-database (+ index 3))
- (byte 8 16)
+ (dpb (aref character-database (+ index 1))
+ (byte 5 16)
(dpb (aref character-database (+ index 2))
(byte 8 8)
- (aref character-database (1+ index))))))
+ (aref character-database (+ index 3))))))
(declaim (ftype (sfunction (t) (unsigned-byte 8)) ucd-general-category))
(defun ucd-general-category (char)
#!+sb-doc
"The argument must be a character object; UPPER-CASE-P returns T if the
argument is an upper-case character, NIL otherwise."
- (= (ucd-value-0 char) 0))
+ (< (ucd-value-0 char) 4))
(defun lower-case-p (char)
#!+sb-doc
"The argument must be a character object; LOWER-CASE-P returns T if the
argument is a lower-case character, NIL otherwise."
- (= (ucd-value-0 char) 1))
+ (< 3 (ucd-value-0 char) 8))
(defun both-case-p (char)
#!+sb-doc
"The argument must be a character object. BOTH-CASE-P returns T if the
argument is an alphabetic character and if the character exists in both upper
and lower case. For ASCII, this is the same as ALPHA-CHAR-P."
- (< (ucd-value-0 char) 2))
+ (< (ucd-value-0 char) 8))
(defun digit-char-p (char &optional (radix 10.))
#!+sb-doc
#!+sb-doc
"Return CHAR converted to upper-case if that is possible. Don't convert
lowercase eszet (U+DF)."
- (if (= (ucd-value-0 char) 1)
+ (if (< 3 (ucd-value-0 char) 8)
(code-char (ucd-value-1 char))
char))
(defun char-downcase (char)
#!+sb-doc
"Return CHAR converted to lower-case if that is possible."
- (if (= (ucd-value-0 char) 0)
+ (if (< (ucd-value-0 char) 4)
(code-char (ucd-value-1 char))
char))
(and (typep weight 'fixnum)
(>= weight 0) (< weight radix) (< weight 36)
(code-char (if (< weight 10) (+ 48 weight) (+ 55 weight)))))
+\f
+(defun char-decomposition-info (char)
+ (aref **character-database** (+ 6 (* 8 (ucd-value-0 char)))))
+
+(defun char-decomposition (char)
+ (let* ((cp (char-code char))
+ (cp-high (ash cp -8))
+ (decompositions **character-decompositions**)
+ (long-decompositions **character-long-decompositions**)
+ (index (+ #x1100
+ (ash (aref decompositions cp-high) 10)
+ (ash (ldb (byte 8 0) cp) 2)))
+ (v0 (aref decompositions index))
+ (v1 (aref decompositions (+ index 1)))
+ (v2 (aref decompositions (+ index 2)))
+ (v3 (aref decompositions (+ index 3)))
+ (length (dpb v0 (byte 8 3) (ldb (byte 3 5) v1)))
+ (entry (dpb (ldb (byte 5 0) v1) (byte 5 16)
+ (dpb v2 (byte 8 8) v3))))
+ (if (= length 1)
+ (string (code-char entry))
+ (let ((result (make-string length))
+ (e (* 4 entry)))
+ (dotimes (i length result)
+ (let ((code (dpb (aref long-decompositions (+ e 1))
+ (byte 8 16)
+ (dpb (aref long-decompositions (+ e 2))
+ (byte 8 8)
+ (aref long-decompositions (+ e 3))))))
+ (setf (char result i) (code-char code)))
+ (incf e 4))))))
+
+(defun decompose-char (char)
+ (if (= (char-decomposition-info char) 0)
+ (string char)
+ (char-decomposition char)))