all messages for Emacs-related lists mirrored at yhetil.org
 help / color / mirror / code / Atom feed
* bug#24603: [RFC 00/18] Improvement to casing
@ 2016-10-04  1:05 Michal Nazarewicz
  2016-10-04  1:10 ` bug#24603: [RFC 01/18] Add tests for casefiddle.c Michal Nazarewicz
                   ` (2 more replies)
  0 siblings, 3 replies; 89+ messages in thread
From: Michal Nazarewicz @ 2016-10-04  1:05 UTC (permalink / raw)
  To: 24603

This is work in progress with a known bug: if casing region changes
length (e.g. fish becomes FISH) neither point moves correctly nor undo
information is recorded correctly.  There could also be some minor
improvements to documentation here and there.

Overall, this is mature enough (and probably too-big already) to send
a request for comments.

Michal Nazarewicz (18):
  Add tests for casefiddle.c
  Generate upcase and downcase tables from Unicode data
  Don’t assume character can be either upper- or lower-case when casing
  Split casify_object into multiple functions
  Introduce case_character function
  Add support for title-casing letters
  Split up casify_region function.
  Support casing characters which map into multiple code points
  Implement special sigma casing rule
  Implement Turkic dotless and dotted i handling when casing strings
  Implement casing rules for Lithuanian
  Implement rules for title-casing Dutch ij ‘letter’
  Add some tricky Unicode characters to regex test
  Factor out character category lookup to separate function
  Base lower- and upper-case tests on Unicode properties
  Refactor character class checking; optimise ASCII case
  Optimise character class matching in regexes
  Fix case-fold-search character class matching

 .gitignore                       |   1 +
 admin/unidata/README             |   4 +
 admin/unidata/SpecialCasing.txt  | 281 +++++++++++++
 etc/NEWS                         |  25 +-
 lisp/international/characters.el | 338 +++------------
 src/Makefile.in                  |   3 +
 src/buffer.h                     |  17 +-
 src/casefiddle.c                 | 864 ++++++++++++++++++++++++++++++---------
 src/character.c                  | 151 +++----
 src/character.h                  |  76 +++-
 src/deps.mk                      |   2 +-
 src/keyboard.c                   |  25 +-
 src/make-special-casing.py       | 189 +++++++++
 src/regex.c                      | 119 +++---
 test/lisp/char-fold-tests.el     |  12 +-
 test/src/casefiddle-tests.el     | 262 ++++++++++++
 test/src/regex-tests.el          |  62 ++-
 17 files changed, 1786 insertions(+), 645 deletions(-)
 create mode 100644 admin/unidata/SpecialCasing.txt
 create mode 100644 src/make-special-casing.py
 create mode 100644 test/src/casefiddle-tests.el

-- 
2.8.0.rc3.226.g39d4020






^ permalink raw reply	[flat|nested] 89+ messages in thread

* bug#24603: [RFC 01/18] Add tests for casefiddle.c
  2016-10-04  1:05 bug#24603: [RFC 00/18] Improvement to casing Michal Nazarewicz
@ 2016-10-04  1:10 ` Michal Nazarewicz
  2016-10-04  1:10   ` bug#24603: [RFC 02/18] Generate upcase and downcase tables from Unicode data Michal Nazarewicz
                     ` (16 more replies)
  2016-10-17 22:03 ` bug#24603: [PATCH 0/3] Case table updates Michal Nazarewicz
  2017-03-09 21:51 ` bug#24603: [PATCHv5 00/11] Casing improvements Michal Nazarewicz
  2 siblings, 17 replies; 89+ messages in thread
From: Michal Nazarewicz @ 2016-10-04  1:10 UTC (permalink / raw)
  To: 24603

Fixes cases marked FIXME upcoming in followup commits.

* test/src/casefiddle-tests.el (casefiddle-tests-char-properties,
casefiddle-tests-case-table, casefiddle-tests-casing-character,
casefiddle-tests-casing): New test cases.
---
 test/src/casefiddle-tests.el | 190 +++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 190 insertions(+)
 create mode 100644 test/src/casefiddle-tests.el

diff --git a/test/src/casefiddle-tests.el b/test/src/casefiddle-tests.el
new file mode 100644
index 0000000..4b2eeaf
--- /dev/null
+++ b/test/src/casefiddle-tests.el
@@ -0,0 +1,190 @@
+;;; casefiddle-tests.el --- tests for casefiddle.c functions -*- lexical-binding: t -*-
+
+;; Copyright (C) 2015-2016 Free Software Foundation, Inc.
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs 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.
+
+;; GNU Emacs 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 <http://www.gnu.org/licenses/>.
+
+;;; Code:
+
+(require 'ert)
+
+(ert-deftest casefiddle-tests-char-properties ()
+  "Sanity check of character Unicode properties."
+  (should-not
+   (let (errors)
+     ;;            character  uppercase  lowercase  titlecase
+     (dolist (test '((?A nil ?a nil)
+                     (?a ?A nil ?A)
+                     (?Ł nil ?ł nil)
+                     (?ł ?Ł nil ?Ł)
+
+                     (?DŽ nil ?dž ?Dž)
+                     (?Dž ?DŽ ?dž ?Dž)
+                     (?dž ?DŽ nil ?Dž)
+
+                     (?Σ nil ?σ nil)
+                     (?σ ?Σ nil ?Σ)
+                     (?ς ?Σ nil ?Σ)
+
+                     (?ⅷ ?Ⅷ nil ?Ⅷ)
+                     (?Ⅷ nil ?ⅷ nil)))
+       (let ((ch (car test))
+             (expected (cdr test))
+             (props '(uppercase lowercase titlecase)))
+         (while props
+           (let ((got (get-char-code-property ch (car props))))
+             (unless (equal (car expected) got)
+               (push (format "\n%c %s; expected: %s but got: %s"
+                             ch (car props) (car expected) got)
+                     errors)))
+           (setq props (cdr props) expected (cdr expected)))))
+     (when errors
+       (mapconcat (lambda (line) line) (nreverse errors) "")))))
+
+
+(defconst casefiddle-tests--characters
+  ;; character  uppercase  lowercase  titlecase
+  '((?A ?A ?a ?A)
+    (?a ?A ?a ?A)
+    (?Ł ?Ł ?ł ?Ł)
+    (?ł ?Ł ?ł ?Ł)
+
+    ;; FIXME: We should have:
+    ;;(?DŽ ?DŽ ?dž ?Dž)
+    ;; but instead we have:
+    (?DŽ ?DŽ ?dž ?DŽ)
+    ;; FIXME: Those two are broken at the moment:
+    ;;(?Dž ?DŽ ?dž ?Dž)
+    ;;(?dž ?DŽ ?dž ?Dž)
+
+    (?Σ ?Σ ?σ ?Σ)
+    (?σ ?Σ ?σ ?Σ)
+    ;; FIXME: Another broken one:
+    ;;(?ς ?Σ ?ς ?Σ)
+
+    (?Ⅷ ?Ⅷ ?ⅷ ?Ⅷ)
+    (?ⅷ ?Ⅷ ?ⅷ ?Ⅷ)))
+
+
+(ert-deftest casefiddle-tests-case-table ()
+  "Sanity check of down and up case tables."
+  (should-not
+   (let (errors
+         (up (case-table-get-table (current-case-table) 'up))
+         (down (case-table-get-table (current-case-table) 'down)))
+     (dolist (test casefiddle-tests--characters)
+       (let ((ch (car test))
+             (expected (cdr test))
+             (props '(uppercase lowercase))
+             (tabs (list up down)))
+         (while props
+           (let ((got (aref (car tabs) ch)))
+             (unless (equal (car expected) got)
+               (push (format "\n%c %s; expected: %s but got: %s"
+                             ch (car props) (car expected) got)
+                     errors)))
+           (setq props (cdr props) tabs (cdr tabs) expected (cdr expected)))))
+     (when errors
+       (mapconcat (lambda (line) line) (nreverse errors) "")))))
+
+
+(ert-deftest casefiddle-tests-casing-character ()
+  (should-not
+   (let (errors)
+     (dolist (test casefiddle-tests--characters)
+       (let ((ch (car test))
+             (expected (cdr test))
+             (funcs '(upcase downcase capitalize)))
+         (while funcs
+           (let ((got (funcall (car funcs) ch)))
+             (unless (equal (car expected) got)
+               (push (format "\n%c %s; expected: %s but got: %s"
+                             ch (car funcs) (car expected) got)
+                     errors)))
+           (setq funcs (cdr funcs) expected (cdr expected)))))
+     (when errors
+       (mapconcat (lambda (line) line) (nreverse errors) "")))))
+
+
+(ert-deftest casefiddle-tests-casing-word ()
+  (with-temp-buffer
+    (dolist (test '((upcase-word     . "FOO Bar")
+                    (downcase-word   . "foo Bar")
+                    (capitalize-word . "Foo Bar")))
+      (dolist (back '(nil t))
+        (delete-region (point-min) (point-max))
+        (insert "foO Bar")
+        (goto-char (+ (if back 4 0) (point-min)))
+        (funcall (car test) (if back -1 1))
+        (should (string-equal (cdr test) (buffer-string)))
+        (should (equal (+ (if back 4 3) (point-min)) (point)))))))
+
+
+(ert-deftest casefiddle-tests-casing ()
+  (should-not
+   (let (errors)
+     (with-temp-buffer
+       (dolist
+           (test
+            ;; input  upcase  downcase  capitalize  upcase-initials
+            '(("Foo baR" "FOO BAR" "foo bar" "Foo Bar" "Foo BaR")
+              ("Ⅷ ⅷ" "Ⅷ Ⅷ" "ⅷ ⅷ" "Ⅷ Ⅷ" "Ⅷ Ⅷ")
+              ;; FIXME: Everything below is broken at the moment.  Here’s what
+              ;; should happen:
+              ;;("DŽUNGLA" "DŽUNGLA" "džungla" "Džungla" "DžUNGLA")
+              ;;("Džungla" "DŽUNGLA" "džungla" "Džungla" "Džungla")
+              ;;("džungla" "DŽUNGLA" "džungla" "Džungla" "Džungla")
+              ;;("define" "DEFINE" "define" "Define" "Define")
+              ;;("fish" "FIsh" "fish" "Fish" "Fish")
+              ;;("Straße" "STRASSE" "straße" "Straße" "Straße")
+              ;;("ΌΣΟΣ" "ΌΣΟΣ" "όσος" "Όσος" "Όσος")
+              ;;("όσος" "ΌΣΟΣ" "όσος" "Όσος" "Όσος")
+              ;; And here’s what is actually happening:
+              ("DŽUNGLA" "DŽUNGLA" "džungla" "DŽungla" "DŽUNGLA")
+              ("Džungla" "DžUNGLA" "džungla" "Džungla" "Džungla")
+              ("džungla" "DŽUNGLA" "džungla" "DŽungla" "DŽungla")
+              ("define" "DEfiNE" "define" "Define" "Define")
+              ("fish" "fiSH" "fish" "fish" "fish")
+              ("Straße" "STRAßE" "straße" "Straße" "Straße")
+              ("ΌΣΟΣ" "ΌΣΟΣ" "όσοσ" "Όσοσ" "ΌΣΟΣ")
+              ("όσος" "ΌΣΟς" "όσος" "Όσος" "Όσος"))
+            (nreverse errors))
+         (let* ((input (car test))
+                (expected (cdr test))
+                (check (lambda (func got)
+                         (unless (string-equal got (car expected))
+                           (let ((fmt (length (symbol-name func))))
+                             (setq fmt (format "\n%%%ds: %%s" (max fmt 8)))
+                             (push (format (concat fmt fmt fmt)
+                                           func input
+                                           "expected" (car expected)
+                                           "but got" got)
+                                   errors))))))
+           (dolist (func '((upcase . upcase-region)
+                           (downcase . downcase-region)
+                           (capitalize . capitalize-region)
+                           (upcase-initials . upcase-initials-region)))
+             (funcall check (car func) (funcall (car func) input))
+             (funcall check (cdr func) (progn
+                                         (delete-region (point-min) (point-max))
+                                         (insert input)
+                                         (funcall (cdr func)
+                                                  (point-min) (point-max))
+                                         (buffer-string)))
+             (setq expected (cdr expected)))))))))
+
+
+;;; casefiddle-tests.el ends here
-- 
2.8.0.rc3.226.g39d4020






^ permalink raw reply related	[flat|nested] 89+ messages in thread

* bug#24603: [RFC 02/18] Generate upcase and downcase tables from Unicode data
  2016-10-04  1:10 ` bug#24603: [RFC 01/18] Add tests for casefiddle.c Michal Nazarewicz
@ 2016-10-04  1:10   ` Michal Nazarewicz
  2016-10-04  7:27     ` Eli Zaretskii
  2016-10-04  1:10   ` bug#24603: [RFC 03/18] Don’t assume character can be either upper- or lower-case when casing Michal Nazarewicz
                     ` (15 subsequent siblings)
  16 siblings, 1 reply; 89+ messages in thread
From: Michal Nazarewicz @ 2016-10-04  1:10 UTC (permalink / raw)
  To: 24603

Use Unicode data to generate case tables instead of mostly repeating
them in lisp code.  Do that in a way which maps ‘Dz’ (and similar)
digraph to ‘dz’ when down- and ‘DZ’ when upcasing.

* lisp/international/characters.el: Remove case-pairs defined with
explicit Lisp code and instead use Unicode character properties.

* test/src/casefiddle-tests.el (casefiddle-tests--characters,
casefiddle-tests-casing): Update test cases which are now working
as they should.
---
 lisp/international/characters.el | 338 ++++++++-------------------------------
 test/src/casefiddle-tests.el     |   7 +-
 2 files changed, 66 insertions(+), 279 deletions(-)

diff --git a/lisp/international/characters.el b/lisp/international/characters.el
index 1757d2b..67b0149 100644
--- a/lisp/international/characters.el
+++ b/lisp/international/characters.el
@@ -543,10 +543,6 @@ ?L
   (set-case-syntax ?½ "_" tbl)
   (set-case-syntax ?¾ "_" tbl)
   (set-case-syntax ?¿ "." tbl)
-  (let ((c 192))
-    (while (<= c 222)
-      (set-case-syntax-pair c (+ c 32) tbl)
-      (setq c (1+ c))))
   (set-case-syntax ?× "_" tbl)
   (set-case-syntax ?ß "w" tbl)
   (set-case-syntax ?÷ "_" tbl)
@@ -558,101 +554,8 @@ ?L
     (modify-category-entry c ?l)
     (setq c (1+ c)))
 
-  (let ((pair-ranges '((#x0100 . #x012F)
-		       (#x0132 . #x0137)
-		       (#x0139 . #x0148)
-		       (#x014a . #x0177)
-		       (#x0179 . #x017E)
-		       (#x0182 . #x0185)
-		       (#x0187 . #x0188)
-		       (#x018B . #x018C)
-		       (#x0191 . #x0192)
-		       (#x0198 . #x0199)
-		       (#x01A0 . #x01A5)
-		       (#x01A7 . #x01A8)
-		       (#x01AC . #x01AD)
-		       (#x01AF . #x01B0)
-		       (#x01B3 . #x01B6)
-		       (#x01B8 . #x01B9)
-		       (#x01BC . #x01BD)
-		       (#x01CD . #x01DC)
-		       (#x01DE . #x01EF)
-		       (#x01F4 . #x01F5)
-		       (#x01F8 . #x021F)
-		       (#x0222 . #x0233)
-		       (#x023B . #x023C)
-		       (#x0241 . #x0242)
-		       (#x0246 . #x024F))))
-    (dolist (elt pair-ranges)
-      (let ((from (car elt)) (to (cdr elt)))
-	(while (< from to)
-	  (set-case-syntax-pair from (1+ from) tbl)
-	  (setq from (+ from 2))))))
-
-  (set-case-syntax-pair ?Ÿ ?ÿ tbl)
-
-  ;; In some languages, such as Turkish, U+0049 LATIN CAPITAL LETTER I
-  ;; and U+0131 LATIN SMALL LETTER DOTLESS I make a case pair, and so
-  ;; do U+0130 LATIN CAPITAL LETTER I WITH DOT ABOVE and U+0069 LATIN
-  ;; SMALL LETTER I.
-
-  ;; We used to set up half of those correspondence unconditionally,
-  ;; but that makes searches slow.  So now we don't set up either half
-  ;; of these correspondences by default.
-
-  ;; (set-downcase-syntax  ?İ ?i tbl)
-  ;; (set-upcase-syntax    ?I ?ı tbl)
-
-  (set-case-syntax-pair ?Ɓ ?ɓ tbl)
-  (set-case-syntax-pair ?Ɔ ?ɔ tbl)
-  (set-case-syntax-pair ?Ɖ ?ɖ tbl)
-  (set-case-syntax-pair ?Ɗ ?ɗ tbl)
-  (set-case-syntax-pair ?Ǝ ?ǝ tbl)
-  (set-case-syntax-pair ?Ə ?ə tbl)
-  (set-case-syntax-pair ?Ɛ ?ɛ tbl)
-  (set-case-syntax-pair ?Ɠ ?ɠ tbl)
-  (set-case-syntax-pair ?Ɣ ?ɣ tbl)
-  (set-case-syntax-pair ?Ɩ ?ɩ tbl)
-  (set-case-syntax-pair ?Ɨ ?ɨ tbl)
-  (set-case-syntax-pair ?Ɯ ?ɯ tbl)
-  (set-case-syntax-pair ?Ɲ ?ɲ tbl)
-  (set-case-syntax-pair ?Ɵ ?ɵ tbl)
-  (set-case-syntax-pair ?Ʀ ?ʀ tbl)
-  (set-case-syntax-pair ?Ʃ ?ʃ tbl)
-  (set-case-syntax-pair ?Ʈ ?ʈ tbl)
-  (set-case-syntax-pair ?Ʊ ?ʊ tbl)
-  (set-case-syntax-pair ?Ʋ ?ʋ tbl)
-  (set-case-syntax-pair ?Ʒ ?ʒ tbl)
-  ;; We use set-downcase-syntax below, since we want upcase of dž
-  ;; return DŽ, not Dž, and the same for the rest.
-  (set-case-syntax-pair ?DŽ ?dž tbl)
-  (set-downcase-syntax ?Dž ?dž tbl)
-  (set-case-syntax-pair ?LJ ?lj tbl)
-  (set-downcase-syntax ?Lj ?lj tbl)
-  (set-case-syntax-pair ?NJ ?nj tbl)
-  (set-downcase-syntax ?Nj ?nj tbl)
-
-  ;; 01F0; F; 006A 030C; # LATIN SMALL LETTER J WITH CARON
-
-  (set-case-syntax-pair ?DZ ?dz tbl)
-  (set-downcase-syntax ?Dz ?dz tbl)
-  (set-case-syntax-pair ?Ƕ ?ƕ tbl)
-  (set-case-syntax-pair ?Ƿ ?ƿ tbl)
-  (set-case-syntax-pair ?Ⱥ ?ⱥ tbl)
-  (set-case-syntax-pair ?Ƚ ?ƚ tbl)
-  (set-case-syntax-pair ?Ⱦ ?ⱦ tbl)
-  (set-case-syntax-pair ?Ƀ ?ƀ tbl)
-  (set-case-syntax-pair ?Ʉ ?ʉ tbl)
-  (set-case-syntax-pair ?Ʌ ?ʌ tbl)
-
   ;; Latin Extended Additional
   (modify-category-entry '(#x1e00 . #x1ef9) ?l)
-  (setq c #x1e00)
-  (while (<= c #x1ef9)
-    (and (zerop (% c 2))
-	 (or (<= c #x1e94) (>= c #x1ea0))
-	 (set-case-syntax-pair c (1+ c) tbl))
-    (setq c (1+ c)))
 
   ;; Latin Extended-C
   (setq c #x2C60)
@@ -660,57 +563,12 @@ ?L
     (modify-category-entry c ?l)
     (setq c (1+ c)))
 
-  (let ((pair-ranges '((#x2C60 . #x2C61)
-                       (#x2C67 . #x2C6C)
-                       (#x2C72 . #x2C73)
-                       (#x2C75 . #x2C76))))
-    (dolist (elt pair-ranges)
-      (let ((from (car elt)) (to (cdr elt)))
-        (while (< from to)
-          (set-case-syntax-pair from (1+ from) tbl)
-          (setq from (+ from 2))))))
-
-  (set-case-syntax-pair ?Ɫ ?ɫ tbl)
-  (set-case-syntax-pair ?Ᵽ ?ᵽ tbl)
-  (set-case-syntax-pair ?Ɽ ?ɽ tbl)
-  (set-case-syntax-pair ?Ɑ ?ɑ tbl)
-  (set-case-syntax-pair ?Ɱ ?ɱ tbl)
-  (set-case-syntax-pair ?Ɐ ?ɐ tbl)
-  (set-case-syntax-pair ?Ɒ ?ɒ tbl)
-  (set-case-syntax-pair ?Ȿ ?ȿ tbl)
-  (set-case-syntax-pair ?Ɀ ?ɀ tbl)
-
   ;; Latin Extended-D
   (setq c #xA720)
   (while (<= c #xA7FF)
     (modify-category-entry c ?l)
     (setq c (1+ c)))
 
-  (let ((pair-ranges '((#xA722 . #xA72F)
-                       (#xA732 . #xA76F)
-                       (#xA779 . #xA77C)
-                       (#xA77E . #xA787)
-                       (#xA78B . #xA78E)
-                       (#xA790 . #xA793)
-                       (#xA796 . #xA7A9)
-                       (#xA7B4 . #xA7B7))))
-    (dolist (elt pair-ranges)
-      (let ((from (car elt)) (to (cdr elt)))
-        (while (< from to)
-          (set-case-syntax-pair from (1+ from) tbl)
-          (setq from (+ from 2))))))
-
-  (set-case-syntax-pair ?Ᵹ ?ᵹ tbl)
-  (set-case-syntax-pair ?Ɦ ?ɦ tbl)
-  (set-case-syntax-pair ?Ɜ ?ɜ tbl)
-  (set-case-syntax-pair ?Ɡ ?ɡ tbl)
-  (set-case-syntax-pair ?Ɬ ?ɬ tbl)
-  (set-case-syntax-pair ?Ɪ ?ɪ tbl)
-  (set-case-syntax-pair ?Ʞ ?ʞ tbl)
-  (set-case-syntax-pair ?Ʇ ?ʇ tbl)
-  (set-case-syntax-pair ?Ʝ ?ʝ tbl)
-  (set-case-syntax-pair ?Ꭓ ?ꭓ tbl)
-
   ;; Latin Extended-E
   (setq c #xAB30)
   (while (<= c #xAB64)
@@ -719,102 +577,19 @@ ?L
 
   ;; Greek
   (modify-category-entry '(#x0370 . #x03ff) ?g)
-  (setq c #x0370)
-  (while (<= c #x03ff)
-    (if (or (and (>= c #x0391) (<= c #x03a1))
-	    (and (>= c #x03a3) (<= c #x03ab)))
-	(set-case-syntax-pair c (+ c 32) tbl))
-    (and (>= c #x03da)
-	 (<= c #x03ee)
-	 (zerop (% c 2))
-	 (set-case-syntax-pair c (1+ c) tbl))
-    (setq c (1+ c)))
-  (set-case-syntax-pair ?Ά ?ά tbl)
-  (set-case-syntax-pair ?Έ ?έ tbl)
-  (set-case-syntax-pair ?Ή ?ή tbl)
-  (set-case-syntax-pair ?Ί ?ί tbl)
-  (set-case-syntax-pair ?Ό ?ό tbl)
-  (set-case-syntax-pair ?Ύ ?ύ tbl)
-  (set-case-syntax-pair ?Ώ ?ώ tbl)
 
   ;; Armenian
   (setq c #x531)
-  (while (<= c #x556)
-    (set-case-syntax-pair c (+ c #x30) tbl)
-    (setq c (1+ c)))
 
   ;; Greek Extended
   (modify-category-entry '(#x1f00 . #x1fff) ?g)
-  (setq c #x1f00)
-  (while (<= c #x1fff)
-    (and (<= (logand c #x000f) 7)
-	 (<= c #x1fa7)
-	 (not (memq c '(#x1f16 #x1f17 #x1f56 #x1f57
-			       #x1f50 #x1f52 #x1f54 #x1f56)))
-	 (/= (logand c #x00f0) #x70)
-	 (set-case-syntax-pair (+ c 8) c tbl))
-    (setq c (1+ c)))
-  (set-case-syntax-pair ?Ᾰ ?ᾰ tbl)
-  (set-case-syntax-pair ?Ᾱ ?ᾱ tbl)
-  (set-case-syntax-pair ?Ὰ ?ὰ tbl)
-  (set-case-syntax-pair ?Ά ?ά tbl)
-  (set-case-syntax-pair ?ᾼ ?ᾳ tbl)
-  (set-case-syntax-pair ?Ὲ ?ὲ tbl)
-  (set-case-syntax-pair ?Έ ?έ tbl)
-  (set-case-syntax-pair ?Ὴ ?ὴ tbl)
-  (set-case-syntax-pair ?Ή ?ή tbl)
-  (set-case-syntax-pair ?ῌ ?ῃ tbl)
-  (set-case-syntax-pair ?Ῐ ?ῐ tbl)
-  (set-case-syntax-pair ?Ῑ ?ῑ tbl)
-  (set-case-syntax-pair ?Ὶ ?ὶ tbl)
-  (set-case-syntax-pair ?Ί ?ί tbl)
-  (set-case-syntax-pair ?Ῠ ?ῠ tbl)
-  (set-case-syntax-pair ?Ῡ ?ῡ tbl)
-  (set-case-syntax-pair ?Ὺ ?ὺ tbl)
-  (set-case-syntax-pair ?Ύ ?ύ tbl)
-  (set-case-syntax-pair ?Ῥ ?ῥ tbl)
-  (set-case-syntax-pair ?Ὸ ?ὸ tbl)
-  (set-case-syntax-pair ?Ό ?ό tbl)
-  (set-case-syntax-pair ?Ὼ ?ὼ tbl)
-  (set-case-syntax-pair ?Ώ ?ώ tbl)
-  (set-case-syntax-pair ?ῼ ?ῳ tbl)
 
   ;; cyrillic
   (modify-category-entry '(#x0400 . #x04FF) ?y)
-  (setq c #x0400)
-  (while (<= c #x04ff)
-    (and (>= c #x0400)
-	 (<= c #x040f)
-	 (set-case-syntax-pair c (+ c 80) tbl))
-    (and (>= c #x0410)
-	 (<= c #x042f)
-	 (set-case-syntax-pair c (+ c 32) tbl))
-    (and (zerop (% c 2))
-	 (or (and (>= c #x0460) (<= c #x0480))
-	     (and (>= c #x048c) (<= c #x04be))
-	     (and (>= c #x04d0) (<= c #x052e)))
-	 (set-case-syntax-pair c (1+ c) tbl))
-    (setq c (1+ c)))
-  (set-case-syntax-pair ?Ӂ ?ӂ tbl)
-  (set-case-syntax-pair ?Ӄ ?ӄ tbl)
-  (set-case-syntax-pair ?Ӈ ?ӈ tbl)
-  (set-case-syntax-pair ?Ӌ ?ӌ tbl)
-
   (modify-category-entry '(#xA640 . #xA69F) ?y)
-  (setq c #xA640)
-  (while (<= c #xA66C)
-    (set-case-syntax-pair c (+ c 1) tbl)
-    (setq c (+ c 2)))
-  (setq c #xA680)
-  (while (<= c #xA69A)
-    (set-case-syntax-pair c (+ c 1) tbl)
-    (setq c (+ c 2)))
 
   ;; Georgian
   (setq c #x10A0)
-  (while (<= c #x10CD)
-    (set-case-syntax-pair c (+ c #x1C60) tbl)
-    (setq c (1+ c)))
 
   ;; Cyrillic Extended-C
   (modify-category-entry '(#x1C80 . #x1C8F) ?y)
@@ -844,12 +619,6 @@ ?L
     (set-case-syntax c "." tbl)
     (setq c (1+ c)))
 
-  ;; Roman numerals
-  (setq c #x2160)
-  (while (<= c #x216f)
-    (set-case-syntax-pair c (+ c #x10) tbl)
-    (setq c (1+ c)))
-
   ;; Fixme: The following blocks might be better as symbol rather than
   ;; punctuation.
   ;; Arrows
@@ -873,25 +642,11 @@ ?L
   ;; Circled Latin
   (setq c #x24b6)
   (while (<= c #x24cf)
-    (set-case-syntax-pair c (+ c 26) tbl)
     (modify-category-entry c ?l)
     (modify-category-entry (+ c 26) ?l)
     (setq c (1+ c)))
 
-  ;; Glagolitic
-  (setq c #x2C00)
-  (while (<= c #x2C2E)
-    (set-case-syntax-pair c (+ c 48) tbl)
-    (setq c (1+ c)))
-
   ;; Coptic
-  (let ((pair-ranges '((#x2C80 . #x2CE2)
-		       (#x2CEB . #x2CF2))))
-    (dolist (elt pair-ranges)
-      (let ((from (car elt)) (to (cdr elt)))
-	(while (< from to)
-	  (set-case-syntax-pair from (1+ from) tbl)
-	  (setq from (+ from 2))))))
   ;; There's no Coptic category.  However, Coptic letters that are
   ;; part of the Greek block above get the Greek category, and those
   ;; in this block are derived from Greek letters, so let's be
@@ -901,45 +656,78 @@ ?L
   ;; Fullwidth Latin
   (setq c #xff21)
   (while (<= c #xff3a)
-    (set-case-syntax-pair c (+ c #x20) tbl)
     (modify-category-entry c ?l)
     (modify-category-entry (+ c #x20) ?l)
     (setq c (1+ c)))
 
-  ;; Deseret
-  (setq c #x10400)
-  (while (<= c #x10427)
-    (set-case-syntax-pair c (+ c 28) tbl)
-    (setq c (1+ c)))
+  ;; Combining diacritics
+  (modify-category-entry '(#x300 . #x362) ?^)
+  ;; Combining marks
+  (modify-category-entry '(#x20d0 . #x20ff) ?^)
 
-  ;; Osage
-  (setq c #x104B0)
-  (while (<= c #x104D3)
-    (set-case-syntax-pair c (+ c 40) tbl)
-    (setq c (1+ c)))
+  ;; Set all Letter, uppercase; Letter, lowercase and Letter, titlecase syntax
+  ;; to word.  FIXME: Should this also be done for Letter, modifier and Letter,
+  ;; other?  What about other alphabetic characters?
+  (let ((syn-tab (standard-syntax-table)))
+    (map-char-table
+     (lambda (ch cat)
+       (when (memq cat '(Lu Ll Lt))
+         (modify-syntax-entry ch "w   " syn-tab)))
+     (unicode-property-table-internal 'general-category)))
 
-  ;; Old Hungarian
-  (setq c #x10c80)
-  (while (<= c #x10cb2)
-    (set-case-syntax-pair c (+ c #x40) tbl)
-    (setq c (1+ c)))
+  ;; Set downcase and upcase from Unicode properties
 
-  ;; Warang Citi
-  (setq c #x118a0)
-  (while (<= c #x118bf)
-    (set-case-syntax-pair c (+ c #x20) tbl)
-    (setq c (1+ c)))
+  ;; In some languages, such as Turkish, U+0049 LATIN CAPITAL LETTER I and
+  ;; U+0131 LATIN SMALL LETTER DOTLESS I make a case pair, and so do U+0130
+  ;; LATIN CAPITAL LETTER I WITH DOT ABOVE and U+0069 LATIN SMALL LETTER I.
 
-  ;; Adlam
-  (setq c #x1e900)
-  (while (<= c #x1e921)
-    (set-case-syntax-pair c (+ c #x22) tbl)
-    (setq c (1+ c)))
+  ;; We used to set up half of those correspondence unconditionally, but that
+  ;; makes searches slow.  So now we don't set up either half of these
+  ;; correspondences by default.
 
-  ;; Combining diacritics
-  (modify-category-entry '(#x300 . #x362) ?^)
-  ;; Combining marks
-  (modify-category-entry '(#x20d0 . #x20ff) ?^)
+  ;; (set-downcase-syntax  ?İ ?i tbl)
+  ;; (set-upcase-syntax    ?I ?ı tbl)
+
+  (let ((map-unicode-property
+         (lambda (property func)
+           (map-char-table
+            (lambda (ch cased)
+              ;; ASCII characters skipped due to reasons outlined above.  As of
+              ;; Unicode 9.0, this exception affects the following:
+              ;;   lc(U+0130 İ) = i
+              ;;   uc(U+0131 ı) = I
+              ;;   uc(U+017F ſ) = S
+              ;;   uc(U+212A K) = k
+              (when (> cased 127)
+                (let ((end (if (consp ch) (cdr ch) ch)))
+                  (setq ch (max 128 (if (consp ch) (car ch) ch)))
+                  (while (<= ch end)
+                    (funcall func ch cased)
+                    (setq ch (1+ ch))))))
+            (unicode-property-table-internal property))))
+        (down tbl)
+        (up (case-table-get-table tbl 'up)))
+
+    ;; This works on an assumption that if toUpper(x) != x then toLower(x) ==
+    ;; x (and the opposite for toLower/toUpper).  This doesn’t hold for title
+    ;; case characters but those incorrect mappings will be overwritten later.
+    (funcall map-unicode-property 'uppercase
+             (lambda (lc uc) (aset down lc lc) (aset up uc uc)))
+    (funcall map-unicode-property 'lowercase
+             (lambda (uc lc) (aset down lc lc) (aset up uc uc)))
+
+    ;; Now deal with the actual mapping.  This will correctly assign casing for
+    ;; title-case characters.
+    (funcall map-unicode-property 'uppercase
+             (lambda (lc uc) (aset up lc uc) (aset up uc uc)))
+    (funcall map-unicode-property 'lowercase
+             (lambda (uc lc) (aset down uc lc) (aset down lc lc))))
+
+  ;; Clear out the extra slots so that they will be recomputed from the main
+  ;; (downcase) table and upcase table.  Since we’re side-stepping the usual
+  ;; set-case-syntax-* functions, we need to do it explicitly.
+  (set-char-table-extra-slot tbl 1 nil)
+  (set-char-table-extra-slot tbl 2 nil)
 
   ;; Fixme: syntax for symbols &c
   )
diff --git a/test/src/casefiddle-tests.el b/test/src/casefiddle-tests.el
index 4b2eeaf..ca3657d 100644
--- a/test/src/casefiddle-tests.el
+++ b/test/src/casefiddle-tests.el
@@ -72,8 +72,7 @@ casefiddle-tests--characters
 
     (?Σ ?Σ ?σ ?Σ)
     (?σ ?Σ ?σ ?Σ)
-    ;; FIXME: Another broken one:
-    ;;(?ς ?Σ ?ς ?Σ)
+    (?ς ?Σ ?ς ?Σ)
 
     (?Ⅷ ?Ⅷ ?ⅷ ?Ⅷ)
     (?ⅷ ?Ⅷ ?ⅷ ?Ⅷ)))
@@ -151,7 +150,6 @@ casefiddle-tests--characters
               ;;("fish" "FIsh" "fish" "Fish" "Fish")
               ;;("Straße" "STRASSE" "straße" "Straße" "Straße")
               ;;("ΌΣΟΣ" "ΌΣΟΣ" "όσος" "Όσος" "Όσος")
-              ;;("όσος" "ΌΣΟΣ" "όσος" "Όσος" "Όσος")
               ;; And here’s what is actually happening:
               ("DŽUNGLA" "DŽUNGLA" "džungla" "DŽungla" "DŽUNGLA")
               ("Džungla" "DžUNGLA" "džungla" "Džungla" "Džungla")
@@ -160,7 +158,8 @@ casefiddle-tests--characters
               ("fish" "fiSH" "fish" "fish" "fish")
               ("Straße" "STRAßE" "straße" "Straße" "Straße")
               ("ΌΣΟΣ" "ΌΣΟΣ" "όσοσ" "Όσοσ" "ΌΣΟΣ")
-              ("όσος" "ΌΣΟς" "όσος" "Όσος" "Όσος"))
+
+              ("όσος" "ΌΣΟΣ" "όσος" "Όσος" "Όσος"))
             (nreverse errors))
          (let* ((input (car test))
                 (expected (cdr test))
-- 
2.8.0.rc3.226.g39d4020






^ permalink raw reply related	[flat|nested] 89+ messages in thread

* bug#24603: [RFC 03/18] Don’t assume character can be either upper- or lower-case when casing
  2016-10-04  1:10 ` bug#24603: [RFC 01/18] Add tests for casefiddle.c Michal Nazarewicz
  2016-10-04  1:10   ` bug#24603: [RFC 02/18] Generate upcase and downcase tables from Unicode data Michal Nazarewicz
@ 2016-10-04  1:10   ` Michal Nazarewicz
  2016-10-04  1:10   ` bug#24603: [RFC 04/18] Split casify_object into multiple functions Michal Nazarewicz
                     ` (14 subsequent siblings)
  16 siblings, 0 replies; 89+ messages in thread
From: Michal Nazarewicz @ 2016-10-04  1:10 UTC (permalink / raw)
  To: 24603

A compatibility digraph characters, such as Dž, are neither upper- nor
lower-case.  At the moment however, those are reported as upper-case¹
despite the fact that they change when upper-cased.

Stop checking if a character is upper-case before trying to up-case it
so that title-case characters are handled correctly.

¹ Because they change when converted to lower-case.  Notice an asymmetry
  in that for a character to be considered lower-case it must not be
  upper-case (plus the usual condition of changing when upper-cased).

* src/buffer.h (upcase1): Delete.
(upcase): Change to upcase character unconditionally just like downcase
does it.  This is what upcase1 was.

* src/casefiddle.c (casify_object, casify_region): Use upcase instead
of upcase1 and don’t check !uppercasep(x) before calling upcase.

* src/keyboard.c (read_key_sequence): Don’t check if uppercase(x), just
downcase(x) and see if it changed.

* test/src/casefiddle-tests.el (casefiddle-tests--characters,
casefiddle-tests-casing): Update test cases which are now passing.
---
 etc/NEWS                     |  8 +++++++-
 src/buffer.h                 | 18 +++++++++---------
 src/casefiddle.c             | 20 +++++++-------------
 src/keyboard.c               | 25 +++++++++++++++----------
 test/src/casefiddle-tests.el |  8 ++++----
 5 files changed, 42 insertions(+), 37 deletions(-)

diff --git a/etc/NEWS b/etc/NEWS
index bd94c94..61afcc6 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -235,6 +235,12 @@ same as in modes where the character is not whitespace.
 Instead of only checking the modification time, Emacs now also checks
 the file's actual content before prompting the user.
 
+** Title case characters are properly converted to upper case.
+'upcase', 'upcase-region' et al. convert title case characters (such
+as Dz) into their upper case form (such as DZ).  As a downside,
+'capitalize' and 'upcase-initials' produce awkward words where first
+two letters are upper case, e.g. DŽungla (instead of Džungla).
+
 \f
 * Changes in Specialized Modes and Packages in Emacs 26.1
 
@@ -662,7 +668,7 @@ along with GNU Emacs.  If not, see <http://www.gnu.org/licenses/>.
 
 \f
 Local variables:
-coding: us-ascii
+coding: utf-8
 mode: outline
 paragraph-separate: "[ 	\f]*$"
 end:
diff --git a/src/buffer.h b/src/buffer.h
index 6ac161c..1543f67 100644
--- a/src/buffer.h
+++ b/src/buffer.h
@@ -1349,28 +1349,28 @@ downcase (int c)
   return NATNUMP (down) ? XFASTINT (down) : c;
 }
 
-/* True if C is upper case.  */
-INLINE bool uppercasep (int c) { return downcase (c) != c; }
-
-/* Upcase a character C known to be not upper case.  */
+/* Upcase a character C, or make no change if that cannot be done. */
 INLINE int
-upcase1 (int c)
+upcase (int c)
 {
   Lisp_Object upcase_table = BVAR (current_buffer, upcase_table);
   Lisp_Object up = CHAR_TABLE_REF (upcase_table, c);
   return NATNUMP (up) ? XFASTINT (up) : c;
 }
 
+/* True if C is upper case.  */
+INLINE bool uppercasep (int c)
+{
+  return downcase (c) != c;
+}
+
 /* True if C is lower case.  */
 INLINE bool
 lowercasep (int c)
 {
-  return !uppercasep (c) && upcase1 (c) != c;
+  return !uppercasep (c) && upcase (c) != c;
 }
 
-/* Upcase a character C, or make no change if that cannot be done.  */
-INLINE int upcase (int c) { return uppercasep (c) ? c : upcase1 (c); }
-
 INLINE_HEADER_END
 
 #endif /* EMACS_BUFFER_H */
diff --git a/src/casefiddle.c b/src/casefiddle.c
index 2d32f49..b86f485 100644
--- a/src/casefiddle.c
+++ b/src/casefiddle.c
@@ -64,13 +64,9 @@ casify_object (enum case_action flag, Lisp_Object obj)
 	multibyte = 1;
       if (! multibyte)
 	MAKE_CHAR_MULTIBYTE (c1);
-      c = downcase (c1);
-      if (inword)
-	XSETFASTINT (obj, c | flags);
-      else if (c == (XFASTINT (obj) & ~flagbits))
+      c = flag == CASE_DOWN ? downcase (c1) : upcase (c1);
+      if (c != c1)
 	{
-	  if (! inword)
-	    c = upcase1 (c1);
 	  if (! multibyte)
 	    MAKE_CHAR_UNIBYTE (c);
 	  XSETFASTINT (obj, c | flags);
@@ -95,7 +91,7 @@ casify_object (enum case_action flag, Lisp_Object obj)
 	    c = downcase (c);
 	  else if (!uppercasep (c)
 		   && (!inword || flag != CASE_CAPITALIZE_UP))
-	    c = upcase1 (c1);
+	    c = upcase (c1);
 	  if ((int) flag >= (int) CASE_CAPITALIZE)
 	    inword = (SYNTAX (c) == Sword);
 	  if (c != c1)
@@ -127,9 +123,8 @@ casify_object (enum case_action flag, Lisp_Object obj)
 	  c = STRING_CHAR_AND_LENGTH (SDATA (obj) + i_byte, len);
 	  if (inword && flag != CASE_CAPITALIZE_UP)
 	    c = downcase (c);
-	  else if (!uppercasep (c)
-		   && (!inword || flag != CASE_CAPITALIZE_UP))
-	    c = upcase1 (c);
+	  else if (!inword || flag != CASE_CAPITALIZE_UP)
+	    c = upcase (c);
 	  if ((int) flag >= (int) CASE_CAPITALIZE)
 	    inword = (SYNTAX (c) == Sword);
 	  o += CHAR_STRING (c, o);
@@ -236,9 +231,8 @@ casify_region (enum case_action flag, Lisp_Object b, Lisp_Object e)
       c2 = c;
       if (inword && flag != CASE_CAPITALIZE_UP)
 	c = downcase (c);
-      else if (!uppercasep (c)
-	       && (!inword || flag != CASE_CAPITALIZE_UP))
-	c = upcase1 (c);
+      else if (!inword || flag != CASE_CAPITALIZE_UP)
+	c = upcase (c);
       if ((int) flag >= (int) CASE_CAPITALIZE)
 	inword = ((SYNTAX (c) == Sword)
 		  && (inword || !syntax_prefix_flag_p (c)));
diff --git a/src/keyboard.c b/src/keyboard.c
index ca40c6e..2115fc9 100644
--- a/src/keyboard.c
+++ b/src/keyboard.c
@@ -9633,22 +9633,26 @@ read_key_sequence (Lisp_Object *keybuf, int bufsize, Lisp_Object prompt,
 	 use the corresponding lower-case letter instead.  */
       if (NILP (current_binding)
 	  && /* indec.start >= t && fkey.start >= t && */ keytran.start >= t
-	  && INTEGERP (key)
-	  && ((CHARACTERP (make_number (XINT (key) & ~CHAR_MODIFIER_MASK))
-	       && uppercasep (XINT (key) & ~CHAR_MODIFIER_MASK))
-	      || (XINT (key) & shift_modifier)))
+	  && INTEGERP (key))
 	{
 	  Lisp_Object new_key;
+	  int k = XINT (key);
+
+	  if (k & shift_modifier)
+	    XSETINT (new_key, k & ~shift_modifier);
+	  else if (CHARACTERP (make_number (k & ~CHAR_MODIFIER_MASK)))
+	    {
+	      int dc = downcase(k & ~CHAR_MODIFIER_MASK);
+	      if (dc == (k & ~CHAR_MODIFIER_MASK))
+		goto not_upcase;
+	      XSETINT (new_key, dc | (k & CHAR_MODIFIER_MASK));
+	    }
+	  else
+	    goto not_upcase;
 
 	  original_uppercase = key;
 	  original_uppercase_position = t - 1;
 
-	  if (XINT (key) & shift_modifier)
-	    XSETINT (new_key, XINT (key) & ~shift_modifier);
-	  else
-	    XSETINT (new_key, (downcase (XINT (key) & ~CHAR_MODIFIER_MASK)
-			       | (XINT (key) & CHAR_MODIFIER_MASK)));
-
 	  /* We have to do this unconditionally, regardless of whether
 	     the lower-case char is defined in the keymaps, because they
 	     might get translated through function-key-map.  */
@@ -9659,6 +9663,7 @@ read_key_sequence (Lisp_Object *keybuf, int bufsize, Lisp_Object prompt,
 	  goto replay_sequence;
 	}
 
+    not_upcase:
       if (NILP (current_binding)
 	  && help_char_p (EVENT_HEAD (key)) && t > 1)
 	    {
diff --git a/test/src/casefiddle-tests.el b/test/src/casefiddle-tests.el
index ca3657d..8d9bf01 100644
--- a/test/src/casefiddle-tests.el
+++ b/test/src/casefiddle-tests.el
@@ -62,13 +62,13 @@ casefiddle-tests--characters
     (?Ł ?Ł ?ł ?Ł)
     (?ł ?Ł ?ł ?Ł)
 
-    ;; FIXME: We should have:
+    ;; FIXME: Commented one is what we want.
     ;;(?DŽ ?DŽ ?dž ?Dž)
-    ;; but instead we have:
     (?DŽ ?DŽ ?dž ?DŽ)
-    ;; FIXME: Those two are broken at the moment:
     ;;(?Dž ?DŽ ?dž ?Dž)
+    (?Dž ?DŽ ?dž ?DŽ)
     ;;(?dž ?DŽ ?dž ?Dž)
+    (?dž ?DŽ ?dž ?DŽ)
 
     (?Σ ?Σ ?σ ?Σ)
     (?σ ?Σ ?σ ?Σ)
@@ -152,7 +152,7 @@ casefiddle-tests--characters
               ;;("ΌΣΟΣ" "ΌΣΟΣ" "όσος" "Όσος" "Όσος")
               ;; And here’s what is actually happening:
               ("DŽUNGLA" "DŽUNGLA" "džungla" "DŽungla" "DŽUNGLA")
-              ("Džungla" "DžUNGLA" "džungla" "Džungla" "Džungla")
+              ("Džungla" "DŽUNGLA" "džungla" "DŽungla" "DŽungla")
               ("džungla" "DŽUNGLA" "džungla" "DŽungla" "DŽungla")
               ("define" "DEfiNE" "define" "Define" "Define")
               ("fish" "fiSH" "fish" "fish" "fish")
-- 
2.8.0.rc3.226.g39d4020






^ permalink raw reply related	[flat|nested] 89+ messages in thread

* bug#24603: [RFC 04/18] Split casify_object into multiple functions
  2016-10-04  1:10 ` bug#24603: [RFC 01/18] Add tests for casefiddle.c Michal Nazarewicz
  2016-10-04  1:10   ` bug#24603: [RFC 02/18] Generate upcase and downcase tables from Unicode data Michal Nazarewicz
  2016-10-04  1:10   ` bug#24603: [RFC 03/18] Don’t assume character can be either upper- or lower-case when casing Michal Nazarewicz
@ 2016-10-04  1:10   ` Michal Nazarewicz
  2016-10-04  1:10   ` bug#24603: [RFC 05/18] Introduce case_character function Michal Nazarewicz
                     ` (13 subsequent siblings)
  16 siblings, 0 replies; 89+ messages in thread
From: Michal Nazarewicz @ 2016-10-04  1:10 UTC (permalink / raw)
  To: 24603

casify_object had three major cases to cover and those were mostly
independent of each other.  Move those branches to separate function
so it’s easier to comprehend each individual case.

While at it, use somewhat more descriptive ch and cased variable names
rather than c and c1.

* src/casefiddle.c (casify_object): Split into…
(do_casify_integer, do_casify_multibyte_string,
do_casify_unibyte_string): …new functions.
---
 src/casefiddle.c | 196 +++++++++++++++++++++++++++++--------------------------
 1 file changed, 104 insertions(+), 92 deletions(-)

diff --git a/src/casefiddle.c b/src/casefiddle.c
index b86f485..47ebdf0 100644
--- a/src/casefiddle.c
+++ b/src/casefiddle.c
@@ -32,108 +32,120 @@ along with GNU Emacs.  If not, see <http://www.gnu.org/licenses/>.  */
 enum case_action {CASE_UP, CASE_DOWN, CASE_CAPITALIZE, CASE_CAPITALIZE_UP};
 \f
 static Lisp_Object
-casify_object (enum case_action flag, Lisp_Object obj)
+do_casify_integer (enum case_action flag, Lisp_Object obj)
+{
+  int flagbits = (CHAR_ALT | CHAR_SUPER | CHAR_HYPER
+		  | CHAR_SHIFT | CHAR_CTL | CHAR_META);
+  int flags, ch = XFASTINT (obj), cased;
+  bool multibyte;
+
+  /* If the character has higher bits set above the flags, return it unchanged.
+     It is not a real character.  */
+  if (UNSIGNED_CMP (ch, >, flagbits))
+    return obj;
+
+  flags = ch & flagbits;
+  ch = ch & ~flagbits;
+
+  /* FIXME: Even if enable-multibyte-characters is nil, we may manipulate
+     multibyte chars.  This means we have a bug for latin-1 chars since when we
+     receive an int 128-255 we can't tell whether it's an eight-bit byte or
+     a latin-1 char.  */
+  multibyte = (ch >= 256 ||
+	       !NILP (BVAR (current_buffer, enable_multibyte_characters)));
+  if (! multibyte)
+    MAKE_CHAR_MULTIBYTE (ch);
+  cased = flag == CASE_DOWN ? downcase (ch) : upcase (ch);
+  if (cased == ch)
+    return obj;
+
+  if (! multibyte)
+    MAKE_CHAR_UNIBYTE (cased);
+  XSETFASTINT (obj, cased | flags);
+  return obj;
+}
+
+static Lisp_Object
+do_casify_multibyte_string (enum case_action flag, Lisp_Object obj)
+{
+  ptrdiff_t i, i_byte, size = SCHARS (obj);
+  bool inword = flag == CASE_DOWN;
+  int len, ch, cased;
+  USE_SAFE_ALLOCA;
+  ptrdiff_t o_size;
+  if (INT_MULTIPLY_WRAPV (size, MAX_MULTIBYTE_LENGTH, &o_size))
+    o_size = PTRDIFF_MAX;
+  unsigned char *dst = SAFE_ALLOCA (o_size);
+  unsigned char *o = dst;
+
+  for (i = i_byte = 0; i < size; i++, i_byte += len)
+    {
+      if (o_size - MAX_MULTIBYTE_LENGTH < o - dst)
+	string_overflow ();
+      ch = STRING_CHAR_AND_LENGTH (SDATA (obj) + i_byte, len);
+      if (inword && flag != CASE_CAPITALIZE_UP)
+	cased = downcase (ch);
+      else if (!inword || flag != CASE_CAPITALIZE_UP)
+	cased = upcase (ch);
+      else
+	cased = ch;
+      if ((int) flag >= (int) CASE_CAPITALIZE)
+	inword = (SYNTAX (ch) == Sword);
+      o += CHAR_STRING (cased, o);
+    }
+  eassert (o - dst <= o_size);
+  obj = make_multibyte_string ((char *) dst, size, o - dst);
+  SAFE_FREE ();
+  return obj;
+}
+
+static Lisp_Object
+do_casify_unibyte_string (enum case_action flag, Lisp_Object obj)
 {
-  int c, c1;
+  ptrdiff_t i, size = SCHARS (obj);
   bool inword = flag == CASE_DOWN;
+  int ch, cased;
+
+  obj = Fcopy_sequence (obj);
+  for (i = 0; i < size; i++)
+    {
+      ch = SREF (obj, i);
+      MAKE_CHAR_MULTIBYTE (ch);
+      cased = ch;
+      if (inword && flag != CASE_CAPITALIZE_UP)
+	ch = downcase (ch);
+      else if (!uppercasep (ch)
+	       && (!inword || flag != CASE_CAPITALIZE_UP))
+	ch = upcase (cased);
+      if ((int) flag >= (int) CASE_CAPITALIZE)
+	inword = (SYNTAX (ch) == Sword);
+      if (ch == cased)
+	continue;
+      MAKE_CHAR_UNIBYTE (ch);
+      /* If the char can't be converted to a valid byte, just don't change it */
+      if (ch >= 0 && ch < 256)
+	SSET (obj, i, ch);
+    }
+  return obj;
+}
 
+static Lisp_Object
+casify_object (enum case_action flag, Lisp_Object obj)
+{
   /* If the case table is flagged as modified, rescan it.  */
   if (NILP (XCHAR_TABLE (BVAR (current_buffer, downcase_table))->extras[1]))
     Fset_case_table (BVAR (current_buffer, downcase_table));
 
   if (INTEGERP (obj))
-    {
-      int flagbits = (CHAR_ALT | CHAR_SUPER | CHAR_HYPER
-		      | CHAR_SHIFT | CHAR_CTL | CHAR_META);
-      int flags = XINT (obj) & flagbits;
-      bool multibyte = ! NILP (BVAR (current_buffer,
-				     enable_multibyte_characters));
-
-      /* If the character has higher bits set
-	 above the flags, return it unchanged.
-	 It is not a real character.  */
-      if (UNSIGNED_CMP (XFASTINT (obj), >, flagbits))
-	return obj;
-
-      c1 = XFASTINT (obj) & ~flagbits;
-      /* FIXME: Even if enable-multibyte-characters is nil, we may
-	 manipulate multibyte chars.  This means we have a bug for latin-1
-	 chars since when we receive an int 128-255 we can't tell whether
-	 it's an eight-bit byte or a latin-1 char.  */
-      if (c1 >= 256)
-	multibyte = 1;
-      if (! multibyte)
-	MAKE_CHAR_MULTIBYTE (c1);
-      c = flag == CASE_DOWN ? downcase (c1) : upcase (c1);
-      if (c != c1)
-	{
-	  if (! multibyte)
-	    MAKE_CHAR_UNIBYTE (c);
-	  XSETFASTINT (obj, c | flags);
-	}
-      return obj;
-    }
-
-  if (!STRINGP (obj))
+    return do_casify_integer (flag, obj);
+  else if (!STRINGP (obj))
     wrong_type_argument (Qchar_or_string_p, obj);
-  else if (!STRING_MULTIBYTE (obj))
-    {
-      ptrdiff_t i;
-      ptrdiff_t size = SCHARS (obj);
-
-      obj = Fcopy_sequence (obj);
-      for (i = 0; i < size; i++)
-	{
-	  c = SREF (obj, i);
-	  MAKE_CHAR_MULTIBYTE (c);
-	  c1 = c;
-	  if (inword && flag != CASE_CAPITALIZE_UP)
-	    c = downcase (c);
-	  else if (!uppercasep (c)
-		   && (!inword || flag != CASE_CAPITALIZE_UP))
-	    c = upcase (c1);
-	  if ((int) flag >= (int) CASE_CAPITALIZE)
-	    inword = (SYNTAX (c) == Sword);
-	  if (c != c1)
-	    {
-	      MAKE_CHAR_UNIBYTE (c);
-	      /* If the char can't be converted to a valid byte, just don't
-		 change it.  */
-	      if (c >= 0 && c < 256)
-		SSET (obj, i, c);
-	    }
-	}
-      return obj;
-    }
+  else if (!SCHARS (obj))
+    return obj;
+  else if (STRING_MULTIBYTE (obj))
+    return do_casify_multibyte_string (flag, obj);
   else
-    {
-      ptrdiff_t i, i_byte, size = SCHARS (obj);
-      int len;
-      USE_SAFE_ALLOCA;
-      ptrdiff_t o_size;
-      if (INT_MULTIPLY_WRAPV (size, MAX_MULTIBYTE_LENGTH, &o_size))
-	o_size = PTRDIFF_MAX;
-      unsigned char *dst = SAFE_ALLOCA (o_size);
-      unsigned char *o = dst;
-
-      for (i = i_byte = 0; i < size; i++, i_byte += len)
-	{
-	  if (o_size - MAX_MULTIBYTE_LENGTH < o - dst)
-	    string_overflow ();
-	  c = STRING_CHAR_AND_LENGTH (SDATA (obj) + i_byte, len);
-	  if (inword && flag != CASE_CAPITALIZE_UP)
-	    c = downcase (c);
-	  else if (!inword || flag != CASE_CAPITALIZE_UP)
-	    c = upcase (c);
-	  if ((int) flag >= (int) CASE_CAPITALIZE)
-	    inword = (SYNTAX (c) == Sword);
-	  o += CHAR_STRING (c, o);
-	}
-      eassert (o - dst <= o_size);
-      obj = make_multibyte_string ((char *) dst, size, o - dst);
-      SAFE_FREE ();
-      return obj;
-    }
+    return do_casify_unibyte_string (flag, obj);
 }
 
 DEFUN ("upcase", Fupcase, Supcase, 1, 1, 0,
-- 
2.8.0.rc3.226.g39d4020






^ permalink raw reply related	[flat|nested] 89+ messages in thread

* bug#24603: [RFC 05/18] Introduce case_character function
  2016-10-04  1:10 ` bug#24603: [RFC 01/18] Add tests for casefiddle.c Michal Nazarewicz
                     ` (2 preceding siblings ...)
  2016-10-04  1:10   ` bug#24603: [RFC 04/18] Split casify_object into multiple functions Michal Nazarewicz
@ 2016-10-04  1:10   ` Michal Nazarewicz
  2016-10-04  1:10   ` bug#24603: [RFC 06/18] Add support for title-casing letters Michal Nazarewicz
                     ` (12 subsequent siblings)
  16 siblings, 0 replies; 89+ messages in thread
From: Michal Nazarewicz @ 2016-10-04  1:10 UTC (permalink / raw)
  To: 24603

Move single-character casing logic into a separate function so that
it is collected in a single place.  This will make future changes to
the logic easier.

* src/casefiddle.c (struct casing_context, prepare_casing_context): New
sturcture for saving casing context and function to initialise it.
(case_character): New function which cases character base on provided
context.
(do_casify_integer, do_casify_multibyte_string,
do_casify_unibyte_string, casify_object, casify_region): Convert to
use casing_context and case_character.
---
 src/casefiddle.c | 135 +++++++++++++++++++++++++++++++------------------------
 1 file changed, 77 insertions(+), 58 deletions(-)

diff --git a/src/casefiddle.c b/src/casefiddle.c
index 47ebdf0..2fbd23b 100644
--- a/src/casefiddle.c
+++ b/src/casefiddle.c
@@ -30,9 +30,56 @@ along with GNU Emacs.  If not, see <http://www.gnu.org/licenses/>.  */
 #include "keymap.h"
 
 enum case_action {CASE_UP, CASE_DOWN, CASE_CAPITALIZE, CASE_CAPITALIZE_UP};
+
+/* State for casing individual characters.  */
+struct casing_context {
+  /* User-requested action. */
+  enum case_action flag;
+  /* If true, function operates on a buffer as opposed to a string or character.
+     When run on a buffer, syntax_prefix_flag_p is taken into account when
+     determined inword flag. */
+  bool inbuffer;
+  /* Conceptually, this denotes whether we are inside of a word except
+     that if flag is CASE_UP it’s always false and if flag is CASE_DOWN
+     this is always true. */
+  bool inword;
+};
+
+/* Initialise CTX structure and prepares related global data for casing
+   characters. */
+static void
+prepare_casing_context (struct casing_context *ctx,
+			enum case_action flag, bool inbuffer)
+{
+  ctx->flag = flag;
+  ctx->inbuffer = inbuffer;
+  ctx->inword = flag == CASE_DOWN;
+
+  /* If the case table is flagged as modified, rescan it.  */
+  if (NILP (XCHAR_TABLE (BVAR (current_buffer, downcase_table))->extras[1]))
+    Fset_case_table (BVAR (current_buffer, downcase_table));
+
+  if (inbuffer && (int) flag >= (int) CASE_CAPITALIZE)
+    SETUP_BUFFER_SYNTAX_TABLE ();	/* For syntax_prefix_flag_p.  */
+}
+
+/* Based on CTX, case character CH accordingly.  Update CTX as necessary.
+   Return cased character. */
+static int
+case_character (struct casing_context *ctx, int ch)
+{
+  if (ctx->inword)
+    ch = ctx->flag == CASE_CAPITALIZE_UP ? ch : downcase (ch);
+  else
+    ch = upcase(ch);
+  if ((int) ctx->flag >= (int) CASE_CAPITALIZE)
+    ctx->inword = SYNTAX (ch) == Sword &&
+      (!ctx->inbuffer || ctx->inword || !syntax_prefix_flag_p (ch));
+  return ch;
+}
 \f
 static Lisp_Object
-do_casify_integer (enum case_action flag, Lisp_Object obj)
+do_casify_integer (struct casing_context *ctx, Lisp_Object obj)
 {
   int flagbits = (CHAR_ALT | CHAR_SUPER | CHAR_HYPER
 		  | CHAR_SHIFT | CHAR_CTL | CHAR_META);
@@ -55,7 +102,7 @@ do_casify_integer (enum case_action flag, Lisp_Object obj)
 	       !NILP (BVAR (current_buffer, enable_multibyte_characters)));
   if (! multibyte)
     MAKE_CHAR_MULTIBYTE (ch);
-  cased = flag == CASE_DOWN ? downcase (ch) : upcase (ch);
+  cased = case_character (ctx, ch);
   if (cased == ch)
     return obj;
 
@@ -66,10 +113,9 @@ do_casify_integer (enum case_action flag, Lisp_Object obj)
 }
 
 static Lisp_Object
-do_casify_multibyte_string (enum case_action flag, Lisp_Object obj)
+do_casify_multibyte_string (struct casing_context *ctx, Lisp_Object obj)
 {
   ptrdiff_t i, i_byte, size = SCHARS (obj);
-  bool inword = flag == CASE_DOWN;
   int len, ch, cased;
   USE_SAFE_ALLOCA;
   ptrdiff_t o_size;
@@ -83,14 +129,7 @@ do_casify_multibyte_string (enum case_action flag, Lisp_Object obj)
       if (o_size - MAX_MULTIBYTE_LENGTH < o - dst)
 	string_overflow ();
       ch = STRING_CHAR_AND_LENGTH (SDATA (obj) + i_byte, len);
-      if (inword && flag != CASE_CAPITALIZE_UP)
-	cased = downcase (ch);
-      else if (!inword || flag != CASE_CAPITALIZE_UP)
-	cased = upcase (ch);
-      else
-	cased = ch;
-      if ((int) flag >= (int) CASE_CAPITALIZE)
-	inword = (SYNTAX (ch) == Sword);
+      cased = case_character (ctx, ch);
       o += CHAR_STRING (cased, o);
     }
   eassert (o - dst <= o_size);
@@ -100,10 +139,9 @@ do_casify_multibyte_string (enum case_action flag, Lisp_Object obj)
 }
 
 static Lisp_Object
-do_casify_unibyte_string (enum case_action flag, Lisp_Object obj)
+do_casify_unibyte_string (struct casing_context *ctx, Lisp_Object obj)
 {
   ptrdiff_t i, size = SCHARS (obj);
-  bool inword = flag == CASE_DOWN;
   int ch, cased;
 
   obj = Fcopy_sequence (obj);
@@ -111,20 +149,13 @@ do_casify_unibyte_string (enum case_action flag, Lisp_Object obj)
     {
       ch = SREF (obj, i);
       MAKE_CHAR_MULTIBYTE (ch);
-      cased = ch;
-      if (inword && flag != CASE_CAPITALIZE_UP)
-	ch = downcase (ch);
-      else if (!uppercasep (ch)
-	       && (!inword || flag != CASE_CAPITALIZE_UP))
-	ch = upcase (cased);
-      if ((int) flag >= (int) CASE_CAPITALIZE)
-	inword = (SYNTAX (ch) == Sword);
+      cased = case_character (ctx, ch);
       if (ch == cased)
 	continue;
-      MAKE_CHAR_UNIBYTE (ch);
+      MAKE_CHAR_UNIBYTE (cased);
       /* If the char can't be converted to a valid byte, just don't change it */
-      if (ch >= 0 && ch < 256)
-	SSET (obj, i, ch);
+      if (cased >= 0 && cased < 256)
+	SSET (obj, i, cased);
     }
   return obj;
 }
@@ -132,20 +163,19 @@ do_casify_unibyte_string (enum case_action flag, Lisp_Object obj)
 static Lisp_Object
 casify_object (enum case_action flag, Lisp_Object obj)
 {
-  /* If the case table is flagged as modified, rescan it.  */
-  if (NILP (XCHAR_TABLE (BVAR (current_buffer, downcase_table))->extras[1]))
-    Fset_case_table (BVAR (current_buffer, downcase_table));
+  struct casing_context ctx;
+  prepare_casing_context (&ctx, flag, false);
 
   if (INTEGERP (obj))
-    return do_casify_integer (flag, obj);
+    return do_casify_integer (&ctx, obj);
   else if (!STRINGP (obj))
     wrong_type_argument (Qchar_or_string_p, obj);
   else if (!SCHARS (obj))
     return obj;
   else if (STRING_MULTIBYTE (obj))
-    return do_casify_multibyte_string (flag, obj);
+    return do_casify_multibyte_string (&ctx, obj);
   else
-    return do_casify_unibyte_string (flag, obj);
+    return do_casify_unibyte_string (&ctx, obj);
 }
 
 DEFUN ("upcase", Fupcase, Supcase, 1, 1, 0,
@@ -196,8 +226,6 @@ The argument object is not altered--the value is a copy.  */)
 static void
 casify_region (enum case_action flag, Lisp_Object b, Lisp_Object e)
 {
-  int c;
-  bool inword = flag == CASE_DOWN;
   bool multibyte = !NILP (BVAR (current_buffer, enable_multibyte_characters));
   ptrdiff_t start, end;
   ptrdiff_t start_byte;
@@ -208,14 +236,12 @@ casify_region (enum case_action flag, Lisp_Object b, Lisp_Object e)
   ptrdiff_t opoint = PT;
   ptrdiff_t opoint_byte = PT_BYTE;
 
+  struct casing_context ctx;
+
   if (EQ (b, e))
     /* Not modifying because nothing marked */
     return;
 
-  /* If the case table is flagged as modified, rescan it.  */
-  if (NILP (XCHAR_TABLE (BVAR (current_buffer, downcase_table))->extras[1]))
-    Fset_case_table (BVAR (current_buffer, downcase_table));
-
   validate_region (&b, &e);
   start = XFASTINT (b);
   end = XFASTINT (e);
@@ -223,32 +249,25 @@ casify_region (enum case_action flag, Lisp_Object b, Lisp_Object e)
   record_change (start, end - start);
   start_byte = CHAR_TO_BYTE (start);
 
-  SETUP_BUFFER_SYNTAX_TABLE ();	/* For syntax_prefix_flag_p.  */
+  prepare_casing_context (&ctx, flag, true);
 
   while (start < end)
     {
-      int c2, len;
+      int ch, cased, len;
 
       if (multibyte)
 	{
-	  c = FETCH_MULTIBYTE_CHAR (start_byte);
-	  len = CHAR_BYTES (c);
+	  ch = FETCH_MULTIBYTE_CHAR (start_byte);
+	  len = CHAR_BYTES (ch);
 	}
       else
 	{
-	  c = FETCH_BYTE (start_byte);
-	  MAKE_CHAR_MULTIBYTE (c);
+	  ch = FETCH_BYTE (start_byte);
+	  MAKE_CHAR_MULTIBYTE (ch);
 	  len = 1;
 	}
-      c2 = c;
-      if (inword && flag != CASE_CAPITALIZE_UP)
-	c = downcase (c);
-      else if (!inword || flag != CASE_CAPITALIZE_UP)
-	c = upcase (c);
-      if ((int) flag >= (int) CASE_CAPITALIZE)
-	inword = ((SYNTAX (c) == Sword)
-		  && (inword || !syntax_prefix_flag_p (c)));
-      if (c != c2)
+      cased = case_character (&ctx, ch);
+      if (ch != cased)
 	{
 	  last = start;
 	  if (first < 0)
@@ -256,18 +275,18 @@ casify_region (enum case_action flag, Lisp_Object b, Lisp_Object e)
 
 	  if (! multibyte)
 	    {
-	      MAKE_CHAR_UNIBYTE (c);
-	      FETCH_BYTE (start_byte) = c;
+	      MAKE_CHAR_UNIBYTE (cased);
+	      FETCH_BYTE (start_byte) = cased;
 	    }
-	  else if (ASCII_CHAR_P (c2) && ASCII_CHAR_P (c))
-	    FETCH_BYTE (start_byte) = c;
+	  else if (ASCII_CHAR_P (cased) && ASCII_CHAR_P (ch))
+	    FETCH_BYTE (start_byte) = cased;
 	  else
 	    {
-	      int tolen = CHAR_BYTES (c);
+	      int tolen = CHAR_BYTES (cased);
 	      int j;
 	      unsigned char str[MAX_MULTIBYTE_LENGTH];
 
-	      CHAR_STRING (c, str);
+	      CHAR_STRING (cased, str);
 	      if (len == tolen)
 		{
 		  /* Length is unchanged.  */
-- 
2.8.0.rc3.226.g39d4020






^ permalink raw reply related	[flat|nested] 89+ messages in thread

* bug#24603: [RFC 06/18] Add support for title-casing letters
  2016-10-04  1:10 ` bug#24603: [RFC 01/18] Add tests for casefiddle.c Michal Nazarewicz
                     ` (3 preceding siblings ...)
  2016-10-04  1:10   ` bug#24603: [RFC 05/18] Introduce case_character function Michal Nazarewicz
@ 2016-10-04  1:10   ` Michal Nazarewicz
  2016-10-04  1:10   ` bug#24603: [RFC 07/18] Split up casify_region function Michal Nazarewicz
                     ` (11 subsequent siblings)
  16 siblings, 0 replies; 89+ messages in thread
From: Michal Nazarewicz @ 2016-10-04  1:10 UTC (permalink / raw)
  To: 24603

* src/casefiddle.c (struct casing_context, prepare_casing_context): Add
titlecase_char_table member.  It’s set to the ‘titlecase’ Unicode
property table if capitalisation has been requested.
(case_character): Make use of the titlecase_char_table to title-case
initial characters when capitalising.

* test/src/casefiddle-tests.el (casefiddle-tests--characters,
casefiddle-tests-casing): Update test cases which are now passing.
---
 etc/NEWS                     | 10 ++++++----
 src/casefiddle.c             | 27 +++++++++++++++++++++------
 test/src/casefiddle-tests.el | 39 ++++++++++++++++++++++++++-------------
 3 files changed, 53 insertions(+), 23 deletions(-)

diff --git a/etc/NEWS b/etc/NEWS
index 61afcc6..f2bbead 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -235,11 +235,13 @@ same as in modes where the character is not whitespace.
 Instead of only checking the modification time, Emacs now also checks
 the file's actual content before prompting the user.
 
-** Title case characters are properly converted to upper case.
+** Title case characters are properly cased (from and into).
 'upcase', 'upcase-region' et al. convert title case characters (such
-as Dz) into their upper case form (such as DZ).  As a downside,
-'capitalize' and 'upcase-initials' produce awkward words where first
-two letters are upper case, e.g. DŽungla (instead of Džungla).
+as Dz) into their upper case form (such as DZ).
+
+Similarly, 'capitalize', 'upcase-initials' et al. make use of
+title-case forms of initial characters (correctly producing for example
+Džungla instead of incorrect DŽungla).
 
 \f
 * Changes in Specialized Modes and Packages in Emacs 26.1
diff --git a/src/casefiddle.c b/src/casefiddle.c
index 2fbd23b..b3ffa86 100644
--- a/src/casefiddle.c
+++ b/src/casefiddle.c
@@ -33,6 +33,10 @@ enum case_action {CASE_UP, CASE_DOWN, CASE_CAPITALIZE, CASE_CAPITALIZE_UP};
 
 /* State for casing individual characters.  */
 struct casing_context {
+  /* A char-table with title-case character mappings or nil.  It being non-nil
+     implies flag being CASE_CAPITALIZE or CASE_CAPITALIZE_UP (but the reverse
+     is not true).  */
+  Lisp_Object titlecase_char_table;
   /* User-requested action. */
   enum case_action flag;
   /* If true, function operates on a buffer as opposed to a string or character.
@@ -54,6 +58,9 @@ prepare_casing_context (struct casing_context *ctx,
   ctx->flag = flag;
   ctx->inbuffer = inbuffer;
   ctx->inword = flag == CASE_DOWN;
+  ctx->titlecase_char_table = (int)flag >= (int)CASE_CAPITALIZE
+    ? uniprop_table (intern_c_string ("titlecase"))
+    : Qnil;
 
   /* If the case table is flagged as modified, rescan it.  */
   if (NILP (XCHAR_TABLE (BVAR (current_buffer, downcase_table))->extras[1]))
@@ -68,10 +75,16 @@ prepare_casing_context (struct casing_context *ctx,
 static int
 case_character (struct casing_context *ctx, int ch)
 {
+  Lisp_Object prop;
+
   if (ctx->inword)
     ch = ctx->flag == CASE_CAPITALIZE_UP ? ch : downcase (ch);
+  else if (!NILP (ctx->titlecase_char_table) &&
+	   CHARACTERP (prop = CHAR_TABLE_REF (ctx->titlecase_char_table, ch)))
+    ch = XFASTINT (prop);
   else
     ch = upcase(ch);
+
   if ((int) ctx->flag >= (int) CASE_CAPITALIZE)
     ctx->inword = SYNTAX (ch) == Sword &&
       (!ctx->inbuffer || ctx->inword || !syntax_prefix_flag_p (ch));
@@ -199,8 +212,8 @@ The argument object is not altered--the value is a copy.  */)
 
 DEFUN ("capitalize", Fcapitalize, Scapitalize, 1, 1, 0,
        doc: /* Convert argument to capitalized form and return that.
-This means that each word's first character is upper case
-and the rest is lower case.
+This means that each word's first character is upper case (more
+precisely, if available, title case) and the rest is lower case.
 The argument may be a character or string.  The result has the same type.
 The argument object is not altered--the value is a copy.  */)
   (Lisp_Object obj)
@@ -212,7 +225,8 @@ The argument object is not altered--the value is a copy.  */)
 
 DEFUN ("upcase-initials", Fupcase_initials, Supcase_initials, 1, 1, 0,
        doc: /* Convert the initial of each word in the argument to upper case.
-Do not change the other letters of each word.
+(More precisely, if available, initial of each word is converted to
+title-case).  Do not change the other letters of each word.
 The argument may be a character or string.  The result has the same type.
 The argument object is not altered--the value is a copy.  */)
   (Lisp_Object obj)
@@ -376,8 +390,8 @@ point and the mark is operated on.  */)
 
 DEFUN ("capitalize-region", Fcapitalize_region, Scapitalize_region, 2, 2, "r",
        doc: /* Convert the region to capitalized form.
-Capitalized form means each word's first character is upper case
-and the rest of it is lower case.
+Capitalized form means each word's first character is upper case (more
+precisely, if available, title case) and the rest of it is lower case.
 In programs, give two arguments, the starting and ending
 character positions to operate on.  */)
   (Lisp_Object beg, Lisp_Object end)
@@ -391,7 +405,8 @@ character positions to operate on.  */)
 DEFUN ("upcase-initials-region", Fupcase_initials_region,
        Supcase_initials_region, 2, 2, "r",
        doc: /* Upcase the initial of each word in the region.
-Subsequent letters of each word are not changed.
+(More precisely, if available, initial of each word is converted to
+title-case).  Subsequent letters of each word are not changed.
 In programs, give two arguments, the starting and ending
 character positions to operate on.  */)
   (Lisp_Object beg, Lisp_Object end)
diff --git a/test/src/casefiddle-tests.el b/test/src/casefiddle-tests.el
index 8d9bf01..def74a0 100644
--- a/test/src/casefiddle-tests.el
+++ b/test/src/casefiddle-tests.el
@@ -62,13 +62,9 @@ casefiddle-tests--characters
     (?Ł ?Ł ?ł ?Ł)
     (?ł ?Ł ?ł ?Ł)
 
-    ;; FIXME: Commented one is what we want.
-    ;;(?DŽ ?DŽ ?dž ?Dž)
-    (?DŽ ?DŽ ?dž ?DŽ)
-    ;;(?Dž ?DŽ ?dž ?Dž)
-    (?Dž ?DŽ ?dž ?DŽ)
-    ;;(?dž ?DŽ ?dž ?Dž)
-    (?dž ?DŽ ?dž ?DŽ)
+    (?DŽ ?DŽ ?dž ?Dž)
+    (?Dž ?DŽ ?dž ?Dž)
+    (?dž ?DŽ ?dž ?Dž)
 
     (?Σ ?Σ ?σ ?Σ)
     (?σ ?Σ ?σ ?Σ)
@@ -141,19 +137,19 @@ casefiddle-tests--characters
             ;; input  upcase  downcase  capitalize  upcase-initials
             '(("Foo baR" "FOO BAR" "foo bar" "Foo Bar" "Foo BaR")
               ("Ⅷ ⅷ" "Ⅷ Ⅷ" "ⅷ ⅷ" "Ⅷ Ⅷ" "Ⅷ Ⅷ")
+              ;; "DžUNGLA" is an unfortunate result but it’s really best we can
+              ;; do while still being consistent.  Hopefully, users only ever
+              ;; use upcase-initials on camelCase identifiers not real words.
+              ("DŽUNGLA" "DŽUNGLA" "džungla" "Džungla" "DžUNGLA")
+              ("Džungla" "DŽUNGLA" "džungla" "Džungla" "Džungla")
+              ("džungla" "DŽUNGLA" "džungla" "Džungla" "Džungla")
               ;; FIXME: Everything below is broken at the moment.  Here’s what
               ;; should happen:
-              ;;("DŽUNGLA" "DŽUNGLA" "džungla" "Džungla" "DžUNGLA")
-              ;;("Džungla" "DŽUNGLA" "džungla" "Džungla" "Džungla")
-              ;;("džungla" "DŽUNGLA" "džungla" "Džungla" "Džungla")
               ;;("define" "DEFINE" "define" "Define" "Define")
               ;;("fish" "FIsh" "fish" "Fish" "Fish")
               ;;("Straße" "STRASSE" "straße" "Straße" "Straße")
               ;;("ΌΣΟΣ" "ΌΣΟΣ" "όσος" "Όσος" "Όσος")
               ;; And here’s what is actually happening:
-              ("DŽUNGLA" "DŽUNGLA" "džungla" "DŽungla" "DŽUNGLA")
-              ("Džungla" "DŽUNGLA" "džungla" "DŽungla" "DŽungla")
-              ("džungla" "DŽUNGLA" "džungla" "DŽungla" "DŽungla")
               ("define" "DEfiNE" "define" "Define" "Define")
               ("fish" "fiSH" "fish" "fish" "fish")
               ("Straße" "STRAßE" "straße" "Straße" "Straße")
@@ -186,4 +182,21 @@ casefiddle-tests--characters
              (setq expected (cdr expected)))))))))
 
 
+(ert-deftest casefiddle-tests-char-casing ()
+  ;;             input upcase downcase [titlecase]
+  (dolist (test '((?a ?A ?a) (?A ?A ?a)
+                  (?ł ?Ł ?ł) (?Ł ?Ł ?ł)
+                  (?ß ?ß ?ß) (?ẞ ?ẞ ?ß)
+                  (?ⅷ ?Ⅷ ?ⅷ) (?Ⅷ ?Ⅷ ?ⅷ)
+                  (?DŽ ?DŽ ?dž ?Dž) (?Dž ?DŽ ?dž ?Dž) (?dž ?DŽ ?dž ?Dž)))
+    (let ((ch (car test))
+          (up (nth 1 test))
+          (lo (nth 2 test))
+          (tc (or (nth 3 test) (nth 1 test))))
+      (should (eq up (upcase ch)))
+      (should (eq lo (downcase ch)))
+      (should (eq tc (capitalize ch)))
+      (should (eq tc (upcase-initials ch))))))
+
+
 ;;; casefiddle-tests.el ends here
-- 
2.8.0.rc3.226.g39d4020






^ permalink raw reply related	[flat|nested] 89+ messages in thread

* bug#24603: [RFC 07/18] Split up casify_region function.
  2016-10-04  1:10 ` bug#24603: [RFC 01/18] Add tests for casefiddle.c Michal Nazarewicz
                     ` (4 preceding siblings ...)
  2016-10-04  1:10   ` bug#24603: [RFC 06/18] Add support for title-casing letters Michal Nazarewicz
@ 2016-10-04  1:10   ` Michal Nazarewicz
  2016-10-04  7:17     ` Eli Zaretskii
  2016-10-04  1:10   ` bug#24603: [RFC 08/18] Support casing characters which map into multiple code points Michal Nazarewicz
                     ` (10 subsequent siblings)
  16 siblings, 1 reply; 89+ messages in thread
From: Michal Nazarewicz @ 2016-10-04  1:10 UTC (permalink / raw)
  To: 24603

No functional changes at this time but splitting casify_region into
a function dealing with multibyte and another dealing with unibyte
buffers will make future code changes slightly easier.

* src/casefiddle.c (casify_region): Move most of the code into two
new functions:
(do_casify_multibyte_region, do_casify_unibyte_region): new functions.
---
 src/casefiddle.c | 159 ++++++++++++++++++++++++++++++-------------------------
 1 file changed, 86 insertions(+), 73 deletions(-)

diff --git a/src/casefiddle.c b/src/casefiddle.c
index b3ffa86..a016871 100644
--- a/src/casefiddle.c
+++ b/src/casefiddle.c
@@ -234,102 +234,115 @@ The argument object is not altered--the value is a copy.  */)
   return casify_object (CASE_CAPITALIZE_UP, obj);
 }
 \f
-/* flag is CASE_UP, CASE_DOWN or CASE_CAPITALIZE or CASE_CAPITALIZE_UP.
-   b and e specify range of buffer to operate on. */
-
-static void
-casify_region (enum case_action flag, Lisp_Object b, Lisp_Object e)
+/* Based on CTX, case region in a multibyte buffer from POS to *ENDP.  Return
+   first position that has changed and save last position in *ENDP.  If no
+   characters were changed, return -1 and *ENDP is unspecified. */
+static ptrdiff_t
+do_casify_unibyte_region (struct casing_context *ctx,
+			  ptrdiff_t pos, ptrdiff_t *endp)
 {
-  bool multibyte = !NILP (BVAR (current_buffer, enable_multibyte_characters));
-  ptrdiff_t start, end;
-  ptrdiff_t start_byte;
+  ptrdiff_t first = -1, last = -1;  /* Position of first and last changes. */
+  ptrdiff_t end = *endp;
+  int ch, cased;
 
-  /* Position of first and last changes.  */
-  ptrdiff_t first = -1, last;
+  for (; pos < end; ++pos)
+    {
+      ch = FETCH_BYTE (pos);
+      MAKE_CHAR_MULTIBYTE (ch);
 
-  ptrdiff_t opoint = PT;
-  ptrdiff_t opoint_byte = PT_BYTE;
+      cased = case_character (ctx, ch);
+      if (cased == ch)
+	continue;
 
-  struct casing_context ctx;
+      last = pos;
+      if (first < 0)
+	first = pos;
 
-  if (EQ (b, e))
-    /* Not modifying because nothing marked */
-    return;
+      MAKE_CHAR_UNIBYTE (cased);
+      FETCH_BYTE (pos) = cased;
+    }
 
-  validate_region (&b, &e);
-  start = XFASTINT (b);
-  end = XFASTINT (e);
-  modify_text (start, end);
-  record_change (start, end - start);
-  start_byte = CHAR_TO_BYTE (start);
+  *endp = last + 1;
+  return first;
+}
 
-  prepare_casing_context (&ctx, flag, true);
+/* Based on CTX, case region in a unibyte buffer from POS to *ENDP.  Return
+   first position that has changed and save last position in *ENDP.  If no
+   characters were changed, return -1 and *ENDP is unspecified. */
+static ptrdiff_t
+do_casify_multibyte_region (struct casing_context *ctx,
+                           ptrdiff_t pos, ptrdiff_t *endp)
+{
+  ptrdiff_t first = -1, last = -1;  /* Position of first and last changes. */
+  ptrdiff_t pos_byte = CHAR_TO_BYTE (pos), end = *endp;
+  ptrdiff_t opoint = PT;
+  int ch, cased, len;
 
-  while (start < end)
+  while (pos < end)
     {
-      int ch, cased, len;
-
-      if (multibyte)
-	{
-	  ch = FETCH_MULTIBYTE_CHAR (start_byte);
-	  len = CHAR_BYTES (ch);
-	}
-      else
-	{
-	  ch = FETCH_BYTE (start_byte);
-	  MAKE_CHAR_MULTIBYTE (ch);
-	  len = 1;
-	}
-      cased = case_character (&ctx, ch);
-      if (ch != cased)
+      ch = STRING_CHAR_AND_LENGTH (BYTE_POS_ADDR (pos_byte), len);
+      cased = case_character (ctx, ch);
+      if (cased != ch)
 	{
-	  last = start;
+	  last = pos;
 	  if (first < 0)
-	    first = start;
+	    first = pos;
 
-	  if (! multibyte)
-	    {
-	      MAKE_CHAR_UNIBYTE (cased);
-	      FETCH_BYTE (start_byte) = cased;
-	    }
-	  else if (ASCII_CHAR_P (cased) && ASCII_CHAR_P (ch))
-	    FETCH_BYTE (start_byte) = cased;
+	  if (ASCII_CHAR_P (cased) && ASCII_CHAR_P (ch))
+	    FETCH_BYTE (pos_byte) = cased;
 	  else
 	    {
-	      int tolen = CHAR_BYTES (cased);
-	      int j;
 	      unsigned char str[MAX_MULTIBYTE_LENGTH];
-
-	      CHAR_STRING (cased, str);
-	      if (len == tolen)
-		{
-		  /* Length is unchanged.  */
-		  for (j = 0; j < len; ++j)
-		    FETCH_BYTE (start_byte + j) = str[j];
-		}
+	      int totlen = CHAR_STRING (cased, str);
+	      if (len == totlen)
+		memcpy (BYTE_POS_ADDR (pos_byte), str, len);
 	      else
-		{
-		  /* Replace one character with the other,
-		     keeping text properties the same.  */
-		  replace_range_2 (start, start_byte,
-				   start + 1, start_byte + len,
-				   (char *) str, 1, tolen,
-				   0);
-		  len = tolen;
-		}
+		/* Replace one character with the other(s), keeping text
+		   properties the same.  */
+		replace_range_2 (pos, pos_byte, pos + 1, pos_byte + len,
+				 (char *) str, 9, totlen, 0);
+	      len = totlen;
 	    }
 	}
-      start++;
-      start_byte += len;
+      pos++;
+      pos_byte += len;
     }
 
   if (PT != opoint)
-    TEMP_SET_PT_BOTH (opoint, opoint_byte);
+    TEMP_SET_PT_BOTH (opoint, CHAR_TO_BYTE (opoint));
+
+  *endp = last;
+  return first;
+}
+
+/* flag is CASE_UP, CASE_DOWN or CASE_CAPITALIZE or CASE_CAPITALIZE_UP.
+   b and e specify range of buffer to operate on. */
+static void
+casify_region (enum case_action flag, Lisp_Object b, Lisp_Object e)
+{
+  struct casing_context ctx;
+  ptrdiff_t start, end;
+
+  if (EQ (b, e))
+    /* Not modifying because nothing marked */
+    return;
+
+  validate_region (&b, &e);
+  start = XFASTINT (b);
+  end = XFASTINT (e);
+  modify_text (start, end);
+  record_change (start, end - start);
+  prepare_casing_context (&ctx, flag, true);
+
+  if (NILP (BVAR (current_buffer, enable_multibyte_characters)))
+    start = do_casify_unibyte_region (&ctx, start, &end);
+  else
+    start = do_casify_multibyte_region (&ctx, start, &end);
 
-  if (first >= 0)
+  if (start >= 0)
     {
-      signal_after_change (first, last + 1 - first, last + 1 - first);
-      update_compositions (first, last + 1, CHECK_ALL);
+      signal_after_change (start, end + 1 - start, end + 1 - start);
+      update_compositions (start, end + 1, CHECK_ALL);
     }
 }
 
-- 
2.8.0.rc3.226.g39d4020






^ permalink raw reply related	[flat|nested] 89+ messages in thread

* bug#24603: [RFC 08/18] Support casing characters which map into multiple code points
  2016-10-04  1:10 ` bug#24603: [RFC 01/18] Add tests for casefiddle.c Michal Nazarewicz
                     ` (5 preceding siblings ...)
  2016-10-04  1:10   ` bug#24603: [RFC 07/18] Split up casify_region function Michal Nazarewicz
@ 2016-10-04  1:10   ` Michal Nazarewicz
  2016-10-04  7:38     ` Eli Zaretskii
  2016-10-04  1:10   ` bug#24603: [RFC 09/18] Implement special sigma casing rule Michal Nazarewicz
                     ` (9 subsequent siblings)
  16 siblings, 1 reply; 89+ messages in thread
From: Michal Nazarewicz @ 2016-10-04  1:10 UTC (permalink / raw)
  To: 24603

Implement uncoditional special casing rules defined in Unicode standard.

Among other thigs, they deal with cases when a single code point is
replaced by multiple ones becasue simple character does not exist (e.g.
fi ligature turning into FL) or is not commonly used (e.g. ß turning into
SS).

* admin/unidata/SpecialCasing.txt: New data file pulled from Unicode
standard distribution.
* admin/unidata/README: Mention SpecialCasing.txt.

* src/make-special-casing.py: New script to generate special-casing.h
file from the SpecialCasing.txt data file.

* src/casefiddle.c: Include special-casing.h so special casing rules are
available and can be used in the translation unit.

(struct casing_str_buf): New structure for representing short strings.
It’s used to compactly encode special casing rules.

(case_character_imlp): New function which can handle one-to-many
character mappings.
(case_character, case_single_character): Wrappers for the above
functions.  The former may map one character to multiple code points
while the latter does what the former used to do (i.e. handles
one-to-one mappings only).

(do_casify_integer, do_casify_unibyte_string,
do_casify_unibyte_region): Use case_single_character.
(do_casify_multibyte_string, do_casify_multibyte_region): Support new
features of case_character.
* (do_casify_region): Updated after do_casify_multibyte_string changes.

(upcase, capitalize, upcase-initials): Update documentation to mention
limitations when working on characters.

* test/src/casefiddle-tests.el (casefiddle-tests-casing): Update test
cases which are now passing.

* test/lisp/char-fold-tests.el (char-fold--ascii-upcase,
char-fold--ascii-downcase): New functions which behave like old ‘upcase’
and ‘downcase’.
(char-fold--test-match-exactly): Use the new functions.  This is needed
because otherwise fi and similar characters are turned into their multi-
-character representation.
---
 .gitignore                      |   1 +
 admin/unidata/README            |   4 +
 admin/unidata/SpecialCasing.txt | 281 ++++++++++++++++++++++++++++++++++++++++
 etc/NEWS                        |  16 ++-
 src/Makefile.in                 |   3 +
 src/casefiddle.c                | 218 ++++++++++++++++++++++---------
 src/deps.mk                     |   2 +-
 src/make-special-casing.py      | 189 +++++++++++++++++++++++++++
 test/lisp/char-fold-tests.el    |  12 +-
 test/src/casefiddle-tests.el    |   9 +-
 10 files changed, 658 insertions(+), 77 deletions(-)
 create mode 100644 admin/unidata/SpecialCasing.txt
 create mode 100644 src/make-special-casing.py

diff --git a/.gitignore b/.gitignore
index 15f9c56..a07f972 100644
--- a/.gitignore
+++ b/.gitignore
@@ -79,6 +79,7 @@ lib/warn-on-use.h
 src/buildobj.h
 src/globals.h
 src/lisp.mk
+src/special-casing.h
 
 # Lisp-level sources built by 'make'.
 *cus-load.el
diff --git a/admin/unidata/README b/admin/unidata/README
index 534670c..06a6666 100644
--- a/admin/unidata/README
+++ b/admin/unidata/README
@@ -24,3 +24,7 @@ http://www.unicode.org/Public/8.0.0/ucd/Blocks.txt
 NormalizationTest.txt
 http://www.unicode.org/Public/UNIDATA/NormalizationTest.txt
 2016-07-16
+
+SpecialCasing.txt
+http://unicode.org/Public/UNIDATA/SpecialCasing.txt
+2016-03-03
diff --git a/admin/unidata/SpecialCasing.txt b/admin/unidata/SpecialCasing.txt
new file mode 100644
index 0000000..b23fa7f
--- /dev/null
+++ b/admin/unidata/SpecialCasing.txt
@@ -0,0 +1,281 @@
+# SpecialCasing-9.0.0.txt
+# Date: 2016-03-02, 18:55:13 GMT
+# © 2016 Unicode®, Inc.
+# Unicode and the Unicode Logo are registered trademarks of Unicode, Inc. in the U.S. and other countries.
+# For terms of use, see http://www.unicode.org/terms_of_use.html
+#
+# Unicode Character Database
+#   For documentation, see http://www.unicode.org/reports/tr44/
+#
+# Special Casing
+#
+# This file is a supplement to the UnicodeData.txt file. It does not define any
+# properties, but rather provides additional information about the casing of
+# Unicode characters, for situations when casing incurs a change in string length
+# or is dependent on context or locale. For compatibility, the UnicodeData.txt
+# file only contains simple case mappings for characters where they are one-to-one
+# and independent of context and language. The data in this file, combined with
+# the simple case mappings in UnicodeData.txt, defines the full case mappings
+# Lowercase_Mapping (lc), Titlecase_Mapping (tc), and Uppercase_Mapping (uc).
+#
+# Note that the preferred mechanism for defining tailored casing operations is
+# the Unicode Common Locale Data Repository (CLDR). For more information, see the
+# discussion of case mappings and case algorithms in the Unicode Standard.
+#
+# All code points not listed in this file that do not have a simple case mappings
+# in UnicodeData.txt map to themselves.
+# ================================================================================
+# Format
+# ================================================================================
+# The entries in this file are in the following machine-readable format:
+#
+# <code>; <lower>; <title>; <upper>; (<condition_list>;)? # <comment>
+#
+# <code>, <lower>, <title>, and <upper> provide the respective full case mappings
+# of <code>, expressed as character values in hex. If there is more than one character,
+# they are separated by spaces. Other than as used to separate elements, spaces are
+# to be ignored.
+#
+# The <condition_list> is optional. Where present, it consists of one or more language IDs
+# or casing contexts, separated by spaces. In these conditions:
+# - A condition list overrides the normal behavior if all of the listed conditions are true.
+# - The casing context is always the context of the characters in the original string,
+#   NOT in the resulting string.
+# - Case distinctions in the condition list are not significant.
+# - Conditions preceded by "Not_" represent the negation of the condition.
+# The condition list is not represented in the UCD as a formal property.
+#
+# A language ID is defined by BCP 47, with '-' and '_' treated equivalently.
+#
+# A casing context for a character is defined by Section 3.13 Default Case Algorithms
+# of The Unicode Standard.
+#
+# Parsers of this file must be prepared to deal with future additions to this format:
+#  * Additional contexts
+#  * Additional fields
+# ================================================================================
+
+# ================================================================================
+# Unconditional mappings
+# ================================================================================
+
+# The German es-zed is special--the normal mapping is to SS.
+# Note: the titlecase should never occur in practice. It is equal to titlecase(uppercase(<es-zed>))
+
+00DF; 00DF; 0053 0073; 0053 0053; # LATIN SMALL LETTER SHARP S
+
+# Preserve canonical equivalence for I with dot. Turkic is handled below.
+
+0130; 0069 0307; 0130; 0130; # LATIN CAPITAL LETTER I WITH DOT ABOVE
+
+# Ligatures
+
+FB00; FB00; 0046 0066; 0046 0046; # LATIN SMALL LIGATURE FF
+FB01; FB01; 0046 0069; 0046 0049; # LATIN SMALL LIGATURE FI
+FB02; FB02; 0046 006C; 0046 004C; # LATIN SMALL LIGATURE FL
+FB03; FB03; 0046 0066 0069; 0046 0046 0049; # LATIN SMALL LIGATURE FFI
+FB04; FB04; 0046 0066 006C; 0046 0046 004C; # LATIN SMALL LIGATURE FFL
+FB05; FB05; 0053 0074; 0053 0054; # LATIN SMALL LIGATURE LONG S T
+FB06; FB06; 0053 0074; 0053 0054; # LATIN SMALL LIGATURE ST
+
+0587; 0587; 0535 0582; 0535 0552; # ARMENIAN SMALL LIGATURE ECH YIWN
+FB13; FB13; 0544 0576; 0544 0546; # ARMENIAN SMALL LIGATURE MEN NOW
+FB14; FB14; 0544 0565; 0544 0535; # ARMENIAN SMALL LIGATURE MEN ECH
+FB15; FB15; 0544 056B; 0544 053B; # ARMENIAN SMALL LIGATURE MEN INI
+FB16; FB16; 054E 0576; 054E 0546; # ARMENIAN SMALL LIGATURE VEW NOW
+FB17; FB17; 0544 056D; 0544 053D; # ARMENIAN SMALL LIGATURE MEN XEH
+
+# No corresponding uppercase precomposed character
+
+0149; 0149; 02BC 004E; 02BC 004E; # LATIN SMALL LETTER N PRECEDED BY APOSTROPHE
+0390; 0390; 0399 0308 0301; 0399 0308 0301; # GREEK SMALL LETTER IOTA WITH DIALYTIKA AND TONOS
+03B0; 03B0; 03A5 0308 0301; 03A5 0308 0301; # GREEK SMALL LETTER UPSILON WITH DIALYTIKA AND TONOS
+01F0; 01F0; 004A 030C; 004A 030C; # LATIN SMALL LETTER J WITH CARON
+1E96; 1E96; 0048 0331; 0048 0331; # LATIN SMALL LETTER H WITH LINE BELOW
+1E97; 1E97; 0054 0308; 0054 0308; # LATIN SMALL LETTER T WITH DIAERESIS
+1E98; 1E98; 0057 030A; 0057 030A; # LATIN SMALL LETTER W WITH RING ABOVE
+1E99; 1E99; 0059 030A; 0059 030A; # LATIN SMALL LETTER Y WITH RING ABOVE
+1E9A; 1E9A; 0041 02BE; 0041 02BE; # LATIN SMALL LETTER A WITH RIGHT HALF RING
+1F50; 1F50; 03A5 0313; 03A5 0313; # GREEK SMALL LETTER UPSILON WITH PSILI
+1F52; 1F52; 03A5 0313 0300; 03A5 0313 0300; # GREEK SMALL LETTER UPSILON WITH PSILI AND VARIA
+1F54; 1F54; 03A5 0313 0301; 03A5 0313 0301; # GREEK SMALL LETTER UPSILON WITH PSILI AND OXIA
+1F56; 1F56; 03A5 0313 0342; 03A5 0313 0342; # GREEK SMALL LETTER UPSILON WITH PSILI AND PERISPOMENI
+1FB6; 1FB6; 0391 0342; 0391 0342; # GREEK SMALL LETTER ALPHA WITH PERISPOMENI
+1FC6; 1FC6; 0397 0342; 0397 0342; # GREEK SMALL LETTER ETA WITH PERISPOMENI
+1FD2; 1FD2; 0399 0308 0300; 0399 0308 0300; # GREEK SMALL LETTER IOTA WITH DIALYTIKA AND VARIA
+1FD3; 1FD3; 0399 0308 0301; 0399 0308 0301; # GREEK SMALL LETTER IOTA WITH DIALYTIKA AND OXIA
+1FD6; 1FD6; 0399 0342; 0399 0342; # GREEK SMALL LETTER IOTA WITH PERISPOMENI
+1FD7; 1FD7; 0399 0308 0342; 0399 0308 0342; # GREEK SMALL LETTER IOTA WITH DIALYTIKA AND PERISPOMENI
+1FE2; 1FE2; 03A5 0308 0300; 03A5 0308 0300; # GREEK SMALL LETTER UPSILON WITH DIALYTIKA AND VARIA
+1FE3; 1FE3; 03A5 0308 0301; 03A5 0308 0301; # GREEK SMALL LETTER UPSILON WITH DIALYTIKA AND OXIA
+1FE4; 1FE4; 03A1 0313; 03A1 0313; # GREEK SMALL LETTER RHO WITH PSILI
+1FE6; 1FE6; 03A5 0342; 03A5 0342; # GREEK SMALL LETTER UPSILON WITH PERISPOMENI
+1FE7; 1FE7; 03A5 0308 0342; 03A5 0308 0342; # GREEK SMALL LETTER UPSILON WITH DIALYTIKA AND PERISPOMENI
+1FF6; 1FF6; 03A9 0342; 03A9 0342; # GREEK SMALL LETTER OMEGA WITH PERISPOMENI
+
+# IMPORTANT-when iota-subscript (0345) is uppercased or titlecased,
+#  the result will be incorrect unless the iota-subscript is moved to the end
+#  of any sequence of combining marks. Otherwise, the accents will go on the capital iota.
+#  This process can be achieved by first transforming the text to NFC before casing.
+#  E.g. <alpha><iota_subscript><acute> is uppercased to <ALPHA><acute><IOTA>
+
+# The following cases are already in the UnicodeData.txt file, so are only commented here.
+
+# 0345; 0345; 0345; 0399; # COMBINING GREEK YPOGEGRAMMENI
+
+# All letters with YPOGEGRAMMENI (iota-subscript) or PROSGEGRAMMENI (iota adscript)
+# have special uppercases.
+# Note: characters with PROSGEGRAMMENI are actually titlecase, not uppercase!
+
+1F80; 1F80; 1F88; 1F08 0399; # GREEK SMALL LETTER ALPHA WITH PSILI AND YPOGEGRAMMENI
+1F81; 1F81; 1F89; 1F09 0399; # GREEK SMALL LETTER ALPHA WITH DASIA AND YPOGEGRAMMENI
+1F82; 1F82; 1F8A; 1F0A 0399; # GREEK SMALL LETTER ALPHA WITH PSILI AND VARIA AND YPOGEGRAMMENI
+1F83; 1F83; 1F8B; 1F0B 0399; # GREEK SMALL LETTER ALPHA WITH DASIA AND VARIA AND YPOGEGRAMMENI
+1F84; 1F84; 1F8C; 1F0C 0399; # GREEK SMALL LETTER ALPHA WITH PSILI AND OXIA AND YPOGEGRAMMENI
+1F85; 1F85; 1F8D; 1F0D 0399; # GREEK SMALL LETTER ALPHA WITH DASIA AND OXIA AND YPOGEGRAMMENI
+1F86; 1F86; 1F8E; 1F0E 0399; # GREEK SMALL LETTER ALPHA WITH PSILI AND PERISPOMENI AND YPOGEGRAMMENI
+1F87; 1F87; 1F8F; 1F0F 0399; # GREEK SMALL LETTER ALPHA WITH DASIA AND PERISPOMENI AND YPOGEGRAMMENI
+1F88; 1F80; 1F88; 1F08 0399; # GREEK CAPITAL LETTER ALPHA WITH PSILI AND PROSGEGRAMMENI
+1F89; 1F81; 1F89; 1F09 0399; # GREEK CAPITAL LETTER ALPHA WITH DASIA AND PROSGEGRAMMENI
+1F8A; 1F82; 1F8A; 1F0A 0399; # GREEK CAPITAL LETTER ALPHA WITH PSILI AND VARIA AND PROSGEGRAMMENI
+1F8B; 1F83; 1F8B; 1F0B 0399; # GREEK CAPITAL LETTER ALPHA WITH DASIA AND VARIA AND PROSGEGRAMMENI
+1F8C; 1F84; 1F8C; 1F0C 0399; # GREEK CAPITAL LETTER ALPHA WITH PSILI AND OXIA AND PROSGEGRAMMENI
+1F8D; 1F85; 1F8D; 1F0D 0399; # GREEK CAPITAL LETTER ALPHA WITH DASIA AND OXIA AND PROSGEGRAMMENI
+1F8E; 1F86; 1F8E; 1F0E 0399; # GREEK CAPITAL LETTER ALPHA WITH PSILI AND PERISPOMENI AND PROSGEGRAMMENI
+1F8F; 1F87; 1F8F; 1F0F 0399; # GREEK CAPITAL LETTER ALPHA WITH DASIA AND PERISPOMENI AND PROSGEGRAMMENI
+1F90; 1F90; 1F98; 1F28 0399; # GREEK SMALL LETTER ETA WITH PSILI AND YPOGEGRAMMENI
+1F91; 1F91; 1F99; 1F29 0399; # GREEK SMALL LETTER ETA WITH DASIA AND YPOGEGRAMMENI
+1F92; 1F92; 1F9A; 1F2A 0399; # GREEK SMALL LETTER ETA WITH PSILI AND VARIA AND YPOGEGRAMMENI
+1F93; 1F93; 1F9B; 1F2B 0399; # GREEK SMALL LETTER ETA WITH DASIA AND VARIA AND YPOGEGRAMMENI
+1F94; 1F94; 1F9C; 1F2C 0399; # GREEK SMALL LETTER ETA WITH PSILI AND OXIA AND YPOGEGRAMMENI
+1F95; 1F95; 1F9D; 1F2D 0399; # GREEK SMALL LETTER ETA WITH DASIA AND OXIA AND YPOGEGRAMMENI
+1F96; 1F96; 1F9E; 1F2E 0399; # GREEK SMALL LETTER ETA WITH PSILI AND PERISPOMENI AND YPOGEGRAMMENI
+1F97; 1F97; 1F9F; 1F2F 0399; # GREEK SMALL LETTER ETA WITH DASIA AND PERISPOMENI AND YPOGEGRAMMENI
+1F98; 1F90; 1F98; 1F28 0399; # GREEK CAPITAL LETTER ETA WITH PSILI AND PROSGEGRAMMENI
+1F99; 1F91; 1F99; 1F29 0399; # GREEK CAPITAL LETTER ETA WITH DASIA AND PROSGEGRAMMENI
+1F9A; 1F92; 1F9A; 1F2A 0399; # GREEK CAPITAL LETTER ETA WITH PSILI AND VARIA AND PROSGEGRAMMENI
+1F9B; 1F93; 1F9B; 1F2B 0399; # GREEK CAPITAL LETTER ETA WITH DASIA AND VARIA AND PROSGEGRAMMENI
+1F9C; 1F94; 1F9C; 1F2C 0399; # GREEK CAPITAL LETTER ETA WITH PSILI AND OXIA AND PROSGEGRAMMENI
+1F9D; 1F95; 1F9D; 1F2D 0399; # GREEK CAPITAL LETTER ETA WITH DASIA AND OXIA AND PROSGEGRAMMENI
+1F9E; 1F96; 1F9E; 1F2E 0399; # GREEK CAPITAL LETTER ETA WITH PSILI AND PERISPOMENI AND PROSGEGRAMMENI
+1F9F; 1F97; 1F9F; 1F2F 0399; # GREEK CAPITAL LETTER ETA WITH DASIA AND PERISPOMENI AND PROSGEGRAMMENI
+1FA0; 1FA0; 1FA8; 1F68 0399; # GREEK SMALL LETTER OMEGA WITH PSILI AND YPOGEGRAMMENI
+1FA1; 1FA1; 1FA9; 1F69 0399; # GREEK SMALL LETTER OMEGA WITH DASIA AND YPOGEGRAMMENI
+1FA2; 1FA2; 1FAA; 1F6A 0399; # GREEK SMALL LETTER OMEGA WITH PSILI AND VARIA AND YPOGEGRAMMENI
+1FA3; 1FA3; 1FAB; 1F6B 0399; # GREEK SMALL LETTER OMEGA WITH DASIA AND VARIA AND YPOGEGRAMMENI
+1FA4; 1FA4; 1FAC; 1F6C 0399; # GREEK SMALL LETTER OMEGA WITH PSILI AND OXIA AND YPOGEGRAMMENI
+1FA5; 1FA5; 1FAD; 1F6D 0399; # GREEK SMALL LETTER OMEGA WITH DASIA AND OXIA AND YPOGEGRAMMENI
+1FA6; 1FA6; 1FAE; 1F6E 0399; # GREEK SMALL LETTER OMEGA WITH PSILI AND PERISPOMENI AND YPOGEGRAMMENI
+1FA7; 1FA7; 1FAF; 1F6F 0399; # GREEK SMALL LETTER OMEGA WITH DASIA AND PERISPOMENI AND YPOGEGRAMMENI
+1FA8; 1FA0; 1FA8; 1F68 0399; # GREEK CAPITAL LETTER OMEGA WITH PSILI AND PROSGEGRAMMENI
+1FA9; 1FA1; 1FA9; 1F69 0399; # GREEK CAPITAL LETTER OMEGA WITH DASIA AND PROSGEGRAMMENI
+1FAA; 1FA2; 1FAA; 1F6A 0399; # GREEK CAPITAL LETTER OMEGA WITH PSILI AND VARIA AND PROSGEGRAMMENI
+1FAB; 1FA3; 1FAB; 1F6B 0399; # GREEK CAPITAL LETTER OMEGA WITH DASIA AND VARIA AND PROSGEGRAMMENI
+1FAC; 1FA4; 1FAC; 1F6C 0399; # GREEK CAPITAL LETTER OMEGA WITH PSILI AND OXIA AND PROSGEGRAMMENI
+1FAD; 1FA5; 1FAD; 1F6D 0399; # GREEK CAPITAL LETTER OMEGA WITH DASIA AND OXIA AND PROSGEGRAMMENI
+1FAE; 1FA6; 1FAE; 1F6E 0399; # GREEK CAPITAL LETTER OMEGA WITH PSILI AND PERISPOMENI AND PROSGEGRAMMENI
+1FAF; 1FA7; 1FAF; 1F6F 0399; # GREEK CAPITAL LETTER OMEGA WITH DASIA AND PERISPOMENI AND PROSGEGRAMMENI
+1FB3; 1FB3; 1FBC; 0391 0399; # GREEK SMALL LETTER ALPHA WITH YPOGEGRAMMENI
+1FBC; 1FB3; 1FBC; 0391 0399; # GREEK CAPITAL LETTER ALPHA WITH PROSGEGRAMMENI
+1FC3; 1FC3; 1FCC; 0397 0399; # GREEK SMALL LETTER ETA WITH YPOGEGRAMMENI
+1FCC; 1FC3; 1FCC; 0397 0399; # GREEK CAPITAL LETTER ETA WITH PROSGEGRAMMENI
+1FF3; 1FF3; 1FFC; 03A9 0399; # GREEK SMALL LETTER OMEGA WITH YPOGEGRAMMENI
+1FFC; 1FF3; 1FFC; 03A9 0399; # GREEK CAPITAL LETTER OMEGA WITH PROSGEGRAMMENI
+
+# Some characters with YPOGEGRAMMENI also have no corresponding titlecases
+
+1FB2; 1FB2; 1FBA 0345; 1FBA 0399; # GREEK SMALL LETTER ALPHA WITH VARIA AND YPOGEGRAMMENI
+1FB4; 1FB4; 0386 0345; 0386 0399; # GREEK SMALL LETTER ALPHA WITH OXIA AND YPOGEGRAMMENI
+1FC2; 1FC2; 1FCA 0345; 1FCA 0399; # GREEK SMALL LETTER ETA WITH VARIA AND YPOGEGRAMMENI
+1FC4; 1FC4; 0389 0345; 0389 0399; # GREEK SMALL LETTER ETA WITH OXIA AND YPOGEGRAMMENI
+1FF2; 1FF2; 1FFA 0345; 1FFA 0399; # GREEK SMALL LETTER OMEGA WITH VARIA AND YPOGEGRAMMENI
+1FF4; 1FF4; 038F 0345; 038F 0399; # GREEK SMALL LETTER OMEGA WITH OXIA AND YPOGEGRAMMENI
+
+1FB7; 1FB7; 0391 0342 0345; 0391 0342 0399; # GREEK SMALL LETTER ALPHA WITH PERISPOMENI AND YPOGEGRAMMENI
+1FC7; 1FC7; 0397 0342 0345; 0397 0342 0399; # GREEK SMALL LETTER ETA WITH PERISPOMENI AND YPOGEGRAMMENI
+1FF7; 1FF7; 03A9 0342 0345; 03A9 0342 0399; # GREEK SMALL LETTER OMEGA WITH PERISPOMENI AND YPOGEGRAMMENI
+
+# ================================================================================
+# Conditional Mappings
+# The remainder of this file provides conditional casing data used to produce 
+# full case mappings.
+# ================================================================================
+# Language-Insensitive Mappings
+# These are characters whose full case mappings do not depend on language, but do
+# depend on context (which characters come before or after). For more information
+# see the header of this file and the Unicode Standard.
+# ================================================================================
+
+# Special case for final form of sigma
+
+03A3; 03C2; 03A3; 03A3; Final_Sigma; # GREEK CAPITAL LETTER SIGMA
+
+# Note: the following cases for non-final are already in the UnicodeData.txt file.
+
+# 03A3; 03C3; 03A3; 03A3; # GREEK CAPITAL LETTER SIGMA
+# 03C3; 03C3; 03A3; 03A3; # GREEK SMALL LETTER SIGMA
+# 03C2; 03C2; 03A3; 03A3; # GREEK SMALL LETTER FINAL SIGMA
+
+# Note: the following cases are not included, since they would case-fold in lowercasing
+
+# 03C3; 03C2; 03A3; 03A3; Final_Sigma; # GREEK SMALL LETTER SIGMA
+# 03C2; 03C3; 03A3; 03A3; Not_Final_Sigma; # GREEK SMALL LETTER FINAL SIGMA
+
+# ================================================================================
+# Language-Sensitive Mappings
+# These are characters whose full case mappings depend on language and perhaps also
+# context (which characters come before or after). For more information
+# see the header of this file and the Unicode Standard.
+# ================================================================================
+
+# Lithuanian
+
+# Lithuanian retains the dot in a lowercase i when followed by accents.
+
+# Remove DOT ABOVE after "i" with upper or titlecase
+
+0307; 0307; ; ; lt After_Soft_Dotted; # COMBINING DOT ABOVE
+
+# Introduce an explicit dot above when lowercasing capital I's and J's
+# whenever there are more accents above.
+# (of the accents used in Lithuanian: grave, acute, tilde above, and ogonek)
+
+0049; 0069 0307; 0049; 0049; lt More_Above; # LATIN CAPITAL LETTER I
+004A; 006A 0307; 004A; 004A; lt More_Above; # LATIN CAPITAL LETTER J
+012E; 012F 0307; 012E; 012E; lt More_Above; # LATIN CAPITAL LETTER I WITH OGONEK
+00CC; 0069 0307 0300; 00CC; 00CC; lt; # LATIN CAPITAL LETTER I WITH GRAVE
+00CD; 0069 0307 0301; 00CD; 00CD; lt; # LATIN CAPITAL LETTER I WITH ACUTE
+0128; 0069 0307 0303; 0128; 0128; lt; # LATIN CAPITAL LETTER I WITH TILDE
+
+# ================================================================================
+
+# Turkish and Azeri
+
+# I and i-dotless; I-dot and i are case pairs in Turkish and Azeri
+# The following rules handle those cases.
+
+0130; 0069; 0130; 0130; tr; # LATIN CAPITAL LETTER I WITH DOT ABOVE
+0130; 0069; 0130; 0130; az; # LATIN CAPITAL LETTER I WITH DOT ABOVE
+
+# When lowercasing, remove dot_above in the sequence I + dot_above, which will turn into i.
+# This matches the behavior of the canonically equivalent I-dot_above
+
+0307; ; 0307; 0307; tr After_I; # COMBINING DOT ABOVE
+0307; ; 0307; 0307; az After_I; # COMBINING DOT ABOVE
+
+# When lowercasing, unless an I is before a dot_above, it turns into a dotless i.
+
+0049; 0131; 0049; 0049; tr Not_Before_Dot; # LATIN CAPITAL LETTER I
+0049; 0131; 0049; 0049; az Not_Before_Dot; # LATIN CAPITAL LETTER I
+
+# When uppercasing, i turns into a dotted capital I
+
+0069; 0069; 0130; 0130; tr; # LATIN SMALL LETTER I
+0069; 0069; 0130; 0130; az; # LATIN SMALL LETTER I
+
+# Note: the following case is already in the UnicodeData.txt file.
+
+# 0131; 0131; 0049; 0049; tr; # LATIN SMALL LETTER DOTLESS I
+
+# EOF
+
diff --git a/etc/NEWS b/etc/NEWS
index f2bbead..3396f9f 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -235,13 +235,17 @@ same as in modes where the character is not whitespace.
 Instead of only checking the modification time, Emacs now also checks
 the file's actual content before prompting the user.
 
-** Title case characters are properly cased (from and into).
-'upcase', 'upcase-region' et al. convert title case characters (such
-as Dz) into their upper case form (such as DZ).
+** Various casing improvements.
 
-Similarly, 'capitalize', 'upcase-initials' et al. make use of
-title-case forms of initial characters (correctly producing for example
-Džungla instead of incorrect DŽungla).
+*** 'upcase', 'upcase-region' et al. convert title case characters
+(such as Dz) into their upper case form (such as DZ).
+
+*** 'capitalize', 'upcase-initials' et al. make use of title-case forms
+of initial characters (correctly producing for example Džungla instead
+of incorrect DŽungla).
+
+*** Characters which turn into multiple ones when cased are correctly handled.
+For example, fi ligature is converted to FI when upper cased.
 
 \f
 * Changes in Specialized Modes and Packages in Emacs 26.1
diff --git a/src/Makefile.in b/src/Makefile.in
index 89f7a92..98a6181 100644
--- a/src/Makefile.in
+++ b/src/Makefile.in
@@ -711,6 +711,9 @@ $(lwlibdir)/TAGS: FORCE
 tags: TAGS ../lisp/TAGS $(lwlibdir)/TAGS
 .PHONY: tags
 
+special-casing.h: make-special-casing.py ../admin/unidata/SpecialCasing.txt
+	$(AM_V_GEN)
+	python $^ $@
 
 ### Bootstrapping.
 
diff --git a/src/casefiddle.c b/src/casefiddle.c
index a016871..35ff674 100644
--- a/src/casefiddle.c
+++ b/src/casefiddle.c
@@ -29,8 +29,16 @@ along with GNU Emacs.  If not, see <http://www.gnu.org/licenses/>.  */
 #include "composite.h"
 #include "keymap.h"
 
+struct casing_str_buf {
+  unsigned char data[MAX_MULTIBYTE_LENGTH > 6 ? MAX_MULTIBYTE_LENGTH : 6];
+  unsigned char len_chars;
+  unsigned char len_bytes;
+};
+
 enum case_action {CASE_UP, CASE_DOWN, CASE_CAPITALIZE, CASE_CAPITALIZE_UP};
 
+#include "special-casing.h"
+
 /* State for casing individual characters.  */
 struct casing_context {
   /* A char-table with title-case character mappings or nil.  It being non-nil
@@ -70,25 +78,90 @@ prepare_casing_context (struct casing_context *ctx,
     SETUP_BUFFER_SYNTAX_TABLE ();	/* For syntax_prefix_flag_p.  */
 }
 
-/* Based on CTX, case character CH accordingly.  Update CTX as necessary.
-   Return cased character. */
+/* Based on CTX, case character CH.  If BUF is NULL, return cased character.
+   Otherwise, if BUF is non-NULL, save result in it and return whether the
+   character has been changed.
+
+   Since meaning of return value depends on arguments, it’s more convenient to
+   use case_single_character or case_character instead. */
 static int
-case_character (struct casing_context *ctx, int ch)
+case_character_impl (struct casing_str_buf *buf,
+		     struct casing_context *ctx, int ch)
 {
+  enum case_action flag;
   Lisp_Object prop;
+  bool was_inword;
+  int cased;
 
-  if (ctx->inword)
-    ch = ctx->flag == CASE_CAPITALIZE_UP ? ch : downcase (ch);
+  /* Update inword state */
+  was_inword = ctx->inword;
+  if ((int) ctx->flag >= (int) CASE_CAPITALIZE)
+    ctx->inword = SYNTAX (ch) == Sword &&
+      (!ctx->inbuffer || was_inword || !syntax_prefix_flag_p (ch));
+
+  /* Normalise flag so its one of CASE_UP, CASE_DOWN or CASE_CAPITALIZE. */
+  if (!was_inword)
+    flag = ctx->flag == CASE_UP ? CASE_UP : CASE_CAPITALIZE;
+  else if (ctx->flag != CASE_CAPITALIZE_UP)
+    flag = CASE_DOWN;
+  else
+    {
+      cased = ch;
+      goto done;
+    }
+
+  /* Look through the special casing entries. */
+  if (buf)
+    {
+      const special_casing_char_t *it;
+      for (it = special_casing_code_points; *it && *it <= ch; ++it)
+	if (*it == ch)
+	  {
+	    const struct casing_str_buf *entry = special_casing_entries +
+	      ((it - special_casing_code_points) * 3 + (int)flag);
+	    memcpy (buf, entry, sizeof *buf);
+	    buf->len_chars &= ~SPECIAL_CASING_NO_CHANGE_BIT;
+	    return !(entry->len_chars & SPECIAL_CASING_NO_CHANGE_BIT);
+	  }
+    }
+
+  /* Handle simple, one-to-one case. */
+  if (flag == CASE_DOWN)
+    cased = downcase (ch);
   else if (!NILP (ctx->titlecase_char_table) &&
 	   CHARACTERP (prop = CHAR_TABLE_REF (ctx->titlecase_char_table, ch)))
-    ch = XFASTINT (prop);
+    cased = XFASTINT (prop);
   else
-    ch = upcase(ch);
+    cased = upcase(ch);
+
+  /* And we’re done. */
+ done:
+  if (!buf)
+    return cased;
+  buf->len_chars = 1;
+  buf->len_bytes = CHAR_STRING (cased, buf->data);
+  return cased != ch;
+}
 
-  if ((int) ctx->flag >= (int) CASE_CAPITALIZE)
-    ctx->inword = SYNTAX (ch) == Sword &&
-      (!ctx->inbuffer || ctx->inword || !syntax_prefix_flag_p (ch));
-  return ch;
+/* Based on CTX, case character CH accordingly.  Update CTX as necessary.
+   Return cased character.
+
+   Special casing rules (such as upcase(fi) = FI) are not handled.  For
+   characters whose casing results in multiple code points, the character is
+   returned unchanged. */
+static inline int
+case_single_character (struct casing_context *ctx, int ch)
+{
+  return case_character_impl (NULL, ctx, ch);
+}
+
+/* Save in BUF result of casing character CH.  Return whether casing changed the
+   character.  This is like case_single_character but also handles one-to-many
+   casing rules. */
+static inline bool
+case_character (struct casing_str_buf *buf, struct casing_context *ctx, int ch)
+{
+  return case_character_impl (buf, ctx, ch);
 }
 \f
 static Lisp_Object
@@ -115,7 +188,7 @@ do_casify_integer (struct casing_context *ctx, Lisp_Object obj)
 	       !NILP (BVAR (current_buffer, enable_multibyte_characters)));
   if (! multibyte)
     MAKE_CHAR_MULTIBYTE (ch);
-  cased = case_character (ctx, ch);
+  cased = case_single_character (ctx, ch);
   if (cased == ch)
     return obj;
 
@@ -128,25 +201,34 @@ do_casify_integer (struct casing_context *ctx, Lisp_Object obj)
 static Lisp_Object
 do_casify_multibyte_string (struct casing_context *ctx, Lisp_Object obj)
 {
-  ptrdiff_t i, i_byte, size = SCHARS (obj);
-  int len, ch, cased;
+  /* We assume data is the first member of casing_str_buf structure so that if
+     we cast a (char *) into (struct casing_str_buf *) the representation of the
+     character is at the beginning of the buffer.  This is why we don’t need
+     separate struct casing_str_buf object but rather write directly to o. */
+  typedef char static_assertion[offsetof(struct casing_str_buf, data) ? -1 : 1];
+
+  ptrdiff_t size = SCHARS (obj), n;
+  int ch;
   USE_SAFE_ALLOCA;
-  ptrdiff_t o_size;
-  if (INT_MULTIPLY_WRAPV (size, MAX_MULTIBYTE_LENGTH, &o_size))
-    o_size = PTRDIFF_MAX;
-  unsigned char *dst = SAFE_ALLOCA (o_size);
+  if (INT_MULTIPLY_WRAPV (size, MAX_MULTIBYTE_LENGTH, &n) ||
+      INT_ADD_WRAPV (n, sizeof(struct casing_str_buf), &n))
+    n = PTRDIFF_MAX;
+  unsigned char *const dst = SAFE_ALLOCA (n), *const dst_end = dst + n;
   unsigned char *o = dst;
 
-  for (i = i_byte = 0; i < size; i++, i_byte += len)
+  const unsigned char *src = SDATA (obj);
+
+  for (n = 0; size; --size)
     {
-      if (o_size - MAX_MULTIBYTE_LENGTH < o - dst)
+      if (dst_end - o < sizeof(struct casing_str_buf))
 	string_overflow ();
-      ch = STRING_CHAR_AND_LENGTH (SDATA (obj) + i_byte, len);
-      cased = case_character (ctx, ch);
-      o += CHAR_STRING (cased, o);
+      ch = STRING_CHAR_ADVANCE (src);
+      case_character ((void *)o, ctx, ch);
+      n += ((struct casing_str_buf *)o)->len_chars;
+      o += ((struct casing_str_buf *)o)->len_bytes;
     }
-  eassert (o - dst <= o_size);
-  obj = make_multibyte_string ((char *) dst, size, o - dst);
+  eassert (o <= dst_end);
+  obj = make_multibyte_string ((char *) dst, n, o - dst);
   SAFE_FREE ();
   return obj;
 }
@@ -162,7 +244,7 @@ do_casify_unibyte_string (struct casing_context *ctx, Lisp_Object obj)
     {
       ch = SREF (obj, i);
       MAKE_CHAR_MULTIBYTE (ch);
-      cased = case_character (ctx, ch);
+      cased = case_single_character (ctx, ch);
       if (ch == cased)
 	continue;
       MAKE_CHAR_UNIBYTE (cased);
@@ -194,7 +276,9 @@ casify_object (enum case_action flag, Lisp_Object obj)
 DEFUN ("upcase", Fupcase, Supcase, 1, 1, 0,
        doc: /* Convert argument to upper case and return that.
 The argument may be a character or string.  The result has the same type.
-The argument object is not altered--the value is a copy.
+The argument object is not altered--the value is a copy.  If argument
+is a character, characters which map to multiple code points when
+cased, e.g. fi, are returned unchanged.
 See also `capitalize', `downcase' and `upcase-initials'.  */)
   (Lisp_Object obj)
 {
@@ -215,7 +299,9 @@ DEFUN ("capitalize", Fcapitalize, Scapitalize, 1, 1, 0,
 This means that each word's first character is upper case (more
 precisely, if available, title case) and the rest is lower case.
 The argument may be a character or string.  The result has the same type.
-The argument object is not altered--the value is a copy.  */)
+The argument object is not altered--the value is a copy.  If argument
+is a character, characters which map to multiple code points when
+cased, e.g. fi, are returned unchanged.  */)
   (Lisp_Object obj)
 {
   return casify_object (CASE_CAPITALIZE, obj);
@@ -228,7 +314,9 @@ DEFUN ("upcase-initials", Fupcase_initials, Supcase_initials, 1, 1, 0,
 (More precisely, if available, initial of each word is converted to
 title-case).  Do not change the other letters of each word.
 The argument may be a character or string.  The result has the same type.
-The argument object is not altered--the value is a copy.  */)
+The argument object is not altered--the value is a copy.  If argument
+is a character, characters which map to multiple code points when
+cased, e.g. fi, are returned unchanged.  */)
   (Lisp_Object obj)
 {
   return casify_object (CASE_CAPITALIZE_UP, obj);
@@ -250,7 +338,7 @@ do_casify_unibyte_region (struct casing_context *ctx,
       ch = FETCH_BYTE (pos);
       MAKE_CHAR_MULTIBYTE (ch);
 
-      cased = case_character (ctx, ch);
+      cased = case_single_character (ctx, ch);
       if (cased == ch)
 	continue;
 
@@ -271,48 +359,54 @@ do_casify_unibyte_region (struct casing_context *ctx,
    characters were changed, return -1 and *ENDP is unspecified. */
 static ptrdiff_t
 do_casify_multibyte_region (struct casing_context *ctx,
-                           ptrdiff_t pos, ptrdiff_t *endp)
+			    ptrdiff_t *startp, ptrdiff_t *endp)
 {
   ptrdiff_t first = -1, last = -1;  /* Position of first and last changes. */
-  ptrdiff_t pos_byte = CHAR_TO_BYTE (pos), end = *endp;
-  ptrdiff_t opoint = PT;
+  ptrdiff_t pos = *startp, pos_byte = CHAR_TO_BYTE (pos), size = *endp - pos;
+  ptrdiff_t opoint = PT, added;
+  struct casing_str_buf buf;
   int ch, cased, len;
 
-  while (pos < end)
+  for (; size; --size)
     {
       ch = STRING_CHAR_AND_LENGTH (BYTE_POS_ADDR (pos_byte), len);
-      cased = case_character (ctx, ch);
-      if (cased != ch)
+
+      if (!case_character (&buf, ctx, ch))
 	{
-	  last = pos;
-	  if (first < 0)
-	    first = pos;
-
-	  if (ASCII_CHAR_P (cased) && ASCII_CHAR_P (ch))
-	    FETCH_BYTE (pos_byte) = cased;
-	  else
-	    {
-	      unsigned char str[MAX_MULTIBYTE_LENGTH];
-	      int totlen = CHAR_STRING (cased, str);
-	      if (len == totlen)
-		memcpy (BYTE_POS_ADDR (pos_byte), str, len);
-	      else
-		/* Replace one character with the other(s), keeping text
-		   properties the same.  */
-		replace_range_2 (pos, pos_byte, pos + 1, pos_byte + len,
-				 (char *) str, 9, totlen, 0);
-	      len = totlen;
-	    }
+	  pos_byte += len;
+	  ++pos;
+	  continue;
 	}
-      pos++;
-      pos_byte += len;
+
+      last = pos + buf.len_chars;
+      if (first < 0)
+	first = pos;
+
+      if (buf.len_chars == 1 && buf.len_bytes == len)
+	memcpy (BYTE_POS_ADDR (pos_byte), buf.data, len);
+      else
+	{
+	  /* Replace one character with the other(s), keeping text
+	     properties the same.  */
+	  replace_range_2 (pos, pos_byte, pos + 1, pos_byte + len,
+			   (const char *) buf.data, buf.len_chars,
+			   buf.len_bytes,
+			   0);
+	  added += buf.len_chars - 1;
+	  if (opoint > pos)
+	    opoint += buf.len_chars - 1;
+	}
+
+      pos_byte += buf.len_bytes;
+      pos += buf.len_chars;
     }
 
   if (PT != opoint)
     TEMP_SET_PT_BOTH (opoint, CHAR_TO_BYTE (opoint));
 
+  *startp = first;
   *endp = last;
-  return first;
+  return added;
 }
 
 /* flag is CASE_UP, CASE_DOWN or CASE_CAPITALIZE or CASE_CAPITALIZE_UP.
@@ -320,8 +414,8 @@ do_casify_multibyte_region (struct casing_context *ctx,
 static void
 casify_region (enum case_action flag, Lisp_Object b, Lisp_Object e)
 {
+  ptrdiff_t start, end, added;
   struct casing_context ctx;
-  ptrdiff_t start, end;
 
   if (EQ (b, e))
     /* Not modifying because nothing marked */
@@ -337,12 +431,12 @@ casify_region (enum case_action flag, Lisp_Object b, Lisp_Object e)
   if (NILP (BVAR (current_buffer, enable_multibyte_characters)))
     start = do_casify_unibyte_region (&ctx, start, &end);
   else
-    start = do_casify_multibyte_region (&ctx, start, &end);
+    added = do_casify_multibyte_region (&ctx, &start, &end);
 
   if (start >= 0)
     {
-      signal_after_change (start, end + 1 - start, end + 1 - start);
-      update_compositions (start, end + 1, CHECK_ALL);
+      signal_after_change (start, end - start - added, end - start);
+      update_compositions (start, end, CHECK_ALL);
     }
 }
 
diff --git a/src/deps.mk b/src/deps.mk
index 72f68ca..1c24414 100644
--- a/src/deps.mk
+++ b/src/deps.mk
@@ -49,7 +49,7 @@ callproc.o: callproc.c epaths.h buffer.h commands.h lisp.h $(config_h) \
    composite.h w32.h blockinput.h atimer.h systime.h frame.h termhooks.h \
    buffer.h gnutls.h dispextern.h ../lib/unistd.h globals.h
 casefiddle.o: casefiddle.c syntax.h commands.h buffer.h character.h \
-   composite.h keymap.h lisp.h globals.h $(config_h)
+   composite.h keymap.h special-casing.h lisp.h globals.h $(config_h)
 casetab.o: casetab.c buffer.h character.h lisp.h globals.h $(config_h)
 category.o: category.c category.h buffer.h charset.h keymap.h	\
    character.h lisp.h globals.h $(config_h)
diff --git a/src/make-special-casing.py b/src/make-special-casing.py
new file mode 100644
index 0000000..e8725e3
--- /dev/null
+++ b/src/make-special-casing.py
@@ -0,0 +1,189 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+"""generate-special-casing.py --- generate special-casing.h file
+Copyright (C) 2016 Free Software Foundation, Inc.
+
+This file is part of GNU Emacs.
+
+GNU Emacs 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.
+
+GNU Emacs 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 <http://www.gnu.org/licenses/>.
+"""
+
+import os
+import re
+import sys
+import tempfile
+import textwrap
+
+TEMPLATE = '''\
+/* Special case mapping rules.  Only unconditional entries are included.
+   This file is automatically generated from SpecialCasing.txt file
+   distributed with Unicode standard by %(generator)s.
+   Do not edit manually. */
+
+#include <stdint.h>
+
+struct special_casing_static_asserts {
+%(asserts)s
+};
+
+typedef %(code_point_type)s special_casing_char_t;
+
+/* Zero-terminated, sorted list. */
+static const special_casing_char_t special_casing_code_points[] = {
+%(code_points)s
+};
+
+/* If buf.len_chars has this bit set, the character maps to itself. */
+#define SPECIAL_CASING_NO_CHANGE_BIT 0x80
+
+static const struct casing_str_buf special_casing_entries[] = {
+%(entries)s
+};
+'''
+
+MAX_DATA_BYTES_LENGTH = 6
+
+ASSERTS = (
+    ('casing_str_buf_data_must_be_at_least_%d_chars' % MAX_DATA_BYTES_LENGTH,
+     'sizeof ((struct casing_str_buf*)0)->data >= %d' % MAX_DATA_BYTES_LENGTH),
+    ('CASE_UP_must_equal_0', 'CASE_UP == 0'),
+    ('CASE_DOWN_must_equal_1', 'CASE_DOWN == 1'),
+    ('CASE_CAPITALIZE_must_equal_2', 'CASE_CAPITALIZE == 2')
+)
+
+
+def encode(code, code_points):
+    """Convert a space-separated list of code points into UTF-8 C-string.
+
+    Args:
+        code: Code point this mapping is for.
+        code_points: A space-separated list of hexadecimal numbers representing
+            code points in desired representation.
+
+    Returns:
+        A (literal, len_chars, len_bytes) tuple.  len_chars may be zero if code
+        point maps to itself.
+    """
+    code_points = [int(cp, 16) for cp in code_points.split()]
+    len_chars = len(code_points)
+    if len_chars == 1 and code_points[0] == code:
+        len_chars = len_chars | 0x80
+    val = ''.join(unichr(cp) for cp in code_points).encode('utf-8')
+    ret = ''
+    for ch in val:
+        o = ord(ch)
+        if o < 32 or o >= 127:
+            ch = '\\x%02x' % o
+        ret += ch
+    return '"%s"' % ret, len_chars, len(val)
+
+
+def read_entries(fd):
+    """Read entries from SpecialCasing.txt file.
+
+    Conditional entries are ignored.
+
+    Args:
+        fd: File object to read data from.
+
+    Returns:
+        A list of [code, up_lit, up_len, down_lit, down_len, title_lit,
+        title_len, comment] lists.
+    """
+    idx_lower = 1
+    idx_title = 2
+    idx_upper = 3
+    # This order must match CASE_UP, CASE_DOWN and CASE_CAPITALIZE_UP from
+    # casefiddle.c.  This ordering (which is different than in SpecialCasing.txt
+    # file) is checked via static asserts included in the generated file.
+    indexes = (idx_upper, idx_lower, idx_title)
+
+    entries = []
+    for line in fd:
+        line = line.strip()
+        if not line or line[0] == '#':
+            continue
+
+        line = re.split(r';\s*', line)
+        if len(line) == 6:
+            # Conditional special casing don’t go into special-casing.h
+            #sys.stderr.write('make-special-casing: %s: conditions present '
+            #                 '(%s), ignoring\n' % (line[0], line[4]))
+            continue
+
+        code = int(line[0], 16)
+        entry = [code]
+
+        for i in indexes:
+            val = encode(code, line[i])
+            entry.append(val)
+            if val:
+                # The data structure we’re using assumes that all C strings are
+                # no more than six bytes (excluding NUL terminator).  Enforce
+                # that here.
+                assert val[2] <= MAX_DATA_BYTES_LENGTH, (code, i, val)
+
+        entry.append(line[4].strip(' #').capitalize())
+        entries.append(entry)
+
+    entries.sort()
+    return entries
+
+
+def format_output(entries):
+    # If all code points are 16-bit prefer using uint16_t since it makes the
+    # array smaller and more cache friendly.
+    if all(entry[0] <= 0xffff for entry in entries):
+        cp_type, cp_fmt = 'uint16_t', '%04X'
+    else:
+        cp_type, cp_fmt = 'uint32_t', '%06X'
+
+    fmt = '0x%sU' % cp_fmt;
+    code_points = ', '.join(fmt % entry[0] for entry in entries)
+
+    lines = []
+    for entry in entries:
+        lines.append('    /* U+%s %%s */' % cp_fmt % (entry[0], entry[4]))
+        for val in entry[1:4]:
+            lines.append('    { %s, %d, %d },' % val)
+    lines[-1] = lines[-1].rstrip(',')
+
+    return TEMPLATE % {
+        'generator': os.path.basename(__file__),
+        'asserts': '\n'.join('  char %s[%s ? 1 : -1];' % p for p in ASSERTS),
+        'code_point_type': cp_type,
+        'code_points': textwrap.fill(code_points + ', 0', width=80,
+                                     initial_indent='    ',
+                                     subsequent_indent='    '),
+        'entries': '\n'.join(lines)
+    }
+
+
+def main(argv):
+    if len(argv) != 3:
+        sys.stderr.write('usage: %s SpecialCasing.txt special-casing.h\n' %
+                         argv[0])
+        sys.exit(1)
+
+    with open(argv[1]) as fd:
+        entries = read_entries(fd)
+
+    data = format_output(entries)
+
+    with open(argv[2], 'w') as fd:
+        fd.write(data)
+
+
+if __name__ == '__main__':
+    main(sys.argv)
diff --git a/test/lisp/char-fold-tests.el b/test/lisp/char-fold-tests.el
index 485254a..821c701 100644
--- a/test/lisp/char-fold-tests.el
+++ b/test/lisp/char-fold-tests.el
@@ -54,6 +54,14 @@ char-fold--test-search-with-contents
        (concat w1 "\s\n\s\t\f\t\n\r\t" w2)
        (concat w1 (make-string 10 ?\s) w2)))))
 
+(defun char-fold--ascii-upcase (string)
+  "Like `upcase' but acts on ASCII characters only."
+  (replace-regexp-in-string "[a-z]+" 'upcase string))
+
+(defun char-fold--ascii-downcase (string)
+  "Like `downcase' but acts on ASCII characters only."
+  (replace-regexp-in-string "[a-z]+" 'downcase string))
+
 (defun char-fold--test-match-exactly (string &rest strings-to-match)
   (let ((re (concat "\\`" (char-fold-to-regexp string) "\\'")))
     (dolist (it strings-to-match)
@@ -61,8 +69,8 @@ char-fold--test-match-exactly
     ;; Case folding
     (let ((case-fold-search t))
       (dolist (it strings-to-match)
-        (should (string-match (upcase re) (downcase it)))
-        (should (string-match (downcase re) (upcase it)))))))
+        (should (string-match (char-fold--ascii-upcase re) (downcase it)))
+        (should (string-match (char-fold--ascii-downcase re) (upcase it)))))))
 
 (ert-deftest char-fold--test-some-defaults ()
   (dolist (it '(("ffl" . "ffl") ("ffi" . "ffi")
diff --git a/test/src/casefiddle-tests.el b/test/src/casefiddle-tests.el
index def74a0..ae557d7 100644
--- a/test/src/casefiddle-tests.el
+++ b/test/src/casefiddle-tests.el
@@ -143,16 +143,13 @@ casefiddle-tests--characters
               ("DŽUNGLA" "DŽUNGLA" "džungla" "Džungla" "DžUNGLA")
               ("Džungla" "DŽUNGLA" "džungla" "Džungla" "Džungla")
               ("džungla" "DŽUNGLA" "džungla" "Džungla" "Džungla")
+              ("define" "DEFINE" "define" "Define" "Define")
+              ("fish" "FISH" "fish" "Fish" "Fish")
+              ("Straße" "STRASSE" "straße" "Straße" "Straße")
               ;; FIXME: Everything below is broken at the moment.  Here’s what
               ;; should happen:
-              ;;("define" "DEFINE" "define" "Define" "Define")
-              ;;("fish" "FIsh" "fish" "Fish" "Fish")
-              ;;("Straße" "STRASSE" "straße" "Straße" "Straße")
               ;;("ΌΣΟΣ" "ΌΣΟΣ" "όσος" "Όσος" "Όσος")
               ;; And here’s what is actually happening:
-              ("define" "DEfiNE" "define" "Define" "Define")
-              ("fish" "fiSH" "fish" "fish" "fish")
-              ("Straße" "STRAßE" "straße" "Straße" "Straße")
               ("ΌΣΟΣ" "ΌΣΟΣ" "όσοσ" "Όσοσ" "ΌΣΟΣ")
 
               ("όσος" "ΌΣΟΣ" "όσος" "Όσος" "Όσος"))
-- 
2.8.0.rc3.226.g39d4020






^ permalink raw reply related	[flat|nested] 89+ messages in thread

* bug#24603: [RFC 09/18] Implement special sigma casing rule
  2016-10-04  1:10 ` bug#24603: [RFC 01/18] Add tests for casefiddle.c Michal Nazarewicz
                     ` (6 preceding siblings ...)
  2016-10-04  1:10   ` bug#24603: [RFC 08/18] Support casing characters which map into multiple code points Michal Nazarewicz
@ 2016-10-04  1:10   ` Michal Nazarewicz
  2016-10-04  7:22     ` Eli Zaretskii
  2016-10-04  1:10   ` bug#24603: [RFC 10/18] Implement Turkic dotless and dotted i handling when casing strings Michal Nazarewicz
                     ` (8 subsequent siblings)
  16 siblings, 1 reply; 89+ messages in thread
From: Michal Nazarewicz @ 2016-10-04  1:10 UTC (permalink / raw)
  To: 24603

In Greek, a sigma character has two lower case forms which depend on
their position in the word.  Implement logic determining it.

* src/casefiddle.c (struct casing_context, case_character_impl): Don’t
assume inword is true when flag is CASE_UP and false when flag is
CASE_DOWN.  For final sigma detection we need this information tracked
reliably.
(CAPITAL_SIGMA, SMALL_SIGMA, SMALL_FINAL_SIGMA): New macros defining
Unicode code point of different forms of sigma letter.
(do_casify_multibyte_string, do_casify_multibyte_region): Update after
changes to case_character.

* test/src/casefiddle-tests.el (casefiddle-tests-casing): Add test
cases for final sigma.
---
 etc/NEWS                     |  4 +++
 src/casefiddle.c             | 72 +++++++++++++++++++++++++++++++++-----------
 test/src/casefiddle-tests.el | 15 +++++----
 3 files changed, 67 insertions(+), 24 deletions(-)

diff --git a/etc/NEWS b/etc/NEWS
index 3396f9f..4516812 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -247,6 +247,10 @@ of incorrect DŽungla).
 *** Characters which turn into multiple ones when cased are correctly handled.
 For example, fi ligature is converted to FI when upper cased.
 
+*** Greek small sigma is correctly handled when at the end of the word.
+Strings such as ΌΣΟΣ are now correctly converted to Όσος when
+capitalised to follow rules of Greek spelling.
+
 \f
 * Changes in Specialized Modes and Packages in Emacs 26.1
 
diff --git a/src/casefiddle.c b/src/casefiddle.c
index 35ff674..ace589c 100644
--- a/src/casefiddle.c
+++ b/src/casefiddle.c
@@ -51,9 +51,7 @@ struct casing_context {
      When run on a buffer, syntax_prefix_flag_p is taken into account when
      determined inword flag. */
   bool inbuffer;
-  /* Conceptually, this denotes whether we are inside of a word except
-     that if flag is CASE_UP it’s always false and if flag is CASE_DOWN
-     this is always true. */
+  /* Whether we are inside of a word. */
   bool inword;
 };
 
@@ -65,7 +63,7 @@ prepare_casing_context (struct casing_context *ctx,
 {
   ctx->flag = flag;
   ctx->inbuffer = inbuffer;
-  ctx->inword = flag == CASE_DOWN;
+  ctx->inword = false;
   ctx->titlecase_char_table = (int)flag >= (int)CASE_CAPITALIZE
     ? uniprop_table (intern_c_string ("titlecase"))
     : Qnil;
@@ -95,15 +93,16 @@ case_character_impl (struct casing_str_buf *buf,
 
   /* Update inword state */
   was_inword = ctx->inword;
-  if ((int) ctx->flag >= (int) CASE_CAPITALIZE)
-    ctx->inword = SYNTAX (ch) == Sword &&
-      (!ctx->inbuffer || was_inword || !syntax_prefix_flag_p (ch));
+  ctx->inword = SYNTAX (ch) == Sword &&
+    (!ctx->inbuffer || was_inword || !syntax_prefix_flag_p (ch));
 
   /* Normalise flag so its one of CASE_UP, CASE_DOWN or CASE_CAPITALIZE. */
-  if (!was_inword)
-    flag = ctx->flag == CASE_UP ? CASE_UP : CASE_CAPITALIZE;
+  if (ctx->flag == CASE_CAPITALIZE)
+    flag = (enum case_action)((int)ctx->flag - was_inword);
   else if (ctx->flag != CASE_CAPITALIZE_UP)
-    flag = CASE_DOWN;
+    flag = ctx->flag;
+  else if (!was_inword)
+    flag = CASE_CAPITALIZE;
   else
     {
       cased = ch;
@@ -142,7 +141,18 @@ case_character_impl (struct casing_str_buf *buf,
   buf->len_bytes = CHAR_STRING (cased, buf->data);
   return cased != ch;
 }
+\f
+/* In Greek, lower case sigma has two forms: one when used in the middle and one
+   when used at the end of a word.  Below is to help handle those cases when
+   casing.
+
+   The rule does not conflict with any other casing rules so while it is
+   a conditional one, it is independent on language. */
 
+#define CAPITAL_SIGMA     0x03A3
+#define SMALL_SIGMA       0x03C3
+#define SMALL_FINAL_SIGMA 0x03C2
+\f
 /* Based on CTX, case character CH accordingly.  Update CTX as necessary.
    Return cased character.
 
@@ -156,12 +166,34 @@ case_single_character (struct casing_context *ctx, int ch)
 }
 
 /* Save in BUF result of casing character CH.  Return whether casing changed the
-   character.  This is like case_single_character but also handles one-to-many
-   casing rules. */
-static inline bool
-case_character (struct casing_str_buf *buf, struct casing_context *ctx, int ch)
+   character.
+
+   If not-NULL, NEXT points to the next character in the cased string.  If NULL,
+   it is assumed current character is the last one being cased.  This is used to
+   apply some rules which depend on proceeding state.
+
+   This is like case_single_character but also handles one-to-many casing
+   rules. */
+static bool
+case_character (struct casing_str_buf *buf, struct casing_context *ctx,
+		int ch, const unsigned char *next)
 {
-  return case_character_impl (buf, ctx, ch);
+  bool changed, was_inword;
+
+  was_inword = ctx->inword;
+  changed = case_character_impl (buf, ctx, ch);
+
+  /* If we have just down-cased a capital sigma and the next character no longer
+     has a word syntax (i.e. current character is end of word), use final
+     sigma. */
+  if (was_inword && ch == CAPITAL_SIGMA && changed &&
+      (!next || SYNTAX (STRING_CHAR (next)) != Sword))
+    {
+      buf->len_bytes = CHAR_STRING (SMALL_FINAL_SIGMA, buf->data);
+      buf->len_chars = 1;
+    }
+
+  return changed;
 }
 \f
 static Lisp_Object
@@ -223,7 +255,7 @@ do_casify_multibyte_string (struct casing_context *ctx, Lisp_Object obj)
       if (dst_end - o < sizeof(struct casing_str_buf))
 	string_overflow ();
       ch = STRING_CHAR_ADVANCE (src);
-      case_character ((void *)o, ctx, ch);
+      case_character ((void *)o, ctx, ch, size > 1 ? src : NULL);
       n += ((struct casing_str_buf *)o)->len_chars;
       o += ((struct casing_str_buf *)o)->len_bytes;
     }
@@ -365,13 +397,17 @@ do_casify_multibyte_region (struct casing_context *ctx,
   ptrdiff_t pos = *startp, pos_byte = CHAR_TO_BYTE (pos), size = *endp - pos;
   ptrdiff_t opoint = PT, added;
   struct casing_str_buf buf;
-  int ch, cased, len;
+  bool changed;
+  int ch, len;
 
   for (; size; --size)
     {
       ch = STRING_CHAR_AND_LENGTH (BYTE_POS_ADDR (pos_byte), len);
+      changed = case_character (
+	  &buf, ctx, ch,
+	  size > 1 ? BYTE_POS_ADDR (pos_byte + len) : NULL);
 
-      if (!case_character (&buf, ctx, ch))
+      if (!changed)
 	{
 	  pos_byte += len;
 	  ++pos;
diff --git a/test/src/casefiddle-tests.el b/test/src/casefiddle-tests.el
index ae557d7..1bd745e 100644
--- a/test/src/casefiddle-tests.el
+++ b/test/src/casefiddle-tests.el
@@ -146,13 +146,16 @@ casefiddle-tests--characters
               ("define" "DEFINE" "define" "Define" "Define")
               ("fish" "FISH" "fish" "Fish" "Fish")
               ("Straße" "STRASSE" "straße" "Straße" "Straße")
-              ;; FIXME: Everything below is broken at the moment.  Here’s what
-              ;; should happen:
-              ;;("ΌΣΟΣ" "ΌΣΟΣ" "όσος" "Όσος" "Όσος")
-              ;; And here’s what is actually happening:
-              ("ΌΣΟΣ" "ΌΣΟΣ" "όσοσ" "Όσοσ" "ΌΣΟΣ")
 
-              ("όσος" "ΌΣΟΣ" "όσος" "Όσος" "Όσος"))
+              ;; The word repeated twice to test behaviour at the end of a word
+              ;; inside of an input string as well as at the end of the string.
+              ("ΌΣΟΣ ΌΣΟΣ" "ΌΣΟΣ ΌΣΟΣ" "όσος όσος" "Όσος Όσος" "ΌΣΟΣ ΌΣΟΣ")
+              ;; What should be done with sole sigma?  It is ‘final’ but on the
+              ;; other hand it does not form a word.  Let’s use regular sigma.
+              ("Σ Σ" "Σ Σ" "σ σ" "Σ Σ" "Σ Σ")
+              ("όσος" "ΌΣΟΣ" "όσος" "Όσος" "Όσος")
+              ;; If sigma is already lower case, we don’t want to change it.
+              ("όσοσ" "ΌΣΟΣ" "όσοσ" "Όσοσ" "Όσοσ"))
             (nreverse errors))
          (let* ((input (car test))
                 (expected (cdr test))
-- 
2.8.0.rc3.226.g39d4020






^ permalink raw reply related	[flat|nested] 89+ messages in thread

* bug#24603: [RFC 10/18] Implement Turkic dotless and dotted i handling when casing strings
  2016-10-04  1:10 ` bug#24603: [RFC 01/18] Add tests for casefiddle.c Michal Nazarewicz
                     ` (7 preceding siblings ...)
  2016-10-04  1:10   ` bug#24603: [RFC 09/18] Implement special sigma casing rule Michal Nazarewicz
@ 2016-10-04  1:10   ` Michal Nazarewicz
  2016-10-04  7:12     ` Eli Zaretskii
  2016-10-04  1:10   ` bug#24603: [RFC 11/18] Implement casing rules for Lithuanian Michal Nazarewicz
                     ` (7 subsequent siblings)
  16 siblings, 1 reply; 89+ messages in thread
From: Michal Nazarewicz @ 2016-10-04  1:10 UTC (permalink / raw)
  To: 24603

Implement part of Unicode special handling rules for Azeri and Turkish
languages, namely ‘i’ is paired with ‘İ’ while ‘ı’ is paired with ‘I’.

* src/casefiddle.c (struct casing_context, prepare_casing_context): Add
flag for handling of Turkic i.
(case_character_impl): Extract flag normalisation to a new function:
(normalise_flag): New function.
(case_single_character): Update after above changes.
(case_character): Rename to:
(case_characters): Add handling of Turkic i.
(do_casify_multibyte_string, do_casify_multibyte_region): Update to use
renamed case_characters.

* test/src/casefiddle-tests.el (casefiddle-tests-casing): Add test
cases for dotless and dotted i’s.
---
 src/casefiddle.c             | 251 ++++++++++++++++++++++++++++++-------------
 test/src/casefiddle-tests.el |  37 ++++++-
 2 files changed, 213 insertions(+), 75 deletions(-)

diff --git a/src/casefiddle.c b/src/casefiddle.c
index ace589c..2a7aa64 100644
--- a/src/casefiddle.c
+++ b/src/casefiddle.c
@@ -35,7 +35,8 @@ struct casing_str_buf {
   unsigned char len_bytes;
 };
 
-enum case_action {CASE_UP, CASE_DOWN, CASE_CAPITALIZE, CASE_CAPITALIZE_UP};
+enum case_action {CASE_UP, CASE_DOWN, CASE_CAPITALIZE, CASE_CAPITALIZE_UP,
+		  /* Only for internal use: */ CASE_NO_ACTION};
 
 #include "special-casing.h"
 
@@ -53,6 +54,8 @@ struct casing_context {
   bool inbuffer;
   /* Whether we are inside of a word. */
   bool inword;
+  /* Whether to apply Azeri/Turkish rules for dotted and dotless i. */
+  bool treat_turkic_i;
 };
 
 /* Initialise CTX structure and prepares related global data for casing
@@ -61,6 +64,8 @@ static void
 prepare_casing_context (struct casing_context *ctx,
 			enum case_action flag, bool inbuffer)
 {
+  Lisp_Object lang, l, tr, az;
+
   ctx->flag = flag;
   ctx->inbuffer = inbuffer;
   ctx->inword = false;
@@ -68,42 +73,77 @@ prepare_casing_context (struct casing_context *ctx,
     ? uniprop_table (intern_c_string ("titlecase"))
     : Qnil;
 
+  ctx->treat_turkic_i = false;
+
   /* If the case table is flagged as modified, rescan it.  */
   if (NILP (XCHAR_TABLE (BVAR (current_buffer, downcase_table))->extras[1]))
     Fset_case_table (BVAR (current_buffer, downcase_table));
 
   if (inbuffer && (int) flag >= (int) CASE_CAPITALIZE)
     SETUP_BUFFER_SYNTAX_TABLE ();	/* For syntax_prefix_flag_p.  */
+
+  /* FIXME: Is current-iso639-language the best source of that information? */
+  lang = Vcurrent_iso639_language;
+  tr = intern_c_string ("tr");
+  az = intern_c_string ("az");
+  if (SYMBOLP (lang))
+    {
+      l = lang;
+      goto check_language;
+    }
+  while (CONSP (lang))
+    {
+      l = XCAR (lang);
+      lang = XCDR (lang);
+    check_language:
+      if (EQ (l, tr) || EQ (l, az))
+       {
+         ctx->treat_turkic_i = true;
+         break;
+       }
+    }
+}
+
+/* Normalise CFG->flag and return CASE_UP, CASE_DOWN, CASE_CAPITALIZE or
+   CASE_NO_ACTION.  The latter if CFG->flag is CASE_CAPITALIZE_UP and we are
+   inside of a word. */
+static enum case_action
+normalise_flag (struct casing_context *ctx)
+{
+  /* Normalise flag so its one of CASE_UP, CASE_DOWN or CASE_CAPITALIZE. */
+  switch (ctx->flag) {
+  case CASE_CAPITALIZE:
+    return (enum case_action)((int)ctx->flag - ctx->inword);
+  case CASE_CAPITALIZE_UP:
+    return ctx->inword ? CASE_NO_ACTION : CASE_CAPITALIZE;
+  default:
+    return ctx->flag;
+  }
 }
 
-/* Based on CTX, case character CH.  If BUF is NULL, return cased character.
-   Otherwise, if BUF is non-NULL, save result in it and return whether the
-   character has been changed.
+/* Based on CTX and FLAG, case character CH.  If BUF is NULL, return cased
+   character.  Otherwise, if BUF is non-NULL, save result in it and return 0 if
+   the character changed or -1 if it didn’t.
+
+   FLAG may be one of CASE_UP, CASE_DOWN, CASE_CAPITALIZE (title-case if
+   possible, upper-aces otherwise) or CASE_NO_ACTION.  CTX->inword is not taken
+   into account when interpreting FLAG (it may be taken into account for other
+   decisions though).
 
    Since meaning of return value depends on arguments, it’s more convenient to
-   use case_single_character or case_character instead. */
+   use case_single_character or case_characters instead. */
 static int
 case_character_impl (struct casing_str_buf *buf,
-		     struct casing_context *ctx, int ch)
+		     struct casing_context *ctx, enum case_action flag, int ch)
 {
-  enum case_action flag;
   Lisp_Object prop;
-  bool was_inword;
   int cased;
 
   /* Update inword state */
-  was_inword = ctx->inword;
   ctx->inword = SYNTAX (ch) == Sword &&
-    (!ctx->inbuffer || was_inword || !syntax_prefix_flag_p (ch));
+    (!ctx->inbuffer || ctx->inword || !syntax_prefix_flag_p (ch));
 
-  /* Normalise flag so its one of CASE_UP, CASE_DOWN or CASE_CAPITALIZE. */
-  if (ctx->flag == CASE_CAPITALIZE)
-    flag = (enum case_action)((int)ctx->flag - was_inword);
-  else if (ctx->flag != CASE_CAPITALIZE_UP)
-    flag = ctx->flag;
-  else if (!was_inword)
-    flag = CASE_CAPITALIZE;
-  else
+  if (flag == CASE_NO_ACTION)
     {
       cased = ch;
       goto done;
@@ -120,7 +160,7 @@ case_character_impl (struct casing_str_buf *buf,
 	      ((it - special_casing_code_points) * 3 + (int)flag);
 	    memcpy (buf, entry, sizeof *buf);
 	    buf->len_chars &= ~SPECIAL_CASING_NO_CHANGE_BIT;
-	    return !(entry->len_chars & SPECIAL_CASING_NO_CHANGE_BIT);
+	    return entry->len_chars & SPECIAL_CASING_NO_CHANGE_BIT ? -1 : 0;
 	  }
     }
 
@@ -139,7 +179,7 @@ case_character_impl (struct casing_str_buf *buf,
     return cased;
   buf->len_chars = 1;
   buf->len_bytes = CHAR_STRING (cased, buf->data);
-  return cased != ch;
+  return cased == ch ? -1 : 0;
 }
 \f
 /* In Greek, lower case sigma has two forms: one when used in the middle and one
@@ -152,6 +192,13 @@ case_character_impl (struct casing_str_buf *buf,
 #define CAPITAL_SIGMA     0x03A3
 #define SMALL_SIGMA       0x03C3
 #define SMALL_FINAL_SIGMA 0x03C2
+
+/* Azeri and Turkish have dotless and dotted i.  An upper case of i is
+   İ while lower case of I is ı. */
+
+#define CAPITAL_DOTTED_I    0x130
+#define SMALL_DOTLESS_I     0x131
+#define COMBINING_DOT_ABOVE 0x307
 \f
 /* Based on CTX, case character CH accordingly.  Update CTX as necessary.
    Return cased character.
@@ -162,38 +209,88 @@ case_character_impl (struct casing_str_buf *buf,
 static inline int
 case_single_character (struct casing_context *ctx, int ch)
 {
-  return case_character_impl (NULL, ctx, ch);
+  enum case_action flag = normalise_flag (ctx);
+  return case_character_impl (NULL, ctx, flag, ch);
 }
 
-/* Save in BUF result of casing character CH.  Return whether casing changed the
-   character.
+/* Save in BUF result of casing character CH.
 
    If not-NULL, NEXT points to the next character in the cased string.  If NULL,
    it is assumed current character is the last one being cased.  This is used to
    apply some rules which depend on proceeding state.
 
-   This is like case_single_character but also handles one-to-many casing
-   rules. */
-static bool
-case_character (struct casing_str_buf *buf, struct casing_context *ctx,
-		int ch, const unsigned char *next)
+   Return:
+   - -1 if character has not been changed,
+   - 0 if the character has changed or
+   - a positive number if the character CH and the one following it (pointed by
+     NEXT) map to character saved in BUF.  Returned value is the length in bytes
+     of the next character.
+
+   This is like case_single_character but also handles one-to-many as well as
+   many-to-one and many-to-many casing rules. */
+static int
+case_characters (struct casing_str_buf *buf, struct casing_context *ctx,
+		 int ch, const unsigned char *next)
 {
-  bool changed, was_inword;
+  enum case_action flag = normalise_flag (ctx);
 
-  was_inword = ctx->inword;
-  changed = case_character_impl (buf, ctx, ch);
+  if (flag != CASE_NO_ACTION && __builtin_expect(ctx->treat_turkic_i, false))
+    {
+      bool dot_above = false;
+      int cased = ch;
 
-  /* If we have just down-cased a capital sigma and the next character no longer
-     has a word syntax (i.e. current character is end of word), use final
-     sigma. */
-  if (was_inword && ch == CAPITAL_SIGMA && changed &&
-      (!next || SYNTAX (STRING_CHAR (next)) != Sword))
+      switch (ch) {
+      case 'I':
+	if (flag == CASE_DOWN)
+	  {
+	    dot_above = next && STRING_CHAR (next) == COMBINING_DOT_ABOVE;
+	    cased = dot_above ? 'i' : SMALL_DOTLESS_I;
+	  }
+	break;
+
+      case 'i':
+	if (flag == CASE_UP || flag == CASE_CAPITALIZE)
+	  cased = CAPITAL_DOTTED_I;
+	break;
+
+      case CAPITAL_DOTTED_I:
+	if (flag == CASE_DOWN)
+	  cased = 'i';
+	break;
+
+      case SMALL_DOTLESS_I:
+	if (flag == CASE_UP || flag == CASE_CAPITALIZE)
+	  cased = 'I';
+	break;
+
+      default:
+	goto not_turkic_i;
+      }
+
+      ctx->inword = true;
+      buf->len_chars = 1;
+      buf->len_bytes = CHAR_STRING (cased, buf->data);
+      if (dot_above)
+	return CHAR_BYTES (COMBINING_DOT_ABOVE);
+      else
+	return ch == cased ? -1 : 0;
+    }
+
+ not_turkic_i:
+  /* Capital sigma down-cases differently based on whether it’s last
+     letter of a word or not. */
+  if (flag == CASE_DOWN && ch == CAPITAL_SIGMA)
     {
-      buf->len_bytes = CHAR_STRING (SMALL_FINAL_SIGMA, buf->data);
+      ch = (ctx->inword && (!next || SYNTAX (STRING_CHAR (next)) != Sword))
+	? SMALL_FINAL_SIGMA : SMALL_SIGMA;
+      buf->len_bytes = CHAR_STRING (ch, buf->data);
       buf->len_chars = 1;
+      ctx->inword = true;
+      return 0;
     }
 
-  return changed;
+  /* Do the casing. */
+  return case_character_impl (buf, ctx, flag, ch);
 }
 \f
 static Lisp_Object
@@ -240,7 +337,7 @@ do_casify_multibyte_string (struct casing_context *ctx, Lisp_Object obj)
   typedef char static_assertion[offsetof(struct casing_str_buf, data) ? -1 : 1];
 
   ptrdiff_t size = SCHARS (obj), n;
-  int ch;
+  int ch, len_bytes;
   USE_SAFE_ALLOCA;
   if (INT_MULTIPLY_WRAPV (size, MAX_MULTIBYTE_LENGTH, &n) ||
       INT_ADD_WRAPV (n, sizeof(struct casing_str_buf), &n))
@@ -250,12 +347,16 @@ do_casify_multibyte_string (struct casing_context *ctx, Lisp_Object obj)
 
   const unsigned char *src = SDATA (obj);
 
-  for (n = 0; size; --size)
+  n = 0;
+  while (size)
     {
       if (dst_end - o < sizeof(struct casing_str_buf))
 	string_overflow ();
       ch = STRING_CHAR_ADVANCE (src);
-      case_character ((void *)o, ctx, ch, size > 1 ? src : NULL);
+      len_bytes = case_characters ((void *)o, ctx, ch, size > 1 ? src : NULL);
+      if (len_bytes > 0)
+	src += len_bytes;
+      size -= len_bytes > 0 ? 2 : 1;
       n += ((struct casing_str_buf *)o)->len_chars;
       o += ((struct casing_str_buf *)o)->len_bytes;
     }
@@ -397,44 +498,50 @@ do_casify_multibyte_region (struct casing_context *ctx,
   ptrdiff_t pos = *startp, pos_byte = CHAR_TO_BYTE (pos), size = *endp - pos;
   ptrdiff_t opoint = PT, added;
   struct casing_str_buf buf;
-  bool changed;
-  int ch, len;
+  int ch, len_bytes, len_chars, ret;
 
-  for (; size; --size)
+  while (size)
     {
-      ch = STRING_CHAR_AND_LENGTH (BYTE_POS_ADDR (pos_byte), len);
-      changed = case_character (
+      ch = STRING_CHAR_AND_LENGTH (BYTE_POS_ADDR (pos_byte), len_bytes);
+      ret = case_characters (
 	  &buf, ctx, ch,
-	  size > 1 ? BYTE_POS_ADDR (pos_byte + len) : NULL);
+	  size > 1 ? BYTE_POS_ADDR (pos_byte + len_bytes) : NULL);
+      len_chars = 1;
 
-      if (!changed)
-	{
-	  pos_byte += len;
-	  ++pos;
-	  continue;
-	}
+      switch (ret) {
+      default:
+	len_chars += 1;
+	/* FALL THROUGH */
 
-      last = pos + buf.len_chars;
-      if (first < 0)
-	first = pos;
+      case 0:
+	len_bytes += ret;
+	len_chars = ret ? 2 : 1;
 
-      if (buf.len_chars == 1 && buf.len_bytes == len)
-	memcpy (BYTE_POS_ADDR (pos_byte), buf.data, len);
-      else
-	{
-	  /* Replace one character with the other(s), keeping text
-	     properties the same.  */
-	  replace_range_2 (pos, pos_byte, pos + 1, pos_byte + len,
-			   (const char *) buf.data, buf.len_chars,
-			   buf.len_bytes,
-			   0);
-	  added += buf.len_chars - 1;
-	  if (opoint > pos)
-	    opoint += buf.len_chars - 1;
-	}
+	last = pos + buf.len_chars;
+	if (first < 0)
+	  first = pos;
+
+	if (ret == 0 && buf.len_chars == 1 && buf.len_bytes == len_bytes)
+	  memcpy (BYTE_POS_ADDR (pos_byte), buf.data, len_bytes);
+	else
+	  {
+	    /* Replace one character with the other(s), keeping text
+	       properties the same.  */
+	    replace_range_2 (pos, pos_byte, pos + len_chars, pos_byte + len_bytes,
+			     (const char *) buf.data, buf.len_chars,
+			     buf.len_bytes,
+			     0);
+	    added += buf.len_chars - len_chars;
+	    if (opoint > pos)
+	      opoint += buf.len_chars - len_chars;
+	  }
 
-      pos_byte += buf.len_bytes;
-      pos += buf.len_chars;
+	/* FALL THOUGH */
+      case -1:
+	size -= len_chars;
+	pos += buf.len_chars;
+	pos_byte += buf.len_bytes;
+      }
     }
 
   if (PT != opoint)
diff --git a/test/src/casefiddle-tests.el b/test/src/casefiddle-tests.el
index 1bd745e..9f5e43f 100644
--- a/test/src/casefiddle-tests.el
+++ b/test/src/casefiddle-tests.el
@@ -134,7 +134,7 @@ casefiddle-tests--characters
      (with-temp-buffer
        (dolist
            (test
-            ;; input  upcase  downcase  capitalize  upcase-initials
+            ;; input  upcase  downcase  capitalize  upcase-initials [locale]
             '(("Foo baR" "FOO BAR" "foo bar" "Foo Bar" "Foo BaR")
               ("Ⅷ ⅷ" "Ⅷ Ⅷ" "ⅷ ⅷ" "Ⅷ Ⅷ" "Ⅷ Ⅷ")
               ;; "DžUNGLA" is an unfortunate result but it’s really best we can
@@ -155,10 +155,41 @@ casefiddle-tests--characters
               ("Σ Σ" "Σ Σ" "σ σ" "Σ Σ" "Σ Σ")
               ("όσος" "ΌΣΟΣ" "όσος" "Όσος" "Όσος")
               ;; If sigma is already lower case, we don’t want to change it.
-              ("όσοσ" "ΌΣΟΣ" "όσοσ" "Όσοσ" "Όσοσ"))
+              ("όσοσ" "ΌΣΟΣ" "όσοσ" "Όσοσ" "Όσοσ")
+
+              ;; There is a language-independent special casing rule which
+              ;; converts İ into i followed by combining dot above that’s why we
+              ;; get the weird \u0307.  Conceptually, it converts i with
+              ;; a soft-dot into an i with a hard-dot so it makes some doze of
+              ;; sense.
+              ("İstanbul" "İSTANBUL" "i\u0307stanbul" "İstanbul" "İstanbul")
+              ("İstanbul" "İSTANBUL" "istanbul" "İstanbul" "İstanbul" 'tr)
+              ("İstanbul" "İSTANBUL" "istanbul" "İstanbul" "İstanbul" 'az)
+              ("istanbul" "ISTANBUL" "istanbul" "Istanbul" "Istanbul")
+              ("istanbul" "İSTANBUL" "istanbul" "İstanbul" "İstanbul" 'tr)
+              ("istanbul" "İSTANBUL" "istanbul" "İstanbul" "İstanbul" 'az)
+              ("Irmak" "IRMAK" "irmak" "Irmak" "Irmak")
+              ("Irmak" "IRMAK" "ırmak" "Irmak" "Irmak" 'tr)
+              ("Irmak" "IRMAK" "ırmak" "Irmak" "Irmak" 'az)
+              ;; FIXME: We explicitly exclude ı→I mapping from the case tables
+              ;; in characters.el which is why instead of:
+              ;;("ırmak" "IRMAK" "ırmak" "Irmak" "Irmak")
+              ;; we actually get:
+              ("ırmak" "ıRMAK" "ırmak" "Irmak" "Irmak")
+              ;; ‘But wait,’ you ask, ‘why capitalise examples work?  This is
+              ;; because those bypass case-table and use character’s Unicode
+              ;; titlecase property.
+              ("ırmak" "IRMAK" "ırmak" "Irmak" "Irmak" 'tr)
+              ("ırmak" "IRMAK" "ırmak" "Irmak" "Irmak" 'az)
+              ;; And for some combining dot above removal.
+              ("I\u0307si\u0307s" "I\u0307Sİ\u0307S" "isi\u0307s"
+                                  "I\u0307si\u0307s" "I\u0307si\u0307s" 'tr)
+              ("I\u0307sI\u0307s" "I\u0307SI\u0307S" "isis"
+                                  "I\u0307sis" "I\u0307sI\u0307s" 'tr))
             (nreverse errors))
-         (let* ((input (car test))
+         (let* ((input (string-to-multibyte (car test)))
                 (expected (cdr test))
+                (current-iso639-language (or (nth 5 test) 'en))
                 (check (lambda (func got)
                          (unless (string-equal got (car expected))
                            (let ((fmt (length (symbol-name func))))
-- 
2.8.0.rc3.226.g39d4020






^ permalink raw reply related	[flat|nested] 89+ messages in thread

* bug#24603: [RFC 11/18] Implement casing rules for Lithuanian
  2016-10-04  1:10 ` bug#24603: [RFC 01/18] Add tests for casefiddle.c Michal Nazarewicz
                     ` (8 preceding siblings ...)
  2016-10-04  1:10   ` bug#24603: [RFC 10/18] Implement Turkic dotless and dotted i handling when casing strings Michal Nazarewicz
@ 2016-10-04  1:10   ` Michal Nazarewicz
  2016-10-04  1:10   ` bug#24603: [RFC 12/18] Implement rules for title-casing Dutch ij ‘letter’ Michal Nazarewicz
                     ` (6 subsequent siblings)
  16 siblings, 0 replies; 89+ messages in thread
From: Michal Nazarewicz @ 2016-10-04  1:10 UTC (permalink / raw)
  To: 24603

In Lithuanian, tittle above lower case i and j are retained even if
there are other diacritics above present.  For that to work, an explicit
combining dot above must be added after i and j or otherwise the
rendering engine will remove the tittle.

* src/casefiddle.c (struct casing_context, prepare_casing_context): Add
lithuanian_tittle member to hold state of Lithuanian rules handling.
(case_lithuanian): New function which implements Lithuanian rules.
(case_characters): Make use of case_lithuanian.

* test/src/casefiddle-tests.el (casefiddle-tests-casing): Add test cases
for Lithuanian rules.
---
 src/casefiddle.c             | 149 +++++++++++++++++++++++++++++++++++++++++--
 test/src/casefiddle-tests.el |  27 +++++++-
 2 files changed, 170 insertions(+), 6 deletions(-)

diff --git a/src/casefiddle.c b/src/casefiddle.c
index 2a7aa64..0377fe6 100644
--- a/src/casefiddle.c
+++ b/src/casefiddle.c
@@ -56,6 +56,16 @@ struct casing_context {
   bool inword;
   /* Whether to apply Azeri/Turkish rules for dotted and dotless i. */
   bool treat_turkic_i;
+
+  /* Whether to use Lithuanian rules for i’s and j’s tittle. */
+  unsigned char lithuanian_tittle;
+#define LT_OFF 0  /* No */
+#define LT_ON 1  /* Yes */
+#define LT_DEL_DOT_ABOVE 2  /* Yes and look out for combining dot above to
+			       delete. */
+#define LT_INS_DOT_ABOVE 3  /* Yes and look out for diacritics combining above
+			       because we may need to inject dot above before
+			       them. */
 };
 
 /* Initialise CTX structure and prepares related global data for casing
@@ -64,7 +74,7 @@ static void
 prepare_casing_context (struct casing_context *ctx,
 			enum case_action flag, bool inbuffer)
 {
-  Lisp_Object lang, l, tr, az;
+  Lisp_Object lang, l, tr, az, lt;
 
   ctx->flag = flag;
   ctx->inbuffer = inbuffer;
@@ -74,6 +84,7 @@ prepare_casing_context (struct casing_context *ctx,
     : Qnil;
 
   ctx->treat_turkic_i = false;
+  ctx->lithuanian_tittle = LT_OFF;
 
   /* If the case table is flagged as modified, rescan it.  */
   if (NILP (XCHAR_TABLE (BVAR (current_buffer, downcase_table))->extras[1]))
@@ -86,6 +97,7 @@ prepare_casing_context (struct casing_context *ctx,
   lang = Vcurrent_iso639_language;
   tr = intern_c_string ("tr");
   az = intern_c_string ("az");
+  lt = intern_c_string ("lt");
   if (SYMBOLP (lang))
     {
       l = lang;
@@ -97,10 +109,9 @@ prepare_casing_context (struct casing_context *ctx,
       lang = XCDR (lang);
     check_language:
       if (EQ (l, tr) || EQ (l, az))
-       {
-         ctx->treat_turkic_i = true;
-         break;
-       }
+	ctx->treat_turkic_i = true;
+      else if (EQ (l, lt))
+	ctx->lithuanian_tittle = LT_ON;
     }
 }
 
@@ -199,6 +210,131 @@ case_character_impl (struct casing_str_buf *buf,
 #define CAPITAL_DOTTED_I    0x130
 #define SMALL_DOTLESS_I     0x131
 #define COMBINING_DOT_ABOVE 0x307
+
+/* Lithuanian retains tittle in lower case i and j when there are more
+   accents above those letters. */
+
+#define CAPITAL_I_WITH_GRAVE  0x0CC
+#define CAPITAL_I_WITH_ACUTE  0x0CD
+#define CAPITAL_I_WITH_TILDE  0x128
+#define CAPITAL_I_WITH_OGONEK 0x12E
+#define SMALL_I_WITH_OGONEK   0x12F
+#define COMBINING_GRAVE_ABOVE 0x300
+#define COMBINING_ACUTE_ABOVE 0x301
+#define COMBINING_TILDE_ABOVE 0x303
+#define COMBINING_OGONEK      0x328
+
+/* Attempt to case CH using rules for Lithuanian i and j.  Return true if
+   character has been cased (in which case it’s saved in BUF), false otherwise.
+   If CTX->lithuanian_tittle is LT_OFF, return false. */
+static bool
+case_lithuanian (struct casing_str_buf *buf, struct casing_context *ctx,
+		 enum case_action flag, int ch)
+{
+  switch (__builtin_expect(ctx->lithuanian_tittle, LT_OFF)) {
+  case LT_OFF:
+    return false;
+
+  case LT_DEL_DOT_ABOVE:
+    /* When upper-casing i or j, a combining dot above that follows it must be
+       removed.  This is true even if there’s a combining ogonek in between.
+       But, if there’s another character combining above in between, combining
+       dot needs to stay (since the dot will be rendered above the other
+       diacritic). */
+    switch (ch) {
+    case COMBINING_DOT_ABOVE:
+      buf->len_chars = buf->len_bytes = 0;
+      ctx->lithuanian_tittle = LT_ON;
+      return true;
+    case COMBINING_GRAVE_ABOVE:
+    case COMBINING_ACUTE_ABOVE:
+    case COMBINING_TILDE_ABOVE:
+      ctx->lithuanian_tittle = LT_ON;
+      return false;
+    case COMBINING_OGONEK:
+      return false;
+    default:
+      ctx->lithuanian_tittle = LT_ON;
+    }
+    break;
+
+  case LT_INS_DOT_ABOVE:
+    /* When lower-casing I or J, if the letter has any accents above,
+       a combining dot above must be added before them.  If we are here, it
+       means that we have lower cased I or J and we’re now on the lookout for
+       accents combining above. */
+    switch (ch) {
+    case COMBINING_GRAVE_ABOVE:
+    case COMBINING_ACUTE_ABOVE:
+    case COMBINING_TILDE_ABOVE:
+      buf->len_chars = 2;
+      buf->len_bytes = CHAR_STRING (COMBINING_DOT_ABOVE, buf->data);
+      buf->len_bytes += CHAR_STRING (ch, buf->data + buf->len_bytes);
+      ctx->lithuanian_tittle = LT_ON;
+      return true;
+    case COMBINING_OGONEK:
+      return false;
+    default:
+      ctx->lithuanian_tittle = LT_ON;
+    }
+    break;
+  }
+
+  switch (flag) {
+  case CASE_UP:
+  case CASE_CAPITALIZE:
+    if (ch == 'i' || ch == 'j')
+      {
+	buf->data[0] = ch ^ ('i' ^ 'I');
+	buf->len_bytes = 1;
+      }
+    else if (ch == SMALL_I_WITH_OGONEK)
+      buf->len_bytes = CHAR_STRING (CAPITAL_I_WITH_OGONEK, buf->data);
+    else
+      break;
+    buf->len_chars = 1;
+    /* Change the state so we’re on the lookout for combining dot above. */
+    ctx->lithuanian_tittle = LT_DEL_DOT_ABOVE;
+    return true;
+
+  case CASE_DOWN:
+    /* Turning I or J to lower case requires combining dot above to be included
+       IF there are any other characters combining above present.  This is so
+       that the tittle is preserved. */
+    switch (ch) {
+    case CAPITAL_I_WITH_GRAVE:
+      ch = 0x80;  /* U+300, "\xCC\x80", combining grave accent */
+      goto has_accent;
+    case CAPITAL_I_WITH_ACUTE:
+      ch = 0x81;  /* U+301, "\xCC \x81", combining acute accent */
+      goto has_accent;
+    case CAPITAL_I_WITH_TILDE:
+      ch = 0x83;  /* U+303, "\xCC\x83", combining tilde */
+    has_accent:
+      memcpy (buf->data, "i\xCC\x87\xCC", 4);
+      buf->data[4] = ch;
+      buf->len_chars = 3;
+      buf->len_bytes = 5;
+      return true;
+
+    case 'I':
+    case 'J':
+      buf->data[0] = ch ^ ('i' ^ 'I');
+      buf->len_bytes = 1;
+      if (false)
+    case CAPITAL_I_WITH_OGONEK:
+	buf->len_bytes = CHAR_STRING (SMALL_I_WITH_OGONEK, buf->data);
+      buf->len_chars = 1;
+      /* Change the state so we’re on the lookout for diacritics combining
+	 above.  If one is found, we need to add combining dot above. */
+      ctx->lithuanian_tittle = LT_INS_DOT_ABOVE;
+      return true;
+    }
+    break;
+  }
+
+  return false;
+}
 \f
 /* Based on CTX, case character CH accordingly.  Update CTX as necessary.
    Return cased character.
@@ -234,6 +370,9 @@ case_characters (struct casing_str_buf *buf, struct casing_context *ctx,
 {
   enum case_action flag = normalise_flag (ctx);
 
+  if (case_lithuanian (buf, ctx, flag, ch))
+    return 0;
+
   if (flag != CASE_NO_ACTION && __builtin_expect(ctx->treat_turkic_i, false))
     {
       bool dot_above = false;
diff --git a/test/src/casefiddle-tests.el b/test/src/casefiddle-tests.el
index 9f5e43f..bae4242 100644
--- a/test/src/casefiddle-tests.el
+++ b/test/src/casefiddle-tests.el
@@ -185,7 +185,32 @@ casefiddle-tests--characters
               ("I\u0307si\u0307s" "I\u0307Sİ\u0307S" "isi\u0307s"
                                   "I\u0307si\u0307s" "I\u0307si\u0307s" 'tr)
               ("I\u0307sI\u0307s" "I\u0307SI\u0307S" "isis"
-                                  "I\u0307sis" "I\u0307sI\u0307s" 'tr))
+                                  "I\u0307sis" "I\u0307sI\u0307s" 'tr)
+
+              ;; Test combining dot above in inserted when needed when lower
+              ;; casing I or J.
+              ("I\u0328\u0300"          ; I + ogonek + grave
+               "I\u0328\u0300" "i\u0328\u0307\u0300"
+               "I\u0328\u0300" "I\u0328\u0300" 'lt)
+
+              ("J\u0328\u0300"          ; J + ogonek + grave
+               "J\u0328\u0300" "j\u0328\u0307\u0300"
+               "J\u0328\u0300" "J\u0328\u0300" 'lt)
+
+              ("Į\u0300"          ; I-ogonek + grave
+               "Į\u0300" "į\u0307\u0300" "Į\u0300" "Į\u0300" 'lt)
+
+              ("Ì Í Ĩ"
+               "Ì Í Ĩ" "i\u0307\u0300 i\u0307\u0301 i\u0307\u0303"
+               "Ì Í Ĩ" "Ì Í Ĩ" 'lt)
+
+              ;; Test combining dot above in removed when upper casing i or j.
+              ("i\u0328\u0307"          ; i + ogonek + dot above
+               "I\u0328" "i\u0328\u0307" "I\u0328" "I\u0328" 'lt)
+              ("j\u0328\u0307"          ; j + ogonek + dot above
+               "J\u0328" "j\u0328\u0307" "J\u0328" "J\u0328" 'lt)
+              ("į\u0307"                ; i-ogonek + dot above
+               "Į" "į\u0307" "Į" "Į" 'lt))
             (nreverse errors))
          (let* ((input (string-to-multibyte (car test)))
                 (expected (cdr test))
-- 
2.8.0.rc3.226.g39d4020






^ permalink raw reply related	[flat|nested] 89+ messages in thread

* bug#24603: [RFC 12/18] Implement rules for title-casing Dutch ij ‘letter’
  2016-10-04  1:10 ` bug#24603: [RFC 01/18] Add tests for casefiddle.c Michal Nazarewicz
                     ` (9 preceding siblings ...)
  2016-10-04  1:10   ` bug#24603: [RFC 11/18] Implement casing rules for Lithuanian Michal Nazarewicz
@ 2016-10-04  1:10   ` Michal Nazarewicz
  2016-10-04  1:10   ` bug#24603: [RFC 13/18] Add some tricky Unicode characters to regex test Michal Nazarewicz
                     ` (5 subsequent siblings)
  16 siblings, 0 replies; 89+ messages in thread
From: Michal Nazarewicz @ 2016-10-04  1:10 UTC (permalink / raw)
  To: 24603

Dutch treats ‘ij’ as a single letter and when capitalising a word it
should be capitalised as a single letter (i.e. ‘ij’ becomes ‘IJ’).
Implement that.

* src/casefiddle.c (casify_context): Add treat_dutch_ij member for
determining whether special handling of ij is necessary.
(prepare_cosify_context): Set treat_dutch_ij to true when in Dutch
locale and capitalising.
(dutch_ij_p_impl, dutch_ij_p, handle_dutch_ij_impl,
handle_dutch_ij): New routines for detecting and handling when ‘ij’
must be upcased together.
(do_casify_multibyte_string, do_casify_unibyte_string,
do_casify_unibyte_region, do_casify_multibyte_region): Implement
handling of Dutch ij.
---
 src/casefiddle.c             | 49 +++++++++++++++++++++++++++++++++++++++++++-
 test/src/casefiddle-tests.el |  6 +++++-
 2 files changed, 53 insertions(+), 2 deletions(-)

diff --git a/src/casefiddle.c b/src/casefiddle.c
index 0377fe6..0de7814 100644
--- a/src/casefiddle.c
+++ b/src/casefiddle.c
@@ -66,6 +66,27 @@ struct casing_context {
 #define LT_INS_DOT_ABOVE 3  /* Yes and look out for diacritics combining above
 			       because we may need to inject dot above before
 			       them. */
+
+  /* In Dutch, ‘ij’ is a digraph and when capitalised the whole thing is upper
+     cased.  Unicode has ‘ij’ and ‘IJ’ (with proper casing mappings) but they
+     aren’t always used so we cannot/should not rely on them.
+
+     Note that rule for capitalising ‘ij’ as a single letter is not present in
+     Unicode 9.0’s SpecialCasing.txt.  On the flip side, Firefox implements this
+     as well so we’re not completely alone.
+
+     There are words where ‘ij’ are two separate letters (such as bijectie or
+     bijoux) in which case the capitalisation rules do not apply.  I (mina86)
+     have googled this a little and couldn’t find a Dutch word which beings with
+     ‘ij’ that is not a digraph so we should be in the clear since we only care
+     about the initial. */
+
+  /* Whether to apply Dutch rules for title-casing ij as IJ.  Non-zero
+     value implies flag is CASE_CAPITALIZE or CASE_CAPITALIZE_UP. */
+  unsigned char treat_dutch_ij;
+#define NL_OFF 0  /* No */
+#define NL_ON 1  /* Yes */
+#define NL_UPCASE_J 2  /* Yes and the previous character was upcased ‘i’. */
 };
 
 /* Initialise CTX structure and prepares related global data for casing
@@ -74,7 +95,7 @@ static void
 prepare_casing_context (struct casing_context *ctx,
 			enum case_action flag, bool inbuffer)
 {
-  Lisp_Object lang, l, tr, az, lt;
+  Lisp_Object lang, l, tr, az, lt, nl;
 
   ctx->flag = flag;
   ctx->inbuffer = inbuffer;
@@ -85,6 +106,7 @@ prepare_casing_context (struct casing_context *ctx,
 
   ctx->treat_turkic_i = false;
   ctx->lithuanian_tittle = LT_OFF;
+  ctx->treat_dutch_ij = NL_OFF;
 
   /* If the case table is flagged as modified, rescan it.  */
   if (NILP (XCHAR_TABLE (BVAR (current_buffer, downcase_table))->extras[1]))
@@ -98,6 +120,7 @@ prepare_casing_context (struct casing_context *ctx,
   tr = intern_c_string ("tr");
   az = intern_c_string ("az");
   lt = intern_c_string ("lt");
+  nl = intern_c_string ("nl");
   if (SYMBOLP (lang))
     {
       l = lang;
@@ -112,6 +135,8 @@ prepare_casing_context (struct casing_context *ctx,
 	ctx->treat_turkic_i = true;
       else if (EQ (l, lt))
 	ctx->lithuanian_tittle = LT_ON;
+      else if (EQ (l, nl))
+	ctx->treat_dutch_ij = (int) flag >= (int) CASE_CAPITALIZE;
     }
 }
 
@@ -154,6 +179,28 @@ case_character_impl (struct casing_str_buf *buf,
   ctx->inword = SYNTAX (ch) == Sword &&
     (!ctx->inbuffer || ctx->inword || !syntax_prefix_flag_p (ch));
 
+  /* Handle dutch ij.  We need to do it here before the flag == CASE_NO_ACTION
+     check.  Note that non-zero treat_dutch_ij implies ctx->flag being ≥
+     CASE_CAPITALIZE. */
+  switch (__builtin_expect(ctx->treat_dutch_ij, NL_OFF)) {
+  case NL_ON:
+    if (ch == 'i' && flag == CASE_CAPITALIZE)
+      {
+	ctx->treat_dutch_ij = NL_UPCASE_J;
+	cased = 'I';
+	goto done;
+      }
+    break;
+  case NL_UPCASE_J:
+    ctx->treat_dutch_ij = NL_ON;
+    if (ch == 'j')
+      {
+	cased = 'J';
+	goto done;
+      }
+  }
+
+  /* We are inside of a word and capitalising initials only. */
   if (flag == CASE_NO_ACTION)
     {
       cased = ch;
diff --git a/test/src/casefiddle-tests.el b/test/src/casefiddle-tests.el
index bae4242..3857f08 100644
--- a/test/src/casefiddle-tests.el
+++ b/test/src/casefiddle-tests.el
@@ -210,7 +210,11 @@ casefiddle-tests--characters
               ("j\u0328\u0307"          ; j + ogonek + dot above
                "J\u0328" "j\u0328\u0307" "J\u0328" "J\u0328" 'lt)
               ("į\u0307"                ; i-ogonek + dot above
-               "Į" "į\u0307" "Į" "Į" 'lt))
+               "Į" "į\u0307" "Į" "Į" 'lt)
+
+              ;; Dutch 'ij' is capitalised as single digraph.
+              ("ijsland" "IJSLAND" "ijsland" "Ijsland" "Ijsland")
+              ("ijsland" "IJSLAND" "ijsland" "IJsland" "IJsland" 'nl))
             (nreverse errors))
          (let* ((input (string-to-multibyte (car test)))
                 (expected (cdr test))
-- 
2.8.0.rc3.226.g39d4020






^ permalink raw reply related	[flat|nested] 89+ messages in thread

* bug#24603: [RFC 13/18] Add some tricky Unicode characters to regex test
  2016-10-04  1:10 ` bug#24603: [RFC 01/18] Add tests for casefiddle.c Michal Nazarewicz
                     ` (10 preceding siblings ...)
  2016-10-04  1:10   ` bug#24603: [RFC 12/18] Implement rules for title-casing Dutch ij ‘letter’ Michal Nazarewicz
@ 2016-10-04  1:10   ` Michal Nazarewicz
  2016-10-04  1:10   ` bug#24603: [RFC 14/18] Factor out character category lookup to separate function Michal Nazarewicz
                     ` (4 subsequent siblings)
  16 siblings, 0 replies; 89+ messages in thread
From: Michal Nazarewicz @ 2016-10-04  1:10 UTC (permalink / raw)
  To: 24603

* test/src/regex-tests.el: Include capital ‘DZ’ dygraph, sharp ‘s’,
capital ligature ‘IJ’, small ligature ‘fi’, title-case dygraph ‘Dz’,
all three forms of Greek sigma and and IPA ɕ symbol in the regex tests.
---
 test/src/regex-tests.el | 25 ++++++++++++++-----------
 1 file changed, 14 insertions(+), 11 deletions(-)

diff --git a/test/src/regex-tests.el b/test/src/regex-tests.el
index c4844c7..fa66ff1 100644
--- a/test/src/regex-tests.el
+++ b/test/src/regex-tests.el
@@ -65,27 +65,30 @@ regex--test-cc
         (skip-chars-forward (concat "[:" name ":]\u2622"))
         (should (or (equal (point) p) (equal (point) (1+ p))))))))
 
-(dolist (test '(("alnum" "abcABC012łąka" "-, \t\n")
-                ("alpha" "abcABCłąka" "-,012 \t\n")
+(dolist (test '(("alnum" "abcABC012łąkaDZßIJfiDzΣσςɕ" "-, \t\n")
+                ("alpha" "abcABCłąkaDZßIJfiDzΣσςɕ" "-,012 \t\n")
                 ("digit" "012" "abcABCłąka-, \t\n")
                 ("xdigit" "0123aBc" "łąk-, \t\n")
-                ("upper" "ABCŁĄKA" "abc012-, \t\n")
-                ("lower" "abcłąka" "ABC012-, \t\n")
+                ("upper" "ABCŁĄKADZIJΣ" "abcß0fiσςɕ12-, \t\n")
+                ;; FIXME: ßfiɕ are all lower case (even though they don’t have
+                ;; (single-character) upper-case form).
+                ("lower" "abcłąkaσς" "ABC012DZIJΣ-, \t\n")
 
-                ("word" "abcABC012\u2620" "-, \t\n")
+                ("word" "abcABC012\u2620DZßIJfiDzΣσςɕ" "-, \t\n")
 
                 ("punct" ".,-" "abcABC012\u2620 \t\n")
                 ("cntrl" "\1\2\t\n" ".,-abcABC012\u2620 ")
-                ("graph" "abcłąka\u2620-," " \t\n\1")
-                ("print" "abcłąka\u2620-, " "\t\n\1")
+                ("graph" "abcłąka\u2620-,DZßIJfiDzΣσςɕ" " \t\n\1")
+                ("print" "abcłąka\u2620-,DZßIJfiDzΣσςɕ " "\t\n\1")
 
                 ("space" " \t\n\u2001" "abcABCł0123")
                 ("blank" " \t" "\n\u2001")
 
-                ("ascii" "abcABC012 \t\n\1" "łą\u2620")
-                ("nonascii" "łą\u2622" "abcABC012 \t\n\1")
-                ("unibyte" "abcABC012 \t\n\1" "łą\u2622")
-                ("multibyte" "łą\u2622" "abcABC012 \t\n\1")))
+                ("ascii" "abcABC012 \t\n\1" "łą\u2620DZßIJfiDzΣσςɕ")
+                ("nonascii" "łą\u2622DZßIJfiDzΣσςɕ" "abcABC012 \t\n\1")
+                ;; Note: sharp s is unibyte since it’s code point is below 256.
+                ("unibyte" "abcABC012ß \t\n\1" "łą\u2622DZIJfiDzΣσςɕ")
+                ("multibyte" "łą\u2622DZIJfiDzΣσςɕ" "abcABC012ß \t\n\1")))
   (let ((name (intern (concat "regex-tests-" (car test) "-character-class")))
         (doc (concat "Perform sanity test of regexes using " (car test)
                      " character class.
-- 
2.8.0.rc3.226.g39d4020






^ permalink raw reply related	[flat|nested] 89+ messages in thread

* bug#24603: [RFC 14/18] Factor out character category lookup to separate function
  2016-10-04  1:10 ` bug#24603: [RFC 01/18] Add tests for casefiddle.c Michal Nazarewicz
                     ` (11 preceding siblings ...)
  2016-10-04  1:10   ` bug#24603: [RFC 13/18] Add some tricky Unicode characters to regex test Michal Nazarewicz
@ 2016-10-04  1:10   ` Michal Nazarewicz
  2016-10-04  1:10   ` bug#24603: [RFC 15/18] Base lower- and upper-case tests on Unicode properties Michal Nazarewicz
                     ` (3 subsequent siblings)
  16 siblings, 0 replies; 89+ messages in thread
From: Michal Nazarewicz @ 2016-10-04  1:10 UTC (permalink / raw)
  To: 24603

* src/character.c (char_unicode_category): New function returning Unicode
general category of specified character.
(alphabeticp, alphanumericp, graphicp, printablep): Use the above.
---
 src/character.c | 33 +++++++++++++++------------------
 1 file changed, 15 insertions(+), 18 deletions(-)

diff --git a/src/character.c b/src/character.c
index 75a7dab..1e49536 100644
--- a/src/character.c
+++ b/src/character.c
@@ -960,14 +960,18 @@ character is not ASCII nor 8-bit character, an error is signaled.  */)
   return make_number (c);
 }
 
+static unicode_category_t
+char_unicode_category (int c)
+{
+  Lisp_Object category = CHAR_TABLE_REF (Vunicode_category_table, c);
+  return INTEGERP (category) ? XINT (category) : UNICODE_CATEGORY_UNKNOWN;
+}
+
 /* Return true if C is an alphabetic character.  */
 bool
 alphabeticp (int c)
 {
-  Lisp_Object category = CHAR_TABLE_REF (Vunicode_category_table, c);
-  if (! INTEGERP (category))
-    return false;
-  EMACS_INT gen_cat = XINT (category);
+  unicode_category_t gen_cat = char_unicode_category (c);
 
   /* See UTS #18.  There are additional characters that should be
      here, those designated as Other_uppercase, Other_lowercase,
@@ -987,10 +991,7 @@ alphabeticp (int c)
 bool
 alphanumericp (int c)
 {
-  Lisp_Object category = CHAR_TABLE_REF (Vunicode_category_table, c);
-  if (! INTEGERP (category))
-    return false;
-  EMACS_INT gen_cat = XINT (category);
+  unicode_category_t gen_cat = char_unicode_category (c);
 
   /* See UTS #18.  Same comment as for alphabeticp applies.  FIXME. */
   return (gen_cat == UNICODE_CATEGORY_Lu
@@ -1009,13 +1010,11 @@ alphanumericp (int c)
 bool
 graphicp (int c)
 {
-  Lisp_Object category = CHAR_TABLE_REF (Vunicode_category_table, c);
-  if (! INTEGERP (category))
-    return false;
-  EMACS_INT gen_cat = XINT (category);
+  unicode_category_t gen_cat = char_unicode_category (c);
 
   /* See UTS #18.  */
-  return (!(gen_cat == UNICODE_CATEGORY_Zs /* space separator */
+  return (!(gen_cat == UNICODE_CATEGORY_UNKNOWN
+	    || gen_cat == UNICODE_CATEGORY_Zs /* space separator */
 	    || gen_cat == UNICODE_CATEGORY_Zl /* line separator */
 	    || gen_cat == UNICODE_CATEGORY_Zp /* paragraph separator */
 	    || gen_cat == UNICODE_CATEGORY_Cc /* control */
@@ -1027,13 +1026,11 @@ graphicp (int c)
 bool
 printablep (int c)
 {
-  Lisp_Object category = CHAR_TABLE_REF (Vunicode_category_table, c);
-  if (! INTEGERP (category))
-    return false;
-  EMACS_INT gen_cat = XINT (category);
+  unicode_category_t gen_cat = char_unicode_category (c);
 
   /* See UTS #18.  */
-  return (!(gen_cat == UNICODE_CATEGORY_Cc /* control */
+  return (!(gen_cat == UNICODE_CATEGORY_UNKNOWN
+	    || gen_cat == UNICODE_CATEGORY_Cc /* control */
 	    || gen_cat == UNICODE_CATEGORY_Cs /* surrogate */
 	    || gen_cat == UNICODE_CATEGORY_Cn)); /* unassigned */
 }
-- 
2.8.0.rc3.226.g39d4020






^ permalink raw reply related	[flat|nested] 89+ messages in thread

* bug#24603: [RFC 15/18] Base lower- and upper-case tests on Unicode properties
  2016-10-04  1:10 ` bug#24603: [RFC 01/18] Add tests for casefiddle.c Michal Nazarewicz
                     ` (12 preceding siblings ...)
  2016-10-04  1:10   ` bug#24603: [RFC 14/18] Factor out character category lookup to separate function Michal Nazarewicz
@ 2016-10-04  1:10   ` Michal Nazarewicz
  2016-10-04  6:54     ` Eli Zaretskii
  2016-10-04  1:10   ` bug#24603: [RFC 16/18] Refactor character class checking; optimise ASCII case Michal Nazarewicz
                     ` (2 subsequent siblings)
  16 siblings, 1 reply; 89+ messages in thread
From: Michal Nazarewicz @ 2016-10-04  1:10 UTC (permalink / raw)
  To: 24603

Not all lower case characters have a simple upper case form.
For example, an fi ligature has no one-character upper case mapping.
Similarly, ɕ from IPA block has no upper case form at all.

It isn’t therefore sufficient to lookup character’s upper-case form to
determine whether it’s lower-case or not.  As such, rewrite the tests
to be based on Unicode properties.

* src/buffer.h (uppercasep, lowercasep): Delete.
* src/character.c (uppercasep, lowercasep): New functions which base
their test on Unicode character properties rather than case table.
* test/src/regex-tests.el (regex-tests-lower-character-class): Include
fi ligature, σ letter and ς IPA symbol in the test.
---
 etc/NEWS                |  7 +++++++
 src/buffer.h            | 13 -------------
 src/character.c         | 24 ++++++++++++++++++++++++
 src/character.h         |  2 ++
 test/src/regex-tests.el |  4 +---
 5 files changed, 34 insertions(+), 16 deletions(-)

diff --git a/etc/NEWS b/etc/NEWS
index 4516812..727af59 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -251,6 +251,10 @@ For example, fi ligature is converted to FI when upper cased.
 Strings such as ΌΣΟΣ are now correctly converted to Όσος when
 capitalised to follow rules of Greek spelling.
 
+*** 'upper' and 'lower' classes match characters w/o simple cased forms.
+For instance, ß letter and fi ligature are now matched by [[:lower:]]
+regular expression.
+
 \f
 * Changes in Specialized Modes and Packages in Emacs 26.1
 
@@ -512,6 +516,9 @@ of curved quotes in format arguments to functions like 'message' and
 now generate less chatter and more-compact diagnostics.  The auxiliary
 function 'check-declare-errmsg' has been removed.
 
+** 'upper' and 'lower' character classes are unaffected by case table
+since they are now based purely on Unicode properties.
+
 \f
 * Lisp Changes in Emacs 26.1
 
diff --git a/src/buffer.h b/src/buffer.h
index 1543f67..aade0ea 100644
--- a/src/buffer.h
+++ b/src/buffer.h
@@ -1358,19 +1358,6 @@ upcase (int c)
   return NATNUMP (up) ? XFASTINT (up) : c;
 }
 
-/* True if C is upper case.  */
-INLINE bool uppercasep (int c)
-{
-  return downcase (c) != c;
-}
-
-/* True if C is lower case.  */
-INLINE bool
-lowercasep (int c)
-{
-  return !uppercasep (c) && upcase (c) != c;
-}
-
 INLINE_HEADER_END
 
 #endif /* EMACS_BUFFER_H */
diff --git a/src/character.c b/src/character.c
index 1e49536..707ae10 100644
--- a/src/character.c
+++ b/src/character.c
@@ -967,6 +967,30 @@ char_unicode_category (int c)
   return INTEGERP (category) ? XINT (category) : UNICODE_CATEGORY_UNKNOWN;
 }
 
+/* Return true if C is a upper case character.  This does not imply mean it
+   has a lower case form. */
+bool
+uppercasep (int c)
+{
+  unicode_category_t gen_cat = char_unicode_category (c);
+
+  /* See UTS #18.  There are additional characters that should be
+     here, those designated as Other_uppercase; FIXME.  */
+  return gen_cat == UNICODE_CATEGORY_Lu;
+}
+
+/* Return true if C is a lower case character.  This does not imply mean it
+   has an upper case form. */
+bool
+lowercasep (int c)
+{
+  unicode_category_t gen_cat = char_unicode_category (c);
+
+  /* See UTS #18.  There are additional characters that should be
+     here, those designated as Other_lowercase; FIXME.  */
+  return gen_cat == UNICODE_CATEGORY_Ll;
+}
+
 /* Return true if C is an alphabetic character.  */
 bool
 alphabeticp (int c)
diff --git a/src/character.h b/src/character.h
index fc8a0dd..5931c5c 100644
--- a/src/character.h
+++ b/src/character.h
@@ -676,6 +676,8 @@ extern ptrdiff_t lisp_string_width (Lisp_Object, ptrdiff_t,
 extern Lisp_Object Vchar_unify_table;
 extern Lisp_Object string_escape_byte8 (Lisp_Object);
 
+extern bool uppercasep (int);
+extern bool lowercasep (int);
 extern bool alphabeticp (int);
 extern bool alphanumericp (int);
 extern bool graphicp (int);
diff --git a/test/src/regex-tests.el b/test/src/regex-tests.el
index fa66ff1..fc50344 100644
--- a/test/src/regex-tests.el
+++ b/test/src/regex-tests.el
@@ -70,9 +70,7 @@ regex--test-cc
                 ("digit" "012" "abcABCłąka-, \t\n")
                 ("xdigit" "0123aBc" "łąk-, \t\n")
                 ("upper" "ABCŁĄKADZIJΣ" "abcß0fiσςɕ12-, \t\n")
-                ;; FIXME: ßfiɕ are all lower case (even though they don’t have
-                ;; (single-character) upper-case form).
-                ("lower" "abcłąkaσς" "ABC012DZIJΣ-, \t\n")
+                ("lower" "abcłąkaßfiσς" "ABC012DZIJΣ-, \t\n")
 
                 ("word" "abcABC012\u2620DZßIJfiDzΣσςɕ" "-, \t\n")
 
-- 
2.8.0.rc3.226.g39d4020






^ permalink raw reply related	[flat|nested] 89+ messages in thread

* bug#24603: [RFC 16/18] Refactor character class checking; optimise ASCII case
  2016-10-04  1:10 ` bug#24603: [RFC 01/18] Add tests for casefiddle.c Michal Nazarewicz
                     ` (13 preceding siblings ...)
  2016-10-04  1:10   ` bug#24603: [RFC 15/18] Base lower- and upper-case tests on Unicode properties Michal Nazarewicz
@ 2016-10-04  1:10   ` Michal Nazarewicz
  2016-10-04  7:48     ` Eli Zaretskii
  2016-10-04  1:10   ` bug#24603: [RFC 17/18] Optimise character class matching in regexes Michal Nazarewicz
  2016-10-04  1:10   ` bug#24603: [RFC 18/18] Fix case-fold-search character class matching Michal Nazarewicz
  16 siblings, 1 reply; 89+ messages in thread
From: Michal Nazarewicz @ 2016-10-04  1:10 UTC (permalink / raw)
  To: 24603

Use a lookup table to map Unicode general categories to character
categories.  This generalises lowercasep, uppercasep et al. functions.

Furthermore, provide another lookup table for ASCII characters such that
the common case can be optimised and Unicode general category lookup
avoided.

Using lookup table in place of conditions may theoretically improve
performance even though I have not measure it.  Moreover though, having
the lookup table will allow regex engine to be optimised in an upcoming
patch.  Stay tuned. ;)

* src/character.c (alphanumericp, alphabeticp, uppercasep, lowercasep,
graphicp, printablep): Replaced with static inline functions define in
the header file.
(category_char_bits): New lookup table mapping Unicode
general category to character classes.
(ascii_char_bits): New lookup table mapping ASCII characters to
character classes.

* src/character.h (unicode_alphanumericp, unicode_alphabeticp,
unicode_uppercasep, unicode_lowercasep, unicode_graphicp,
unicode_printablep, _ascii_alphanumericp, _ascii_alphabeticp,
_ascii_uppercasep, _ascii_lowercasep, _ascii_graphicp,
_ascii_printablep): New static inline functions which are special cases
of respective unprefixed functions.

* src/regex.c (ISALNUM, ISALPHA): Remove special cases for ASCII
characters since alphanumericp and uppercasep already handle those.
---
 src/character.c | 168 +++++++++++++++++++++++++-------------------------------
 src/character.h |  77 +++++++++++++++++++++++---
 src/regex.c     |  20 ++-----
 3 files changed, 151 insertions(+), 114 deletions(-)

diff --git a/src/character.c b/src/character.c
index 707ae10..63f89d3 100644
--- a/src/character.c
+++ b/src/character.c
@@ -960,104 +960,88 @@ character is not ASCII nor 8-bit character, an error is signaled.  */)
   return make_number (c);
 }
 
-static unicode_category_t
+/* Return C’s Unicode general category (or UNICODE_CATEGORY_UNKNOWN). */
+unicode_category_t
 char_unicode_category (int c)
 {
   Lisp_Object category = CHAR_TABLE_REF (Vunicode_category_table, c);
   return INTEGERP (category) ? XINT (category) : UNICODE_CATEGORY_UNKNOWN;
 }
 
-/* Return true if C is a upper case character.  This does not imply mean it
-   has a lower case form. */
-bool
-uppercasep (int c)
-{
-  unicode_category_t gen_cat = char_unicode_category (c);
-
-  /* See UTS #18.  There are additional characters that should be
-     here, those designated as Other_uppercase; FIXME.  */
-  return gen_cat == UNICODE_CATEGORY_Lu;
-}
-
-/* Return true if C is a lower case character.  This does not imply mean it
-   has an upper case form. */
-bool
-lowercasep (int c)
-{
-  unicode_category_t gen_cat = char_unicode_category (c);
-
-  /* See UTS #18.  There are additional characters that should be
-     here, those designated as Other_lowercase; FIXME.  */
-  return gen_cat == UNICODE_CATEGORY_Ll;
-}
-
-/* Return true if C is an alphabetic character.  */
-bool
-alphabeticp (int c)
-{
-  unicode_category_t gen_cat = char_unicode_category (c);
-
-  /* See UTS #18.  There are additional characters that should be
-     here, those designated as Other_uppercase, Other_lowercase,
-     and Other_alphabetic; FIXME.  */
-  return (gen_cat == UNICODE_CATEGORY_Lu
-	  || gen_cat == UNICODE_CATEGORY_Ll
-	  || gen_cat == UNICODE_CATEGORY_Lt
-	  || gen_cat == UNICODE_CATEGORY_Lm
-	  || gen_cat == UNICODE_CATEGORY_Lo
-	  || gen_cat == UNICODE_CATEGORY_Mn
-	  || gen_cat == UNICODE_CATEGORY_Mc
-	  || gen_cat == UNICODE_CATEGORY_Me
-	  || gen_cat == UNICODE_CATEGORY_Nl);
-}
-
-/* Return true if C is a alphabetic or decimal-number character.  */
-bool
-alphanumericp (int c)
-{
-  unicode_category_t gen_cat = char_unicode_category (c);
-
-  /* See UTS #18.  Same comment as for alphabeticp applies.  FIXME. */
-  return (gen_cat == UNICODE_CATEGORY_Lu
-	  || gen_cat == UNICODE_CATEGORY_Ll
-	  || gen_cat == UNICODE_CATEGORY_Lt
-	  || gen_cat == UNICODE_CATEGORY_Lm
-	  || gen_cat == UNICODE_CATEGORY_Lo
-	  || gen_cat == UNICODE_CATEGORY_Mn
-	  || gen_cat == UNICODE_CATEGORY_Mc
-	  || gen_cat == UNICODE_CATEGORY_Me
-	  || gen_cat == UNICODE_CATEGORY_Nl
-	  || gen_cat == UNICODE_CATEGORY_Nd);
-}
-
-/* Return true if C is a graphic character.  */
-bool
-graphicp (int c)
-{
-  unicode_category_t gen_cat = char_unicode_category (c);
-
-  /* See UTS #18.  */
-  return (!(gen_cat == UNICODE_CATEGORY_UNKNOWN
-	    || gen_cat == UNICODE_CATEGORY_Zs /* space separator */
-	    || gen_cat == UNICODE_CATEGORY_Zl /* line separator */
-	    || gen_cat == UNICODE_CATEGORY_Zp /* paragraph separator */
-	    || gen_cat == UNICODE_CATEGORY_Cc /* control */
-	    || gen_cat == UNICODE_CATEGORY_Cs /* surrogate */
-	    || gen_cat == UNICODE_CATEGORY_Cn)); /* unassigned */
-}
-
-/* Return true if C is a printable character.  */
-bool
-printablep (int c)
-{
-  unicode_category_t gen_cat = char_unicode_category (c);
-
-  /* See UTS #18.  */
-  return (!(gen_cat == UNICODE_CATEGORY_UNKNOWN
-	    || gen_cat == UNICODE_CATEGORY_Cc /* control */
-	    || gen_cat == UNICODE_CATEGORY_Cs /* surrogate */
-	    || gen_cat == UNICODE_CATEGORY_Cn)); /* unassigned */
-}
+#define CHAR_BIT_ALNUM_ CHAR_BIT_ALNUM | CHAR_BIT_GRAPH | CHAR_BIT_PRINT
+#define CHAR_BIT_ALPHA_ CHAR_BIT_ALPHA | CHAR_BIT_ALNUM_
+
+/* See UTS #18 and DerivedCoreProperties.txt.  alpha, alnum, upper and
+   lower are missing some characters, namely those designated as
+   Other_uppercase, Other_lowercase and Other_alphabetic; FIXME.  */
+
+const unsigned char category_char_bits[] = {
+  [UNICODE_CATEGORY_UNKNOWN] = 0,
+  [UNICODE_CATEGORY_Lu] = CHAR_BIT_ALPHA_ | CHAR_BIT_UPPER,
+  [UNICODE_CATEGORY_Ll] = CHAR_BIT_ALPHA_ | CHAR_BIT_LOWER,
+  [UNICODE_CATEGORY_Lt] = CHAR_BIT_ALPHA_,
+  [UNICODE_CATEGORY_Lm] = CHAR_BIT_ALPHA_,
+  [UNICODE_CATEGORY_Lo] = CHAR_BIT_ALPHA_,
+  [UNICODE_CATEGORY_Mn] = CHAR_BIT_ALPHA_,
+  [UNICODE_CATEGORY_Mc] = CHAR_BIT_ALPHA_,
+  [UNICODE_CATEGORY_Me] = CHAR_BIT_ALPHA_,
+  [UNICODE_CATEGORY_Nd] = CHAR_BIT_ALNUM_,
+  [UNICODE_CATEGORY_Nl] = CHAR_BIT_ALPHA_,
+  [UNICODE_CATEGORY_No] = CHAR_BIT_PRINT | CHAR_BIT_GRAPH,
+  [UNICODE_CATEGORY_Pc] = CHAR_BIT_PRINT | CHAR_BIT_GRAPH,
+  [UNICODE_CATEGORY_Pd] = CHAR_BIT_PRINT | CHAR_BIT_GRAPH,
+  [UNICODE_CATEGORY_Ps] = CHAR_BIT_PRINT | CHAR_BIT_GRAPH,
+  [UNICODE_CATEGORY_Pe] = CHAR_BIT_PRINT | CHAR_BIT_GRAPH,
+  [UNICODE_CATEGORY_Pi] = CHAR_BIT_PRINT | CHAR_BIT_GRAPH,
+  [UNICODE_CATEGORY_Pf] = CHAR_BIT_PRINT | CHAR_BIT_GRAPH,
+  [UNICODE_CATEGORY_Po] = CHAR_BIT_PRINT | CHAR_BIT_GRAPH,
+  [UNICODE_CATEGORY_Sm] = CHAR_BIT_PRINT | CHAR_BIT_GRAPH,
+  [UNICODE_CATEGORY_Sc] = CHAR_BIT_PRINT | CHAR_BIT_GRAPH,
+  [UNICODE_CATEGORY_Sk] = CHAR_BIT_PRINT | CHAR_BIT_GRAPH,
+  [UNICODE_CATEGORY_So] = CHAR_BIT_PRINT | CHAR_BIT_GRAPH,
+  [UNICODE_CATEGORY_Zs] = CHAR_BIT_PRINT,
+  [UNICODE_CATEGORY_Zl] = CHAR_BIT_PRINT,
+  [UNICODE_CATEGORY_Zp] = CHAR_BIT_PRINT,
+  [UNICODE_CATEGORY_Cc] = 0,
+  [UNICODE_CATEGORY_Cf] = CHAR_BIT_PRINT | CHAR_BIT_GRAPH,
+  [UNICODE_CATEGORY_Cs] = 0,
+  [UNICODE_CATEGORY_Co] = CHAR_BIT_PRINT | CHAR_BIT_GRAPH,
+  [UNICODE_CATEGORY_Cn] = 0,
+};
+
+#undef CHAR_BIT_ALNUM_
+#undef CHAR_BIT_ALPHA_
+
+#define P_ CHAR_BIT_PRINT
+#define G_ CHAR_BIT_GRAPH | P_
+#define N_ CHAR_BIT_ALNUM | G_
+#define U_ CHAR_BIT_UPPER | CHAR_BIT_ALPHA | N_
+#define L_ CHAR_BIT_LOWER | CHAR_BIT_ALPHA | N_
+
+const unsigned char ascii_char_bits[] = {
+/*\0  ...                                                     \17 */
+   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+/*\20  ...                                                    \37 */
+   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+/*' ' '!' '"' '#' '$' '%' '&' '´' '(' ')' '*' '+' ',' '-' '.' '/' */
+  P_, G_, G_, G_, G_, G_, G_, G_, G_, G_, G_, G_, G_, G_, G_, G_,
+/*'0' '1' '2' '3' '4' '5' '6' '7' '8' '9' ':' ';' '<' '=' '>' '?' */
+  N_, N_, N_, N_, N_, N_, N_, N_, N_, N_, G_, G_, G_, G_, G_, G_,
+/*'@' 'A' 'B' 'C' 'D' 'E' 'F' 'G' 'H' 'I' 'J' 'K' 'L' 'M' 'N' 'O' */
+  G_, U_, U_, U_, U_, U_, U_, U_, U_, U_, U_, U_, U_, U_, U_, U_,
+/*'P' 'Q' 'R' 'S' 'T' 'U' 'V' 'W' 'X' 'Y' 'Z' '[' '\' ']' '^' '_' */
+  U_, U_, U_, U_, U_, U_, U_, U_, U_, U_, U_, G_, G_, G_, G_, G_,
+/*'`' 'a' 'b' 'c' 'd' 'e' 'f' 'g' 'h' 'i' 'j' 'k' 'l' 'm' 'n' 'o' */
+  G_, L_, L_, L_, L_, L_, L_, L_, L_, L_, L_, L_, L_, L_, L_, L_,
+/*'p' 'q' 'r' 's' 't' 'u' 'v' 'w' 'x' 'y' 'z' '{' '|' '}' '~' \177 */
+  L_, L_, L_, L_, L_, L_, L_, L_, L_, L_, L_, G_, G_, G_, G_,  0,
+};
+
+#undef P_
+#undef G_
+#undef N_
+#undef U_
+#undef L_
 
 void
 syms_of_character (void)
diff --git a/src/character.h b/src/character.h
index 5931c5c..6dc95ad 100644
--- a/src/character.h
+++ b/src/character.h
@@ -652,8 +652,78 @@ typedef enum {
   UNICODE_CATEGORY_Cs,
   UNICODE_CATEGORY_Co,
   UNICODE_CATEGORY_Cn
+  /* Don’t forget to extend category_char_bits in character.c when new entries
+     are added here. */
 } unicode_category_t;
 
+extern unicode_category_t char_unicode_category (int);
+
+/* Limited set of character categories which syntax-independent.  Testing of
+ * those characters do not require any run-time data, e.g. do not depend on
+ * syntax table. */
+#define CHAR_BIT_ALNUM        (1 << 0)
+#define CHAR_BIT_ALPHA        (1 << 1)
+#define CHAR_BIT_UPPER        (1 << 2)
+#define CHAR_BIT_LOWER        (1 << 3)
+#define CHAR_BIT_GRAPH        (1 << 4)
+#define CHAR_BIT_PRINT        (1 << 5)
+
+/* Map from Unicode general category to character classes the character is in.
+ *
+ * Only character classes defined by CHAR_BIT_* above are present.
+ *
+ * This is an array of bit fields so for example ‘category_char_bits[gc] &
+ * CHAR_BIT_ALPHA’ tells you whether characters in general category GC are
+ * alphabetic or not. */
+extern const unsigned char category_char_bits[];
+
+/* Map from ASCII character to character classes the character is in.
+ *
+ * Only character classes defined by CHAR_BIT_* above are present.
+ *
+ * This is an array of bit fields so for example ascii_char_bits[ch] &
+ * CHAR_BIT_ALPHA’ tells you whether character CH is alphabetic or not. */
+extern const unsigned char ascii_char_bits[128];
+
+#define DEFINE_CHAR_TEST(name, bit)					\
+  static inline bool unicode_ ## name (int c) {			\
+    return category_char_bits[char_unicode_category(c)] & bit;		\
+  }									\
+  static inline bool _ascii_ ## name (int c) {				\
+    return ascii_char_bits[c] & bit;					\
+  }									\
+  static inline bool name (int c) {					\
+    return (unsigned)c < (sizeof ascii_char_bits / sizeof *ascii_char_bits) ? \
+      _ascii_ ## name (c) : unicode_ ## name (c);			\
+  }
+
+/* For TEST being one of:
+     alphanumericp
+     alphabeticp
+     uppercasep
+     lowercasep
+     graphicp
+     printablep
+   define
+     bool TEST (int c);
+     bool unicode_TEST (int c);
+     bool _ascii_TEST (int c);
+   which test whether C has given character property.  TEST works for any
+   character, Unicode or not.  unicode_TEST works for any character as well but
+   is potentially slower for ASCII characters (since it requires Unicode
+   category lookup).  _ascii_TEST works for ASCII characters only and creates
+   naked singularity if non-ASCII character is passed to it. */
+
+DEFINE_CHAR_TEST (alphanumericp, CHAR_BIT_ALNUM)
+DEFINE_CHAR_TEST (alphabeticp, CHAR_BIT_ALPHA)
+DEFINE_CHAR_TEST (uppercasep, CHAR_BIT_UPPER)
+DEFINE_CHAR_TEST (lowercasep, CHAR_BIT_LOWER)
+DEFINE_CHAR_TEST (graphicp, CHAR_BIT_GRAPH)
+DEFINE_CHAR_TEST (printablep, CHAR_BIT_PRINT)
+
+#undef DEFINE_CHAR_TEST
+
+
 extern EMACS_INT char_resolve_modifier_mask (EMACS_INT) ATTRIBUTE_CONST;
 extern int char_string (unsigned, unsigned char *);
 extern int string_char (const unsigned char *,
@@ -676,13 +746,6 @@ extern ptrdiff_t lisp_string_width (Lisp_Object, ptrdiff_t,
 extern Lisp_Object Vchar_unify_table;
 extern Lisp_Object string_escape_byte8 (Lisp_Object);
 
-extern bool uppercasep (int);
-extern bool lowercasep (int);
-extern bool alphabeticp (int);
-extern bool alphanumericp (int);
-extern bool graphicp (int);
-extern bool printablep (int);
-
 /* Return a translation table of id number ID.  */
 #define GET_TRANSLATION_TABLE(id) \
   (XCDR (XVECTOR (Vtranslation_table_vector)->contents[(id)]))
diff --git a/src/regex.c b/src/regex.c
index 1917a84..02da1fb 100644
--- a/src/regex.c
+++ b/src/regex.c
@@ -313,6 +313,11 @@ enum syntaxcode { Swhitespace = 0, Sword = 1, Ssymbol = 2 };
 
 /* The rest must handle multibyte characters.  */
 
+# define ISALNUM(c) alphanumericp (c)
+# define ISALPHA(c) alphabeticp (c)
+# define ISUPPER(c) uppercasep (c)
+# define ISLOWER(c) lowercasep (c)
+
 # define ISGRAPH(c) (SINGLE_BYTE_CHAR_P (c)				\
 		     ? (c) > ' ' && !((c) >= 0177 && (c) <= 0240)	\
 		     : graphicp (c))
@@ -321,19 +326,6 @@ enum syntaxcode { Swhitespace = 0, Sword = 1, Ssymbol = 2 };
 		    ? (c) >= ' ' && !((c) >= 0177 && (c) <= 0237)	\
 		     : printablep (c))
 
-# define ISALNUM(c) (IS_REAL_ASCII (c)			\
-		    ? (((c) >= 'a' && (c) <= 'z')	\
-		       || ((c) >= 'A' && (c) <= 'Z')	\
-		       || ((c) >= '0' && (c) <= '9'))	\
-		    : alphanumericp (c))
-
-# define ISALPHA(c) (IS_REAL_ASCII (c)			\
-		    ? (((c) >= 'a' && (c) <= 'z')	\
-		       || ((c) >= 'A' && (c) <= 'Z'))	\
-		    : alphabeticp (c))
-
-# define ISLOWER(c) lowercasep (c)
-
 # define ISPUNCT(c) (IS_REAL_ASCII (c)				\
 		    ? ((c) > ' ' && (c) < 0177			\
 		       && !(((c) >= 'a' && (c) <= 'z')		\
@@ -343,8 +335,6 @@ enum syntaxcode { Swhitespace = 0, Sword = 1, Ssymbol = 2 };
 
 # define ISSPACE(c) (SYNTAX (c) == Swhitespace)
 
-# define ISUPPER(c) uppercasep (c)
-
 # define ISWORD(c) (SYNTAX (c) == Sword)
 
 #else /* not emacs */
-- 
2.8.0.rc3.226.g39d4020






^ permalink raw reply related	[flat|nested] 89+ messages in thread

* bug#24603: [RFC 17/18] Optimise character class matching in regexes
  2016-10-04  1:10 ` bug#24603: [RFC 01/18] Add tests for casefiddle.c Michal Nazarewicz
                     ` (14 preceding siblings ...)
  2016-10-04  1:10   ` bug#24603: [RFC 16/18] Refactor character class checking; optimise ASCII case Michal Nazarewicz
@ 2016-10-04  1:10   ` Michal Nazarewicz
  2016-10-04  1:10   ` bug#24603: [RFC 18/18] Fix case-fold-search character class matching Michal Nazarewicz
  16 siblings, 0 replies; 89+ messages in thread
From: Michal Nazarewicz @ 2016-10-04  1:10 UTC (permalink / raw)
  To: 24603

Use lookup tables defined in src/character.h to bundle checks together
if possible.  For example, ‘[[:lower:][:digit:]]’ would perform an
equivalence of ‘lowercasep(ch) || numericp(ch)’ check.  Now, such checks
are done all at once with at most one Unicode general category lookup.

Similarly, do at most one syntax table lookup by unrolling macros
testing character properties.

* src/regex.c (execute_charset): Use category_char_bits and call SYNTAX
at most once.

* test/src/regex-tests.el (regex-tests--letter-character-classes): New
test case for various character classes relating to letters etc.
---
 src/regex.c             | 86 +++++++++++++++++++++++++++++++------------------
 test/src/regex-tests.el | 45 ++++++++++++++++++++++++++
 2 files changed, 100 insertions(+), 31 deletions(-)

diff --git a/src/regex.c b/src/regex.c
index 02da1fb..bfd04a1 100644
--- a/src/regex.c
+++ b/src/regex.c
@@ -1789,16 +1789,26 @@ struct range_table_work_area
 /* Bits used to implement the multibyte-part of the various character classes
    such as [:alnum:] in a charset's range table.  The code currently assumes
    that only the low 16 bits are used.  */
-#define BIT_WORD	0x1
-#define BIT_LOWER	0x2
-#define BIT_PUNCT	0x4
-#define BIT_SPACE	0x8
-#define BIT_UPPER	0x10
-#define BIT_MULTIBYTE	0x20
-#define BIT_ALPHA	0x40
-#define BIT_ALNUM	0x80
-#define BIT_GRAPH	0x100
-#define BIT_PRINT	0x200
+#ifdef emacs
+#  define BIT_ALNUM	CHAR_BIT_ALNUM
+#  define BIT_ALPHA	CHAR_BIT_ALPHA
+#  define BIT_UPPER	CHAR_BIT_UPPER
+#  define BIT_LOWER	CHAR_BIT_LOWER
+#  define BIT_GRAPH	CHAR_BIT_GRAPH
+#  define BIT_PRINT	CHAR_BIT_PRINT
+#else
+#  define BIT_ALNUM	(1 << 0)
+#  define BIT_ALPHA	(1 << 1)
+#  define BIT_UPPER	(1 << 2)
+#  define BIT_LOWER	(1 << 3)
+#  define BIT_GRAPH	(1 << 4)
+#  define BIT_PRINT	(1 << 5)
+#endif
+#define BIT_WORD	(BIT_PRINT << 1)
+#define BIT_PUNCT	(BIT_PRINT << 2)
+#define BIT_SPACE	(BIT_PRINT << 3)
+#define BIT_MULTIBYTE	(BIT_PRINT << 4)
+
 \f
 
 /* Set the bit for character C in a list.  */
@@ -1988,9 +1998,6 @@ re_wctype_parse (const unsigned char **strp, unsigned limit)
            2 [:print:]
            2 [:cntrl:]
            1 [:ff:]
-
-     If you update this list, consider also updating chain of or’ed conditions
-     in execute_charset function.
    */
 
   switch (it - beg) {
@@ -4657,28 +4664,45 @@ execute_charset (const_re_char **pp, unsigned c, unsigned corig, bool unibyte)
   else if (rtp)
     {
       int class_bits = CHARSET_RANGE_TABLE_BITS (p);
+      int bits;
       re_wchar_t range_start, range_end;
 
-  /* Sort tests by the most commonly used classes with some adjustment to which
-     tests are easiest to perform.  Take a look at comment in re_wctype_parse
-     for table with frequencies of character class names. */
-
-      if ((class_bits & BIT_MULTIBYTE) ||
-	  (class_bits & BIT_ALNUM && ISALNUM (c)) ||
-	  (class_bits & BIT_ALPHA && ISALPHA (c)) ||
-	  (class_bits & BIT_SPACE && ISSPACE (c)) ||
-	  (class_bits & BIT_WORD  && ISWORD  (c)) ||
-	  ((class_bits & BIT_UPPER) &&
-	   (ISUPPER (c) || (corig != c &&
-			    c == downcase (corig) && ISLOWER (c)))) ||
-	  ((class_bits & BIT_LOWER) &&
-	   (ISLOWER (c) || (corig != c &&
-			    c == upcase (corig) && ISUPPER(c)))) ||
-	  (class_bits & BIT_PUNCT && ISPUNCT (c)) ||
-	  (class_bits & BIT_GRAPH && ISGRAPH (c)) ||
-	  (class_bits & BIT_PRINT && ISPRINT (c)))
+      if (class_bits & BIT_MULTIBYTE)
 	return !not;
 
+      /* If we are at this point, the character is not an ASCII or single byte
+	 character.  This means that whenever ISFOO macros have special case for
+	 IS_REAL_ASCII (c), we can ignore that. */
+
+      bits = class_bits & (BIT_ALNUM | BIT_ALPHA | BIT_UPPER | BIT_LOWER |
+			   BIT_GRAPH | BIT_PRINT);
+      if (bits)
+	{
+	  int char_bits = category_char_bits[char_unicode_category (c)];
+	  if (bits & char_bits)
+	    return !not;
+
+	  /* Handle case folding. */
+	  if (corig != c)
+	    {
+	      if ((bits & BIT_UPPER) && (char_bits & BIT_LOWER) &&
+		  c == downcase (corig))
+		return !not;
+	      if ((bits & BIT_LOWER) && (char_bits & BIT_UPPER) &&
+		  c == upcase (corig))
+		return !not;
+	    }
+	}
+
+      if (class_bits & (BIT_SPACE | BIT_WORD | BIT_PUNCT))
+	{
+	  enum syntaxcode s = SYNTAX (c);
+	  if (((class_bits & BIT_SPACE) && s == Swhitespace) ||
+	      ((class_bits & BIT_WORD ) && s == Sword) ||
+	      ((class_bits & BIT_PUNCT) && s != Sword))
+	    return !not;
+	}
+
       for (p = *pp; rtp < p; rtp += 2 * 3)
 	{
 	  EXTRACT_CHARACTER (range_start, rtp);
diff --git a/test/src/regex-tests.el b/test/src/regex-tests.el
index fc50344..7617823 100644
--- a/test/src/regex-tests.el
+++ b/test/src/regex-tests.el
@@ -98,6 +98,51 @@ regex--test-cc
     (eval `(ert-deftest ,name () ,doc ,(cons 'regex--test-cc test)) t)))
 
 
+(ert-deftest regex-tests--letter-character-classes ()
+  "Test character classes against various letters types."
+  (should-not
+   (cl-remove-if
+    'not
+    (let ((check-ccs (lambda (ch fold)
+                       (mapconcat
+                        (lambda (str) str)
+                        (let ((case-fold-search fold))
+                          (cl-remove-if-not
+                           (lambda (cc)
+                             (string-match-p (concat "[[:" cc ":]]")
+                                             (string ch)))
+                           '("alnum" "alpha" "upper" "lower")))
+                        " "))))
+      (mapcar
+       (lambda (entry)
+         (let ((ch (car entry)) (expected (cdr entry)))
+           (setq entry
+                 (format "%s | %s | case-fold: %s"
+                         (get-char-code-property ch 'general-category)
+                         (funcall check-ccs ch nil) (funcall check-ccs ch t)))
+           (unless (string-equal expected entry)
+             (format "\n%c        expected: %s\nU+%06X  but got: %s"
+                     ch expected ch entry))))
+       '((?A . "Lu | alnum alpha upper | case-fold: alnum alpha upper lower")
+         (?ẞ . "Lu | alnum alpha upper | case-fold: alnum alpha upper lower")
+         (?DZ . "Lu | alnum alpha upper | case-fold: alnum alpha upper lower")
+         (?a . "Ll | alnum alpha lower | case-fold: alnum alpha upper lower")
+         ;; FIXME: Should match upper when case-fold case
+         ;; (?ł . "Ll | alnum alpha lower | case-fold: alnum alpha upper lower")
+         ;; (?ß . "Ll | alnum alpha lower | case-fold: alnum alpha upper lower")
+         ;; (?fi . "Ll | alnum alpha lower | case-fold: alnum alpha upper lower")
+         ;; (?ɕ . "Ll | alnum alpha lower | case-fold: alnum alpha upper lower")
+         ;; (?dz . "Ll | alnum alpha lower | case-fold: alnum alpha upper lower")
+         (?ł . "Ll | alnum alpha lower | case-fold: alnum alpha lower")
+         (?ß . "Ll | alnum alpha lower | case-fold: alnum alpha lower")
+         (?fi . "Ll | alnum alpha lower | case-fold: alnum alpha lower")
+         (?ɕ . "Ll | alnum alpha lower | case-fold: alnum alpha lower")
+         (?dz . "Ll | alnum alpha lower | case-fold: alnum alpha lower")
+         (?Dz . "Lt | alnum alpha | case-fold: alnum alpha upper lower")
+         (?ʰ . "Lm | alnum alpha | case-fold: alnum alpha")
+         (?º . "Lo | alnum alpha | case-fold: alnum alpha")))))))
+
+
 (defmacro regex-tests-generic-line (comment-char test-file whitelist &rest body)
   "Reads a line of the test file TEST-FILE, skipping
 comments (defined by COMMENT-CHAR), and evaluates the tests in
-- 
2.8.0.rc3.226.g39d4020






^ permalink raw reply related	[flat|nested] 89+ messages in thread

* bug#24603: [RFC 18/18] Fix case-fold-search character class matching
  2016-10-04  1:10 ` bug#24603: [RFC 01/18] Add tests for casefiddle.c Michal Nazarewicz
                     ` (15 preceding siblings ...)
  2016-10-04  1:10   ` bug#24603: [RFC 17/18] Optimise character class matching in regexes Michal Nazarewicz
@ 2016-10-04  1:10   ` Michal Nazarewicz
  16 siblings, 0 replies; 89+ messages in thread
From: Michal Nazarewicz @ 2016-10-04  1:10 UTC (permalink / raw)
  To: 24603

The uppar and lower character classes should match any cased characters
when case-fold-search is enabled.  So ‘[[:upper:]]’ sould match ‘a’ but
also ‘ł’, ‘ß’ and ‘fi’.  Fix character class tests to make that happen.

* src/character.h (CHAR_BIT_TITLE): New character bit for title case
characters (such as Dz).
* src/character.c (category_char_bits): Characters in Lt category are
title case; update lookup table.

* src/regex.c (re_wctype_to_bit): When case-folding is enabled return
any-case bits pattern for RECC_LOWER and RECC_UPPER.
(regex_compile): Update re_wctype_to_bit calls (it has new argument).
(execute_charset): Simplify case-folding case since now it’s encoded
in the bits.  corig argument is no longer necessary.
(mutually_exclusive_p, re_match_2_internal): Update execute_charset (it
no longer has corig argument).

* test/src/regex-tests.el (regex-tests--letter-character-classes): Fix
case-fold letter matching.
---
 src/character.c         |  2 +-
 src/character.h         |  5 +++--
 src/regex.c             | 53 ++++++++++++++++++++-----------------------------
 test/src/regex-tests.el | 16 +++++----------
 4 files changed, 30 insertions(+), 46 deletions(-)

diff --git a/src/character.c b/src/character.c
index 63f89d3..cf42f30 100644
--- a/src/character.c
+++ b/src/character.c
@@ -979,7 +979,7 @@ const unsigned char category_char_bits[] = {
   [UNICODE_CATEGORY_UNKNOWN] = 0,
   [UNICODE_CATEGORY_Lu] = CHAR_BIT_ALPHA_ | CHAR_BIT_UPPER,
   [UNICODE_CATEGORY_Ll] = CHAR_BIT_ALPHA_ | CHAR_BIT_LOWER,
-  [UNICODE_CATEGORY_Lt] = CHAR_BIT_ALPHA_,
+  [UNICODE_CATEGORY_Lt] = CHAR_BIT_ALPHA_ | CHAR_BIT_TITLE,
   [UNICODE_CATEGORY_Lm] = CHAR_BIT_ALPHA_,
   [UNICODE_CATEGORY_Lo] = CHAR_BIT_ALPHA_,
   [UNICODE_CATEGORY_Mn] = CHAR_BIT_ALPHA_,
diff --git a/src/character.h b/src/character.h
index 6dc95ad..f2849e5 100644
--- a/src/character.h
+++ b/src/character.h
@@ -665,8 +665,9 @@ extern unicode_category_t char_unicode_category (int);
 #define CHAR_BIT_ALPHA        (1 << 1)
 #define CHAR_BIT_UPPER        (1 << 2)
 #define CHAR_BIT_LOWER        (1 << 3)
-#define CHAR_BIT_GRAPH        (1 << 4)
-#define CHAR_BIT_PRINT        (1 << 5)
+#define CHAR_BIT_TITLE        (1 << 4)
+#define CHAR_BIT_GRAPH        (1 << 5)
+#define CHAR_BIT_PRINT        (1 << 6)
 
 /* Map from Unicode general category to character classes the character is in.
  *
diff --git a/src/regex.c b/src/regex.c
index bfd04a1..aa8c6ef 100644
--- a/src/regex.c
+++ b/src/regex.c
@@ -1794,6 +1794,7 @@ struct range_table_work_area
 #  define BIT_ALPHA	CHAR_BIT_ALPHA
 #  define BIT_UPPER	CHAR_BIT_UPPER
 #  define BIT_LOWER	CHAR_BIT_LOWER
+#  define BIT_TITLE	CHAR_BIT_TITLE
 #  define BIT_GRAPH	CHAR_BIT_GRAPH
 #  define BIT_PRINT	CHAR_BIT_PRINT
 #else
@@ -1801,8 +1802,9 @@ struct range_table_work_area
 #  define BIT_ALPHA	(1 << 1)
 #  define BIT_UPPER	(1 << 2)
 #  define BIT_LOWER	(1 << 3)
-#  define BIT_GRAPH	(1 << 4)
-#  define BIT_PRINT	(1 << 5)
+#  define BIT_TITLE	(1 << 4)
+#  define BIT_GRAPH	(1 << 5)
+#  define BIT_PRINT	(1 << 6)
 #endif
 #define BIT_WORD	(BIT_PRINT << 1)
 #define BIT_PUNCT	(BIT_PRINT << 2)
@@ -2067,7 +2069,7 @@ re_iswctype (int ch, re_wctype_t cc)
 /* Return a bit-pattern to use in the range-table bits to match multibyte
    chars of class CC.  */
 static int
-re_wctype_to_bit (re_wctype_t cc)
+re_wctype_to_bit (re_wctype_t cc, bool case_fold)
 {
   switch (cc)
     {
@@ -2076,8 +2078,10 @@ re_wctype_to_bit (re_wctype_t cc)
     case RECC_ALPHA: return BIT_ALPHA;
     case RECC_ALNUM: return BIT_ALNUM;
     case RECC_WORD: return BIT_WORD;
-    case RECC_LOWER: return BIT_LOWER;
-    case RECC_UPPER: return BIT_UPPER;
+    case RECC_LOWER:
+      return case_fold ? BIT_LOWER | BIT_UPPER | BIT_TITLE : BIT_LOWER;
+    case RECC_UPPER:
+      return case_fold ? BIT_LOWER | BIT_UPPER | BIT_TITLE : BIT_UPPER;
     case RECC_PUNCT: return BIT_PUNCT;
     case RECC_SPACE: return BIT_SPACE;
     case RECC_GRAPH: return BIT_GRAPH;
@@ -2886,7 +2890,8 @@ regex_compile (const_re_char *pattern, size_t size,
 			    SET_LIST_BIT (c1);
 			}
 		    SET_RANGE_TABLE_WORK_AREA_BIT
-		      (range_table_work, re_wctype_to_bit (cc));
+		      (range_table_work,
+		       re_wctype_to_bit (cc, RE_TRANSLATE_P (translate)));
 #endif	/* emacs */
 		    /* In most cases the matching rule for char classes only
 		       uses the syntax table for multibyte chars, so that the
@@ -4633,11 +4638,10 @@ skip_noops (const_re_char *p, const_re_char *pend)
 
 /* Test if C matches charset op.  *PP points to the charset or charset_not
    opcode.  When the function finishes, *PP will be advanced past that opcode.
-   C is character to test (possibly after translations) and CORIG is original
-   character (i.e. without any translations).  UNIBYTE denotes whether c is
-   unibyte or multibyte character. */
+   C is character to test.  UNIBYTE denotes whether c is unibyte or multibyte
+   character. */
 static bool
-execute_charset (const_re_char **pp, unsigned c, unsigned corig, bool unibyte)
+execute_charset (const_re_char **pp, unsigned c, bool unibyte)
 {
   re_char *p = *pp, *rtp = NULL;
   bool not = (re_opcode_t) *p == charset_not;
@@ -4675,24 +4679,9 @@ execute_charset (const_re_char **pp, unsigned c, unsigned corig, bool unibyte)
 	 IS_REAL_ASCII (c), we can ignore that. */
 
       bits = class_bits & (BIT_ALNUM | BIT_ALPHA | BIT_UPPER | BIT_LOWER |
-			   BIT_GRAPH | BIT_PRINT);
-      if (bits)
-	{
-	  int char_bits = category_char_bits[char_unicode_category (c)];
-	  if (bits & char_bits)
-	    return !not;
-
-	  /* Handle case folding. */
-	  if (corig != c)
-	    {
-	      if ((bits & BIT_UPPER) && (char_bits & BIT_LOWER) &&
-		  c == downcase (corig))
-		return !not;
-	      if ((bits & BIT_LOWER) && (char_bits & BIT_UPPER) &&
-		  c == upcase (corig))
-		return !not;
-	    }
-	}
+			   BIT_TITLE | BIT_GRAPH | BIT_PRINT);
+      if (bits && (category_char_bits[char_unicode_category (c)] & bits))
+	return !not;
 
       if (class_bits & (BIT_SPACE | BIT_WORD | BIT_PUNCT))
 	{
@@ -4772,7 +4761,7 @@ mutually_exclusive_p (struct re_pattern_buffer *bufp, const_re_char *p1,
 	else if ((re_opcode_t) *p1 == charset
 		 || (re_opcode_t) *p1 == charset_not)
 	  {
-	    if (!execute_charset (&p1, c, c, !multibyte || IS_REAL_ASCII (c)))
+	    if (!execute_charset (&p1, c, !multibyte || IS_REAL_ASCII (c)))
 	      {
 		DEBUG_PRINT ("	 No match => fast loop.\n");
 		return 1;
@@ -5482,7 +5471,7 @@ re_match_2_internal (struct re_pattern_buffer *bufp, const_re_char *string1,
 	case charset:
 	case charset_not:
 	  {
-	    register unsigned int c, corig;
+	    register unsigned int c;
 	    int len;
 
 	    /* Whether matching against a unibyte character.  */
@@ -5492,7 +5481,7 @@ re_match_2_internal (struct re_pattern_buffer *bufp, const_re_char *string1,
 			 (re_opcode_t) *(p - 1) == charset_not ? "_not" : "");
 
 	    PREFETCH ();
-	    corig = c = RE_STRING_CHAR_AND_LENGTH (d, len, target_multibyte);
+	    c = RE_STRING_CHAR_AND_LENGTH (d, len, target_multibyte);
 	    if (target_multibyte)
 	      {
 		int c1;
@@ -5524,7 +5513,7 @@ re_match_2_internal (struct re_pattern_buffer *bufp, const_re_char *string1,
 	      }
 
 	    p -= 1;
-	    if (!execute_charset (&p, c, corig, unibyte_char))
+	    if (!execute_charset (&p, c, unibyte_char))
 	      goto fail;
 
 	    d += len;
diff --git a/test/src/regex-tests.el b/test/src/regex-tests.el
index 7617823..4da9ab3 100644
--- a/test/src/regex-tests.el
+++ b/test/src/regex-tests.el
@@ -127,17 +127,11 @@ regex--test-cc
          (?ẞ . "Lu | alnum alpha upper | case-fold: alnum alpha upper lower")
          (?DZ . "Lu | alnum alpha upper | case-fold: alnum alpha upper lower")
          (?a . "Ll | alnum alpha lower | case-fold: alnum alpha upper lower")
-         ;; FIXME: Should match upper when case-fold case
-         ;; (?ł . "Ll | alnum alpha lower | case-fold: alnum alpha upper lower")
-         ;; (?ß . "Ll | alnum alpha lower | case-fold: alnum alpha upper lower")
-         ;; (?fi . "Ll | alnum alpha lower | case-fold: alnum alpha upper lower")
-         ;; (?ɕ . "Ll | alnum alpha lower | case-fold: alnum alpha upper lower")
-         ;; (?dz . "Ll | alnum alpha lower | case-fold: alnum alpha upper lower")
-         (?ł . "Ll | alnum alpha lower | case-fold: alnum alpha lower")
-         (?ß . "Ll | alnum alpha lower | case-fold: alnum alpha lower")
-         (?fi . "Ll | alnum alpha lower | case-fold: alnum alpha lower")
-         (?ɕ . "Ll | alnum alpha lower | case-fold: alnum alpha lower")
-         (?dz . "Ll | alnum alpha lower | case-fold: alnum alpha lower")
+         (?ł . "Ll | alnum alpha lower | case-fold: alnum alpha upper lower")
+         (?ß . "Ll | alnum alpha lower | case-fold: alnum alpha upper lower")
+         (?fi . "Ll | alnum alpha lower | case-fold: alnum alpha upper lower")
+         (?ɕ . "Ll | alnum alpha lower | case-fold: alnum alpha upper lower")
+         (?dz . "Ll | alnum alpha lower | case-fold: alnum alpha upper lower")
          (?Dz . "Lt | alnum alpha | case-fold: alnum alpha upper lower")
          (?ʰ . "Lm | alnum alpha | case-fold: alnum alpha")
          (?º . "Lo | alnum alpha | case-fold: alnum alpha")))))))
-- 
2.8.0.rc3.226.g39d4020






^ permalink raw reply related	[flat|nested] 89+ messages in thread

* bug#24603: [RFC 15/18] Base lower- and upper-case tests on Unicode properties
  2016-10-04  1:10   ` bug#24603: [RFC 15/18] Base lower- and upper-case tests on Unicode properties Michal Nazarewicz
@ 2016-10-04  6:54     ` Eli Zaretskii
  0 siblings, 0 replies; 89+ messages in thread
From: Eli Zaretskii @ 2016-10-04  6:54 UTC (permalink / raw)
  To: Michal Nazarewicz; +Cc: 24603

> From: Michal Nazarewicz <mina86@mina86.com>
> Date: Tue,  4 Oct 2016 03:10:38 +0200
> 
> +** 'upper' and 'lower' character classes are unaffected by case table
> +since they are now based purely on Unicode properties.

This is actually a backward-incompatible change, isn't it?  If so, it
should be in the corresponding section of NEWS.  More importantly,
there should be a way to get back the old behavior, i.e. to force
'upper' and 'lower' use the current case tables.

Better yet, can we use the Unicode properties only where case tables
are insufficient, like in the case of ligatures being broken up into
individual characters by case conversions?  That'd be
backward-compatible, so won't risk breaking existing code.

I'm also okay with a defcustom, by default off, to prefer the Unicode
data, as you did, so that we could in the future make this the default
behavior.  But doing this right now without any transition period and
no way of going back is too radical, I think.

Please also note that Unicode tables are global, very large, and in
many cases tricky to change from Lisp (as compared to simple
char-tables).  So customizing the case conversions that are based
solely on the Unicode tables is much harder and/or has global
implications, unlike the case tables.  With that in mind, I think we
should make the transition smoother, and we should probably add
convenience APIs for customizing the case conversions the Unicode way,
before we switch to that as the default.

Thanks.





^ permalink raw reply	[flat|nested] 89+ messages in thread

* bug#24603: [RFC 10/18] Implement Turkic dotless and dotted i handling when casing strings
  2016-10-04  1:10   ` bug#24603: [RFC 10/18] Implement Turkic dotless and dotted i handling when casing strings Michal Nazarewicz
@ 2016-10-04  7:12     ` Eli Zaretskii
  0 siblings, 0 replies; 89+ messages in thread
From: Eli Zaretskii @ 2016-10-04  7:12 UTC (permalink / raw)
  To: Michal Nazarewicz; +Cc: 24603

> From: Michal Nazarewicz <mina86@mina86.com>
> Date: Tue,  4 Oct 2016 03:10:33 +0200
> 
> +
> +  /* FIXME: Is current-iso639-language the best source of that information? */
> +  lang = Vcurrent_iso639_language;
> +  tr = intern_c_string ("tr");
> +  az = intern_c_string ("az");
> +  if (SYMBOLP (lang))
> +    {
> +      l = lang;
> +      goto check_language;
> +    }
> +  while (CONSP (lang))
> +    {
> +      l = XCAR (lang);
> +      lang = XCDR (lang);
> +    check_language:
> +      if (EQ (l, tr) || EQ (l, az))
> +       {
> +         ctx->treat_turkic_i = true;
> +         break;
> +       }
> +    }

I'm not sure I like this mechanism.  AFAIU, current-iso639-language is
a read-only variable that conveys the outside locale's language.  So
the above would limit this feature to users in the corresponding
locales, which is against Emacs's design as a multilingual system.  We
should allow Lisp applications and users in _any_ locale take
advantage of this feature.

So I suggest a separate variable which, when non-nil, will cause these
conversions to take effect.  Lisp applications could then bind that
variable when they want these special conversions.  (With the eye
towards future developments, as hinted by the rest of Unicode's
SpecialCasing.txt file, perhaps don't make the variable's name mention
a specific language, but instead make its value a language symbol,
such as 'tr or 'az.)  We could make it a defcustom, if we think users
will want to turn this on as their default.

> +/* Normalise CFG->flag and return CASE_UP, CASE_DOWN, CASE_CAPITALIZE or
      ^^^^^^^^^
A nit: we use US English spelling, so "Normalize".

> +static enum case_action
> +normalise_flag (struct casing_context *ctx)
   ^^^^^^^^^
Likewise.

> +{
> +  /* Normalise flag so its one of CASE_UP, CASE_DOWN or CASE_CAPITALIZE. */

This comment repeats what was already said above.

>  /* In Greek, lower case sigma has two forms: one when used in the middle and one
> @@ -152,6 +192,13 @@ case_character_impl (struct casing_str_buf *buf,
>  #define CAPITAL_SIGMA     0x03A3
>  #define SMALL_SIGMA       0x03C3
>  #define SMALL_FINAL_SIGMA 0x03C2
> +
> +/* Azeri and Turkish have dotless and dotted i.  An upper case of i is
> +   İ while lower case of I is ı. */
> +
> +#define CAPITAL_DOTTED_I    0x130
> +#define SMALL_DOTLESS_I     0x131
> +#define COMBINING_DOT_ABOVE 0x307

How about deriving these rules from SpecialCasing.txt and storing them
in some char-table, instead of hard-coding them in C?  That would
allow us to update these features more easily with each release of the
Unicode Standard.

> +  if (flag != CASE_NO_ACTION && __builtin_expect(ctx->treat_turkic_i, false))

I don't think we can use __builtin_expect here, it's AFAIK
non-portable to any platform without glibc.

> +      if (len_bytes > 0)
> +	src += len_bytes;
> +      size -= len_bytes > 0 ? 2 : 1;

Another nit: please use whitespace consistently in the indentation,
either all TABs and spaces, or just spaces.  (I think our default is
the former for now.)

Thanks.





^ permalink raw reply	[flat|nested] 89+ messages in thread

* bug#24603: [RFC 07/18] Split up casify_region function.
  2016-10-04  1:10   ` bug#24603: [RFC 07/18] Split up casify_region function Michal Nazarewicz
@ 2016-10-04  7:17     ` Eli Zaretskii
  2016-10-18  2:27       ` Michal Nazarewicz
  0 siblings, 1 reply; 89+ messages in thread
From: Eli Zaretskii @ 2016-10-04  7:17 UTC (permalink / raw)
  To: Michal Nazarewicz; +Cc: 24603

> From: Michal Nazarewicz <mina86@mina86.com>
> Date: Tue,  4 Oct 2016 03:10:30 +0200
> 
> No functional changes at this time but splitting casify_region into
> a function dealing with multibyte and another dealing with unibyte
> buffers will make future code changes slightly easier.
> 
> * src/casefiddle.c (casify_region): Move most of the code into two
> new functions:
> (do_casify_multibyte_region, do_casify_unibyte_region): new functions.

Please make sure the multibyte version works with embedded eight-bit
characters, in both representations.  Some of the code you removed
could handle those cases.

Thanks.





^ permalink raw reply	[flat|nested] 89+ messages in thread

* bug#24603: [RFC 09/18] Implement special sigma casing rule
  2016-10-04  1:10   ` bug#24603: [RFC 09/18] Implement special sigma casing rule Michal Nazarewicz
@ 2016-10-04  7:22     ` Eli Zaretskii
  0 siblings, 0 replies; 89+ messages in thread
From: Eli Zaretskii @ 2016-10-04  7:22 UTC (permalink / raw)
  To: Michal Nazarewicz; +Cc: 24603

> From: Michal Nazarewicz <mina86@mina86.com>
> Date: Tue,  4 Oct 2016 03:10:32 +0200
> 
> In Greek, a sigma character has two lower case forms which depend on
> their position in the word.  Implement logic determining it.

Once again, can we store the needed conversions from
SpecialCasing.txt, instead of hard-coding them in C?

> +*** Greek small sigma is correctly handled when at the end of the word.
> +Strings such as ΌΣΟΣ are now correctly converted to Όσος when
> +capitalised to follow rules of Greek spelling.
   ^^^^^^^^^^^
US English spelling, please.

Also, I suggest to show the previous "capitalization" results as well,
and explain the difference, as the difference is subtle and may not be
clear to the reader.

Thanks.





^ permalink raw reply	[flat|nested] 89+ messages in thread

* bug#24603: [RFC 02/18] Generate upcase and downcase tables from Unicode data
  2016-10-04  1:10   ` bug#24603: [RFC 02/18] Generate upcase and downcase tables from Unicode data Michal Nazarewicz
@ 2016-10-04  7:27     ` Eli Zaretskii
  2016-10-04 14:54       ` Michal Nazarewicz
  0 siblings, 1 reply; 89+ messages in thread
From: Eli Zaretskii @ 2016-10-04  7:27 UTC (permalink / raw)
  To: Michal Nazarewicz; +Cc: 24603

> From: Michal Nazarewicz <mina86@mina86.com>
> Date: Tue,  4 Oct 2016 03:10:25 +0200
> 
> +  ;; Set all Letter, uppercase; Letter, lowercase and Letter, titlecase syntax
> +  ;; to word.  FIXME: Should this also be done for Letter, modifier and Letter,
> +  ;; other?  What about other alphabetic characters?
> +  (let ((syn-tab (standard-syntax-table)))
> +    (map-char-table
> +     (lambda (ch cat)
> +       (when (memq cat '(Lu Ll Lt))
> +         (modify-syntax-entry ch "w   " syn-tab)))
> +     (unicode-property-table-internal 'general-category)))

The answer to these questions is "as required by backward
compatibility", i.e. compare with the manual setup we had until now.

If that criterion doesn't provide the full answer, I would go by
Unicode guidance, i.e. support all the case conversions specified in
the Unicode character database (UCD).

Thanks.





^ permalink raw reply	[flat|nested] 89+ messages in thread

* bug#24603: [RFC 08/18] Support casing characters which map into multiple code points
  2016-10-04  1:10   ` bug#24603: [RFC 08/18] Support casing characters which map into multiple code points Michal Nazarewicz
@ 2016-10-04  7:38     ` Eli Zaretskii
  2016-10-06 21:40       ` Michal Nazarewicz
  0 siblings, 1 reply; 89+ messages in thread
From: Eli Zaretskii @ 2016-10-04  7:38 UTC (permalink / raw)
  To: Michal Nazarewicz; +Cc: 24603

> From: Michal Nazarewicz <mina86@mina86.com>
> Date: Tue,  4 Oct 2016 03:10:31 +0200
> 
> * src/make-special-casing.py: New script to generate special-casing.h
> file from the SpecialCasing.txt data file.

Please do this without Python, either in Emacs Lisp and/or the tools
already used in admin/unidata, including awk.  Python is still not
available as widely as the other tools.

> +special-casing.h: make-special-casing.py ../admin/unidata/SpecialCasing.txt
> +	$(AM_V_GEN)
> +	python $^ $@

Don't use a literal name of a program, so users could specify their
name and/or absolute file name at build time.  See what we do with
awk, for example.

> +#include "special-casing.h"

Why not a shorter 'casing.h'?

Once again, this stores the casing rules in C, whereas I'd prefer to
have them in tables accessible from Lisp.

> @@ -194,7 +276,9 @@ casify_object (enum case_action flag, Lisp_Object obj)
>  DEFUN ("upcase", Fupcase, Supcase, 1, 1, 0,
>         doc: /* Convert argument to upper case and return that.
>  The argument may be a character or string.  The result has the same type.
> -The argument object is not altered--the value is a copy.
> +The argument object is not altered--the value is a copy.  If argument
> +is a character, characters which map to multiple code points when
> +cased, e.g. fi, are returned unchanged.
>  See also `capitalize', `downcase' and `upcase-initials'.  */)

I think this doc string should say what to do if the application wants
to convert fi into "FI".

Thanks.





^ permalink raw reply	[flat|nested] 89+ messages in thread

* bug#24603: [RFC 16/18] Refactor character class checking; optimise ASCII case
  2016-10-04  1:10   ` bug#24603: [RFC 16/18] Refactor character class checking; optimise ASCII case Michal Nazarewicz
@ 2016-10-04  7:48     ` Eli Zaretskii
  2016-10-17 13:22       ` Michal Nazarewicz
  2016-11-06 19:26       ` Michal Nazarewicz
  0 siblings, 2 replies; 89+ messages in thread
From: Eli Zaretskii @ 2016-10-04  7:48 UTC (permalink / raw)
  To: Michal Nazarewicz; +Cc: 24603

> From: Michal Nazarewicz <mina86@mina86.com>
> Date: Tue,  4 Oct 2016 03:10:39 +0200
> 
> +const unsigned char category_char_bits[] = {
> +  [UNICODE_CATEGORY_UNKNOWN] = 0,
> +  [UNICODE_CATEGORY_Lu] = CHAR_BIT_ALPHA_ | CHAR_BIT_UPPER,
> +  [UNICODE_CATEGORY_Ll] = CHAR_BIT_ALPHA_ | CHAR_BIT_LOWER,

Is this syntax portable enough for us to use it?

> +/* Limited set of character categories which syntax-independent.  Testing of
                                          ^^^^^^^^^^^^^^^^^^^^^^^^
"which are syntax-independent"

> + * those characters do not require any run-time data, e.g. do not depend on
                       ^^^^^^^^^^^^^^                         ^^^^^^^^^^^^^
"does not require" and "does not depend"

Thanks.  I think this change will require a benchmark to make sure we
don't lose too much in terms of performance.





^ permalink raw reply	[flat|nested] 89+ messages in thread

* bug#24603: [RFC 02/18] Generate upcase and downcase tables from Unicode data
  2016-10-04  7:27     ` Eli Zaretskii
@ 2016-10-04 14:54       ` Michal Nazarewicz
  2016-10-04 15:06         ` Eli Zaretskii
  0 siblings, 1 reply; 89+ messages in thread
From: Michal Nazarewicz @ 2016-10-04 14:54 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: 24603

On Tue, Oct 04 2016, Eli Zaretskii wrote:
>> From: Michal Nazarewicz <mina86@mina86.com>
>> Date: Tue,  4 Oct 2016 03:10:25 +0200
>>
>> +  ;; Set all Letter, uppercase; Letter, lowercase and Letter, titlecase syntax
>> +  ;; to word.  FIXME: Should this also be done for Letter, modifier and Letter,
>> +  ;; other?  What about other alphabetic characters?
>> +  (let ((syn-tab (standard-syntax-table)))
>> +    (map-char-table
>> +     (lambda (ch cat)
>> +       (when (memq cat '(Lu Ll Lt))
>> +         (modify-syntax-entry ch "w   " syn-tab)))
>> +     (unicode-property-table-internal 'general-category)))
>
> The answer to these questions is "as required by backward
> compatibility", i.e. compare with the manual setup we had until now.

With that in mind, I’ve applied the following fix to this patch:

---- >8 ----------------------------------------------------------------
@@ -666,14 +666,21 @@ ?L
   (modify-category-entry '(#x20d0 . #x20ff) ?^)

   ;; Set all Letter, uppercase; Letter, lowercase and Letter, titlecase syntax
-  ;; to word.  FIXME: Should this also be done for Letter, modifier and Letter,
-  ;; other?  What about other alphabetic characters?
+  ;; to word.
   (let ((syn-tab (standard-syntax-table)))
     (map-char-table
      (lambda (ch cat)
        (when (memq cat '(Lu Ll Lt))
          (modify-syntax-entry ch "w   " syn-tab)))
-     (unicode-property-table-internal 'general-category)))
+     (unicode-property-table-internal 'general-category))
+
+    ;; Ⅰ through Ⅻ had word syntax in the past so set it here as well.
+    ;; General category of those characers is Number, Letter.
+    (modify-syntax-entry '(#x2160 . #x216b) "w   " syn-tab)
+
+    ;; ⓐ thourgh ⓩ are symbols, other according to Unicode but Emacs set
+    ;; their syntax to word in the past so keep backwards compatibility.
+    (modify-syntax-entry '(#x24D0 . #x24E9) "w   " syn-tab))

   ;; Set downcase and upcase from Unicode properties
---- >8 ----------------------------------------------------------------

With that, if I run

(defun mn-dump-tables ()
  (interactive)
  (switch-to-buffer (get-buffer-create "*Syntax and case tables dump*"))
  (widen)
  (delete-region (point-min) (point-max))
  (insert (emacs-version))
  (insert "\n\n")
  (let ((case-tab (standard-case-table)))
    (dolist (el `(("Syntax" . ,(standard-syntax-table))
                  ("Lower" . ,(case-table-get-table case-tab 'down))
                  ("Upper" . ,(case-table-get-table case-tab 'up))))
      (map-char-table
       (lambda (k v)
         (insert (format "[%s] " (car el)))
         (if (consp k)
             (insert (format "%06x..%06x -> " (car k) (cdr k)))
           (insert (format "        %06x -> " k)))
         (if (numberp v)
             (insert (format "%06x\n" v))
           (insert (format "%s\n" v))))
       (cdr el)))))

I get the following (annotated) differences:

---- >8 ----------------------------------------------------------------
--- orig-tables.txt	2016-10-04 15:30:12.501069384 +0200
+++ modified-tables.txt	2016-10-04 15:31:55.435676953 +0200
@@ -1,4 +1,4 @@
-GNU Emacs 26.0.50.2 (x86_64-unknown-linux-gnu)
+GNU Emacs 26.0.50.3 (x86_64-unknown-linux-gnu)
  of 2016-10-04

 [Syntax] 000000..000008 -> (1)
@@ -44,7 +44,9 @@
 [Syntax]         0000a7 -> (1)
 [Syntax] 0000a8..0000aa -> (3)
 [Syntax]         0000ab -> (1)
-[Syntax] 0000ac..0000b6 -> (3)
+[Syntax] 0000ac..0000b4 -> (3)
+[Syntax]         0000b5 -> (2)  -- micro sign (µ) is now word
+[Syntax]         0000b6 -> (3)
 [Syntax]         0000b7 -> (2)
 [Syntax] 0000b8..0000ba -> (3)
 [Syntax]         0000bb -> (1)
@@ -54,9 +56,7 @@
 [Syntax]         0000d7 -> (3)
 [Syntax] 0000d8..0000f6 -> (2)
 [Syntax]         0000f7 -> (3)
-[Syntax] 0000f8..000148 -> (2)
-[Syntax]         000149 -> (3)  -- ʼn is now word
-[Syntax] 00014a..0002c6 -> (2)
+[Syntax] 0000f8..0002c6 -> (2)
 [Syntax]         0002c7 -> (3)
 [Syntax]         0002c8 -> (2)
 [Syntax]         0002c9 -> (3)
@@ -136,17 +136,11 @@
 [Syntax]         002103 -> (3)
 [Syntax] 002104..002108 -> (2)
 [Syntax]         002109 -> (3)
-[Syntax] 00210a..002112 -> (2)
-[Syntax]         002113 -> (3)  -- ℓ is now word
-[Syntax] 002114..002115 -> (2)
+[Syntax] 00210a..002115 -> (2)
 [Syntax]         002116 -> (1)
 [Syntax] 002117..002120 -> (2)
 [Syntax] 002121..002122 -> (3)
-[Syntax] 002123..002125 -> (2)
-[Syntax]         002126 -> (3)  -- Ohm sign (Ω) is now word
-[Syntax] 002127..00212a -> (2)
-[Syntax]         00212b -> (3)  -- Angstrom sign (Å) is now word
-[Syntax] 00212c..002152 -> (2)
+[Syntax] 002123..002152 -> (2)
 [Syntax] 002153..002154 -> (3)
 [Syntax] 002155..00215a -> (2)
 [Syntax] 00215b..00215e -> (3)
… continued below …
---- >8 ----------------------------------------------------------------

Changes to micro, Ohm and Angstrom signs may be controversial (I
could never understand why those characters are in Unicode) but the
change brings the syntax table in par with Unicode and how other
similar characters are handled (namely, I’m thinking of U+212A:
Kelvin sign).

Changes to ʼn and ℓ seem uncontroversial to me.

Attached below are changes to down and up case tables.  I haven’t
looked at every one of the changes but it appears legitimate to me:
- some additions are for ‘foo becomes foo’ mappings,
- mane are for legitimate missing mappings and
- there are also some fixes to incorrect mapping such as
  𐐀 (Deseret capital letter long i) being mapped to
  𐐜 (Deseret capital letter thee) instead of
  𐐨 (Deseret small letter long i).

---- >8 ----------------------------------------------------------------
… continuation from above …
@@ -677,6 +671,7 @@
 [Lower]         00019a -> 00019a
 [Lower]         00019c -> 00026f
 [Lower]         00019d -> 000272
+[Lower]         00019e -> 00019e
 [Lower]         00019f -> 000275
 [Lower] 0001a0..0001a1 -> 0001a1
 [Lower] 0001a2..0001a3 -> 0001a3
@@ -740,6 +735,7 @@
 [Lower] 00021a..00021b -> 00021b
 [Lower] 00021c..00021d -> 00021d
 [Lower] 00021e..00021f -> 00021f
+[Lower]         000220 -> 00019e
 [Lower] 000222..000223 -> 000223
 [Lower] 000224..000225 -> 000225
 [Lower] 000226..000227 -> 000227
@@ -777,6 +773,7 @@
 [Lower]         000260 -> 000260
 [Lower]         000261 -> 000261
 [Lower]         000263 -> 000263
+[Lower]         000265 -> 000265
 [Lower]         000266 -> 000266
 [Lower]         000268 -> 000268
 [Lower]         000269 -> 000269
@@ -799,6 +796,14 @@
 [Lower]         000292 -> 000292
 [Lower]         00029d -> 00029d
 [Lower]         00029e -> 00029e
+[Lower]         000345 -> 000345
+[Lower] 000370..000371 -> 000371
+[Lower] 000372..000373 -> 000373
+[Lower] 000376..000377 -> 000377
+[Lower]         00037b -> 00037b
+[Lower]         00037c -> 00037c
+[Lower]         00037d -> 00037d
+[Lower]         00037f -> 0003f3
 [Lower]         000386 -> 0003ac
 [Lower]         000388 -> 0003ad
 [Lower]         000389 -> 0003ae
@@ -853,6 +858,7 @@
 [Lower]         0003bf -> 0003bf
 [Lower]         0003c0 -> 0003c0
 [Lower]         0003c1 -> 0003c1
+[Lower]         0003c2 -> 0003c2
 [Lower]         0003c3 -> 0003c3
 [Lower]         0003c4 -> 0003c4
 [Lower]         0003c5 -> 0003c5
@@ -865,6 +871,13 @@
 [Lower]         0003cc -> 0003cc
 [Lower]         0003cd -> 0003cd
 [Lower]         0003ce -> 0003ce
+[Lower]         0003cf -> 0003d7
+[Lower]         0003d0 -> 0003d0
+[Lower]         0003d1 -> 0003d1
+[Lower]         0003d5 -> 0003d5
+[Lower]         0003d6 -> 0003d6
+[Lower]         0003d7 -> 0003d7
+[Lower] 0003d8..0003d9 -> 0003d9
 [Lower] 0003da..0003db -> 0003db
 [Lower] 0003dc..0003dd -> 0003dd
 [Lower] 0003de..0003df -> 0003df
@@ -876,6 +889,18 @@
 [Lower] 0003ea..0003eb -> 0003eb
 [Lower] 0003ec..0003ed -> 0003ed
 [Lower] 0003ee..0003ef -> 0003ef
+[Lower]         0003f0 -> 0003f0
+[Lower]         0003f1 -> 0003f1
+[Lower]         0003f2 -> 0003f2
+[Lower]         0003f3 -> 0003f3
+[Lower]         0003f4 -> 0003b8
+[Lower]         0003f5 -> 0003f5
+[Lower] 0003f7..0003f8 -> 0003f8
+[Lower]         0003f9 -> 0003f2
+[Lower] 0003fa..0003fb -> 0003fb
+[Lower]         0003fd -> 00037b
+[Lower]         0003fe -> 00037c
+[Lower]         0003ff -> 00037d
 [Lower]         000400 -> 000450
 [Lower]         000401 -> 000451
 [Lower]         000402 -> 000452
@@ -989,6 +1014,7 @@
 [Lower] 00047c..00047d -> 00047d
 [Lower] 00047e..00047f -> 00047f
 [Lower] 000480..000481 -> 000481
+[Lower] 00048a..00048b -> 00048b
 [Lower] 00048c..00048d -> 00048d
 [Lower] 00048e..00048f -> 00048f
 [Lower] 000490..000491 -> 000491
@@ -1015,10 +1041,15 @@
 [Lower] 0004ba..0004bb -> 0004bb
 [Lower] 0004bc..0004bd -> 0004bd
 [Lower] 0004be..0004bf -> 0004bf
+[Lower]         0004c0 -> 0004cf
 [Lower] 0004c1..0004c2 -> 0004c2
 [Lower] 0004c3..0004c4 -> 0004c4
+[Lower] 0004c5..0004c6 -> 0004c6
 [Lower] 0004c7..0004c8 -> 0004c8
+[Lower] 0004c9..0004ca -> 0004ca
 [Lower] 0004cb..0004cc -> 0004cc
+[Lower] 0004cd..0004ce -> 0004ce
+[Lower]         0004cf -> 0004cf
 [Lower] 0004d0..0004d1 -> 0004d1
 [Lower] 0004d2..0004d3 -> 0004d3
 [Lower] 0004d4..0004d5 -> 0004d5
@@ -1043,6 +1074,30 @@
 [Lower] 0004fa..0004fb -> 0004fb
 [Lower] 0004fc..0004fd -> 0004fd
 [Lower] 0004fe..0004ff -> 0004ff
+[Lower] 000500..000501 -> 000501
+[Lower] 000502..000503 -> 000503
+[Lower] 000504..000505 -> 000505
+[Lower] 000506..000507 -> 000507
+[Lower] 000508..000509 -> 000509
+[Lower] 00050a..00050b -> 00050b
+[Lower] 00050c..00050d -> 00050d
+[Lower] 00050e..00050f -> 00050f
+[Lower] 000510..000511 -> 000511
+[Lower] 000512..000513 -> 000513
+[Lower] 000514..000515 -> 000515
+[Lower] 000516..000517 -> 000517
+[Lower] 000518..000519 -> 000519
+[Lower] 00051a..00051b -> 00051b
+[Lower] 00051c..00051d -> 00051d
+[Lower] 00051e..00051f -> 00051f
+[Lower] 000520..000521 -> 000521
+[Lower] 000522..000523 -> 000523
+[Lower] 000524..000525 -> 000525
+[Lower] 000526..000527 -> 000527
+[Lower] 000528..000529 -> 000529
+[Lower] 00052a..00052b -> 00052b
+[Lower] 00052c..00052d -> 00052d
+[Lower] 00052e..00052f -> 00052f
 [Lower]         000531 -> 000561
 [Lower]         000532 -> 000562
 [Lower]         000533 -> 000563
@@ -1157,14 +1212,109 @@
 [Lower]         0010c3 -> 002d23
 [Lower]         0010c4 -> 002d24
 [Lower]         0010c5 -> 002d25
-[Lower]         0010c6 -> 002d26
 [Lower]         0010c7 -> 002d27
-[Lower]         0010c8 -> 002d28
-[Lower]         0010c9 -> 002d29
-[Lower]         0010ca -> 002d2a
-[Lower]         0010cb -> 002d2b
-[Lower]         0010cc -> 002d2c
 [Lower]         0010cd -> 002d2d
+[Lower]         0013a0 -> 00ab70
+[Lower]         0013a1 -> 00ab71
+[Lower]         0013a2 -> 00ab72
+[Lower]         0013a3 -> 00ab73
+[Lower]         0013a4 -> 00ab74
+[Lower]         0013a5 -> 00ab75
+[Lower]         0013a6 -> 00ab76
+[Lower]         0013a7 -> 00ab77
+[Lower]         0013a8 -> 00ab78
+[Lower]         0013a9 -> 00ab79
+[Lower]         0013aa -> 00ab7a
+[Lower]         0013ab -> 00ab7b
+[Lower]         0013ac -> 00ab7c
+[Lower]         0013ad -> 00ab7d
+[Lower]         0013ae -> 00ab7e
+[Lower]         0013af -> 00ab7f
+[Lower]         0013b0 -> 00ab80
+[Lower]         0013b1 -> 00ab81
+[Lower]         0013b2 -> 00ab82
+[Lower]         0013b3 -> 00ab83
+[Lower]         0013b4 -> 00ab84
+[Lower]         0013b5 -> 00ab85
+[Lower]         0013b6 -> 00ab86
+[Lower]         0013b7 -> 00ab87
+[Lower]         0013b8 -> 00ab88
+[Lower]         0013b9 -> 00ab89
+[Lower]         0013ba -> 00ab8a
+[Lower]         0013bb -> 00ab8b
+[Lower]         0013bc -> 00ab8c
+[Lower]         0013bd -> 00ab8d
+[Lower]         0013be -> 00ab8e
+[Lower]         0013bf -> 00ab8f
+[Lower]         0013c0 -> 00ab90
+[Lower]         0013c1 -> 00ab91
+[Lower]         0013c2 -> 00ab92
+[Lower]         0013c3 -> 00ab93
+[Lower]         0013c4 -> 00ab94
+[Lower]         0013c5 -> 00ab95
+[Lower]         0013c6 -> 00ab96
+[Lower]         0013c7 -> 00ab97
+[Lower]         0013c8 -> 00ab98
+[Lower]         0013c9 -> 00ab99
+[Lower]         0013ca -> 00ab9a
+[Lower]         0013cb -> 00ab9b
+[Lower]         0013cc -> 00ab9c
+[Lower]         0013cd -> 00ab9d
+[Lower]         0013ce -> 00ab9e
+[Lower]         0013cf -> 00ab9f
+[Lower]         0013d0 -> 00aba0
+[Lower]         0013d1 -> 00aba1
+[Lower]         0013d2 -> 00aba2
+[Lower]         0013d3 -> 00aba3
+[Lower]         0013d4 -> 00aba4
+[Lower]         0013d5 -> 00aba5
+[Lower]         0013d6 -> 00aba6
+[Lower]         0013d7 -> 00aba7
+[Lower]         0013d8 -> 00aba8
+[Lower]         0013d9 -> 00aba9
+[Lower]         0013da -> 00abaa
+[Lower]         0013db -> 00abab
+[Lower]         0013dc -> 00abac
+[Lower]         0013dd -> 00abad
+[Lower]         0013de -> 00abae
+[Lower]         0013df -> 00abaf
+[Lower]         0013e0 -> 00abb0
+[Lower]         0013e1 -> 00abb1
+[Lower]         0013e2 -> 00abb2
+[Lower]         0013e3 -> 00abb3
+[Lower]         0013e4 -> 00abb4
+[Lower]         0013e5 -> 00abb5
+[Lower]         0013e6 -> 00abb6
+[Lower]         0013e7 -> 00abb7
+[Lower]         0013e8 -> 00abb8
+[Lower]         0013e9 -> 00abb9
+[Lower]         0013ea -> 00abba
+[Lower]         0013eb -> 00abbb
+[Lower]         0013ec -> 00abbc
+[Lower]         0013ed -> 00abbd
+[Lower]         0013ee -> 00abbe
+[Lower]         0013ef -> 00abbf
+[Lower]         0013f0 -> 0013f8
+[Lower]         0013f1 -> 0013f9
+[Lower]         0013f2 -> 0013fa
+[Lower]         0013f3 -> 0013fb
+[Lower]         0013f4 -> 0013fc
+[Lower]         0013f5 -> 0013fd
+[Lower]         0013f8 -> 0013f8
+[Lower]         0013f9 -> 0013f9
+[Lower]         0013fa -> 0013fa
+[Lower]         0013fb -> 0013fb
+[Lower]         0013fc -> 0013fc
+[Lower]         0013fd -> 0013fd
+[Lower]         001c80 -> 001c80
+[Lower]         001c81 -> 001c81
+[Lower]         001c82 -> 001c82
+[Lower]         001c83 -> 001c83
+[Lower]         001c84 -> 001c84
+[Lower]         001c85 -> 001c85
+[Lower]         001c86 -> 001c86
+[Lower]         001c87 -> 001c87
+[Lower]         001c88 -> 001c88
 [Lower]         001d79 -> 001d79
 [Lower]         001d7d -> 001d7d
 [Lower] 001e00..001e01 -> 001e01
@@ -1242,6 +1392,8 @@
 [Lower] 001e90..001e91 -> 001e91
 [Lower] 001e92..001e93 -> 001e93
 [Lower] 001e94..001e95 -> 001e95
+[Lower]         001e9b -> 001e9b
+[Lower]         001e9e -> 0000df
 [Lower] 001ea0..001ea1 -> 001ea1
 [Lower] 001ea2..001ea3 -> 001ea3
 [Lower] 001ea4..001ea5 -> 001ea5
@@ -1287,6 +1439,9 @@
 [Lower] 001ef4..001ef5 -> 001ef5
 [Lower] 001ef6..001ef7 -> 001ef7
 [Lower] 001ef8..001ef9 -> 001ef9
+[Lower] 001efa..001efb -> 001efb
+[Lower] 001efc..001efd -> 001efd
+[Lower] 001efe..001eff -> 001eff
 [Lower]         001f00 -> 001f00
 [Lower]         001f01 -> 001f01
 [Lower]         001f02 -> 001f02
@@ -1353,22 +1508,20 @@
 [Lower]         001f43 -> 001f43
 [Lower]         001f44 -> 001f44
 [Lower]         001f45 -> 001f45
-[Lower]         001f46 -> 001f46
-[Lower]         001f47 -> 001f47
 [Lower]         001f48 -> 001f40
 [Lower]         001f49 -> 001f41
 [Lower]         001f4a -> 001f42
 [Lower]         001f4b -> 001f43
 [Lower]         001f4c -> 001f44
 [Lower]         001f4d -> 001f45
-[Lower]         001f4e -> 001f46
-[Lower]         001f4f -> 001f47
 [Lower]         001f51 -> 001f51
 [Lower]         001f53 -> 001f53
 [Lower]         001f55 -> 001f55
+[Lower]         001f57 -> 001f57
 [Lower]         001f59 -> 001f51
 [Lower]         001f5b -> 001f53
 [Lower]         001f5d -> 001f55
+[Lower]         001f5f -> 001f57
 [Lower]         001f60 -> 001f60
 [Lower]         001f61 -> 001f61
 [Lower]         001f62 -> 001f62
@@ -1455,6 +1608,7 @@
 [Lower]         001fba -> 001f70
 [Lower]         001fbb -> 001f71
 [Lower]         001fbc -> 001fb3
+[Lower]         001fbe -> 001fbe
 [Lower]         001fc3 -> 001fc3
 [Lower]         001fc8 -> 001f72
 [Lower]         001fc9 -> 001f73
@@ -1593,6 +1747,10 @@
 [Lower]         00206d -> 00206d
 [Lower]         00206e -> 00206e
 [Lower]         00206f -> 00206f
+[Lower]         002126 -> 0003c9
+[Lower]         00212b -> 0000e5
+[Lower]         002132 -> 00214e
+[Lower]         00214e -> 00214e
 [Lower]         002160 -> 002170
 [Lower]         002161 -> 002171
 [Lower]         002162 -> 002172
@@ -1625,6 +1783,7 @@
 [Lower]         00217d -> 00217d
 [Lower]         00217e -> 00217e
 [Lower]         00217f -> 00217f
+[Lower] 002183..002184 -> 002184
 [Lower]         002190 -> 002190
 [Lower]         002191 -> 002191
 [Lower]         002192 -> 002192
@@ -2525,10 +2684,10 @@
 [Lower] 002cdc..002cdd -> 002cdd
 [Lower] 002cde..002cdf -> 002cdf
 [Lower] 002ce0..002ce1 -> 002ce1
+[Lower] 002ce2..002ce3 -> 002ce3
 [Lower] 002ceb..002cec -> 002cec
 [Lower] 002ced..002cee -> 002cee
-[Lower] 002cef..002cf0 -> 002cf0
-[Lower] 002cf1..002cf2 -> 002cf2
+[Lower] 002cf2..002cf3 -> 002cf3
 [Lower]         002d00 -> 002d00
 [Lower]         002d01 -> 002d01
 [Lower]         002d02 -> 002d02
@@ -2567,13 +2726,7 @@
 [Lower]         002d23 -> 002d23
 [Lower]         002d24 -> 002d24
 [Lower]         002d25 -> 002d25
-[Lower]         002d26 -> 002d26
 [Lower]         002d27 -> 002d27
-[Lower]         002d28 -> 002d28
-[Lower]         002d29 -> 002d29
-[Lower]         002d2a -> 002d2a
-[Lower]         002d2b -> 002d2b
-[Lower]         002d2c -> 002d2c
 [Lower]         002d2d -> 002d2d
 [Lower] 00a640..00a641 -> 00a641
 [Lower] 00a642..00a643 -> 00a643
@@ -2659,7 +2812,7 @@
 [Lower] 00a784..00a785 -> 00a785
 [Lower] 00a786..00a787 -> 00a787
 [Lower] 00a78b..00a78c -> 00a78c
-[Lower] 00a78d..00a78e -> 00a78e
+[Lower]         00a78d -> 000265
 [Lower] 00a790..00a791 -> 00a791
 [Lower] 00a792..00a793 -> 00a793
 [Lower] 00a796..00a797 -> 00a797
@@ -2684,6 +2837,86 @@
 [Lower] 00a7b4..00a7b5 -> 00a7b5
 [Lower] 00a7b6..00a7b7 -> 00a7b7
 [Lower]         00ab53 -> 00ab53
+[Lower]         00ab70 -> 00ab70
+[Lower]         00ab71 -> 00ab71
+[Lower]         00ab72 -> 00ab72
+[Lower]         00ab73 -> 00ab73
+[Lower]         00ab74 -> 00ab74
+[Lower]         00ab75 -> 00ab75
+[Lower]         00ab76 -> 00ab76
+[Lower]         00ab77 -> 00ab77
+[Lower]         00ab78 -> 00ab78
+[Lower]         00ab79 -> 00ab79
+[Lower]         00ab7a -> 00ab7a
+[Lower]         00ab7b -> 00ab7b
+[Lower]         00ab7c -> 00ab7c
+[Lower]         00ab7d -> 00ab7d
+[Lower]         00ab7e -> 00ab7e
+[Lower]         00ab7f -> 00ab7f
+[Lower]         00ab80 -> 00ab80
+[Lower]         00ab81 -> 00ab81
+[Lower]         00ab82 -> 00ab82
+[Lower]         00ab83 -> 00ab83
+[Lower]         00ab84 -> 00ab84
+[Lower]         00ab85 -> 00ab85
+[Lower]         00ab86 -> 00ab86
+[Lower]         00ab87 -> 00ab87
+[Lower]         00ab88 -> 00ab88
+[Lower]         00ab89 -> 00ab89
+[Lower]         00ab8a -> 00ab8a
+[Lower]         00ab8b -> 00ab8b
+[Lower]         00ab8c -> 00ab8c
+[Lower]         00ab8d -> 00ab8d
+[Lower]         00ab8e -> 00ab8e
+[Lower]         00ab8f -> 00ab8f
+[Lower]         00ab90 -> 00ab90
+[Lower]         00ab91 -> 00ab91
+[Lower]         00ab92 -> 00ab92
+[Lower]         00ab93 -> 00ab93
+[Lower]         00ab94 -> 00ab94
+[Lower]         00ab95 -> 00ab95
+[Lower]         00ab96 -> 00ab96
+[Lower]         00ab97 -> 00ab97
+[Lower]         00ab98 -> 00ab98
+[Lower]         00ab99 -> 00ab99
+[Lower]         00ab9a -> 00ab9a
+[Lower]         00ab9b -> 00ab9b
+[Lower]         00ab9c -> 00ab9c
+[Lower]         00ab9d -> 00ab9d
+[Lower]         00ab9e -> 00ab9e
+[Lower]         00ab9f -> 00ab9f
+[Lower]         00aba0 -> 00aba0
+[Lower]         00aba1 -> 00aba1
+[Lower]         00aba2 -> 00aba2
+[Lower]         00aba3 -> 00aba3
+[Lower]         00aba4 -> 00aba4
+[Lower]         00aba5 -> 00aba5
+[Lower]         00aba6 -> 00aba6
+[Lower]         00aba7 -> 00aba7
+[Lower]         00aba8 -> 00aba8
+[Lower]         00aba9 -> 00aba9
+[Lower]         00abaa -> 00abaa
+[Lower]         00abab -> 00abab
+[Lower]         00abac -> 00abac
+[Lower]         00abad -> 00abad
+[Lower]         00abae -> 00abae
+[Lower]         00abaf -> 00abaf
+[Lower]         00abb0 -> 00abb0
+[Lower]         00abb1 -> 00abb1
+[Lower]         00abb2 -> 00abb2
+[Lower]         00abb3 -> 00abb3
+[Lower]         00abb4 -> 00abb4
+[Lower]         00abb5 -> 00abb5
+[Lower]         00abb6 -> 00abb6
+[Lower]         00abb7 -> 00abb7
+[Lower]         00abb8 -> 00abb8
+[Lower]         00abb9 -> 00abb9
+[Lower]         00abba -> 00abba
+[Lower]         00abbb -> 00abbb
+[Lower]         00abbc -> 00abbc
+[Lower]         00abbd -> 00abbd
+[Lower]         00abbe -> 00abbe
+[Lower]         00abbf -> 00abbf
 [Lower]         00ff21 -> 00ff41
 [Lower]         00ff22 -> 00ff42
 [Lower]         00ff23 -> 00ff43
@@ -2736,46 +2969,46 @@
 [Lower]         00ff58 -> 00ff58
 [Lower]         00ff59 -> 00ff59
 [Lower]         00ff5a -> 00ff5a
-[Lower]         010400 -> 01041c
-[Lower]         010401 -> 01041d
-[Lower]         010402 -> 01041e
-[Lower]         010403 -> 01041f
-[Lower]         010404 -> 010420
-[Lower]         010405 -> 010421
-[Lower]         010406 -> 010422
-[Lower]         010407 -> 010423
-[Lower]         010408 -> 010424
-[Lower]         010409 -> 010425
-[Lower]         01040a -> 010426
-[Lower]         01040b -> 010427
-[Lower]         01040c -> 010428
-[Lower]         01040d -> 010429
-[Lower]         01040e -> 01042a
-[Lower]         01040f -> 01042b
-[Lower]         010410 -> 01042c
-[Lower]         010411 -> 01042d
-[Lower]         010412 -> 01042e
-[Lower]         010413 -> 01042f
-[Lower]         010414 -> 010430
-[Lower]         010415 -> 010431
-[Lower]         010416 -> 010432
-[Lower]         010417 -> 010433
-[Lower]         010418 -> 010434
-[Lower]         010419 -> 010435
-[Lower]         01041a -> 010436
-[Lower]         01041b -> 010437
-[Lower]         01041c -> 010438
-[Lower]         01041d -> 010439
-[Lower]         01041e -> 01043a
-[Lower]         01041f -> 01043b
-[Lower]         010420 -> 01043c
-[Lower]         010421 -> 01043d
-[Lower]         010422 -> 01043e
-[Lower]         010423 -> 01043f
-[Lower]         010424 -> 010440
-[Lower]         010425 -> 010441
-[Lower]         010426 -> 010442
-[Lower]         010427 -> 010443
+[Lower]         010400 -> 010428
+[Lower]         010401 -> 010429
+[Lower]         010402 -> 01042a
+[Lower]         010403 -> 01042b
+[Lower]         010404 -> 01042c
+[Lower]         010405 -> 01042d
+[Lower]         010406 -> 01042e
+[Lower]         010407 -> 01042f
+[Lower]         010408 -> 010430
+[Lower]         010409 -> 010431
+[Lower]         01040a -> 010432
+[Lower]         01040b -> 010433
+[Lower]         01040c -> 010434
+[Lower]         01040d -> 010435
+[Lower]         01040e -> 010436
+[Lower]         01040f -> 010437
+[Lower]         010410 -> 010438
+[Lower]         010411 -> 010439
+[Lower]         010412 -> 01043a
+[Lower]         010413 -> 01043b
+[Lower]         010414 -> 01043c
+[Lower]         010415 -> 01043d
+[Lower]         010416 -> 01043e
+[Lower]         010417 -> 01043f
+[Lower]         010418 -> 010440
+[Lower]         010419 -> 010441
+[Lower]         01041a -> 010442
+[Lower]         01041b -> 010443
+[Lower]         01041c -> 010444
+[Lower]         01041d -> 010445
+[Lower]         01041e -> 010446
+[Lower]         01041f -> 010447
+[Lower]         010420 -> 010448
+[Lower]         010421 -> 010449
+[Lower]         010422 -> 01044a
+[Lower]         010423 -> 01044b
+[Lower]         010424 -> 01044c
+[Lower]         010425 -> 01044d
+[Lower]         010426 -> 01044e
+[Lower]         010427 -> 01044f
 [Lower]         010428 -> 010428
 [Lower]         010429 -> 010429
 [Lower]         01042a -> 01042a
@@ -2804,6 +3037,18 @@
 [Lower]         010441 -> 010441
 [Lower]         010442 -> 010442
 [Lower]         010443 -> 010443
+[Lower]         010444 -> 010444
+[Lower]         010445 -> 010445
+[Lower]         010446 -> 010446
+[Lower]         010447 -> 010447
+[Lower]         010448 -> 010448
+[Lower]         010449 -> 010449
+[Lower]         01044a -> 01044a
+[Lower]         01044b -> 01044b
+[Lower]         01044c -> 01044c
+[Lower]         01044d -> 01044d
+[Lower]         01044e -> 01044e
+[Lower]         01044f -> 01044f
 [Lower]         0104b0 -> 0104d8
 [Lower]         0104b1 -> 0104d9
 [Lower]         0104b2 -> 0104da
@@ -3307,7 +3552,7 @@
 [Upper]         0000ae -> 0000ae
 [Upper]         0000b0 -> 0000b0
 [Upper]         0000b1 -> 0000b1
-[Upper]         0000b5 -> 0000b5
+[Upper]         0000b5 -> 0003bc
 [Upper]         0000b7 -> 0000b7
 [Upper]         0000bb -> 0000bb
 [Upper]         0000bc -> 0000bc
@@ -3462,6 +3707,7 @@
 [Upper]         00019a -> 00019a
 [Upper]         00019c -> 00026f
 [Upper]         00019d -> 000272
+[Upper]         00019e -> 00019e
 [Upper]         00019f -> 000275
 [Upper] 0001a0..0001a1 -> 0001a1
 [Upper] 0001a2..0001a3 -> 0001a3
@@ -3525,6 +3771,7 @@
 [Upper] 00021a..00021b -> 00021b
 [Upper] 00021c..00021d -> 00021d
 [Upper] 00021e..00021f -> 00021f
+[Upper]         000220 -> 00019e
 [Upper] 000222..000223 -> 000223
 [Upper] 000224..000225 -> 000225
 [Upper] 000226..000227 -> 000227
@@ -3562,6 +3809,7 @@
 [Upper]         000260 -> 000260
 [Upper]         000261 -> 000261
 [Upper]         000263 -> 000263
+[Upper]         000265 -> 000265
 [Upper]         000266 -> 000266
 [Upper]         000268 -> 000268
 [Upper]         000269 -> 000269
@@ -3584,6 +3832,14 @@
 [Upper]         000292 -> 000292
 [Upper]         00029d -> 00029d
 [Upper]         00029e -> 00029e
+[Upper]         000345 -> 0003b9
+[Upper] 000370..000371 -> 000371
+[Upper] 000372..000373 -> 000373
+[Upper] 000376..000377 -> 000377
+[Upper]         00037b -> 00037b
+[Upper]         00037c -> 00037c
+[Upper]         00037d -> 00037d
+[Upper]         00037f -> 0003f3
 [Upper]         000386 -> 0003ac
 [Upper]         000388 -> 0003ad
 [Upper]         000389 -> 0003ae
@@ -3638,7 +3894,7 @@
 [Upper]         0003bf -> 0003bf
 [Upper]         0003c0 -> 0003c0
 [Upper]         0003c1 -> 0003c1
-[Upper]         0003c3 -> 0003c3
+[Upper] 0003c2..0003c3 -> 0003c3
 [Upper]         0003c4 -> 0003c4
 [Upper]         0003c5 -> 0003c5
 [Upper]         0003c6 -> 0003c6
@@ -3650,6 +3906,13 @@
 [Upper]         0003cc -> 0003cc
 [Upper]         0003cd -> 0003cd
 [Upper]         0003ce -> 0003ce
+[Upper]         0003cf -> 0003d7
+[Upper]         0003d0 -> 0003b2
+[Upper]         0003d1 -> 0003b8
+[Upper]         0003d5 -> 0003c6
+[Upper]         0003d6 -> 0003c0
+[Upper]         0003d7 -> 0003d7
+[Upper] 0003d8..0003d9 -> 0003d9
 [Upper] 0003da..0003db -> 0003db
 [Upper] 0003dc..0003dd -> 0003dd
 [Upper] 0003de..0003df -> 0003df
@@ -3661,6 +3924,18 @@
 [Upper] 0003ea..0003eb -> 0003eb
 [Upper] 0003ec..0003ed -> 0003ed
 [Upper] 0003ee..0003ef -> 0003ef
+[Upper]         0003f0 -> 0003ba
+[Upper]         0003f1 -> 0003c1
+[Upper]         0003f2 -> 0003f2
+[Upper]         0003f3 -> 0003f3
+[Upper]         0003f4 -> 0003b8
+[Upper]         0003f5 -> 0003b5
+[Upper] 0003f7..0003f8 -> 0003f8
+[Upper]         0003f9 -> 0003f2
+[Upper] 0003fa..0003fb -> 0003fb
+[Upper]         0003fd -> 00037b
+[Upper]         0003fe -> 00037c
+[Upper]         0003ff -> 00037d
 [Upper]         000400 -> 000450
 [Upper]         000401 -> 000451
 [Upper]         000402 -> 000452
@@ -3774,6 +4049,7 @@
 [Upper] 00047c..00047d -> 00047d
 [Upper] 00047e..00047f -> 00047f
 [Upper] 000480..000481 -> 000481
+[Upper] 00048a..00048b -> 00048b
 [Upper] 00048c..00048d -> 00048d
 [Upper] 00048e..00048f -> 00048f
 [Upper] 000490..000491 -> 000491
@@ -3800,10 +4076,15 @@
 [Upper] 0004ba..0004bb -> 0004bb
 [Upper] 0004bc..0004bd -> 0004bd
 [Upper] 0004be..0004bf -> 0004bf
+[Upper]         0004c0 -> 0004cf
 [Upper] 0004c1..0004c2 -> 0004c2
 [Upper] 0004c3..0004c4 -> 0004c4
+[Upper] 0004c5..0004c6 -> 0004c6
 [Upper] 0004c7..0004c8 -> 0004c8
+[Upper] 0004c9..0004ca -> 0004ca
 [Upper] 0004cb..0004cc -> 0004cc
+[Upper] 0004cd..0004ce -> 0004ce
+[Upper]         0004cf -> 0004cf
 [Upper] 0004d0..0004d1 -> 0004d1
 [Upper] 0004d2..0004d3 -> 0004d3
 [Upper] 0004d4..0004d5 -> 0004d5
@@ -3828,6 +4109,30 @@
 [Upper] 0004fa..0004fb -> 0004fb
 [Upper] 0004fc..0004fd -> 0004fd
 [Upper] 0004fe..0004ff -> 0004ff
+[Upper] 000500..000501 -> 000501
+[Upper] 000502..000503 -> 000503
+[Upper] 000504..000505 -> 000505
+[Upper] 000506..000507 -> 000507
+[Upper] 000508..000509 -> 000509
+[Upper] 00050a..00050b -> 00050b
+[Upper] 00050c..00050d -> 00050d
+[Upper] 00050e..00050f -> 00050f
+[Upper] 000510..000511 -> 000511
+[Upper] 000512..000513 -> 000513
+[Upper] 000514..000515 -> 000515
+[Upper] 000516..000517 -> 000517
+[Upper] 000518..000519 -> 000519
+[Upper] 00051a..00051b -> 00051b
+[Upper] 00051c..00051d -> 00051d
+[Upper] 00051e..00051f -> 00051f
+[Upper] 000520..000521 -> 000521
+[Upper] 000522..000523 -> 000523
+[Upper] 000524..000525 -> 000525
+[Upper] 000526..000527 -> 000527
+[Upper] 000528..000529 -> 000529
+[Upper] 00052a..00052b -> 00052b
+[Upper] 00052c..00052d -> 00052d
+[Upper] 00052e..00052f -> 00052f
 [Upper]         000531 -> 000561
 [Upper]         000532 -> 000562
 [Upper]         000533 -> 000563
@@ -3942,14 +4247,108 @@
 [Upper]         0010c3 -> 002d23
 [Upper]         0010c4 -> 002d24
 [Upper]         0010c5 -> 002d25
-[Upper]         0010c6 -> 002d26
 [Upper]         0010c7 -> 002d27
-[Upper]         0010c8 -> 002d28
-[Upper]         0010c9 -> 002d29
-[Upper]         0010ca -> 002d2a
-[Upper]         0010cb -> 002d2b
-[Upper]         0010cc -> 002d2c
 [Upper]         0010cd -> 002d2d
+[Upper]         0013a0 -> 00ab70
+[Upper]         0013a1 -> 00ab71
+[Upper]         0013a2 -> 00ab72
+[Upper]         0013a3 -> 00ab73
+[Upper]         0013a4 -> 00ab74
+[Upper]         0013a5 -> 00ab75
+[Upper]         0013a6 -> 00ab76
+[Upper]         0013a7 -> 00ab77
+[Upper]         0013a8 -> 00ab78
+[Upper]         0013a9 -> 00ab79
+[Upper]         0013aa -> 00ab7a
+[Upper]         0013ab -> 00ab7b
+[Upper]         0013ac -> 00ab7c
+[Upper]         0013ad -> 00ab7d
+[Upper]         0013ae -> 00ab7e
+[Upper]         0013af -> 00ab7f
+[Upper]         0013b0 -> 00ab80
+[Upper]         0013b1 -> 00ab81
+[Upper]         0013b2 -> 00ab82
+[Upper]         0013b3 -> 00ab83
+[Upper]         0013b4 -> 00ab84
+[Upper]         0013b5 -> 00ab85
+[Upper]         0013b6 -> 00ab86
+[Upper]         0013b7 -> 00ab87
+[Upper]         0013b8 -> 00ab88
+[Upper]         0013b9 -> 00ab89
+[Upper]         0013ba -> 00ab8a
+[Upper]         0013bb -> 00ab8b
+[Upper]         0013bc -> 00ab8c
+[Upper]         0013bd -> 00ab8d
+[Upper]         0013be -> 00ab8e
+[Upper]         0013bf -> 00ab8f
+[Upper]         0013c0 -> 00ab90
+[Upper]         0013c1 -> 00ab91
+[Upper]         0013c2 -> 00ab92
+[Upper]         0013c3 -> 00ab93
+[Upper]         0013c4 -> 00ab94
+[Upper]         0013c5 -> 00ab95
+[Upper]         0013c6 -> 00ab96
+[Upper]         0013c7 -> 00ab97
+[Upper]         0013c8 -> 00ab98
+[Upper]         0013c9 -> 00ab99
+[Upper]         0013ca -> 00ab9a
+[Upper]         0013cb -> 00ab9b
+[Upper]         0013cc -> 00ab9c
+[Upper]         0013cd -> 00ab9d
+[Upper]         0013ce -> 00ab9e
+[Upper]         0013cf -> 00ab9f
+[Upper]         0013d0 -> 00aba0
+[Upper]         0013d1 -> 00aba1
+[Upper]         0013d2 -> 00aba2
+[Upper]         0013d3 -> 00aba3
+[Upper]         0013d4 -> 00aba4
+[Upper]         0013d5 -> 00aba5
+[Upper]         0013d6 -> 00aba6
+[Upper]         0013d7 -> 00aba7
+[Upper]         0013d8 -> 00aba8
+[Upper]         0013d9 -> 00aba9
+[Upper]         0013da -> 00abaa
+[Upper]         0013db -> 00abab
+[Upper]         0013dc -> 00abac
+[Upper]         0013dd -> 00abad
+[Upper]         0013de -> 00abae
+[Upper]         0013df -> 00abaf
+[Upper]         0013e0 -> 00abb0
+[Upper]         0013e1 -> 00abb1
+[Upper]         0013e2 -> 00abb2
+[Upper]         0013e3 -> 00abb3
+[Upper]         0013e4 -> 00abb4
+[Upper]         0013e5 -> 00abb5
+[Upper]         0013e6 -> 00abb6
+[Upper]         0013e7 -> 00abb7
+[Upper]         0013e8 -> 00abb8
+[Upper]         0013e9 -> 00abb9
+[Upper]         0013ea -> 00abba
+[Upper]         0013eb -> 00abbb
+[Upper]         0013ec -> 00abbc
+[Upper]         0013ed -> 00abbd
+[Upper]         0013ee -> 00abbe
+[Upper]         0013ef -> 00abbf
+[Upper]         0013f0 -> 0013f8
+[Upper]         0013f1 -> 0013f9
+[Upper]         0013f2 -> 0013fa
+[Upper]         0013f3 -> 0013fb
+[Upper]         0013f4 -> 0013fc
+[Upper]         0013f5 -> 0013fd
+[Upper]         0013f8 -> 0013f8
+[Upper]         0013f9 -> 0013f9
+[Upper]         0013fa -> 0013fa
+[Upper]         0013fb -> 0013fb
+[Upper]         0013fc -> 0013fc
+[Upper]         0013fd -> 0013fd
+[Upper]         001c80 -> 000432
+[Upper]         001c81 -> 000434
+[Upper]         001c82 -> 00043e
+[Upper]         001c83 -> 000441
+[Upper] 001c84..001c85 -> 000442
+[Upper]         001c86 -> 00044a
+[Upper]         001c87 -> 000463
+[Upper]         001c88 -> 00a64b
 [Upper]         001d79 -> 001d79
 [Upper]         001d7d -> 001d7d
 [Upper] 001e00..001e01 -> 001e01
@@ -4027,6 +4426,8 @@
 [Upper] 001e90..001e91 -> 001e91
 [Upper] 001e92..001e93 -> 001e93
 [Upper] 001e94..001e95 -> 001e95
+[Upper]         001e9b -> 001e61
+[Upper]         001e9e -> 0000df
 [Upper] 001ea0..001ea1 -> 001ea1
 [Upper] 001ea2..001ea3 -> 001ea3
 [Upper] 001ea4..001ea5 -> 001ea5
@@ -4072,6 +4473,9 @@
 [Upper] 001ef4..001ef5 -> 001ef5
 [Upper] 001ef6..001ef7 -> 001ef7
 [Upper] 001ef8..001ef9 -> 001ef9
+[Upper] 001efa..001efb -> 001efb
+[Upper] 001efc..001efd -> 001efd
+[Upper] 001efe..001eff -> 001eff
 [Upper]         001f00 -> 001f00
 [Upper]         001f01 -> 001f01
 [Upper]         001f02 -> 001f02
@@ -4138,22 +4542,20 @@
 [Upper]         001f43 -> 001f43
 [Upper]         001f44 -> 001f44
 [Upper]         001f45 -> 001f45
-[Upper]         001f46 -> 001f46
-[Upper]         001f47 -> 001f47
 [Upper]         001f48 -> 001f40
 [Upper]         001f49 -> 001f41
 [Upper]         001f4a -> 001f42
 [Upper]         001f4b -> 001f43
 [Upper]         001f4c -> 001f44
 [Upper]         001f4d -> 001f45
-[Upper]         001f4e -> 001f46
-[Upper]         001f4f -> 001f47
 [Upper]         001f51 -> 001f51
 [Upper]         001f53 -> 001f53
 [Upper]         001f55 -> 001f55
+[Upper]         001f57 -> 001f57
 [Upper]         001f59 -> 001f51
 [Upper]         001f5b -> 001f53
 [Upper]         001f5d -> 001f55
+[Upper]         001f5f -> 001f57
 [Upper]         001f60 -> 001f60
 [Upper]         001f61 -> 001f61
 [Upper]         001f62 -> 001f62
@@ -4240,6 +4642,7 @@
 [Upper]         001fba -> 001f70
 [Upper]         001fbb -> 001f71
 [Upper]         001fbc -> 001fb3
+[Upper]         001fbe -> 0003b9
 [Upper]         001fc3 -> 001fc3
 [Upper]         001fc8 -> 001f72
 [Upper]         001fc9 -> 001f73
@@ -4378,6 +4781,10 @@
 [Upper]         00206d -> 00206d
 [Upper]         00206e -> 00206e
 [Upper]         00206f -> 00206f
+[Upper]         002126 -> 0003c9
+[Upper]         00212b -> 0000e5
+[Upper]         002132 -> 00214e
+[Upper]         00214e -> 00214e
 [Upper]         002160 -> 002170
 [Upper]         002161 -> 002171
 [Upper]         002162 -> 002172
@@ -4410,6 +4817,7 @@
 [Upper]         00217d -> 00217d
 [Upper]         00217e -> 00217e
 [Upper]         00217f -> 00217f
+[Upper] 002183..002184 -> 002184
 [Upper]         002190 -> 002190
 [Upper]         002191 -> 002191
 [Upper]         002192 -> 002192
@@ -5310,10 +5718,10 @@
 [Upper] 002cdc..002cdd -> 002cdd
 [Upper] 002cde..002cdf -> 002cdf
 [Upper] 002ce0..002ce1 -> 002ce1
+[Upper] 002ce2..002ce3 -> 002ce3
 [Upper] 002ceb..002cec -> 002cec
 [Upper] 002ced..002cee -> 002cee
-[Upper] 002cef..002cf0 -> 002cf0
-[Upper] 002cf1..002cf2 -> 002cf2
+[Upper] 002cf2..002cf3 -> 002cf3
 [Upper]         002d00 -> 002d00
 [Upper]         002d01 -> 002d01
 [Upper]         002d02 -> 002d02
@@ -5352,13 +5760,7 @@
 [Upper]         002d23 -> 002d23
 [Upper]         002d24 -> 002d24
 [Upper]         002d25 -> 002d25
-[Upper]         002d26 -> 002d26
 [Upper]         002d27 -> 002d27
-[Upper]         002d28 -> 002d28
-[Upper]         002d29 -> 002d29
-[Upper]         002d2a -> 002d2a
-[Upper]         002d2b -> 002d2b
-[Upper]         002d2c -> 002d2c
 [Upper]         002d2d -> 002d2d
 [Upper] 00a640..00a641 -> 00a641
 [Upper] 00a642..00a643 -> 00a643
@@ -5444,7 +5846,7 @@
 [Upper] 00a784..00a785 -> 00a785
 [Upper] 00a786..00a787 -> 00a787
 [Upper] 00a78b..00a78c -> 00a78c
-[Upper] 00a78d..00a78e -> 00a78e
+[Upper]         00a78d -> 000265
 [Upper] 00a790..00a791 -> 00a791
 [Upper] 00a792..00a793 -> 00a793
 [Upper] 00a796..00a797 -> 00a797
@@ -5469,6 +5871,86 @@
 [Upper] 00a7b4..00a7b5 -> 00a7b5
 [Upper] 00a7b6..00a7b7 -> 00a7b7
 [Upper]         00ab53 -> 00ab53
+[Upper]         00ab70 -> 00ab70
+[Upper]         00ab71 -> 00ab71
+[Upper]         00ab72 -> 00ab72
+[Upper]         00ab73 -> 00ab73
+[Upper]         00ab74 -> 00ab74
+[Upper]         00ab75 -> 00ab75
+[Upper]         00ab76 -> 00ab76
+[Upper]         00ab77 -> 00ab77
+[Upper]         00ab78 -> 00ab78
+[Upper]         00ab79 -> 00ab79
+[Upper]         00ab7a -> 00ab7a
+[Upper]         00ab7b -> 00ab7b
+[Upper]         00ab7c -> 00ab7c
+[Upper]         00ab7d -> 00ab7d
+[Upper]         00ab7e -> 00ab7e
+[Upper]         00ab7f -> 00ab7f
+[Upper]         00ab80 -> 00ab80
+[Upper]         00ab81 -> 00ab81
+[Upper]         00ab82 -> 00ab82
+[Upper]         00ab83 -> 00ab83
+[Upper]         00ab84 -> 00ab84
+[Upper]         00ab85 -> 00ab85
+[Upper]         00ab86 -> 00ab86
+[Upper]         00ab87 -> 00ab87
+[Upper]         00ab88 -> 00ab88
+[Upper]         00ab89 -> 00ab89
+[Upper]         00ab8a -> 00ab8a
+[Upper]         00ab8b -> 00ab8b
+[Upper]         00ab8c -> 00ab8c
+[Upper]         00ab8d -> 00ab8d
+[Upper]         00ab8e -> 00ab8e
+[Upper]         00ab8f -> 00ab8f
+[Upper]         00ab90 -> 00ab90
+[Upper]         00ab91 -> 00ab91
+[Upper]         00ab92 -> 00ab92
+[Upper]         00ab93 -> 00ab93
+[Upper]         00ab94 -> 00ab94
+[Upper]         00ab95 -> 00ab95
+[Upper]         00ab96 -> 00ab96
+[Upper]         00ab97 -> 00ab97
+[Upper]         00ab98 -> 00ab98
+[Upper]         00ab99 -> 00ab99
+[Upper]         00ab9a -> 00ab9a
+[Upper]         00ab9b -> 00ab9b
+[Upper]         00ab9c -> 00ab9c
+[Upper]         00ab9d -> 00ab9d
+[Upper]         00ab9e -> 00ab9e
+[Upper]         00ab9f -> 00ab9f
+[Upper]         00aba0 -> 00aba0
+[Upper]         00aba1 -> 00aba1
+[Upper]         00aba2 -> 00aba2
+[Upper]         00aba3 -> 00aba3
+[Upper]         00aba4 -> 00aba4
+[Upper]         00aba5 -> 00aba5
+[Upper]         00aba6 -> 00aba6
+[Upper]         00aba7 -> 00aba7
+[Upper]         00aba8 -> 00aba8
+[Upper]         00aba9 -> 00aba9
+[Upper]         00abaa -> 00abaa
+[Upper]         00abab -> 00abab
+[Upper]         00abac -> 00abac
+[Upper]         00abad -> 00abad
+[Upper]         00abae -> 00abae
+[Upper]         00abaf -> 00abaf
+[Upper]         00abb0 -> 00abb0
+[Upper]         00abb1 -> 00abb1
+[Upper]         00abb2 -> 00abb2
+[Upper]         00abb3 -> 00abb3
+[Upper]         00abb4 -> 00abb4
+[Upper]         00abb5 -> 00abb5
+[Upper]         00abb6 -> 00abb6
+[Upper]         00abb7 -> 00abb7
+[Upper]         00abb8 -> 00abb8
+[Upper]         00abb9 -> 00abb9
+[Upper]         00abba -> 00abba
+[Upper]         00abbb -> 00abbb
+[Upper]         00abbc -> 00abbc
+[Upper]         00abbd -> 00abbd
+[Upper]         00abbe -> 00abbe
+[Upper]         00abbf -> 00abbf
 [Upper]         00ff21 -> 00ff41
 [Upper]         00ff22 -> 00ff42
 [Upper]         00ff23 -> 00ff43
@@ -5521,46 +6003,46 @@
 [Upper]         00ff58 -> 00ff58
 [Upper]         00ff59 -> 00ff59
 [Upper]         00ff5a -> 00ff5a
-[Upper]         010400 -> 010438
-[Upper]         010401 -> 010439
-[Upper]         010402 -> 01043a
-[Upper]         010403 -> 01043b
-[Upper]         010404 -> 01043c
-[Upper]         010405 -> 01043d
-[Upper]         010406 -> 01043e
-[Upper]         010407 -> 01043f
-[Upper]         010408 -> 010440
-[Upper]         010409 -> 010441
-[Upper]         01040a -> 010442
-[Upper]         01040b -> 010443
-[Upper]         01040c -> 010428
-[Upper]         01040d -> 010429
-[Upper]         01040e -> 01042a
-[Upper]         01040f -> 01042b
-[Upper]         010410 -> 01042c
-[Upper]         010411 -> 01042d
-[Upper]         010412 -> 01042e
-[Upper]         010413 -> 01042f
-[Upper]         010414 -> 010430
-[Upper]         010415 -> 010431
-[Upper]         010416 -> 010432
-[Upper]         010417 -> 010433
-[Upper]         010418 -> 010434
-[Upper]         010419 -> 010435
-[Upper]         01041a -> 010436
-[Upper]         01041b -> 010437
-[Upper]         01041c -> 010438
-[Upper]         01041d -> 010439
-[Upper]         01041e -> 01043a
-[Upper]         01041f -> 01043b
-[Upper]         010420 -> 01043c
-[Upper]         010421 -> 01043d
-[Upper]         010422 -> 01043e
-[Upper]         010423 -> 01043f
-[Upper]         010424 -> 010440
-[Upper]         010425 -> 010441
-[Upper]         010426 -> 010442
-[Upper]         010427 -> 010443
+[Upper]         010400 -> 010428
+[Upper]         010401 -> 010429
+[Upper]         010402 -> 01042a
+[Upper]         010403 -> 01042b
+[Upper]         010404 -> 01042c
+[Upper]         010405 -> 01042d
+[Upper]         010406 -> 01042e
+[Upper]         010407 -> 01042f
+[Upper]         010408 -> 010430
+[Upper]         010409 -> 010431
+[Upper]         01040a -> 010432
+[Upper]         01040b -> 010433
+[Upper]         01040c -> 010434
+[Upper]         01040d -> 010435
+[Upper]         01040e -> 010436
+[Upper]         01040f -> 010437
+[Upper]         010410 -> 010438
+[Upper]         010411 -> 010439
+[Upper]         010412 -> 01043a
+[Upper]         010413 -> 01043b
+[Upper]         010414 -> 01043c
+[Upper]         010415 -> 01043d
+[Upper]         010416 -> 01043e
+[Upper]         010417 -> 01043f
+[Upper]         010418 -> 010440
+[Upper]         010419 -> 010441
+[Upper]         01041a -> 010442
+[Upper]         01041b -> 010443
+[Upper]         01041c -> 010444
+[Upper]         01041d -> 010445
+[Upper]         01041e -> 010446
+[Upper]         01041f -> 010447
+[Upper]         010420 -> 010448
+[Upper]         010421 -> 010449
+[Upper]         010422 -> 01044a
+[Upper]         010423 -> 01044b
+[Upper]         010424 -> 01044c
+[Upper]         010425 -> 01044d
+[Upper]         010426 -> 01044e
+[Upper]         010427 -> 01044f
 [Upper]         010428 -> 010428
 [Upper]         010429 -> 010429
 [Upper]         01042a -> 01042a
@@ -5589,6 +6071,18 @@
 [Upper]         010441 -> 010441
 [Upper]         010442 -> 010442
 [Upper]         010443 -> 010443
+[Upper]         010444 -> 010444
+[Upper]         010445 -> 010445
+[Upper]         010446 -> 010446
+[Upper]         010447 -> 010447
+[Upper]         010448 -> 010448
+[Upper]         010449 -> 010449
+[Upper]         01044a -> 01044a
+[Upper]         01044b -> 01044b
+[Upper]         01044c -> 01044c
+[Upper]         01044d -> 01044d
+[Upper]         01044e -> 01044e
+[Upper]         01044f -> 01044f
 [Upper]         0104b0 -> 0104d8
 [Upper]         0104b1 -> 0104d9
 [Upper]         0104b2 -> 0104da





^ permalink raw reply	[flat|nested] 89+ messages in thread

* bug#24603: [RFC 02/18] Generate upcase and downcase tables from Unicode data
  2016-10-04 14:54       ` Michal Nazarewicz
@ 2016-10-04 15:06         ` Eli Zaretskii
  2016-10-04 16:57           ` Michal Nazarewicz
  0 siblings, 1 reply; 89+ messages in thread
From: Eli Zaretskii @ 2016-10-04 15:06 UTC (permalink / raw)
  To: Michal Nazarewicz; +Cc: 24603

> From: Michal Nazarewicz <mina86@mina86.com>
> Cc: 24603@debbugs.gnu.org
> Date: Tue, 04 Oct 2016 16:54:31 +0200
> 
> +    ;; Ⅰ through Ⅻ had word syntax in the past so set it here as well.
> +    ;; General category of those characers is Number, Letter.
> +    (modify-syntax-entry '(#x2160 . #x216b) "w   " syn-tab)
> +
> +    ;; ⓐ thourgh ⓩ are symbols, other according to Unicode but Emacs set
> +    ;; their syntax to word in the past so keep backwards compatibility.
> +    (modify-syntax-entry '(#x24D0 . #x24E9) "w   " syn-tab))

I think we should document all the changes.  If the list of changes is
too long, and cannot be made short enough by summarizing (instead of
showing each individual character), then it probably should go into
some separate file in admin/unidata/.  If it can be short enough, then
a comment in characters.el is a better place, I think.

> I get the following (annotated) differences:

Can you add the name of each character (just one, the leftmost one) to
its line and post the result?  It's hard to read the report when it
only shows codepoints.

Thanks.





^ permalink raw reply	[flat|nested] 89+ messages in thread

* bug#24603: [RFC 02/18] Generate upcase and downcase tables from Unicode data
  2016-10-04 15:06         ` Eli Zaretskii
@ 2016-10-04 16:57           ` Michal Nazarewicz
  2016-10-04 17:27             ` Eli Zaretskii
  0 siblings, 1 reply; 89+ messages in thread
From: Michal Nazarewicz @ 2016-10-04 16:57 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: 24603

On Tue, Oct 04 2016, Eli Zaretskii wrote:
>> From: Michal Nazarewicz <mina86@mina86.com>
>> Cc: 24603@debbugs.gnu.org
>> Date: Tue, 04 Oct 2016 16:54:31 +0200
>> 
>> +    ;; Ⅰ through Ⅻ had word syntax in the past so set it here as well.
>> +    ;; General category of those characers is Number, Letter.
>> +    (modify-syntax-entry '(#x2160 . #x216b) "w   " syn-tab)
>> +
>> +    ;; ⓐ thourgh ⓩ are symbols, other according to Unicode but Emacs set
>> +    ;; their syntax to word in the past so keep backwards compatibility.
>> +    (modify-syntax-entry '(#x24D0 . #x24E9) "w   " syn-tab))
>
> I think we should document all the changes.

I wouldn’t know where to put such documentation.  syntax.texi mentions
existence of standard syntax table but does not describe its content.
standard-syntax-table’s docstring is even less descriptive.

>> I get the following (annotated) differences:
>
> Can you add the name of each character (just one, the leftmost one) to
> its line and post the result?  It's hard to read the report when it
> only shows codepoints.

--- orig-tables.txt	2016-10-04 18:40:27.276408290 +0200
+++ modified-tables.txt	2016-10-04 18:41:34.651421547 +0200
@@ -1,4 +1,4 @@
-GNU Emacs 26.0.50.10 (x86_64-unknown-linux-gnu)
+GNU Emacs 26.0.50.11 (x86_64-unknown-linux-gnu)
  of 2016-10-04
 
 Syntax: 000000..000008 -> (1)
@@ -44,7 +44,9 @@
 Syntax:         0000a7 -> (1)  Section Sign
 Syntax: 0000a8..0000aa -> (3)  Diaeresis
 Syntax:         0000ab -> (1)  Left-Pointing Double Angle Quotation Mark
-Syntax: 0000ac..0000b6 -> (3)  Not Sign
+Syntax: 0000ac..0000b4 -> (3)  Not Sign
+Syntax:         0000b5 -> (2)  Micro Sign
+Syntax:         0000b6 -> (3)  Pilcrow Sign
 Syntax:         0000b7 -> (2)  Middle Dot
 Syntax: 0000b8..0000ba -> (3)  Cedilla
 Syntax:         0000bb -> (1)  Right-Pointing Double Angle Quotation Mark
@@ -54,9 +56,7 @@
 Syntax:         0000d7 -> (3)  Multiplication Sign
 Syntax: 0000d8..0000f6 -> (2)  Latin Capital Letter O With Stroke
 Syntax:         0000f7 -> (3)  Division Sign
-Syntax: 0000f8..000148 -> (2)  Latin Small Letter O With Stroke
-Syntax:         000149 -> (3)  Latin Small Letter N Preceded By Apostrophe
-Syntax: 00014a..0002c6 -> (2)  Latin Capital Letter Eng
+Syntax: 0000f8..0002c6 -> (2)  Latin Small Letter O With Stroke
 Syntax:         0002c7 -> (3)  Caron
 Syntax:         0002c8 -> (2)  Modifier Letter Vertical Line
 Syntax:         0002c9 -> (3)  Modifier Letter Macron
@@ -136,17 +136,11 @@
 Syntax:         002103 -> (3)  Degree Celsius
 Syntax: 002104..002108 -> (2)  Centre Line Symbol
 Syntax:         002109 -> (3)  Degree Fahrenheit
-Syntax: 00210a..002112 -> (2)  Script Small G
-Syntax:         002113 -> (3)  Script Small L
-Syntax: 002114..002115 -> (2)  L B Bar Symbol
+Syntax: 00210a..002115 -> (2)  Script Small G
 Syntax:         002116 -> (1)  Numero Sign
 Syntax: 002117..002120 -> (2)  Sound Recording Copyright
 Syntax: 002121..002122 -> (3)  Telephone Sign
-Syntax: 002123..002125 -> (2)  Versicle
-Syntax:         002126 -> (3)  Ohm Sign
-Syntax: 002127..00212a -> (2)  Inverted Ohm Sign
-Syntax:         00212b -> (3)  Angstrom Sign
-Syntax: 00212c..002152 -> (2)  Script Capital B
+Syntax: 002123..002152 -> (2)  Versicle
 Syntax: 002153..002154 -> (3)  Vulgar Fraction One Third
 Syntax: 002155..00215a -> (2)  Vulgar Fraction One Fifth
 Syntax: 00215b..00215e -> (3)  Vulgar Fraction One Eighth
@@ -677,6 +671,7 @@
 Lower:         00019a -> 00019a  Latin Small Letter L With Bar
 Lower:         00019c -> 00026f  Latin Capital Letter Turned M
 Lower:         00019d -> 000272  Latin Capital Letter N With Left Hook
+Lower:         00019e -> 00019e  Latin Small Letter N With Long Right Leg
 Lower:         00019f -> 000275  Latin Capital Letter O With Middle Tilde
 Lower: 0001a0..0001a1 -> 0001a1  Latin Capital Letter O With Horn
 Lower: 0001a2..0001a3 -> 0001a3  Latin Capital Letter Oi
@@ -740,6 +735,7 @@
 Lower: 00021a..00021b -> 00021b  Latin Capital Letter T With Comma Below
 Lower: 00021c..00021d -> 00021d  Latin Capital Letter Yogh
 Lower: 00021e..00021f -> 00021f  Latin Capital Letter H With Caron
+Lower:         000220 -> 00019e  Latin Capital Letter N With Long Right Leg
 Lower: 000222..000223 -> 000223  Latin Capital Letter Ou
 Lower: 000224..000225 -> 000225  Latin Capital Letter Z With Hook
 Lower: 000226..000227 -> 000227  Latin Capital Letter A With Dot Above
@@ -777,6 +773,7 @@
 Lower:         000260 -> 000260  Latin Small Letter G With Hook
 Lower:         000261 -> 000261  Latin Small Letter Script G
 Lower:         000263 -> 000263  Latin Small Letter Gamma
+Lower:         000265 -> 000265  Latin Small Letter Turned H
 Lower:         000266 -> 000266  Latin Small Letter H With Hook
 Lower:         000268 -> 000268  Latin Small Letter I With Stroke
 Lower:         000269 -> 000269  Latin Small Letter Iota
@@ -799,6 +796,14 @@
 Lower:         000292 -> 000292  Latin Small Letter Ezh
 Lower:         00029d -> 00029d  Latin Small Letter J With Crossed-Tail
 Lower:         00029e -> 00029e  Latin Small Letter Turned K
+Lower:         000345 -> 000345  Combining Greek Ypogegrammeni
+Lower: 000370..000371 -> 000371  Greek Capital Letter Heta
+Lower: 000372..000373 -> 000373  Greek Capital Letter Archaic Sampi
+Lower: 000376..000377 -> 000377  Greek Capital Letter Pamphylian Digamma
+Lower:         00037b -> 00037b  Greek Small Reversed Lunate Sigma Symbol
+Lower:         00037c -> 00037c  Greek Small Dotted Lunate Sigma Symbol
+Lower:         00037d -> 00037d  Greek Small Reversed Dotted Lunate Sigma Symbol
+Lower:         00037f -> 0003f3  Greek Capital Letter Yot
 Lower:         000386 -> 0003ac  Greek Capital Letter Alpha With Tonos
 Lower:         000388 -> 0003ad  Greek Capital Letter Epsilon With Tonos
 Lower:         000389 -> 0003ae  Greek Capital Letter Eta With Tonos
@@ -853,6 +858,7 @@
 Lower:         0003bf -> 0003bf  Greek Small Letter Omicron
 Lower:         0003c0 -> 0003c0  Greek Small Letter Pi
 Lower:         0003c1 -> 0003c1  Greek Small Letter Rho
+Lower:         0003c2 -> 0003c2  Greek Small Letter Final Sigma
 Lower:         0003c3 -> 0003c3  Greek Small Letter Sigma
 Lower:         0003c4 -> 0003c4  Greek Small Letter Tau
 Lower:         0003c5 -> 0003c5  Greek Small Letter Upsilon
@@ -865,6 +871,13 @@
 Lower:         0003cc -> 0003cc  Greek Small Letter Omicron With Tonos
 Lower:         0003cd -> 0003cd  Greek Small Letter Upsilon With Tonos
 Lower:         0003ce -> 0003ce  Greek Small Letter Omega With Tonos
+Lower:         0003cf -> 0003d7  Greek Capital Kai Symbol
+Lower:         0003d0 -> 0003d0  Greek Beta Symbol
+Lower:         0003d1 -> 0003d1  Greek Theta Symbol
+Lower:         0003d5 -> 0003d5  Greek Phi Symbol
+Lower:         0003d6 -> 0003d6  Greek Pi Symbol
+Lower:         0003d7 -> 0003d7  Greek Kai Symbol
+Lower: 0003d8..0003d9 -> 0003d9  Greek Letter Archaic Koppa
 Lower: 0003da..0003db -> 0003db  Greek Letter Stigma
 Lower: 0003dc..0003dd -> 0003dd  Greek Letter Digamma
 Lower: 0003de..0003df -> 0003df  Greek Letter Koppa
@@ -876,6 +889,18 @@
 Lower: 0003ea..0003eb -> 0003eb  Coptic Capital Letter Gangia
 Lower: 0003ec..0003ed -> 0003ed  Coptic Capital Letter Shima
 Lower: 0003ee..0003ef -> 0003ef  Coptic Capital Letter Dei
+Lower:         0003f0 -> 0003f0  Greek Kappa Symbol
+Lower:         0003f1 -> 0003f1  Greek Rho Symbol
+Lower:         0003f2 -> 0003f2  Greek Lunate Sigma Symbol
+Lower:         0003f3 -> 0003f3  Greek Letter Yot
+Lower:         0003f4 -> 0003b8  Greek Capital Theta Symbol
+Lower:         0003f5 -> 0003f5  Greek Lunate Epsilon Symbol
+Lower: 0003f7..0003f8 -> 0003f8  Greek Capital Letter Sho
+Lower:         0003f9 -> 0003f2  Greek Capital Lunate Sigma Symbol
+Lower: 0003fa..0003fb -> 0003fb  Greek Capital Letter San
+Lower:         0003fd -> 00037b  Greek Capital Reversed Lunate Sigma Symbol
+Lower:         0003fe -> 00037c  Greek Capital Dotted Lunate Sigma Symbol
+Lower:         0003ff -> 00037d  Greek Capital Reversed Dotted Lunate Sigma Symbol
 Lower:         000400 -> 000450  Cyrillic Capital Letter Ie With Grave
 Lower:         000401 -> 000451  Cyrillic Capital Letter Io
 Lower:         000402 -> 000452  Cyrillic Capital Letter Dje
@@ -989,6 +1014,7 @@
 Lower: 00047c..00047d -> 00047d  Cyrillic Capital Letter Omega With Titlo
 Lower: 00047e..00047f -> 00047f  Cyrillic Capital Letter Ot
 Lower: 000480..000481 -> 000481  Cyrillic Capital Letter Koppa
+Lower: 00048a..00048b -> 00048b  Cyrillic Capital Letter Short I With Tail
 Lower: 00048c..00048d -> 00048d  Cyrillic Capital Letter Semisoft Sign
 Lower: 00048e..00048f -> 00048f  Cyrillic Capital Letter Er With Tick
 Lower: 000490..000491 -> 000491  Cyrillic Capital Letter Ghe With Upturn
@@ -1015,10 +1041,15 @@
 Lower: 0004ba..0004bb -> 0004bb  Cyrillic Capital Letter Shha
 Lower: 0004bc..0004bd -> 0004bd  Cyrillic Capital Letter Abkhasian Che
 Lower: 0004be..0004bf -> 0004bf  Cyrillic Capital Letter Abkhasian Che With Descender
+Lower:         0004c0 -> 0004cf  Cyrillic Letter Palochka
 Lower: 0004c1..0004c2 -> 0004c2  Cyrillic Capital Letter Zhe With Breve
 Lower: 0004c3..0004c4 -> 0004c4  Cyrillic Capital Letter Ka With Hook
+Lower: 0004c5..0004c6 -> 0004c6  Cyrillic Capital Letter El With Tail
 Lower: 0004c7..0004c8 -> 0004c8  Cyrillic Capital Letter En With Hook
+Lower: 0004c9..0004ca -> 0004ca  Cyrillic Capital Letter En With Tail
 Lower: 0004cb..0004cc -> 0004cc  Cyrillic Capital Letter Khakassian Che
+Lower: 0004cd..0004ce -> 0004ce  Cyrillic Capital Letter Em With Tail
+Lower:         0004cf -> 0004cf  Cyrillic Small Letter Palochka
 Lower: 0004d0..0004d1 -> 0004d1  Cyrillic Capital Letter A With Breve
 Lower: 0004d2..0004d3 -> 0004d3  Cyrillic Capital Letter A With Diaeresis
 Lower: 0004d4..0004d5 -> 0004d5  Cyrillic Capital Ligature A Ie
@@ -1043,6 +1074,30 @@
 Lower: 0004fa..0004fb -> 0004fb  Cyrillic Capital Letter Ghe With Stroke And Hook
 Lower: 0004fc..0004fd -> 0004fd  Cyrillic Capital Letter Ha With Hook
 Lower: 0004fe..0004ff -> 0004ff  Cyrillic Capital Letter Ha With Stroke
+Lower: 000500..000501 -> 000501  Cyrillic Capital Letter Komi De
+Lower: 000502..000503 -> 000503  Cyrillic Capital Letter Komi Dje
+Lower: 000504..000505 -> 000505  Cyrillic Capital Letter Komi Zje
+Lower: 000506..000507 -> 000507  Cyrillic Capital Letter Komi Dzje
+Lower: 000508..000509 -> 000509  Cyrillic Capital Letter Komi Lje
+Lower: 00050a..00050b -> 00050b  Cyrillic Capital Letter Komi Nje
+Lower: 00050c..00050d -> 00050d  Cyrillic Capital Letter Komi Sje
+Lower: 00050e..00050f -> 00050f  Cyrillic Capital Letter Komi Tje
+Lower: 000510..000511 -> 000511  Cyrillic Capital Letter Reversed Ze
+Lower: 000512..000513 -> 000513  Cyrillic Capital Letter El With Hook
+Lower: 000514..000515 -> 000515  Cyrillic Capital Letter Lha
+Lower: 000516..000517 -> 000517  Cyrillic Capital Letter Rha
+Lower: 000518..000519 -> 000519  Cyrillic Capital Letter Yae
+Lower: 00051a..00051b -> 00051b  Cyrillic Capital Letter Qa
+Lower: 00051c..00051d -> 00051d  Cyrillic Capital Letter We
+Lower: 00051e..00051f -> 00051f  Cyrillic Capital Letter Aleut Ka
+Lower: 000520..000521 -> 000521  Cyrillic Capital Letter El With Middle Hook
+Lower: 000522..000523 -> 000523  Cyrillic Capital Letter En With Middle Hook
+Lower: 000524..000525 -> 000525  Cyrillic Capital Letter Pe With Descender
+Lower: 000526..000527 -> 000527  Cyrillic Capital Letter Shha With Descender
+Lower: 000528..000529 -> 000529  Cyrillic Capital Letter En With Left Hook
+Lower: 00052a..00052b -> 00052b  Cyrillic Capital Letter Dzzhe
+Lower: 00052c..00052d -> 00052d  Cyrillic Capital Letter Dche
+Lower: 00052e..00052f -> 00052f  Cyrillic Capital Letter El With Descender
 Lower:         000531 -> 000561  Armenian Capital Letter Ayb
 Lower:         000532 -> 000562  Armenian Capital Letter Ben
 Lower:         000533 -> 000563  Armenian Capital Letter Gim
@@ -1157,14 +1212,109 @@
 Lower:         0010c3 -> 002d23  Georgian Capital Letter We
 Lower:         0010c4 -> 002d24  Georgian Capital Letter Har
 Lower:         0010c5 -> 002d25  Georgian Capital Letter Hoe
-Lower:         0010c6 -> 002d26
 Lower:         0010c7 -> 002d27  Georgian Capital Letter Yn
-Lower:         0010c8 -> 002d28
-Lower:         0010c9 -> 002d29
-Lower:         0010ca -> 002d2a
-Lower:         0010cb -> 002d2b
-Lower:         0010cc -> 002d2c
 Lower:         0010cd -> 002d2d  Georgian Capital Letter Aen
+Lower:         0013a0 -> 00ab70  Cherokee Letter A
+Lower:         0013a1 -> 00ab71  Cherokee Letter E
+Lower:         0013a2 -> 00ab72  Cherokee Letter I
+Lower:         0013a3 -> 00ab73  Cherokee Letter O
+Lower:         0013a4 -> 00ab74  Cherokee Letter U
+Lower:         0013a5 -> 00ab75  Cherokee Letter V
+Lower:         0013a6 -> 00ab76  Cherokee Letter Ga
+Lower:         0013a7 -> 00ab77  Cherokee Letter Ka
+Lower:         0013a8 -> 00ab78  Cherokee Letter Ge
+Lower:         0013a9 -> 00ab79  Cherokee Letter Gi
+Lower:         0013aa -> 00ab7a  Cherokee Letter Go
+Lower:         0013ab -> 00ab7b  Cherokee Letter Gu
+Lower:         0013ac -> 00ab7c  Cherokee Letter Gv
+Lower:         0013ad -> 00ab7d  Cherokee Letter Ha
+Lower:         0013ae -> 00ab7e  Cherokee Letter He
+Lower:         0013af -> 00ab7f  Cherokee Letter Hi
+Lower:         0013b0 -> 00ab80  Cherokee Letter Ho
+Lower:         0013b1 -> 00ab81  Cherokee Letter Hu
+Lower:         0013b2 -> 00ab82  Cherokee Letter Hv
+Lower:         0013b3 -> 00ab83  Cherokee Letter La
+Lower:         0013b4 -> 00ab84  Cherokee Letter Le
+Lower:         0013b5 -> 00ab85  Cherokee Letter Li
+Lower:         0013b6 -> 00ab86  Cherokee Letter Lo
+Lower:         0013b7 -> 00ab87  Cherokee Letter Lu
+Lower:         0013b8 -> 00ab88  Cherokee Letter Lv
+Lower:         0013b9 -> 00ab89  Cherokee Letter Ma
+Lower:         0013ba -> 00ab8a  Cherokee Letter Me
+Lower:         0013bb -> 00ab8b  Cherokee Letter Mi
+Lower:         0013bc -> 00ab8c  Cherokee Letter Mo
+Lower:         0013bd -> 00ab8d  Cherokee Letter Mu
+Lower:         0013be -> 00ab8e  Cherokee Letter Na
+Lower:         0013bf -> 00ab8f  Cherokee Letter Hna
+Lower:         0013c0 -> 00ab90  Cherokee Letter Nah
+Lower:         0013c1 -> 00ab91  Cherokee Letter Ne
+Lower:         0013c2 -> 00ab92  Cherokee Letter Ni
+Lower:         0013c3 -> 00ab93  Cherokee Letter No
+Lower:         0013c4 -> 00ab94  Cherokee Letter Nu
+Lower:         0013c5 -> 00ab95  Cherokee Letter Nv
+Lower:         0013c6 -> 00ab96  Cherokee Letter Qua
+Lower:         0013c7 -> 00ab97  Cherokee Letter Que
+Lower:         0013c8 -> 00ab98  Cherokee Letter Qui
+Lower:         0013c9 -> 00ab99  Cherokee Letter Quo
+Lower:         0013ca -> 00ab9a  Cherokee Letter Quu
+Lower:         0013cb -> 00ab9b  Cherokee Letter Quv
+Lower:         0013cc -> 00ab9c  Cherokee Letter Sa
+Lower:         0013cd -> 00ab9d  Cherokee Letter S
+Lower:         0013ce -> 00ab9e  Cherokee Letter Se
+Lower:         0013cf -> 00ab9f  Cherokee Letter Si
+Lower:         0013d0 -> 00aba0  Cherokee Letter So
+Lower:         0013d1 -> 00aba1  Cherokee Letter Su
+Lower:         0013d2 -> 00aba2  Cherokee Letter Sv
+Lower:         0013d3 -> 00aba3  Cherokee Letter Da
+Lower:         0013d4 -> 00aba4  Cherokee Letter Ta
+Lower:         0013d5 -> 00aba5  Cherokee Letter De
+Lower:         0013d6 -> 00aba6  Cherokee Letter Te
+Lower:         0013d7 -> 00aba7  Cherokee Letter Di
+Lower:         0013d8 -> 00aba8  Cherokee Letter Ti
+Lower:         0013d9 -> 00aba9  Cherokee Letter Do
+Lower:         0013da -> 00abaa  Cherokee Letter Du
+Lower:         0013db -> 00abab  Cherokee Letter Dv
+Lower:         0013dc -> 00abac  Cherokee Letter Dla
+Lower:         0013dd -> 00abad  Cherokee Letter Tla
+Lower:         0013de -> 00abae  Cherokee Letter Tle
+Lower:         0013df -> 00abaf  Cherokee Letter Tli
+Lower:         0013e0 -> 00abb0  Cherokee Letter Tlo
+Lower:         0013e1 -> 00abb1  Cherokee Letter Tlu
+Lower:         0013e2 -> 00abb2  Cherokee Letter Tlv
+Lower:         0013e3 -> 00abb3  Cherokee Letter Tsa
+Lower:         0013e4 -> 00abb4  Cherokee Letter Tse
+Lower:         0013e5 -> 00abb5  Cherokee Letter Tsi
+Lower:         0013e6 -> 00abb6  Cherokee Letter Tso
+Lower:         0013e7 -> 00abb7  Cherokee Letter Tsu
+Lower:         0013e8 -> 00abb8  Cherokee Letter Tsv
+Lower:         0013e9 -> 00abb9  Cherokee Letter Wa
+Lower:         0013ea -> 00abba  Cherokee Letter We
+Lower:         0013eb -> 00abbb  Cherokee Letter Wi
+Lower:         0013ec -> 00abbc  Cherokee Letter Wo
+Lower:         0013ed -> 00abbd  Cherokee Letter Wu
+Lower:         0013ee -> 00abbe  Cherokee Letter Wv
+Lower:         0013ef -> 00abbf  Cherokee Letter Ya
+Lower:         0013f0 -> 0013f8  Cherokee Letter Ye
+Lower:         0013f1 -> 0013f9  Cherokee Letter Yi
+Lower:         0013f2 -> 0013fa  Cherokee Letter Yo
+Lower:         0013f3 -> 0013fb  Cherokee Letter Yu
+Lower:         0013f4 -> 0013fc  Cherokee Letter Yv
+Lower:         0013f5 -> 0013fd  Cherokee Letter Mv
+Lower:         0013f8 -> 0013f8  Cherokee Small Letter Ye
+Lower:         0013f9 -> 0013f9  Cherokee Small Letter Yi
+Lower:         0013fa -> 0013fa  Cherokee Small Letter Yo
+Lower:         0013fb -> 0013fb  Cherokee Small Letter Yu
+Lower:         0013fc -> 0013fc  Cherokee Small Letter Yv
+Lower:         0013fd -> 0013fd  Cherokee Small Letter Mv
+Lower:         001c80 -> 001c80  Cyrillic Small Letter Rounded Ve
+Lower:         001c81 -> 001c81  Cyrillic Small Letter Long-Legged De
+Lower:         001c82 -> 001c82  Cyrillic Small Letter Narrow O
+Lower:         001c83 -> 001c83  Cyrillic Small Letter Wide Es
+Lower:         001c84 -> 001c84  Cyrillic Small Letter Tall Te
+Lower:         001c85 -> 001c85  Cyrillic Small Letter Three-Legged Te
+Lower:         001c86 -> 001c86  Cyrillic Small Letter Tall Hard Sign
+Lower:         001c87 -> 001c87  Cyrillic Small Letter Tall Yat
+Lower:         001c88 -> 001c88  Cyrillic Small Letter Unblended Uk
 Lower:         001d79 -> 001d79  Latin Small Letter Insular G
 Lower:         001d7d -> 001d7d  Latin Small Letter P With Stroke
 Lower: 001e00..001e01 -> 001e01  Latin Capital Letter A With Ring Below
@@ -1242,6 +1392,8 @@
 Lower: 001e90..001e91 -> 001e91  Latin Capital Letter Z With Circumflex
 Lower: 001e92..001e93 -> 001e93  Latin Capital Letter Z With Dot Below
 Lower: 001e94..001e95 -> 001e95  Latin Capital Letter Z With Line Below
+Lower:         001e9b -> 001e9b  Latin Small Letter Long S With Dot Above
+Lower:         001e9e -> 0000df  Latin Capital Letter Sharp S
 Lower: 001ea0..001ea1 -> 001ea1  Latin Capital Letter A With Dot Below
 Lower: 001ea2..001ea3 -> 001ea3  Latin Capital Letter A With Hook Above
 Lower: 001ea4..001ea5 -> 001ea5  Latin Capital Letter A With Circumflex And Acute
@@ -1287,6 +1439,9 @@
 Lower: 001ef4..001ef5 -> 001ef5  Latin Capital Letter Y With Dot Below
 Lower: 001ef6..001ef7 -> 001ef7  Latin Capital Letter Y With Hook Above
 Lower: 001ef8..001ef9 -> 001ef9  Latin Capital Letter Y With Tilde
+Lower: 001efa..001efb -> 001efb  Latin Capital Letter Middle-Welsh Ll
+Lower: 001efc..001efd -> 001efd  Latin Capital Letter Middle-Welsh V
+Lower: 001efe..001eff -> 001eff  Latin Capital Letter Y With Loop
 Lower:         001f00 -> 001f00  Greek Small Letter Alpha With Psili
 Lower:         001f01 -> 001f01  Greek Small Letter Alpha With Dasia
 Lower:         001f02 -> 001f02  Greek Small Letter Alpha With Psili And Varia
@@ -1353,22 +1508,20 @@
 Lower:         001f43 -> 001f43  Greek Small Letter Omicron With Dasia And Varia
 Lower:         001f44 -> 001f44  Greek Small Letter Omicron With Psili And Oxia
 Lower:         001f45 -> 001f45  Greek Small Letter Omicron With Dasia And Oxia
-Lower:         001f46 -> 001f46
-Lower:         001f47 -> 001f47
 Lower:         001f48 -> 001f40  Greek Capital Letter Omicron With Psili
 Lower:         001f49 -> 001f41  Greek Capital Letter Omicron With Dasia
 Lower:         001f4a -> 001f42  Greek Capital Letter Omicron With Psili And Varia
 Lower:         001f4b -> 001f43  Greek Capital Letter Omicron With Dasia And Varia
 Lower:         001f4c -> 001f44  Greek Capital Letter Omicron With Psili And Oxia
 Lower:         001f4d -> 001f45  Greek Capital Letter Omicron With Dasia And Oxia
-Lower:         001f4e -> 001f46
-Lower:         001f4f -> 001f47
 Lower:         001f51 -> 001f51  Greek Small Letter Upsilon With Dasia
 Lower:         001f53 -> 001f53  Greek Small Letter Upsilon With Dasia And Varia
 Lower:         001f55 -> 001f55  Greek Small Letter Upsilon With Dasia And Oxia
+Lower:         001f57 -> 001f57  Greek Small Letter Upsilon With Dasia And Perispomeni
 Lower:         001f59 -> 001f51  Greek Capital Letter Upsilon With Dasia
 Lower:         001f5b -> 001f53  Greek Capital Letter Upsilon With Dasia And Varia
 Lower:         001f5d -> 001f55  Greek Capital Letter Upsilon With Dasia And Oxia
+Lower:         001f5f -> 001f57  Greek Capital Letter Upsilon With Dasia And Perispomeni
 Lower:         001f60 -> 001f60  Greek Small Letter Omega With Psili
 Lower:         001f61 -> 001f61  Greek Small Letter Omega With Dasia
 Lower:         001f62 -> 001f62  Greek Small Letter Omega With Psili And Varia
@@ -1455,6 +1608,7 @@
 Lower:         001fba -> 001f70  Greek Capital Letter Alpha With Varia
 Lower:         001fbb -> 001f71  Greek Capital Letter Alpha With Oxia
 Lower:         001fbc -> 001fb3  Greek Capital Letter Alpha With Prosgegrammeni
+Lower:         001fbe -> 001fbe  Greek Prosgegrammeni
 Lower:         001fc3 -> 001fc3  Greek Small Letter Eta With Ypogegrammeni
 Lower:         001fc8 -> 001f72  Greek Capital Letter Epsilon With Varia
 Lower:         001fc9 -> 001f73  Greek Capital Letter Epsilon With Oxia
@@ -1593,6 +1747,10 @@
 Lower:         00206d -> 00206d  Activate Arabic Form Shaping
 Lower:         00206e -> 00206e  National Digit Shapes
 Lower:         00206f -> 00206f  Nominal Digit Shapes
+Lower:         002126 -> 0003c9  Ohm Sign
+Lower:         00212b -> 0000e5  Angstrom Sign
+Lower:         002132 -> 00214e  Turned Capital F
+Lower:         00214e -> 00214e  Turned Small F
 Lower:         002160 -> 002170  Roman Numeral One
 Lower:         002161 -> 002171  Roman Numeral Two
 Lower:         002162 -> 002172  Roman Numeral Three
@@ -1625,6 +1783,7 @@
 Lower:         00217d -> 00217d  Small Roman Numeral One Hundred
 Lower:         00217e -> 00217e  Small Roman Numeral Five Hundred
 Lower:         00217f -> 00217f  Small Roman Numeral One Thousand
+Lower: 002183..002184 -> 002184  Roman Numeral Reversed One Hundred
 Lower:         002190 -> 002190  Leftwards Arrow
 Lower:         002191 -> 002191  Upwards Arrow
 Lower:         002192 -> 002192  Rightwards Arrow
@@ -2525,10 +2684,10 @@
 Lower: 002cdc..002cdd -> 002cdd  Coptic Capital Letter Old Nubian Shima
 Lower: 002cde..002cdf -> 002cdf  Coptic Capital Letter Old Nubian Ngi
 Lower: 002ce0..002ce1 -> 002ce1  Coptic Capital Letter Old Nubian Nyi
+Lower: 002ce2..002ce3 -> 002ce3  Coptic Capital Letter Old Nubian Wau
 Lower: 002ceb..002cec -> 002cec  Coptic Capital Letter Cryptogrammic Shei
 Lower: 002ced..002cee -> 002cee  Coptic Capital Letter Cryptogrammic Gangia
-Lower: 002cef..002cf0 -> 002cf0  Coptic Combining Ni Above
-Lower: 002cf1..002cf2 -> 002cf2  Coptic Combining Spiritus Lenis
+Lower: 002cf2..002cf3 -> 002cf3  Coptic Capital Letter Bohairic Khei
 Lower:         002d00 -> 002d00  Georgian Small Letter An
 Lower:         002d01 -> 002d01  Georgian Small Letter Ban
 Lower:         002d02 -> 002d02  Georgian Small Letter Gan
@@ -2567,13 +2726,7 @@
 Lower:         002d23 -> 002d23  Georgian Small Letter We
 Lower:         002d24 -> 002d24  Georgian Small Letter Har
 Lower:         002d25 -> 002d25  Georgian Small Letter Hoe
-Lower:         002d26 -> 002d26
 Lower:         002d27 -> 002d27  Georgian Small Letter Yn
-Lower:         002d28 -> 002d28
-Lower:         002d29 -> 002d29
-Lower:         002d2a -> 002d2a
-Lower:         002d2b -> 002d2b
-Lower:         002d2c -> 002d2c
 Lower:         002d2d -> 002d2d  Georgian Small Letter Aen
 Lower: 00a640..00a641 -> 00a641  Cyrillic Capital Letter Zemlya
 Lower: 00a642..00a643 -> 00a643  Cyrillic Capital Letter Dzelo
@@ -2659,7 +2812,7 @@
 Lower: 00a784..00a785 -> 00a785  Latin Capital Letter Insular S
 Lower: 00a786..00a787 -> 00a787  Latin Capital Letter Insular T
 Lower: 00a78b..00a78c -> 00a78c  Latin Capital Letter Saltillo
-Lower: 00a78d..00a78e -> 00a78e  Latin Capital Letter Turned H
+Lower:         00a78d -> 000265  Latin Capital Letter Turned H
 Lower: 00a790..00a791 -> 00a791  Latin Capital Letter N With Descender
 Lower: 00a792..00a793 -> 00a793  Latin Capital Letter C With Bar
 Lower: 00a796..00a797 -> 00a797  Latin Capital Letter B With Flourish
@@ -2684,6 +2837,86 @@
 Lower: 00a7b4..00a7b5 -> 00a7b5  Latin Capital Letter Beta
 Lower: 00a7b6..00a7b7 -> 00a7b7  Latin Capital Letter Omega
 Lower:         00ab53 -> 00ab53  Latin Small Letter Chi
+Lower:         00ab70 -> 00ab70  Cherokee Small Letter A
+Lower:         00ab71 -> 00ab71  Cherokee Small Letter E
+Lower:         00ab72 -> 00ab72  Cherokee Small Letter I
+Lower:         00ab73 -> 00ab73  Cherokee Small Letter O
+Lower:         00ab74 -> 00ab74  Cherokee Small Letter U
+Lower:         00ab75 -> 00ab75  Cherokee Small Letter V
+Lower:         00ab76 -> 00ab76  Cherokee Small Letter Ga
+Lower:         00ab77 -> 00ab77  Cherokee Small Letter Ka
+Lower:         00ab78 -> 00ab78  Cherokee Small Letter Ge
+Lower:         00ab79 -> 00ab79  Cherokee Small Letter Gi
+Lower:         00ab7a -> 00ab7a  Cherokee Small Letter Go
+Lower:         00ab7b -> 00ab7b  Cherokee Small Letter Gu
+Lower:         00ab7c -> 00ab7c  Cherokee Small Letter Gv
+Lower:         00ab7d -> 00ab7d  Cherokee Small Letter Ha
+Lower:         00ab7e -> 00ab7e  Cherokee Small Letter He
+Lower:         00ab7f -> 00ab7f  Cherokee Small Letter Hi
+Lower:         00ab80 -> 00ab80  Cherokee Small Letter Ho
+Lower:         00ab81 -> 00ab81  Cherokee Small Letter Hu
+Lower:         00ab82 -> 00ab82  Cherokee Small Letter Hv
+Lower:         00ab83 -> 00ab83  Cherokee Small Letter La
+Lower:         00ab84 -> 00ab84  Cherokee Small Letter Le
+Lower:         00ab85 -> 00ab85  Cherokee Small Letter Li
+Lower:         00ab86 -> 00ab86  Cherokee Small Letter Lo
+Lower:         00ab87 -> 00ab87  Cherokee Small Letter Lu
+Lower:         00ab88 -> 00ab88  Cherokee Small Letter Lv
+Lower:         00ab89 -> 00ab89  Cherokee Small Letter Ma
+Lower:         00ab8a -> 00ab8a  Cherokee Small Letter Me
+Lower:         00ab8b -> 00ab8b  Cherokee Small Letter Mi
+Lower:         00ab8c -> 00ab8c  Cherokee Small Letter Mo
+Lower:         00ab8d -> 00ab8d  Cherokee Small Letter Mu
+Lower:         00ab8e -> 00ab8e  Cherokee Small Letter Na
+Lower:         00ab8f -> 00ab8f  Cherokee Small Letter Hna
+Lower:         00ab90 -> 00ab90  Cherokee Small Letter Nah
+Lower:         00ab91 -> 00ab91  Cherokee Small Letter Ne
+Lower:         00ab92 -> 00ab92  Cherokee Small Letter Ni
+Lower:         00ab93 -> 00ab93  Cherokee Small Letter No
+Lower:         00ab94 -> 00ab94  Cherokee Small Letter Nu
+Lower:         00ab95 -> 00ab95  Cherokee Small Letter Nv
+Lower:         00ab96 -> 00ab96  Cherokee Small Letter Qua
+Lower:         00ab97 -> 00ab97  Cherokee Small Letter Que
+Lower:         00ab98 -> 00ab98  Cherokee Small Letter Qui
+Lower:         00ab99 -> 00ab99  Cherokee Small Letter Quo
+Lower:         00ab9a -> 00ab9a  Cherokee Small Letter Quu
+Lower:         00ab9b -> 00ab9b  Cherokee Small Letter Quv
+Lower:         00ab9c -> 00ab9c  Cherokee Small Letter Sa
+Lower:         00ab9d -> 00ab9d  Cherokee Small Letter S
+Lower:         00ab9e -> 00ab9e  Cherokee Small Letter Se
+Lower:         00ab9f -> 00ab9f  Cherokee Small Letter Si
+Lower:         00aba0 -> 00aba0  Cherokee Small Letter So
+Lower:         00aba1 -> 00aba1  Cherokee Small Letter Su
+Lower:         00aba2 -> 00aba2  Cherokee Small Letter Sv
+Lower:         00aba3 -> 00aba3  Cherokee Small Letter Da
+Lower:         00aba4 -> 00aba4  Cherokee Small Letter Ta
+Lower:         00aba5 -> 00aba5  Cherokee Small Letter De
+Lower:         00aba6 -> 00aba6  Cherokee Small Letter Te
+Lower:         00aba7 -> 00aba7  Cherokee Small Letter Di
+Lower:         00aba8 -> 00aba8  Cherokee Small Letter Ti
+Lower:         00aba9 -> 00aba9  Cherokee Small Letter Do
+Lower:         00abaa -> 00abaa  Cherokee Small Letter Du
+Lower:         00abab -> 00abab  Cherokee Small Letter Dv
+Lower:         00abac -> 00abac  Cherokee Small Letter Dla
+Lower:         00abad -> 00abad  Cherokee Small Letter Tla
+Lower:         00abae -> 00abae  Cherokee Small Letter Tle
+Lower:         00abaf -> 00abaf  Cherokee Small Letter Tli
+Lower:         00abb0 -> 00abb0  Cherokee Small Letter Tlo
+Lower:         00abb1 -> 00abb1  Cherokee Small Letter Tlu
+Lower:         00abb2 -> 00abb2  Cherokee Small Letter Tlv
+Lower:         00abb3 -> 00abb3  Cherokee Small Letter Tsa
+Lower:         00abb4 -> 00abb4  Cherokee Small Letter Tse
+Lower:         00abb5 -> 00abb5  Cherokee Small Letter Tsi
+Lower:         00abb6 -> 00abb6  Cherokee Small Letter Tso
+Lower:         00abb7 -> 00abb7  Cherokee Small Letter Tsu
+Lower:         00abb8 -> 00abb8  Cherokee Small Letter Tsv
+Lower:         00abb9 -> 00abb9  Cherokee Small Letter Wa
+Lower:         00abba -> 00abba  Cherokee Small Letter We
+Lower:         00abbb -> 00abbb  Cherokee Small Letter Wi
+Lower:         00abbc -> 00abbc  Cherokee Small Letter Wo
+Lower:         00abbd -> 00abbd  Cherokee Small Letter Wu
+Lower:         00abbe -> 00abbe  Cherokee Small Letter Wv
+Lower:         00abbf -> 00abbf  Cherokee Small Letter Ya
 Lower:         00ff21 -> 00ff41  Fullwidth Latin Capital Letter A
 Lower:         00ff22 -> 00ff42  Fullwidth Latin Capital Letter B
 Lower:         00ff23 -> 00ff43  Fullwidth Latin Capital Letter C
@@ -2736,46 +2969,46 @@
 Lower:         00ff58 -> 00ff58  Fullwidth Latin Small Letter X
 Lower:         00ff59 -> 00ff59  Fullwidth Latin Small Letter Y
 Lower:         00ff5a -> 00ff5a  Fullwidth Latin Small Letter Z
-Lower:         010400 -> 01041c  Deseret Capital Letter Long I
-Lower:         010401 -> 01041d  Deseret Capital Letter Long E
-Lower:         010402 -> 01041e  Deseret Capital Letter Long A
-Lower:         010403 -> 01041f  Deseret Capital Letter Long Ah
-Lower:         010404 -> 010420  Deseret Capital Letter Long O
-Lower:         010405 -> 010421  Deseret Capital Letter Long Oo
-Lower:         010406 -> 010422  Deseret Capital Letter Short I
-Lower:         010407 -> 010423  Deseret Capital Letter Short E
-Lower:         010408 -> 010424  Deseret Capital Letter Short A
-Lower:         010409 -> 010425  Deseret Capital Letter Short Ah
-Lower:         01040a -> 010426  Deseret Capital Letter Short O
-Lower:         01040b -> 010427  Deseret Capital Letter Short Oo
-Lower:         01040c -> 010428  Deseret Capital Letter Ay
-Lower:         01040d -> 010429  Deseret Capital Letter Ow
-Lower:         01040e -> 01042a  Deseret Capital Letter Wu
-Lower:         01040f -> 01042b  Deseret Capital Letter Yee
-Lower:         010410 -> 01042c  Deseret Capital Letter H
-Lower:         010411 -> 01042d  Deseret Capital Letter Pee
-Lower:         010412 -> 01042e  Deseret Capital Letter Bee
-Lower:         010413 -> 01042f  Deseret Capital Letter Tee
-Lower:         010414 -> 010430  Deseret Capital Letter Dee
-Lower:         010415 -> 010431  Deseret Capital Letter Chee
-Lower:         010416 -> 010432  Deseret Capital Letter Jee
-Lower:         010417 -> 010433  Deseret Capital Letter Kay
-Lower:         010418 -> 010434  Deseret Capital Letter Gay
-Lower:         010419 -> 010435  Deseret Capital Letter Ef
-Lower:         01041a -> 010436  Deseret Capital Letter Vee
-Lower:         01041b -> 010437  Deseret Capital Letter Eth
-Lower:         01041c -> 010438  Deseret Capital Letter Thee
-Lower:         01041d -> 010439  Deseret Capital Letter Es
-Lower:         01041e -> 01043a  Deseret Capital Letter Zee
-Lower:         01041f -> 01043b  Deseret Capital Letter Esh
-Lower:         010420 -> 01043c  Deseret Capital Letter Zhee
-Lower:         010421 -> 01043d  Deseret Capital Letter Er
-Lower:         010422 -> 01043e  Deseret Capital Letter El
-Lower:         010423 -> 01043f  Deseret Capital Letter Em
-Lower:         010424 -> 010440  Deseret Capital Letter En
-Lower:         010425 -> 010441  Deseret Capital Letter Eng
-Lower:         010426 -> 010442  Deseret Capital Letter Oi
-Lower:         010427 -> 010443  Deseret Capital Letter Ew
+Lower:         010400 -> 010428  Deseret Capital Letter Long I
+Lower:         010401 -> 010429  Deseret Capital Letter Long E
+Lower:         010402 -> 01042a  Deseret Capital Letter Long A
+Lower:         010403 -> 01042b  Deseret Capital Letter Long Ah
+Lower:         010404 -> 01042c  Deseret Capital Letter Long O
+Lower:         010405 -> 01042d  Deseret Capital Letter Long Oo
+Lower:         010406 -> 01042e  Deseret Capital Letter Short I
+Lower:         010407 -> 01042f  Deseret Capital Letter Short E
+Lower:         010408 -> 010430  Deseret Capital Letter Short A
+Lower:         010409 -> 010431  Deseret Capital Letter Short Ah
+Lower:         01040a -> 010432  Deseret Capital Letter Short O
+Lower:         01040b -> 010433  Deseret Capital Letter Short Oo
+Lower:         01040c -> 010434  Deseret Capital Letter Ay
+Lower:         01040d -> 010435  Deseret Capital Letter Ow
+Lower:         01040e -> 010436  Deseret Capital Letter Wu
+Lower:         01040f -> 010437  Deseret Capital Letter Yee
+Lower:         010410 -> 010438  Deseret Capital Letter H
+Lower:         010411 -> 010439  Deseret Capital Letter Pee
+Lower:         010412 -> 01043a  Deseret Capital Letter Bee
+Lower:         010413 -> 01043b  Deseret Capital Letter Tee
+Lower:         010414 -> 01043c  Deseret Capital Letter Dee
+Lower:         010415 -> 01043d  Deseret Capital Letter Chee
+Lower:         010416 -> 01043e  Deseret Capital Letter Jee
+Lower:         010417 -> 01043f  Deseret Capital Letter Kay
+Lower:         010418 -> 010440  Deseret Capital Letter Gay
+Lower:         010419 -> 010441  Deseret Capital Letter Ef
+Lower:         01041a -> 010442  Deseret Capital Letter Vee
+Lower:         01041b -> 010443  Deseret Capital Letter Eth
+Lower:         01041c -> 010444  Deseret Capital Letter Thee
+Lower:         01041d -> 010445  Deseret Capital Letter Es
+Lower:         01041e -> 010446  Deseret Capital Letter Zee
+Lower:         01041f -> 010447  Deseret Capital Letter Esh
+Lower:         010420 -> 010448  Deseret Capital Letter Zhee
+Lower:         010421 -> 010449  Deseret Capital Letter Er
+Lower:         010422 -> 01044a  Deseret Capital Letter El
+Lower:         010423 -> 01044b  Deseret Capital Letter Em
+Lower:         010424 -> 01044c  Deseret Capital Letter En
+Lower:         010425 -> 01044d  Deseret Capital Letter Eng
+Lower:         010426 -> 01044e  Deseret Capital Letter Oi
+Lower:         010427 -> 01044f  Deseret Capital Letter Ew
 Lower:         010428 -> 010428  Deseret Small Letter Long I
 Lower:         010429 -> 010429  Deseret Small Letter Long E
 Lower:         01042a -> 01042a  Deseret Small Letter Long A
@@ -2804,6 +3037,18 @@
 Lower:         010441 -> 010441  Deseret Small Letter Ef
 Lower:         010442 -> 010442  Deseret Small Letter Vee
 Lower:         010443 -> 010443  Deseret Small Letter Eth
+Lower:         010444 -> 010444  Deseret Small Letter Thee
+Lower:         010445 -> 010445  Deseret Small Letter Es
+Lower:         010446 -> 010446  Deseret Small Letter Zee
+Lower:         010447 -> 010447  Deseret Small Letter Esh
+Lower:         010448 -> 010448  Deseret Small Letter Zhee
+Lower:         010449 -> 010449  Deseret Small Letter Er
+Lower:         01044a -> 01044a  Deseret Small Letter El
+Lower:         01044b -> 01044b  Deseret Small Letter Em
+Lower:         01044c -> 01044c  Deseret Small Letter En
+Lower:         01044d -> 01044d  Deseret Small Letter Eng
+Lower:         01044e -> 01044e  Deseret Small Letter Oi
+Lower:         01044f -> 01044f  Deseret Small Letter Ew
 Lower:         0104b0 -> 0104d8  Osage Capital Letter A
 Lower:         0104b1 -> 0104d9  Osage Capital Letter Ai
 Lower:         0104b2 -> 0104da  Osage Capital Letter Ain
@@ -3307,7 +3552,7 @@
 Upper:         0000ae -> 0000ae  Registered Sign
 Upper:         0000b0 -> 0000b0  Degree Sign
 Upper:         0000b1 -> 0000b1  Plus-Minus Sign
-Upper:         0000b5 -> 0000b5  Micro Sign
+Upper:         0000b5 -> 00039c  Micro Sign
 Upper:         0000b7 -> 0000b7  Middle Dot
 Upper:         0000bb -> 0000bb  Right-Pointing Double Angle Quotation Mark
 Upper:         0000bc -> 0000bc  Vulgar Fraction One Quarter
@@ -3462,6 +3707,7 @@
 Upper:         00019a -> 00023d  Latin Small Letter L With Bar
 Upper:         00019c -> 00019c  Latin Capital Letter Turned M
 Upper:         00019d -> 00019d  Latin Capital Letter N With Left Hook
+Upper:         00019e -> 000220  Latin Small Letter N With Long Right Leg
 Upper:         00019f -> 00019f  Latin Capital Letter O With Middle Tilde
 Upper: 0001a0..0001a1 -> 0001a0  Latin Capital Letter O With Horn
 Upper: 0001a2..0001a3 -> 0001a2  Latin Capital Letter Oi
@@ -3480,15 +3726,9 @@
 Upper: 0001b8..0001b9 -> 0001b8  Latin Capital Letter Ezh Reversed
 Upper: 0001bc..0001bd -> 0001bc  Latin Capital Letter Tone Five
 Upper:         0001bf -> 0001f7  Latin Letter Wynn
-Upper:         0001c4 -> 0001c4  Latin Capital Letter Dz With Caron
-Upper:         0001c5 -> 0001c5  Latin Capital Letter D With Small Letter Z With Caron
-Upper:         0001c6 -> 0001c4  Latin Small Letter Dz With Caron
-Upper:         0001c7 -> 0001c7  Latin Capital Letter Lj
-Upper:         0001c8 -> 0001c8  Latin Capital Letter L With Small Letter J
-Upper:         0001c9 -> 0001c7  Latin Small Letter Lj
-Upper:         0001ca -> 0001ca  Latin Capital Letter Nj
-Upper:         0001cb -> 0001cb  Latin Capital Letter N With Small Letter J
-Upper:         0001cc -> 0001ca  Latin Small Letter Nj
+Upper: 0001c4..0001c6 -> 0001c4  Latin Capital Letter Dz With Caron
+Upper: 0001c7..0001c9 -> 0001c7  Latin Capital Letter Lj
+Upper: 0001ca..0001cc -> 0001ca  Latin Capital Letter Nj
 Upper: 0001cd..0001ce -> 0001cd  Latin Capital Letter A With Caron
 Upper: 0001cf..0001d0 -> 0001cf  Latin Capital Letter I With Caron
 Upper: 0001d1..0001d2 -> 0001d1  Latin Capital Letter O With Caron
@@ -3507,9 +3747,7 @@
 Upper: 0001ea..0001eb -> 0001ea  Latin Capital Letter O With Ogonek
 Upper: 0001ec..0001ed -> 0001ec  Latin Capital Letter O With Ogonek And Macron
 Upper: 0001ee..0001ef -> 0001ee  Latin Capital Letter Ezh With Caron
-Upper:         0001f1 -> 0001f1  Latin Capital Letter Dz
-Upper:         0001f2 -> 0001f2  Latin Capital Letter D With Small Letter Z
-Upper:         0001f3 -> 0001f1  Latin Small Letter Dz
+Upper: 0001f1..0001f3 -> 0001f1  Latin Capital Letter Dz
 Upper: 0001f4..0001f5 -> 0001f4  Latin Capital Letter G With Acute
 Upper:         0001f6 -> 0001f6  Latin Capital Letter Hwair
 Upper:         0001f7 -> 0001f7  Latin Capital Letter Wynn
@@ -3533,6 +3771,7 @@
 Upper: 00021a..00021b -> 00021a  Latin Capital Letter T With Comma Below
 Upper: 00021c..00021d -> 00021c  Latin Capital Letter Yogh
 Upper: 00021e..00021f -> 00021e  Latin Capital Letter H With Caron
+Upper:         000220 -> 000220  Latin Capital Letter N With Long Right Leg
 Upper: 000222..000223 -> 000222  Latin Capital Letter Ou
 Upper: 000224..000225 -> 000224  Latin Capital Letter Z With Hook
 Upper: 000226..000227 -> 000226  Latin Capital Letter A With Dot Above
@@ -3570,6 +3809,7 @@
 Upper:         000260 -> 000193  Latin Small Letter G With Hook
 Upper:         000261 -> 00a7ac  Latin Small Letter Script G
 Upper:         000263 -> 000194  Latin Small Letter Gamma
+Upper:         000265 -> 00a78d  Latin Small Letter Turned H
 Upper:         000266 -> 00a7aa  Latin Small Letter H With Hook
 Upper:         000268 -> 000197  Latin Small Letter I With Stroke
 Upper:         000269 -> 000196  Latin Small Letter Iota
@@ -3592,6 +3832,14 @@
 Upper:         000292 -> 0001b7  Latin Small Letter Ezh
 Upper:         00029d -> 00a7b2  Latin Small Letter J With Crossed-Tail
 Upper:         00029e -> 00a7b0  Latin Small Letter Turned K
+Upper:         000345 -> 000399  Combining Greek Ypogegrammeni
+Upper: 000370..000371 -> 000370  Greek Capital Letter Heta
+Upper: 000372..000373 -> 000372  Greek Capital Letter Archaic Sampi
+Upper: 000376..000377 -> 000376  Greek Capital Letter Pamphylian Digamma
+Upper:         00037b -> 0003fd  Greek Small Reversed Lunate Sigma Symbol
+Upper:         00037c -> 0003fe  Greek Small Dotted Lunate Sigma Symbol
+Upper:         00037d -> 0003ff  Greek Small Reversed Dotted Lunate Sigma Symbol
+Upper:         00037f -> 00037f  Greek Capital Letter Yot
 Upper:         000386 -> 000386  Greek Capital Letter Alpha With Tonos
 Upper:         000388 -> 000388  Greek Capital Letter Epsilon With Tonos
 Upper:         000389 -> 000389  Greek Capital Letter Eta With Tonos
@@ -3646,7 +3894,7 @@
 Upper:         0003bf -> 00039f  Greek Small Letter Omicron
 Upper:         0003c0 -> 0003a0  Greek Small Letter Pi
 Upper:         0003c1 -> 0003a1  Greek Small Letter Rho
-Upper:         0003c3 -> 0003a3  Greek Small Letter Sigma
+Upper: 0003c2..0003c3 -> 0003a3  Greek Small Letter Final Sigma
 Upper:         0003c4 -> 0003a4  Greek Small Letter Tau
 Upper:         0003c5 -> 0003a5  Greek Small Letter Upsilon
 Upper:         0003c6 -> 0003a6  Greek Small Letter Phi
@@ -3658,6 +3906,13 @@
 Upper:         0003cc -> 00038c  Greek Small Letter Omicron With Tonos
 Upper:         0003cd -> 00038e  Greek Small Letter Upsilon With Tonos
 Upper:         0003ce -> 00038f  Greek Small Letter Omega With Tonos
+Upper:         0003cf -> 0003cf  Greek Capital Kai Symbol
+Upper:         0003d0 -> 000392  Greek Beta Symbol
+Upper:         0003d1 -> 000398  Greek Theta Symbol
+Upper:         0003d5 -> 0003a6  Greek Phi Symbol
+Upper:         0003d6 -> 0003a0  Greek Pi Symbol
+Upper:         0003d7 -> 0003cf  Greek Kai Symbol
+Upper: 0003d8..0003d9 -> 0003d8  Greek Letter Archaic Koppa
 Upper: 0003da..0003db -> 0003da  Greek Letter Stigma
 Upper: 0003dc..0003dd -> 0003dc  Greek Letter Digamma
 Upper: 0003de..0003df -> 0003de  Greek Letter Koppa
@@ -3669,6 +3924,18 @@
 Upper: 0003ea..0003eb -> 0003ea  Coptic Capital Letter Gangia
 Upper: 0003ec..0003ed -> 0003ec  Coptic Capital Letter Shima
 Upper: 0003ee..0003ef -> 0003ee  Coptic Capital Letter Dei
+Upper:         0003f0 -> 00039a  Greek Kappa Symbol
+Upper:         0003f1 -> 0003a1  Greek Rho Symbol
+Upper:         0003f2 -> 0003f9  Greek Lunate Sigma Symbol
+Upper:         0003f3 -> 00037f  Greek Letter Yot
+Upper:         0003f4 -> 0003f4  Greek Capital Theta Symbol
+Upper:         0003f5 -> 000395  Greek Lunate Epsilon Symbol
+Upper: 0003f7..0003f8 -> 0003f7  Greek Capital Letter Sho
+Upper:         0003f9 -> 0003f9  Greek Capital Lunate Sigma Symbol
+Upper: 0003fa..0003fb -> 0003fa  Greek Capital Letter San
+Upper:         0003fd -> 0003fd  Greek Capital Reversed Lunate Sigma Symbol
+Upper:         0003fe -> 0003fe  Greek Capital Dotted Lunate Sigma Symbol
+Upper:         0003ff -> 0003ff  Greek Capital Reversed Dotted Lunate Sigma Symbol
 Upper:         000400 -> 000400  Cyrillic Capital Letter Ie With Grave
 Upper:         000401 -> 000401  Cyrillic Capital Letter Io
 Upper:         000402 -> 000402  Cyrillic Capital Letter Dje
@@ -3782,6 +4049,7 @@
 Upper: 00047c..00047d -> 00047c  Cyrillic Capital Letter Omega With Titlo
 Upper: 00047e..00047f -> 00047e  Cyrillic Capital Letter Ot
 Upper: 000480..000481 -> 000480  Cyrillic Capital Letter Koppa
+Upper: 00048a..00048b -> 00048a  Cyrillic Capital Letter Short I With Tail
 Upper: 00048c..00048d -> 00048c  Cyrillic Capital Letter Semisoft Sign
 Upper: 00048e..00048f -> 00048e  Cyrillic Capital Letter Er With Tick
 Upper: 000490..000491 -> 000490  Cyrillic Capital Letter Ghe With Upturn
@@ -3808,10 +4076,15 @@
 Upper: 0004ba..0004bb -> 0004ba  Cyrillic Capital Letter Shha
 Upper: 0004bc..0004bd -> 0004bc  Cyrillic Capital Letter Abkhasian Che
 Upper: 0004be..0004bf -> 0004be  Cyrillic Capital Letter Abkhasian Che With Descender
+Upper:         0004c0 -> 0004c0  Cyrillic Letter Palochka
 Upper: 0004c1..0004c2 -> 0004c1  Cyrillic Capital Letter Zhe With Breve
 Upper: 0004c3..0004c4 -> 0004c3  Cyrillic Capital Letter Ka With Hook
+Upper: 0004c5..0004c6 -> 0004c5  Cyrillic Capital Letter El With Tail
 Upper: 0004c7..0004c8 -> 0004c7  Cyrillic Capital Letter En With Hook
+Upper: 0004c9..0004ca -> 0004c9  Cyrillic Capital Letter En With Tail
 Upper: 0004cb..0004cc -> 0004cb  Cyrillic Capital Letter Khakassian Che
+Upper: 0004cd..0004ce -> 0004cd  Cyrillic Capital Letter Em With Tail
+Upper:         0004cf -> 0004c0  Cyrillic Small Letter Palochka
 Upper: 0004d0..0004d1 -> 0004d0  Cyrillic Capital Letter A With Breve
 Upper: 0004d2..0004d3 -> 0004d2  Cyrillic Capital Letter A With Diaeresis
 Upper: 0004d4..0004d5 -> 0004d4  Cyrillic Capital Ligature A Ie
@@ -3836,6 +4109,30 @@
 Upper: 0004fa..0004fb -> 0004fa  Cyrillic Capital Letter Ghe With Stroke And Hook
 Upper: 0004fc..0004fd -> 0004fc  Cyrillic Capital Letter Ha With Hook
 Upper: 0004fe..0004ff -> 0004fe  Cyrillic Capital Letter Ha With Stroke
+Upper: 000500..000501 -> 000500  Cyrillic Capital Letter Komi De
+Upper: 000502..000503 -> 000502  Cyrillic Capital Letter Komi Dje
+Upper: 000504..000505 -> 000504  Cyrillic Capital Letter Komi Zje
+Upper: 000506..000507 -> 000506  Cyrillic Capital Letter Komi Dzje
+Upper: 000508..000509 -> 000508  Cyrillic Capital Letter Komi Lje
+Upper: 00050a..00050b -> 00050a  Cyrillic Capital Letter Komi Nje
+Upper: 00050c..00050d -> 00050c  Cyrillic Capital Letter Komi Sje
+Upper: 00050e..00050f -> 00050e  Cyrillic Capital Letter Komi Tje
+Upper: 000510..000511 -> 000510  Cyrillic Capital Letter Reversed Ze
+Upper: 000512..000513 -> 000512  Cyrillic Capital Letter El With Hook
+Upper: 000514..000515 -> 000514  Cyrillic Capital Letter Lha
+Upper: 000516..000517 -> 000516  Cyrillic Capital Letter Rha
+Upper: 000518..000519 -> 000518  Cyrillic Capital Letter Yae
+Upper: 00051a..00051b -> 00051a  Cyrillic Capital Letter Qa
+Upper: 00051c..00051d -> 00051c  Cyrillic Capital Letter We
+Upper: 00051e..00051f -> 00051e  Cyrillic Capital Letter Aleut Ka
+Upper: 000520..000521 -> 000520  Cyrillic Capital Letter El With Middle Hook
+Upper: 000522..000523 -> 000522  Cyrillic Capital Letter En With Middle Hook
+Upper: 000524..000525 -> 000524  Cyrillic Capital Letter Pe With Descender
+Upper: 000526..000527 -> 000526  Cyrillic Capital Letter Shha With Descender
+Upper: 000528..000529 -> 000528  Cyrillic Capital Letter En With Left Hook
+Upper: 00052a..00052b -> 00052a  Cyrillic Capital Letter Dzzhe
+Upper: 00052c..00052d -> 00052c  Cyrillic Capital Letter Dche
+Upper: 00052e..00052f -> 00052e  Cyrillic Capital Letter El With Descender
 Upper:         000531 -> 000531  Armenian Capital Letter Ayb
 Upper:         000532 -> 000532  Armenian Capital Letter Ben
 Upper:         000533 -> 000533  Armenian Capital Letter Gim
@@ -3950,14 +4247,108 @@
 Upper:         0010c3 -> 0010c3  Georgian Capital Letter We
 Upper:         0010c4 -> 0010c4  Georgian Capital Letter Har
 Upper:         0010c5 -> 0010c5  Georgian Capital Letter Hoe
-Upper:         0010c6 -> 0010c6
 Upper:         0010c7 -> 0010c7  Georgian Capital Letter Yn
-Upper:         0010c8 -> 0010c8
-Upper:         0010c9 -> 0010c9
-Upper:         0010ca -> 0010ca
-Upper:         0010cb -> 0010cb
-Upper:         0010cc -> 0010cc
 Upper:         0010cd -> 0010cd  Georgian Capital Letter Aen
+Upper:         0013a0 -> 0013a0  Cherokee Letter A
+Upper:         0013a1 -> 0013a1  Cherokee Letter E
+Upper:         0013a2 -> 0013a2  Cherokee Letter I
+Upper:         0013a3 -> 0013a3  Cherokee Letter O
+Upper:         0013a4 -> 0013a4  Cherokee Letter U
+Upper:         0013a5 -> 0013a5  Cherokee Letter V
+Upper:         0013a6 -> 0013a6  Cherokee Letter Ga
+Upper:         0013a7 -> 0013a7  Cherokee Letter Ka
+Upper:         0013a8 -> 0013a8  Cherokee Letter Ge
+Upper:         0013a9 -> 0013a9  Cherokee Letter Gi
+Upper:         0013aa -> 0013aa  Cherokee Letter Go
+Upper:         0013ab -> 0013ab  Cherokee Letter Gu
+Upper:         0013ac -> 0013ac  Cherokee Letter Gv
+Upper:         0013ad -> 0013ad  Cherokee Letter Ha
+Upper:         0013ae -> 0013ae  Cherokee Letter He
+Upper:         0013af -> 0013af  Cherokee Letter Hi
+Upper:         0013b0 -> 0013b0  Cherokee Letter Ho
+Upper:         0013b1 -> 0013b1  Cherokee Letter Hu
+Upper:         0013b2 -> 0013b2  Cherokee Letter Hv
+Upper:         0013b3 -> 0013b3  Cherokee Letter La
+Upper:         0013b4 -> 0013b4  Cherokee Letter Le
+Upper:         0013b5 -> 0013b5  Cherokee Letter Li
+Upper:         0013b6 -> 0013b6  Cherokee Letter Lo
+Upper:         0013b7 -> 0013b7  Cherokee Letter Lu
+Upper:         0013b8 -> 0013b8  Cherokee Letter Lv
+Upper:         0013b9 -> 0013b9  Cherokee Letter Ma
+Upper:         0013ba -> 0013ba  Cherokee Letter Me
+Upper:         0013bb -> 0013bb  Cherokee Letter Mi
+Upper:         0013bc -> 0013bc  Cherokee Letter Mo
+Upper:         0013bd -> 0013bd  Cherokee Letter Mu
+Upper:         0013be -> 0013be  Cherokee Letter Na
+Upper:         0013bf -> 0013bf  Cherokee Letter Hna
+Upper:         0013c0 -> 0013c0  Cherokee Letter Nah
+Upper:         0013c1 -> 0013c1  Cherokee Letter Ne
+Upper:         0013c2 -> 0013c2  Cherokee Letter Ni
+Upper:         0013c3 -> 0013c3  Cherokee Letter No
+Upper:         0013c4 -> 0013c4  Cherokee Letter Nu
+Upper:         0013c5 -> 0013c5  Cherokee Letter Nv
+Upper:         0013c6 -> 0013c6  Cherokee Letter Qua
+Upper:         0013c7 -> 0013c7  Cherokee Letter Que
+Upper:         0013c8 -> 0013c8  Cherokee Letter Qui
+Upper:         0013c9 -> 0013c9  Cherokee Letter Quo
+Upper:         0013ca -> 0013ca  Cherokee Letter Quu
+Upper:         0013cb -> 0013cb  Cherokee Letter Quv
+Upper:         0013cc -> 0013cc  Cherokee Letter Sa
+Upper:         0013cd -> 0013cd  Cherokee Letter S
+Upper:         0013ce -> 0013ce  Cherokee Letter Se
+Upper:         0013cf -> 0013cf  Cherokee Letter Si
+Upper:         0013d0 -> 0013d0  Cherokee Letter So
+Upper:         0013d1 -> 0013d1  Cherokee Letter Su
+Upper:         0013d2 -> 0013d2  Cherokee Letter Sv
+Upper:         0013d3 -> 0013d3  Cherokee Letter Da
+Upper:         0013d4 -> 0013d4  Cherokee Letter Ta
+Upper:         0013d5 -> 0013d5  Cherokee Letter De
+Upper:         0013d6 -> 0013d6  Cherokee Letter Te
+Upper:         0013d7 -> 0013d7  Cherokee Letter Di
+Upper:         0013d8 -> 0013d8  Cherokee Letter Ti
+Upper:         0013d9 -> 0013d9  Cherokee Letter Do
+Upper:         0013da -> 0013da  Cherokee Letter Du
+Upper:         0013db -> 0013db  Cherokee Letter Dv
+Upper:         0013dc -> 0013dc  Cherokee Letter Dla
+Upper:         0013dd -> 0013dd  Cherokee Letter Tla
+Upper:         0013de -> 0013de  Cherokee Letter Tle
+Upper:         0013df -> 0013df  Cherokee Letter Tli
+Upper:         0013e0 -> 0013e0  Cherokee Letter Tlo
+Upper:         0013e1 -> 0013e1  Cherokee Letter Tlu
+Upper:         0013e2 -> 0013e2  Cherokee Letter Tlv
+Upper:         0013e3 -> 0013e3  Cherokee Letter Tsa
+Upper:         0013e4 -> 0013e4  Cherokee Letter Tse
+Upper:         0013e5 -> 0013e5  Cherokee Letter Tsi
+Upper:         0013e6 -> 0013e6  Cherokee Letter Tso
+Upper:         0013e7 -> 0013e7  Cherokee Letter Tsu
+Upper:         0013e8 -> 0013e8  Cherokee Letter Tsv
+Upper:         0013e9 -> 0013e9  Cherokee Letter Wa
+Upper:         0013ea -> 0013ea  Cherokee Letter We
+Upper:         0013eb -> 0013eb  Cherokee Letter Wi
+Upper:         0013ec -> 0013ec  Cherokee Letter Wo
+Upper:         0013ed -> 0013ed  Cherokee Letter Wu
+Upper:         0013ee -> 0013ee  Cherokee Letter Wv
+Upper:         0013ef -> 0013ef  Cherokee Letter Ya
+Upper:         0013f0 -> 0013f0  Cherokee Letter Ye
+Upper:         0013f1 -> 0013f1  Cherokee Letter Yi
+Upper:         0013f2 -> 0013f2  Cherokee Letter Yo
+Upper:         0013f3 -> 0013f3  Cherokee Letter Yu
+Upper:         0013f4 -> 0013f4  Cherokee Letter Yv
+Upper:         0013f5 -> 0013f5  Cherokee Letter Mv
+Upper:         0013f8 -> 0013f0  Cherokee Small Letter Ye
+Upper:         0013f9 -> 0013f1  Cherokee Small Letter Yi
+Upper:         0013fa -> 0013f2  Cherokee Small Letter Yo
+Upper:         0013fb -> 0013f3  Cherokee Small Letter Yu
+Upper:         0013fc -> 0013f4  Cherokee Small Letter Yv
+Upper:         0013fd -> 0013f5  Cherokee Small Letter Mv
+Upper:         001c80 -> 000412  Cyrillic Small Letter Rounded Ve
+Upper:         001c81 -> 000414  Cyrillic Small Letter Long-Legged De
+Upper:         001c82 -> 00041e  Cyrillic Small Letter Narrow O
+Upper:         001c83 -> 000421  Cyrillic Small Letter Wide Es
+Upper: 001c84..001c85 -> 000422  Cyrillic Small Letter Tall Te
+Upper:         001c86 -> 00042a  Cyrillic Small Letter Tall Hard Sign
+Upper:         001c87 -> 000462  Cyrillic Small Letter Tall Yat
+Upper:         001c88 -> 00a64a  Cyrillic Small Letter Unblended Uk
 Upper:         001d79 -> 00a77d  Latin Small Letter Insular G
 Upper:         001d7d -> 002c63  Latin Small Letter P With Stroke
 Upper: 001e00..001e01 -> 001e00  Latin Capital Letter A With Ring Below
@@ -4035,6 +4426,8 @@
 Upper: 001e90..001e91 -> 001e90  Latin Capital Letter Z With Circumflex
 Upper: 001e92..001e93 -> 001e92  Latin Capital Letter Z With Dot Below
 Upper: 001e94..001e95 -> 001e94  Latin Capital Letter Z With Line Below
+Upper:         001e9b -> 001e60  Latin Small Letter Long S With Dot Above
+Upper:         001e9e -> 001e9e  Latin Capital Letter Sharp S
 Upper: 001ea0..001ea1 -> 001ea0  Latin Capital Letter A With Dot Below
 Upper: 001ea2..001ea3 -> 001ea2  Latin Capital Letter A With Hook Above
 Upper: 001ea4..001ea5 -> 001ea4  Latin Capital Letter A With Circumflex And Acute
@@ -4080,6 +4473,9 @@
 Upper: 001ef4..001ef5 -> 001ef4  Latin Capital Letter Y With Dot Below
 Upper: 001ef6..001ef7 -> 001ef6  Latin Capital Letter Y With Hook Above
 Upper: 001ef8..001ef9 -> 001ef8  Latin Capital Letter Y With Tilde
+Upper: 001efa..001efb -> 001efa  Latin Capital Letter Middle-Welsh Ll
+Upper: 001efc..001efd -> 001efc  Latin Capital Letter Middle-Welsh V
+Upper: 001efe..001eff -> 001efe  Latin Capital Letter Y With Loop
 Upper:         001f00 -> 001f08  Greek Small Letter Alpha With Psili
 Upper:         001f01 -> 001f09  Greek Small Letter Alpha With Dasia
 Upper:         001f02 -> 001f0a  Greek Small Letter Alpha With Psili And Varia
@@ -4146,22 +4542,20 @@
 Upper:         001f43 -> 001f4b  Greek Small Letter Omicron With Dasia And Varia
 Upper:         001f44 -> 001f4c  Greek Small Letter Omicron With Psili And Oxia
 Upper:         001f45 -> 001f4d  Greek Small Letter Omicron With Dasia And Oxia
-Upper:         001f46 -> 001f4e
-Upper:         001f47 -> 001f4f
 Upper:         001f48 -> 001f48  Greek Capital Letter Omicron With Psili
 Upper:         001f49 -> 001f49  Greek Capital Letter Omicron With Dasia
 Upper:         001f4a -> 001f4a  Greek Capital Letter Omicron With Psili And Varia
 Upper:         001f4b -> 001f4b  Greek Capital Letter Omicron With Dasia And Varia
 Upper:         001f4c -> 001f4c  Greek Capital Letter Omicron With Psili And Oxia
 Upper:         001f4d -> 001f4d  Greek Capital Letter Omicron With Dasia And Oxia
-Upper:         001f4e -> 001f4e
-Upper:         001f4f -> 001f4f
 Upper:         001f51 -> 001f59  Greek Small Letter Upsilon With Dasia
 Upper:         001f53 -> 001f5b  Greek Small Letter Upsilon With Dasia And Varia
 Upper:         001f55 -> 001f5d  Greek Small Letter Upsilon With Dasia And Oxia
+Upper:         001f57 -> 001f5f  Greek Small Letter Upsilon With Dasia And Perispomeni
 Upper:         001f59 -> 001f59  Greek Capital Letter Upsilon With Dasia
 Upper:         001f5b -> 001f5b  Greek Capital Letter Upsilon With Dasia And Varia
 Upper:         001f5d -> 001f5d  Greek Capital Letter Upsilon With Dasia And Oxia
+Upper:         001f5f -> 001f5f  Greek Capital Letter Upsilon With Dasia And Perispomeni
 Upper:         001f60 -> 001f68  Greek Small Letter Omega With Psili
 Upper:         001f61 -> 001f69  Greek Small Letter Omega With Dasia
 Upper:         001f62 -> 001f6a  Greek Small Letter Omega With Psili And Varia
@@ -4248,6 +4642,7 @@
 Upper:         001fba -> 001fba  Greek Capital Letter Alpha With Varia
 Upper:         001fbb -> 001fbb  Greek Capital Letter Alpha With Oxia
 Upper:         001fbc -> 001fbc  Greek Capital Letter Alpha With Prosgegrammeni
+Upper:         001fbe -> 000399  Greek Prosgegrammeni
 Upper:         001fc3 -> 001fcc  Greek Small Letter Eta With Ypogegrammeni
 Upper:         001fc8 -> 001fc8  Greek Capital Letter Epsilon With Varia
 Upper:         001fc9 -> 001fc9  Greek Capital Letter Epsilon With Oxia
@@ -4386,6 +4781,10 @@
 Upper:         00206d -> 00206d  Activate Arabic Form Shaping
 Upper:         00206e -> 00206e  National Digit Shapes
 Upper:         00206f -> 00206f  Nominal Digit Shapes
+Upper:         002126 -> 002126  Ohm Sign
+Upper:         00212b -> 00212b  Angstrom Sign
+Upper:         002132 -> 002132  Turned Capital F
+Upper:         00214e -> 002132  Turned Small F
 Upper:         002160 -> 002160  Roman Numeral One
 Upper:         002161 -> 002161  Roman Numeral Two
 Upper:         002162 -> 002162  Roman Numeral Three
@@ -4418,6 +4817,7 @@
 Upper:         00217d -> 00216d  Small Roman Numeral One Hundred
 Upper:         00217e -> 00216e  Small Roman Numeral Five Hundred
 Upper:         00217f -> 00216f  Small Roman Numeral One Thousand
+Upper: 002183..002184 -> 002183  Roman Numeral Reversed One Hundred
 Upper:         002190 -> 002190  Leftwards Arrow
 Upper:         002191 -> 002191  Upwards Arrow
 Upper:         002192 -> 002192  Rightwards Arrow
@@ -5318,10 +5718,10 @@
 Upper: 002cdc..002cdd -> 002cdc  Coptic Capital Letter Old Nubian Shima
 Upper: 002cde..002cdf -> 002cde  Coptic Capital Letter Old Nubian Ngi
 Upper: 002ce0..002ce1 -> 002ce0  Coptic Capital Letter Old Nubian Nyi
+Upper: 002ce2..002ce3 -> 002ce2  Coptic Capital Letter Old Nubian Wau
 Upper: 002ceb..002cec -> 002ceb  Coptic Capital Letter Cryptogrammic Shei
 Upper: 002ced..002cee -> 002ced  Coptic Capital Letter Cryptogrammic Gangia
-Upper: 002cef..002cf0 -> 002cef  Coptic Combining Ni Above
-Upper: 002cf1..002cf2 -> 002cf1  Coptic Combining Spiritus Lenis
+Upper: 002cf2..002cf3 -> 002cf2  Coptic Capital Letter Bohairic Khei
 Upper:         002d00 -> 0010a0  Georgian Small Letter An
 Upper:         002d01 -> 0010a1  Georgian Small Letter Ban
 Upper:         002d02 -> 0010a2  Georgian Small Letter Gan
@@ -5360,13 +5760,7 @@
 Upper:         002d23 -> 0010c3  Georgian Small Letter We
 Upper:         002d24 -> 0010c4  Georgian Small Letter Har
 Upper:         002d25 -> 0010c5  Georgian Small Letter Hoe
-Upper:         002d26 -> 0010c6
 Upper:         002d27 -> 0010c7  Georgian Small Letter Yn
-Upper:         002d28 -> 0010c8
-Upper:         002d29 -> 0010c9
-Upper:         002d2a -> 0010ca
-Upper:         002d2b -> 0010cb
-Upper:         002d2c -> 0010cc
 Upper:         002d2d -> 0010cd  Georgian Small Letter Aen
 Upper: 00a640..00a641 -> 00a640  Cyrillic Capital Letter Zemlya
 Upper: 00a642..00a643 -> 00a642  Cyrillic Capital Letter Dzelo
@@ -5452,7 +5846,7 @@
 Upper: 00a784..00a785 -> 00a784  Latin Capital Letter Insular S
 Upper: 00a786..00a787 -> 00a786  Latin Capital Letter Insular T
 Upper: 00a78b..00a78c -> 00a78b  Latin Capital Letter Saltillo
-Upper: 00a78d..00a78e -> 00a78d  Latin Capital Letter Turned H
+Upper:         00a78d -> 00a78d  Latin Capital Letter Turned H
 Upper: 00a790..00a791 -> 00a790  Latin Capital Letter N With Descender
 Upper: 00a792..00a793 -> 00a792  Latin Capital Letter C With Bar
 Upper: 00a796..00a797 -> 00a796  Latin Capital Letter B With Flourish
@@ -5477,6 +5871,86 @@
 Upper: 00a7b4..00a7b5 -> 00a7b4  Latin Capital Letter Beta
 Upper: 00a7b6..00a7b7 -> 00a7b6  Latin Capital Letter Omega
 Upper:         00ab53 -> 00a7b3  Latin Small Letter Chi
+Upper:         00ab70 -> 0013a0  Cherokee Small Letter A
+Upper:         00ab71 -> 0013a1  Cherokee Small Letter E
+Upper:         00ab72 -> 0013a2  Cherokee Small Letter I
+Upper:         00ab73 -> 0013a3  Cherokee Small Letter O
+Upper:         00ab74 -> 0013a4  Cherokee Small Letter U
+Upper:         00ab75 -> 0013a5  Cherokee Small Letter V
+Upper:         00ab76 -> 0013a6  Cherokee Small Letter Ga
+Upper:         00ab77 -> 0013a7  Cherokee Small Letter Ka
+Upper:         00ab78 -> 0013a8  Cherokee Small Letter Ge
+Upper:         00ab79 -> 0013a9  Cherokee Small Letter Gi
+Upper:         00ab7a -> 0013aa  Cherokee Small Letter Go
+Upper:         00ab7b -> 0013ab  Cherokee Small Letter Gu
+Upper:         00ab7c -> 0013ac  Cherokee Small Letter Gv
+Upper:         00ab7d -> 0013ad  Cherokee Small Letter Ha
+Upper:         00ab7e -> 0013ae  Cherokee Small Letter He
+Upper:         00ab7f -> 0013af  Cherokee Small Letter Hi
+Upper:         00ab80 -> 0013b0  Cherokee Small Letter Ho
+Upper:         00ab81 -> 0013b1  Cherokee Small Letter Hu
+Upper:         00ab82 -> 0013b2  Cherokee Small Letter Hv
+Upper:         00ab83 -> 0013b3  Cherokee Small Letter La
+Upper:         00ab84 -> 0013b4  Cherokee Small Letter Le
+Upper:         00ab85 -> 0013b5  Cherokee Small Letter Li
+Upper:         00ab86 -> 0013b6  Cherokee Small Letter Lo
+Upper:         00ab87 -> 0013b7  Cherokee Small Letter Lu
+Upper:         00ab88 -> 0013b8  Cherokee Small Letter Lv
+Upper:         00ab89 -> 0013b9  Cherokee Small Letter Ma
+Upper:         00ab8a -> 0013ba  Cherokee Small Letter Me
+Upper:         00ab8b -> 0013bb  Cherokee Small Letter Mi
+Upper:         00ab8c -> 0013bc  Cherokee Small Letter Mo
+Upper:         00ab8d -> 0013bd  Cherokee Small Letter Mu
+Upper:         00ab8e -> 0013be  Cherokee Small Letter Na
+Upper:         00ab8f -> 0013bf  Cherokee Small Letter Hna
+Upper:         00ab90 -> 0013c0  Cherokee Small Letter Nah
+Upper:         00ab91 -> 0013c1  Cherokee Small Letter Ne
+Upper:         00ab92 -> 0013c2  Cherokee Small Letter Ni
+Upper:         00ab93 -> 0013c3  Cherokee Small Letter No
+Upper:         00ab94 -> 0013c4  Cherokee Small Letter Nu
+Upper:         00ab95 -> 0013c5  Cherokee Small Letter Nv
+Upper:         00ab96 -> 0013c6  Cherokee Small Letter Qua
+Upper:         00ab97 -> 0013c7  Cherokee Small Letter Que
+Upper:         00ab98 -> 0013c8  Cherokee Small Letter Qui
+Upper:         00ab99 -> 0013c9  Cherokee Small Letter Quo
+Upper:         00ab9a -> 0013ca  Cherokee Small Letter Quu
+Upper:         00ab9b -> 0013cb  Cherokee Small Letter Quv
+Upper:         00ab9c -> 0013cc  Cherokee Small Letter Sa
+Upper:         00ab9d -> 0013cd  Cherokee Small Letter S
+Upper:         00ab9e -> 0013ce  Cherokee Small Letter Se
+Upper:         00ab9f -> 0013cf  Cherokee Small Letter Si
+Upper:         00aba0 -> 0013d0  Cherokee Small Letter So
+Upper:         00aba1 -> 0013d1  Cherokee Small Letter Su
+Upper:         00aba2 -> 0013d2  Cherokee Small Letter Sv
+Upper:         00aba3 -> 0013d3  Cherokee Small Letter Da
+Upper:         00aba4 -> 0013d4  Cherokee Small Letter Ta
+Upper:         00aba5 -> 0013d5  Cherokee Small Letter De
+Upper:         00aba6 -> 0013d6  Cherokee Small Letter Te
+Upper:         00aba7 -> 0013d7  Cherokee Small Letter Di
+Upper:         00aba8 -> 0013d8  Cherokee Small Letter Ti
+Upper:         00aba9 -> 0013d9  Cherokee Small Letter Do
+Upper:         00abaa -> 0013da  Cherokee Small Letter Du
+Upper:         00abab -> 0013db  Cherokee Small Letter Dv
+Upper:         00abac -> 0013dc  Cherokee Small Letter Dla
+Upper:         00abad -> 0013dd  Cherokee Small Letter Tla
+Upper:         00abae -> 0013de  Cherokee Small Letter Tle
+Upper:         00abaf -> 0013df  Cherokee Small Letter Tli
+Upper:         00abb0 -> 0013e0  Cherokee Small Letter Tlo
+Upper:         00abb1 -> 0013e1  Cherokee Small Letter Tlu
+Upper:         00abb2 -> 0013e2  Cherokee Small Letter Tlv
+Upper:         00abb3 -> 0013e3  Cherokee Small Letter Tsa
+Upper:         00abb4 -> 0013e4  Cherokee Small Letter Tse
+Upper:         00abb5 -> 0013e5  Cherokee Small Letter Tsi
+Upper:         00abb6 -> 0013e6  Cherokee Small Letter Tso
+Upper:         00abb7 -> 0013e7  Cherokee Small Letter Tsu
+Upper:         00abb8 -> 0013e8  Cherokee Small Letter Tsv
+Upper:         00abb9 -> 0013e9  Cherokee Small Letter Wa
+Upper:         00abba -> 0013ea  Cherokee Small Letter We
+Upper:         00abbb -> 0013eb  Cherokee Small Letter Wi
+Upper:         00abbc -> 0013ec  Cherokee Small Letter Wo
+Upper:         00abbd -> 0013ed  Cherokee Small Letter Wu
+Upper:         00abbe -> 0013ee  Cherokee Small Letter Wv
+Upper:         00abbf -> 0013ef  Cherokee Small Letter Ya
 Upper:         00ff21 -> 00ff21  Fullwidth Latin Capital Letter A
 Upper:         00ff22 -> 00ff22  Fullwidth Latin Capital Letter B
 Upper:         00ff23 -> 00ff23  Fullwidth Latin Capital Letter C
@@ -5569,34 +6043,46 @@
 Upper:         010425 -> 010425  Deseret Capital Letter Eng
 Upper:         010426 -> 010426  Deseret Capital Letter Oi
 Upper:         010427 -> 010427  Deseret Capital Letter Ew
-Upper:         010428 -> 01040c  Deseret Small Letter Long I
-Upper:         010429 -> 01040d  Deseret Small Letter Long E
-Upper:         01042a -> 01040e  Deseret Small Letter Long A
-Upper:         01042b -> 01040f  Deseret Small Letter Long Ah
-Upper:         01042c -> 010410  Deseret Small Letter Long O
-Upper:         01042d -> 010411  Deseret Small Letter Long Oo
-Upper:         01042e -> 010412  Deseret Small Letter Short I
-Upper:         01042f -> 010413  Deseret Small Letter Short E
-Upper:         010430 -> 010414  Deseret Small Letter Short A
-Upper:         010431 -> 010415  Deseret Small Letter Short Ah
-Upper:         010432 -> 010416  Deseret Small Letter Short O
-Upper:         010433 -> 010417  Deseret Small Letter Short Oo
-Upper:         010434 -> 010418  Deseret Small Letter Ay
-Upper:         010435 -> 010419  Deseret Small Letter Ow
-Upper:         010436 -> 01041a  Deseret Small Letter Wu
-Upper:         010437 -> 01041b  Deseret Small Letter Yee
-Upper:         010438 -> 01041c  Deseret Small Letter H
-Upper:         010439 -> 01041d  Deseret Small Letter Pee
-Upper:         01043a -> 01041e  Deseret Small Letter Bee
-Upper:         01043b -> 01041f  Deseret Small Letter Tee
-Upper:         01043c -> 010420  Deseret Small Letter Dee
-Upper:         01043d -> 010421  Deseret Small Letter Chee
-Upper:         01043e -> 010422  Deseret Small Letter Jee
-Upper:         01043f -> 010423  Deseret Small Letter Kay
-Upper:         010440 -> 010424  Deseret Small Letter Gay
-Upper:         010441 -> 010425  Deseret Small Letter Ef
-Upper:         010442 -> 010426  Deseret Small Letter Vee
-Upper:         010443 -> 010427  Deseret Small Letter Eth
+Upper:         010428 -> 010400  Deseret Small Letter Long I
+Upper:         010429 -> 010401  Deseret Small Letter Long E
+Upper:         01042a -> 010402  Deseret Small Letter Long A
+Upper:         01042b -> 010403  Deseret Small Letter Long Ah
+Upper:         01042c -> 010404  Deseret Small Letter Long O
+Upper:         01042d -> 010405  Deseret Small Letter Long Oo
+Upper:         01042e -> 010406  Deseret Small Letter Short I
+Upper:         01042f -> 010407  Deseret Small Letter Short E
+Upper:         010430 -> 010408  Deseret Small Letter Short A
+Upper:         010431 -> 010409  Deseret Small Letter Short Ah
+Upper:         010432 -> 01040a  Deseret Small Letter Short O
+Upper:         010433 -> 01040b  Deseret Small Letter Short Oo
+Upper:         010434 -> 01040c  Deseret Small Letter Ay
+Upper:         010435 -> 01040d  Deseret Small Letter Ow
+Upper:         010436 -> 01040e  Deseret Small Letter Wu
+Upper:         010437 -> 01040f  Deseret Small Letter Yee
+Upper:         010438 -> 010410  Deseret Small Letter H
+Upper:         010439 -> 010411  Deseret Small Letter Pee
+Upper:         01043a -> 010412  Deseret Small Letter Bee
+Upper:         01043b -> 010413  Deseret Small Letter Tee
+Upper:         01043c -> 010414  Deseret Small Letter Dee
+Upper:         01043d -> 010415  Deseret Small Letter Chee
+Upper:         01043e -> 010416  Deseret Small Letter Jee
+Upper:         01043f -> 010417  Deseret Small Letter Kay
+Upper:         010440 -> 010418  Deseret Small Letter Gay
+Upper:         010441 -> 010419  Deseret Small Letter Ef
+Upper:         010442 -> 01041a  Deseret Small Letter Vee
+Upper:         010443 -> 01041b  Deseret Small Letter Eth
+Upper:         010444 -> 01041c  Deseret Small Letter Thee
+Upper:         010445 -> 01041d  Deseret Small Letter Es
+Upper:         010446 -> 01041e  Deseret Small Letter Zee
+Upper:         010447 -> 01041f  Deseret Small Letter Esh
+Upper:         010448 -> 010420  Deseret Small Letter Zhee
+Upper:         010449 -> 010421  Deseret Small Letter Er
+Upper:         01044a -> 010422  Deseret Small Letter El
+Upper:         01044b -> 010423  Deseret Small Letter Em
+Upper:         01044c -> 010424  Deseret Small Letter En
+Upper:         01044d -> 010425  Deseret Small Letter Eng
+Upper:         01044e -> 010426  Deseret Small Letter Oi
+Upper:         01044f -> 010427  Deseret Small Letter Ew
 Upper:         0104b0 -> 0104b0  Osage Capital Letter A
 Upper:         0104b1 -> 0104b1  Osage Capital Letter Ai
 Upper:         0104b2 -> 0104b2  Osage Capital Letter Ain

-- 
Best regards
ミハウ “𝓶𝓲𝓷𝓪86” ナザレヴイツ
«If at first you don’t succeed, give up skydiving»





^ permalink raw reply	[flat|nested] 89+ messages in thread

* bug#24603: [RFC 02/18] Generate upcase and downcase tables from Unicode data
  2016-10-04 16:57           ` Michal Nazarewicz
@ 2016-10-04 17:27             ` Eli Zaretskii
  2016-10-04 17:44               ` Eli Zaretskii
  0 siblings, 1 reply; 89+ messages in thread
From: Eli Zaretskii @ 2016-10-04 17:27 UTC (permalink / raw)
  To: Michal Nazarewicz; +Cc: 24603

> From: Michal Nazarewicz <mina86@mina86.com>
> Cc: 24603@debbugs.gnu.org
> Date: Tue, 04 Oct 2016 18:57:03 +0200
> 
> > I think we should document all the changes.
> 
> I wouldn’t know where to put such documentation.

On a separate file under admin/unidata/, if we cannot find a better
place.

> > Can you add the name of each character (just one, the leftmost one) to
> > its line and post the result?  It's hard to read the report when it
> > only shows codepoints.
> 
> --- orig-tables.txt	2016-10-04 18:40:27.276408290 +0200
> +++ modified-tables.txt	2016-10-04 18:41:34.651421547 +0200

Thanks, this all looks good to me: mostly additions for new
characters, most deletions are for reserved codepoints and the rest
are corrections for mistakes.





^ permalink raw reply	[flat|nested] 89+ messages in thread

* bug#24603: [RFC 02/18] Generate upcase and downcase tables from Unicode data
  2016-10-04 17:27             ` Eli Zaretskii
@ 2016-10-04 17:44               ` Eli Zaretskii
  2016-10-06 20:29                 ` Michal Nazarewicz
  0 siblings, 1 reply; 89+ messages in thread
From: Eli Zaretskii @ 2016-10-04 17:44 UTC (permalink / raw)
  To: mina86; +Cc: 24603

> Date: Tue, 04 Oct 2016 20:27:02 +0300
> From: Eli Zaretskii <eliz@gnu.org>
> Cc: 24603@debbugs.gnu.org
> 
> > From: Michal Nazarewicz <mina86@mina86.com>
> > Cc: 24603@debbugs.gnu.org
> > Date: Tue, 04 Oct 2016 18:57:03 +0200
> > 
> > > I think we should document all the changes.
> > 
> > I wouldn’t know where to put such documentation.
> 
> On a separate file under admin/unidata/, if we cannot find a better
> place.

Or maybe just mention in the commit log the URL of the message in the
bugtracker's records, where you posted the diffs, it might be good
enough.





^ permalink raw reply	[flat|nested] 89+ messages in thread

* bug#24603: [RFC 02/18] Generate upcase and downcase tables from Unicode data
  2016-10-04 17:44               ` Eli Zaretskii
@ 2016-10-06 20:29                 ` Michal Nazarewicz
  2016-10-07  6:52                   ` Eli Zaretskii
  0 siblings, 1 reply; 89+ messages in thread
From: Michal Nazarewicz @ 2016-10-06 20:29 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: 24603

[-- Attachment #1: Type: text/plain, Size: 812 bytes --]

On Tue, Oct 04 2016, Eli Zaretskii wrote:
>> Date: Tue, 04 Oct 2016 20:27:02 +0300
>> From: Eli Zaretskii <eliz@gnu.org>
>> Cc: 24603@debbugs.gnu.org
>> 
>> > From: Michal Nazarewicz <mina86@mina86.com>
>> > Cc: 24603@debbugs.gnu.org
>> > Date: Tue, 04 Oct 2016 18:57:03 +0200
>> > 
>> > > I think we should document all the changes.
>> > 
>> > I wouldn’t know where to put such documentation.
>> 
>> On a separate file under admin/unidata/, if we cannot find a better
>> place.
>
> Or maybe just mention in the commit log the URL of the message in the
> bugtracker's records, where you posted the diffs, it might be good
> enough.

That’s easy enough.

-- 
Best regards
ミハウ “𝓶𝓲𝓷𝓪86” ナザレヴイツ
«If at first you don’t succeed, give up skydiving»


[-- Attachment #2: 0002-Generate-upcase-and-downcase-tables-from-Unicode-dat.patch --]
[-- Type: text/x-diff, Size: 17671 bytes --]

From 9d2fd43c4d442543a650a4d3cb95b0c2aa6a0c4e Mon Sep 17 00:00:00 2001
From: Michal Nazarewicz <mina86@mina86.com>
Date: Mon, 19 Sep 2016 00:23:40 +0200
Subject: [PATCH 02/19] Generate upcase and downcase tables from Unicode data
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Use Unicode data to generate case tables instead of mostly repeating
them in lisp code.  Do that in a way which maps ‘Dz’ (and similar)
digraph to ‘dz’ when down- and ‘DZ’ when upcasing.

https://debbugs.gnu.org/cgi/bugreport.cgi?msg=89;bug=24603 lists all
changes to syntax table and case tables introduced by this commit.

* lisp/international/characters.el: Remove case-pairs defined with
explicit Lisp code and instead use Unicode character properties.

* test/src/casefiddle-tests.el (casefiddle-tests--characters,
casefiddle-tests-casing): Update test cases which are now working
as they should.
---
 lisp/international/characters.el | 345 ++++++++-------------------------------
 test/src/casefiddle-tests.el     |   7 +-
 2 files changed, 73 insertions(+), 279 deletions(-)

diff --git a/lisp/international/characters.el b/lisp/international/characters.el
index 1757d2b..8dd9c73 100644
--- a/lisp/international/characters.el
+++ b/lisp/international/characters.el
@@ -543,10 +543,6 @@ ?L
   (set-case-syntax ?½ "_" tbl)
   (set-case-syntax ?¾ "_" tbl)
   (set-case-syntax ?¿ "." tbl)
-  (let ((c 192))
-    (while (<= c 222)
-      (set-case-syntax-pair c (+ c 32) tbl)
-      (setq c (1+ c))))
   (set-case-syntax ?× "_" tbl)
   (set-case-syntax ?ß "w" tbl)
   (set-case-syntax ?÷ "_" tbl)
@@ -558,101 +554,8 @@ ?L
     (modify-category-entry c ?l)
     (setq c (1+ c)))
 
-  (let ((pair-ranges '((#x0100 . #x012F)
-		       (#x0132 . #x0137)
-		       (#x0139 . #x0148)
-		       (#x014a . #x0177)
-		       (#x0179 . #x017E)
-		       (#x0182 . #x0185)
-		       (#x0187 . #x0188)
-		       (#x018B . #x018C)
-		       (#x0191 . #x0192)
-		       (#x0198 . #x0199)
-		       (#x01A0 . #x01A5)
-		       (#x01A7 . #x01A8)
-		       (#x01AC . #x01AD)
-		       (#x01AF . #x01B0)
-		       (#x01B3 . #x01B6)
-		       (#x01B8 . #x01B9)
-		       (#x01BC . #x01BD)
-		       (#x01CD . #x01DC)
-		       (#x01DE . #x01EF)
-		       (#x01F4 . #x01F5)
-		       (#x01F8 . #x021F)
-		       (#x0222 . #x0233)
-		       (#x023B . #x023C)
-		       (#x0241 . #x0242)
-		       (#x0246 . #x024F))))
-    (dolist (elt pair-ranges)
-      (let ((from (car elt)) (to (cdr elt)))
-	(while (< from to)
-	  (set-case-syntax-pair from (1+ from) tbl)
-	  (setq from (+ from 2))))))
-
-  (set-case-syntax-pair ?Ÿ ?ÿ tbl)
-
-  ;; In some languages, such as Turkish, U+0049 LATIN CAPITAL LETTER I
-  ;; and U+0131 LATIN SMALL LETTER DOTLESS I make a case pair, and so
-  ;; do U+0130 LATIN CAPITAL LETTER I WITH DOT ABOVE and U+0069 LATIN
-  ;; SMALL LETTER I.
-
-  ;; We used to set up half of those correspondence unconditionally,
-  ;; but that makes searches slow.  So now we don't set up either half
-  ;; of these correspondences by default.
-
-  ;; (set-downcase-syntax  ?İ ?i tbl)
-  ;; (set-upcase-syntax    ?I ?ı tbl)
-
-  (set-case-syntax-pair ?Ɓ ?ɓ tbl)
-  (set-case-syntax-pair ?Ɔ ?ɔ tbl)
-  (set-case-syntax-pair ?Ɖ ?ɖ tbl)
-  (set-case-syntax-pair ?Ɗ ?ɗ tbl)
-  (set-case-syntax-pair ?Ǝ ?ǝ tbl)
-  (set-case-syntax-pair ?Ə ?ə tbl)
-  (set-case-syntax-pair ?Ɛ ?ɛ tbl)
-  (set-case-syntax-pair ?Ɠ ?ɠ tbl)
-  (set-case-syntax-pair ?Ɣ ?ɣ tbl)
-  (set-case-syntax-pair ?Ɩ ?ɩ tbl)
-  (set-case-syntax-pair ?Ɨ ?ɨ tbl)
-  (set-case-syntax-pair ?Ɯ ?ɯ tbl)
-  (set-case-syntax-pair ?Ɲ ?ɲ tbl)
-  (set-case-syntax-pair ?Ɵ ?ɵ tbl)
-  (set-case-syntax-pair ?Ʀ ?ʀ tbl)
-  (set-case-syntax-pair ?Ʃ ?ʃ tbl)
-  (set-case-syntax-pair ?Ʈ ?ʈ tbl)
-  (set-case-syntax-pair ?Ʊ ?ʊ tbl)
-  (set-case-syntax-pair ?Ʋ ?ʋ tbl)
-  (set-case-syntax-pair ?Ʒ ?ʒ tbl)
-  ;; We use set-downcase-syntax below, since we want upcase of dž
-  ;; return DŽ, not Dž, and the same for the rest.
-  (set-case-syntax-pair ?DŽ ?dž tbl)
-  (set-downcase-syntax ?Dž ?dž tbl)
-  (set-case-syntax-pair ?LJ ?lj tbl)
-  (set-downcase-syntax ?Lj ?lj tbl)
-  (set-case-syntax-pair ?NJ ?nj tbl)
-  (set-downcase-syntax ?Nj ?nj tbl)
-
-  ;; 01F0; F; 006A 030C; # LATIN SMALL LETTER J WITH CARON
-
-  (set-case-syntax-pair ?DZ ?dz tbl)
-  (set-downcase-syntax ?Dz ?dz tbl)
-  (set-case-syntax-pair ?Ƕ ?ƕ tbl)
-  (set-case-syntax-pair ?Ƿ ?ƿ tbl)
-  (set-case-syntax-pair ?Ⱥ ?ⱥ tbl)
-  (set-case-syntax-pair ?Ƚ ?ƚ tbl)
-  (set-case-syntax-pair ?Ⱦ ?ⱦ tbl)
-  (set-case-syntax-pair ?Ƀ ?ƀ tbl)
-  (set-case-syntax-pair ?Ʉ ?ʉ tbl)
-  (set-case-syntax-pair ?Ʌ ?ʌ tbl)
-
   ;; Latin Extended Additional
   (modify-category-entry '(#x1e00 . #x1ef9) ?l)
-  (setq c #x1e00)
-  (while (<= c #x1ef9)
-    (and (zerop (% c 2))
-	 (or (<= c #x1e94) (>= c #x1ea0))
-	 (set-case-syntax-pair c (1+ c) tbl))
-    (setq c (1+ c)))
 
   ;; Latin Extended-C
   (setq c #x2C60)
@@ -660,57 +563,12 @@ ?L
     (modify-category-entry c ?l)
     (setq c (1+ c)))
 
-  (let ((pair-ranges '((#x2C60 . #x2C61)
-                       (#x2C67 . #x2C6C)
-                       (#x2C72 . #x2C73)
-                       (#x2C75 . #x2C76))))
-    (dolist (elt pair-ranges)
-      (let ((from (car elt)) (to (cdr elt)))
-        (while (< from to)
-          (set-case-syntax-pair from (1+ from) tbl)
-          (setq from (+ from 2))))))
-
-  (set-case-syntax-pair ?Ɫ ?ɫ tbl)
-  (set-case-syntax-pair ?Ᵽ ?ᵽ tbl)
-  (set-case-syntax-pair ?Ɽ ?ɽ tbl)
-  (set-case-syntax-pair ?Ɑ ?ɑ tbl)
-  (set-case-syntax-pair ?Ɱ ?ɱ tbl)
-  (set-case-syntax-pair ?Ɐ ?ɐ tbl)
-  (set-case-syntax-pair ?Ɒ ?ɒ tbl)
-  (set-case-syntax-pair ?Ȿ ?ȿ tbl)
-  (set-case-syntax-pair ?Ɀ ?ɀ tbl)
-
   ;; Latin Extended-D
   (setq c #xA720)
   (while (<= c #xA7FF)
     (modify-category-entry c ?l)
     (setq c (1+ c)))
 
-  (let ((pair-ranges '((#xA722 . #xA72F)
-                       (#xA732 . #xA76F)
-                       (#xA779 . #xA77C)
-                       (#xA77E . #xA787)
-                       (#xA78B . #xA78E)
-                       (#xA790 . #xA793)
-                       (#xA796 . #xA7A9)
-                       (#xA7B4 . #xA7B7))))
-    (dolist (elt pair-ranges)
-      (let ((from (car elt)) (to (cdr elt)))
-        (while (< from to)
-          (set-case-syntax-pair from (1+ from) tbl)
-          (setq from (+ from 2))))))
-
-  (set-case-syntax-pair ?Ᵹ ?ᵹ tbl)
-  (set-case-syntax-pair ?Ɦ ?ɦ tbl)
-  (set-case-syntax-pair ?Ɜ ?ɜ tbl)
-  (set-case-syntax-pair ?Ɡ ?ɡ tbl)
-  (set-case-syntax-pair ?Ɬ ?ɬ tbl)
-  (set-case-syntax-pair ?Ɪ ?ɪ tbl)
-  (set-case-syntax-pair ?Ʞ ?ʞ tbl)
-  (set-case-syntax-pair ?Ʇ ?ʇ tbl)
-  (set-case-syntax-pair ?Ʝ ?ʝ tbl)
-  (set-case-syntax-pair ?Ꭓ ?ꭓ tbl)
-
   ;; Latin Extended-E
   (setq c #xAB30)
   (while (<= c #xAB64)
@@ -719,102 +577,19 @@ ?L
 
   ;; Greek
   (modify-category-entry '(#x0370 . #x03ff) ?g)
-  (setq c #x0370)
-  (while (<= c #x03ff)
-    (if (or (and (>= c #x0391) (<= c #x03a1))
-	    (and (>= c #x03a3) (<= c #x03ab)))
-	(set-case-syntax-pair c (+ c 32) tbl))
-    (and (>= c #x03da)
-	 (<= c #x03ee)
-	 (zerop (% c 2))
-	 (set-case-syntax-pair c (1+ c) tbl))
-    (setq c (1+ c)))
-  (set-case-syntax-pair ?Ά ?ά tbl)
-  (set-case-syntax-pair ?Έ ?έ tbl)
-  (set-case-syntax-pair ?Ή ?ή tbl)
-  (set-case-syntax-pair ?Ί ?ί tbl)
-  (set-case-syntax-pair ?Ό ?ό tbl)
-  (set-case-syntax-pair ?Ύ ?ύ tbl)
-  (set-case-syntax-pair ?Ώ ?ώ tbl)
 
   ;; Armenian
   (setq c #x531)
-  (while (<= c #x556)
-    (set-case-syntax-pair c (+ c #x30) tbl)
-    (setq c (1+ c)))
 
   ;; Greek Extended
   (modify-category-entry '(#x1f00 . #x1fff) ?g)
-  (setq c #x1f00)
-  (while (<= c #x1fff)
-    (and (<= (logand c #x000f) 7)
-	 (<= c #x1fa7)
-	 (not (memq c '(#x1f16 #x1f17 #x1f56 #x1f57
-			       #x1f50 #x1f52 #x1f54 #x1f56)))
-	 (/= (logand c #x00f0) #x70)
-	 (set-case-syntax-pair (+ c 8) c tbl))
-    (setq c (1+ c)))
-  (set-case-syntax-pair ?Ᾰ ?ᾰ tbl)
-  (set-case-syntax-pair ?Ᾱ ?ᾱ tbl)
-  (set-case-syntax-pair ?Ὰ ?ὰ tbl)
-  (set-case-syntax-pair ?Ά ?ά tbl)
-  (set-case-syntax-pair ?ᾼ ?ᾳ tbl)
-  (set-case-syntax-pair ?Ὲ ?ὲ tbl)
-  (set-case-syntax-pair ?Έ ?έ tbl)
-  (set-case-syntax-pair ?Ὴ ?ὴ tbl)
-  (set-case-syntax-pair ?Ή ?ή tbl)
-  (set-case-syntax-pair ?ῌ ?ῃ tbl)
-  (set-case-syntax-pair ?Ῐ ?ῐ tbl)
-  (set-case-syntax-pair ?Ῑ ?ῑ tbl)
-  (set-case-syntax-pair ?Ὶ ?ὶ tbl)
-  (set-case-syntax-pair ?Ί ?ί tbl)
-  (set-case-syntax-pair ?Ῠ ?ῠ tbl)
-  (set-case-syntax-pair ?Ῡ ?ῡ tbl)
-  (set-case-syntax-pair ?Ὺ ?ὺ tbl)
-  (set-case-syntax-pair ?Ύ ?ύ tbl)
-  (set-case-syntax-pair ?Ῥ ?ῥ tbl)
-  (set-case-syntax-pair ?Ὸ ?ὸ tbl)
-  (set-case-syntax-pair ?Ό ?ό tbl)
-  (set-case-syntax-pair ?Ὼ ?ὼ tbl)
-  (set-case-syntax-pair ?Ώ ?ώ tbl)
-  (set-case-syntax-pair ?ῼ ?ῳ tbl)
 
   ;; cyrillic
   (modify-category-entry '(#x0400 . #x04FF) ?y)
-  (setq c #x0400)
-  (while (<= c #x04ff)
-    (and (>= c #x0400)
-	 (<= c #x040f)
-	 (set-case-syntax-pair c (+ c 80) tbl))
-    (and (>= c #x0410)
-	 (<= c #x042f)
-	 (set-case-syntax-pair c (+ c 32) tbl))
-    (and (zerop (% c 2))
-	 (or (and (>= c #x0460) (<= c #x0480))
-	     (and (>= c #x048c) (<= c #x04be))
-	     (and (>= c #x04d0) (<= c #x052e)))
-	 (set-case-syntax-pair c (1+ c) tbl))
-    (setq c (1+ c)))
-  (set-case-syntax-pair ?Ӂ ?ӂ tbl)
-  (set-case-syntax-pair ?Ӄ ?ӄ tbl)
-  (set-case-syntax-pair ?Ӈ ?ӈ tbl)
-  (set-case-syntax-pair ?Ӌ ?ӌ tbl)
-
   (modify-category-entry '(#xA640 . #xA69F) ?y)
-  (setq c #xA640)
-  (while (<= c #xA66C)
-    (set-case-syntax-pair c (+ c 1) tbl)
-    (setq c (+ c 2)))
-  (setq c #xA680)
-  (while (<= c #xA69A)
-    (set-case-syntax-pair c (+ c 1) tbl)
-    (setq c (+ c 2)))
 
   ;; Georgian
   (setq c #x10A0)
-  (while (<= c #x10CD)
-    (set-case-syntax-pair c (+ c #x1C60) tbl)
-    (setq c (1+ c)))
 
   ;; Cyrillic Extended-C
   (modify-category-entry '(#x1C80 . #x1C8F) ?y)
@@ -844,12 +619,6 @@ ?L
     (set-case-syntax c "." tbl)
     (setq c (1+ c)))
 
-  ;; Roman numerals
-  (setq c #x2160)
-  (while (<= c #x216f)
-    (set-case-syntax-pair c (+ c #x10) tbl)
-    (setq c (1+ c)))
-
   ;; Fixme: The following blocks might be better as symbol rather than
   ;; punctuation.
   ;; Arrows
@@ -873,25 +642,11 @@ ?L
   ;; Circled Latin
   (setq c #x24b6)
   (while (<= c #x24cf)
-    (set-case-syntax-pair c (+ c 26) tbl)
     (modify-category-entry c ?l)
     (modify-category-entry (+ c 26) ?l)
     (setq c (1+ c)))
 
-  ;; Glagolitic
-  (setq c #x2C00)
-  (while (<= c #x2C2E)
-    (set-case-syntax-pair c (+ c 48) tbl)
-    (setq c (1+ c)))
-
   ;; Coptic
-  (let ((pair-ranges '((#x2C80 . #x2CE2)
-		       (#x2CEB . #x2CF2))))
-    (dolist (elt pair-ranges)
-      (let ((from (car elt)) (to (cdr elt)))
-	(while (< from to)
-	  (set-case-syntax-pair from (1+ from) tbl)
-	  (setq from (+ from 2))))))
   ;; There's no Coptic category.  However, Coptic letters that are
   ;; part of the Greek block above get the Greek category, and those
   ;; in this block are derived from Greek letters, so let's be
@@ -901,45 +656,85 @@ ?L
   ;; Fullwidth Latin
   (setq c #xff21)
   (while (<= c #xff3a)
-    (set-case-syntax-pair c (+ c #x20) tbl)
     (modify-category-entry c ?l)
     (modify-category-entry (+ c #x20) ?l)
     (setq c (1+ c)))
 
-  ;; Deseret
-  (setq c #x10400)
-  (while (<= c #x10427)
-    (set-case-syntax-pair c (+ c 28) tbl)
-    (setq c (1+ c)))
+  ;; Combining diacritics
+  (modify-category-entry '(#x300 . #x362) ?^)
+  ;; Combining marks
+  (modify-category-entry '(#x20d0 . #x20ff) ?^)
 
-  ;; Osage
-  (setq c #x104B0)
-  (while (<= c #x104D3)
-    (set-case-syntax-pair c (+ c 40) tbl)
-    (setq c (1+ c)))
+  ;; Set all Letter, uppercase; Letter, lowercase and Letter, titlecase syntax
+  ;; to word.
+  (let ((syn-tab (standard-syntax-table)))
+    (map-char-table
+     (lambda (ch cat)
+       (when (memq cat '(Lu Ll Lt))
+         (modify-syntax-entry ch "w   " syn-tab)))
+     (unicode-property-table-internal 'general-category))
 
-  ;; Old Hungarian
-  (setq c #x10c80)
-  (while (<= c #x10cb2)
-    (set-case-syntax-pair c (+ c #x40) tbl)
-    (setq c (1+ c)))
+    ;; Ⅰ through Ⅻ had word syntax in the past so set it here as well.
+    ;; General category of those characers is Number, Letter.
+    (modify-syntax-entry '(#x2160 . #x216b) "w   " syn-tab)
 
-  ;; Warang Citi
-  (setq c #x118a0)
-  (while (<= c #x118bf)
-    (set-case-syntax-pair c (+ c #x20) tbl)
-    (setq c (1+ c)))
+    ;; ⓐ thourgh ⓩ are symbols, other according to Unicode but Emacs set
+    ;; their syntax to word in the past so keep backwards compatibility.
+    (modify-syntax-entry '(#x24D0 . #x24E9) "w   " syn-tab))
 
-  ;; Adlam
-  (setq c #x1e900)
-  (while (<= c #x1e921)
-    (set-case-syntax-pair c (+ c #x22) tbl)
-    (setq c (1+ c)))
+  ;; Set downcase and upcase from Unicode properties
 
-  ;; Combining diacritics
-  (modify-category-entry '(#x300 . #x362) ?^)
-  ;; Combining marks
-  (modify-category-entry '(#x20d0 . #x20ff) ?^)
+  ;; In some languages, such as Turkish, U+0049 LATIN CAPITAL LETTER I and
+  ;; U+0131 LATIN SMALL LETTER DOTLESS I make a case pair, and so do U+0130
+  ;; LATIN CAPITAL LETTER I WITH DOT ABOVE and U+0069 LATIN SMALL LETTER I.
+
+  ;; We used to set up half of those correspondence unconditionally, but that
+  ;; makes searches slow.  So now we don't set up either half of these
+  ;; correspondences by default.
+
+  ;; (set-downcase-syntax  ?İ ?i tbl)
+  ;; (set-upcase-syntax    ?I ?ı tbl)
+
+  (let ((map-unicode-property
+         (lambda (property func)
+           (map-char-table
+            (lambda (ch cased)
+              ;; ASCII characters skipped due to reasons outlined above.  As of
+              ;; Unicode 9.0, this exception affects the following:
+              ;;   lc(U+0130 İ) = i
+              ;;   uc(U+0131 ı) = I
+              ;;   uc(U+017F ſ) = S
+              ;;   uc(U+212A K) = k
+              (when (> cased 127)
+                (let ((end (if (consp ch) (cdr ch) ch)))
+                  (setq ch (max 128 (if (consp ch) (car ch) ch)))
+                  (while (<= ch end)
+                    (funcall func ch cased)
+                    (setq ch (1+ ch))))))
+            (unicode-property-table-internal property))))
+        (down tbl)
+        (up (case-table-get-table tbl 'up)))
+
+    ;; This works on an assumption that if toUpper(x) != x then toLower(x) ==
+    ;; x (and the opposite for toLower/toUpper).  This doesn’t hold for title
+    ;; case characters but those incorrect mappings will be overwritten later.
+    (funcall map-unicode-property 'uppercase
+             (lambda (lc uc) (aset down lc lc) (aset up uc uc)))
+    (funcall map-unicode-property 'lowercase
+             (lambda (uc lc) (aset down lc lc) (aset up uc uc)))
+
+    ;; Now deal with the actual mapping.  This will correctly assign casing for
+    ;; title-case characters.
+    (funcall map-unicode-property 'uppercase
+             (lambda (lc uc) (aset up lc uc) (aset up uc uc)))
+    (funcall map-unicode-property 'lowercase
+             (lambda (uc lc) (aset down uc lc) (aset down lc lc))))
+
+  ;; Clear out the extra slots so that they will be recomputed from the main
+  ;; (downcase) table and upcase table.  Since we’re side-stepping the usual
+  ;; set-case-syntax-* functions, we need to do it explicitly.
+  (set-char-table-extra-slot tbl 1 nil)
+  (set-char-table-extra-slot tbl 2 nil)
 
   ;; Fixme: syntax for symbols &c
   )
diff --git a/test/src/casefiddle-tests.el b/test/src/casefiddle-tests.el
index 4b2eeaf..ca3657d 100644
--- a/test/src/casefiddle-tests.el
+++ b/test/src/casefiddle-tests.el
@@ -72,8 +72,7 @@ casefiddle-tests--characters
 
     (?Σ ?Σ ?σ ?Σ)
     (?σ ?Σ ?σ ?Σ)
-    ;; FIXME: Another broken one:
-    ;;(?ς ?Σ ?ς ?Σ)
+    (?ς ?Σ ?ς ?Σ)
 
     (?Ⅷ ?Ⅷ ?ⅷ ?Ⅷ)
     (?ⅷ ?Ⅷ ?ⅷ ?Ⅷ)))
@@ -151,7 +150,6 @@ casefiddle-tests--characters
               ;;("fish" "FIsh" "fish" "Fish" "Fish")
               ;;("Straße" "STRASSE" "straße" "Straße" "Straße")
               ;;("ΌΣΟΣ" "ΌΣΟΣ" "όσος" "Όσος" "Όσος")
-              ;;("όσος" "ΌΣΟΣ" "όσος" "Όσος" "Όσος")
               ;; And here’s what is actually happening:
               ("DŽUNGLA" "DŽUNGLA" "džungla" "DŽungla" "DŽUNGLA")
               ("Džungla" "DžUNGLA" "džungla" "Džungla" "Džungla")
@@ -160,7 +158,8 @@ casefiddle-tests--characters
               ("fish" "fiSH" "fish" "fish" "fish")
               ("Straße" "STRAßE" "straße" "Straße" "Straße")
               ("ΌΣΟΣ" "ΌΣΟΣ" "όσοσ" "Όσοσ" "ΌΣΟΣ")
-              ("όσος" "ΌΣΟς" "όσος" "Όσος" "Όσος"))
+
+              ("όσος" "ΌΣΟΣ" "όσος" "Όσος" "Όσος"))
             (nreverse errors))
          (let* ((input (car test))
                 (expected (cdr test))
-- 
2.8.0.rc3.226.g39d4020


^ permalink raw reply related	[flat|nested] 89+ messages in thread

* bug#24603: [RFC 08/18] Support casing characters which map into multiple code points
  2016-10-04  7:38     ` Eli Zaretskii
@ 2016-10-06 21:40       ` Michal Nazarewicz
  2016-10-07  7:46         ` Eli Zaretskii
  0 siblings, 1 reply; 89+ messages in thread
From: Michal Nazarewicz @ 2016-10-06 21:40 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: 24603

On Tue, Oct 04 2016, Eli Zaretskii wrote:
>> From: Michal Nazarewicz <mina86@mina86.com>
>> Date: Tue,  4 Oct 2016 03:10:31 +0200
>> 
>> * src/make-special-casing.py: New script to generate special-casing.h
>> file from the SpecialCasing.txt data file.
>
> Please do this without Python, either in Emacs Lisp and/or the tools
> already used in admin/unidata, including awk.  Python is still not
> available as widely as the other tools.

Will do.

>> +special-casing.h: make-special-casing.py ../admin/unidata/SpecialCasing.txt
>> +	$(AM_V_GEN)
>> +	python $^ $@
>
> Don't use a literal name of a program, so users could specify their
> name and/or absolute file name at build time.  See what we do with
> awk, for example.

Will do.

>> +#include "special-casing.h"
>
> Why not a shorter 'casing.h'?

It includes data from SpecialCasing.txt only so I figured
‘special-casing.h’ would be a more descriptive name.  I can change it to
‘casing.h’ if you prefer.

> Once again, this stores the casing rules in C, whereas I'd prefer to
> have them in tables accessible from Lisp.

There are a few reasons to hard-code the special casing rules in C.

Some of them have conditions (does are implemented in later patches)
which are non-trivial to encode in Lisp.  Some look backwards
(e.g. After_Soft_Dotted) and some look forward (e.g. Not_Before_Dot) and
not necessarily only one character forward (e.g. More_Above).

By hard-coding the implementation, each of the predicates can be handled
in a custom way such that the code only ever looks at current and one
character forward.  Not to mention that is likely faster.

Furthermore, by not having the data in Lisp I can make certain
assumptions.  For example that a single character will get changed into
a sequence of at most six bytes.  Having to deal with arbitrary data
that user may have put in the lisp data would further complicate the
code and if the flexibility is not worth it.

There is also the aspect that not all of the language-dependent rules
implemented in this patchset are part of Unicode.  Dutch IJ (when
spelled as separate ASCII characters) is not covered by
SpecialCasing.txt.  Similarly, I might also get around to implementing
Irish rules¹.  Mixing information from SpecialCasing.txt and other
sources feels a bit messy.

¹ https://bugzilla.mozilla.org/show_bug.cgi?id=1018805

>> @@ -194,7 +276,9 @@ casify_object (enum case_action flag, Lisp_Object obj)
>>  DEFUN ("upcase", Fupcase, Supcase, 1, 1, 0,
>>         doc: /* Convert argument to upper case and return that.
>>  The argument may be a character or string.  The result has the same type.
>> -The argument object is not altered--the value is a copy.
>> +The argument object is not altered--the value is a copy.  If argument
>> +is a character, characters which map to multiple code points when
>> +cased, e.g. fi, are returned unchanged.
>>  See also `capitalize', `downcase' and `upcase-initials'.  */)
>
> I think this doc string should say what to do if the application wants
> to convert fi into "FI".

Perhaps it would be better to describe it in Info page and link that
from the docstrings?  The reason I’m suggesting that is that there are
11 functions defined in src/casefiddle.c and a lot of the documentation
like that (some of which upcoming in future patches) should be included
in all of them but that would mean either repeating the same thing over
and over or linking to one particular function, but then which one
should be the special one?  If all of this was moved to Info page and it
linked from docstring, the problem would go away.

-- 
Best regards
ミハウ “𝓶𝓲𝓷𝓪86” ナザレヴイツ
«If at first you don’t succeed, give up skydiving»





^ permalink raw reply	[flat|nested] 89+ messages in thread

* bug#24603: [RFC 02/18] Generate upcase and downcase tables from Unicode data
  2016-10-06 20:29                 ` Michal Nazarewicz
@ 2016-10-07  6:52                   ` Eli Zaretskii
  0 siblings, 0 replies; 89+ messages in thread
From: Eli Zaretskii @ 2016-10-07  6:52 UTC (permalink / raw)
  To: Michal Nazarewicz; +Cc: 24603

> From: Michal Nazarewicz <mina86@mina86.com>
> Cc: 24603@debbugs.gnu.org
> Date: Thu, 06 Oct 2016 22:29:06 +0200
> 
> >> > > I think we should document all the changes.
> >> > 
> >> > I wouldn’t know where to put such documentation.
> >> 
> >> On a separate file under admin/unidata/, if we cannot find a better
> >> place.
> >
> > Or maybe just mention in the commit log the URL of the message in the
> > bugtracker's records, where you posted the diffs, it might be good
> > enough.
> 
> That’s easy enough.

Thanks, this is what I had in mind, indeed.





^ permalink raw reply	[flat|nested] 89+ messages in thread

* bug#24603: [RFC 08/18] Support casing characters which map into multiple code points
  2016-10-06 21:40       ` Michal Nazarewicz
@ 2016-10-07  7:46         ` Eli Zaretskii
  2017-01-28 23:48           ` Michal Nazarewicz
  0 siblings, 1 reply; 89+ messages in thread
From: Eli Zaretskii @ 2016-10-07  7:46 UTC (permalink / raw)
  To: Michal Nazarewicz; +Cc: 24603

> From: Michal Nazarewicz <mina86@mina86.com>
> Cc: 24603@debbugs.gnu.org
> Date: Thu, 06 Oct 2016 23:40:11 +0200
> 
> >> +#include "special-casing.h"
> >
> > Why not a shorter 'casing.h'?
> 
> It includes data from SpecialCasing.txt only so I figured
> ‘special-casing.h’ would be a more descriptive name.  I can change it to
> ‘casing.h’ if you prefer.

Shorter names are easier to deal with.  Also, the "special" part might
beg the question: where's the "normal" part.  But it's a minor nit,
admittedly.  If you feel strongly about your name, I won't fight that.

> > Once again, this stores the casing rules in C, whereas I'd prefer to
> > have them in tables accessible from Lisp.
> 
> There are a few reasons to hard-code the special casing rules in C.
> 
> Some of them have conditions (does are implemented in later patches)
> which are non-trivial to encode in Lisp.  Some look backwards
> (e.g. After_Soft_Dotted) and some look forward (e.g. Not_Before_Dot) and
> not necessarily only one character forward (e.g. More_Above).
> 
> By hard-coding the implementation, each of the predicates can be handled
> in a custom way such that the code only ever looks at current and one
> character forward.  Not to mention that is likely faster.
> 
> Furthermore, by not having the data in Lisp I can make certain
> assumptions.  For example that a single character will get changed into
> a sequence of at most six bytes.  Having to deal with arbitrary data
> that user may have put in the lisp data would further complicate the
> code and if the flexibility is not worth it.

It doesn't have to be arbitrary Lisp data.  It could be just a set of
flags stored in a Lisp structure whose implementation is in C.

It's IMO okay to have this hard-coded in C, if a Lisp based
implementation would be unreasonably complex and inelegant.  But I
don't see it should be quite yet; maybe I'm missing something.  May I
suggest that you try designing this, and if it turns out to be too
cumbersome, come back with the evidence?

> There is also the aspect that not all of the language-dependent rules
> implemented in this patchset are part of Unicode.  Dutch IJ (when
> spelled as separate ASCII characters) is not covered by
> SpecialCasing.txt.

The way we deal with such augmentations is by having most of the data
auto-generated, and some of it maintained manually.  One example is
the current characters.el and charscript.el it loads.  Can we use a
similar approach in this case?  Experience shows that maintaining
everything manually is error-prone and a huge maintenance head-ache in
the long run, what with a new version of the Unicode Standard
available at least once a year.

> >> @@ -194,7 +276,9 @@ casify_object (enum case_action flag, Lisp_Object obj)
> >>  DEFUN ("upcase", Fupcase, Supcase, 1, 1, 0,
> >>         doc: /* Convert argument to upper case and return that.
> >>  The argument may be a character or string.  The result has the same type.
> >> -The argument object is not altered--the value is a copy.
> >> +The argument object is not altered--the value is a copy.  If argument
> >> +is a character, characters which map to multiple code points when
> >> +cased, e.g. fi, are returned unchanged.
> >>  See also `capitalize', `downcase' and `upcase-initials'.  */)
> >
> > I think this doc string should say what to do if the application wants
> > to convert fi into "FI".
> 
> Perhaps it would be better to describe it in Info page and link that
> from the docstrings?

Fine with me.

Thanks.





^ permalink raw reply	[flat|nested] 89+ messages in thread

* bug#24603: [RFC 16/18] Refactor character class checking; optimise ASCII case
  2016-10-04  7:48     ` Eli Zaretskii
@ 2016-10-17 13:22       ` Michal Nazarewicz
  2016-11-06 19:26       ` Michal Nazarewicz
  1 sibling, 0 replies; 89+ messages in thread
From: Michal Nazarewicz @ 2016-10-17 13:22 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: 24603

On Tue, Oct 04 2016, Eli Zaretskii wrote:
>> From: Michal Nazarewicz <mina86@mina86.com>
>> Date: Tue,  4 Oct 2016 03:10:39 +0200
>> 
>> +const unsigned char category_char_bits[] = {
>> +  [UNICODE_CATEGORY_UNKNOWN] = 0,
>> +  [UNICODE_CATEGORY_Lu] = CHAR_BIT_ALPHA_ | CHAR_BIT_UPPER,
>> +  [UNICODE_CATEGORY_Ll] = CHAR_BIT_ALPHA_ | CHAR_BIT_LOWER,
>
> Is this syntax portable enough for us to use it?

It’s a C99 feature and C99 compiler is required since Emacs 25.1.

>> +/* Limited set of character categories which syntax-independent.  Testing of
>                                           ^^^^^^^^^^^^^^^^^^^^^^^^
> "which are syntax-independent"
>
>> + * those characters do not require any run-time data, e.g. do not depend on
>                        ^^^^^^^^^^^^^^                         ^^^^^^^^^^^^^
> "does not require" and "does not depend"

Both done.

> Thanks.  I think this change will require a benchmark to make sure we
> don't lose too much in terms of performance.

Will do.

-- 
Best regards
ミハウ “𝓶𝓲𝓷𝓪86” ナザレヴイツ
«If at first you don’t succeed, give up skydiving»





^ permalink raw reply	[flat|nested] 89+ messages in thread

* bug#24603: [PATCH 0/3] Case table updates
  2016-10-04  1:05 bug#24603: [RFC 00/18] Improvement to casing Michal Nazarewicz
  2016-10-04  1:10 ` bug#24603: [RFC 01/18] Add tests for casefiddle.c Michal Nazarewicz
@ 2016-10-17 22:03 ` Michal Nazarewicz
  2016-10-17 22:03   ` bug#24603: [PATCH 1/3] Add tests for casefiddle.c Michal Nazarewicz
                     ` (3 more replies)
  2017-03-09 21:51 ` bug#24603: [PATCHv5 00/11] Casing improvements Michal Nazarewicz
  2 siblings, 4 replies; 89+ messages in thread
From: Michal Nazarewicz @ 2016-10-17 22:03 UTC (permalink / raw)
  To: 24603

As I continue working on the patchset, it keeps on growing.  To
somewhat limit that, I’ll start applying the patches.

Unless there are objections, I’ll commit the following three patches
in a few days.

The first two map to the first two from original sumbission.  The
first gained tests for byte-8 characters and the second includes
changes requested by Eli.

The third is a new patch.

Michal Nazarewicz (3):
  Add tests for casefiddle.c
  Generate upcase and downcase tables from Unicode data
  Don’t generate ‘X maps to X’ entries in case tables

 lisp/international/characters.el | 335 +++++++--------------------------------
 test/src/casefiddle-tests.el     | 250 +++++++++++++++++++++++++++++
 2 files changed, 310 insertions(+), 275 deletions(-)
 create mode 100644 test/src/casefiddle-tests.el

-- 
2.8.0.rc3.226.g39d4020






^ permalink raw reply	[flat|nested] 89+ messages in thread

* bug#24603: [PATCH 1/3] Add tests for casefiddle.c
  2016-10-17 22:03 ` bug#24603: [PATCH 0/3] Case table updates Michal Nazarewicz
@ 2016-10-17 22:03   ` Michal Nazarewicz
  2016-10-17 22:03   ` bug#24603: [PATCH 2/3] Generate upcase and downcase tables from Unicode data Michal Nazarewicz
                     ` (2 subsequent siblings)
  3 siblings, 0 replies; 89+ messages in thread
From: Michal Nazarewicz @ 2016-10-17 22:03 UTC (permalink / raw)
  To: 24603

Fixes cases marked FIXME upcoming in followup commits.

* test/src/casefiddle-tests.el (casefiddle-tests-char-properties,
casefiddle-tests-case-table, casefiddle-tests-casing-character,
casefiddle-tests-casing, casefiddle-tests-casing-byte8,
casefiddle-tests-casing-byte8-with-changes): New tests.
(casefiddle-tests--test-casing): New helper function for runnig
some of the tests.
---
 test/src/casefiddle-tests.el | 251 +++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 251 insertions(+)
 create mode 100644 test/src/casefiddle-tests.el

diff --git a/test/src/casefiddle-tests.el b/test/src/casefiddle-tests.el
new file mode 100644
index 0000000..b5a77a1
--- /dev/null
+++ b/test/src/casefiddle-tests.el
@@ -0,0 +1,251 @@
+;;; casefiddle-tests.el --- tests for casefiddle.c functions -*- lexical-binding: t -*-
+
+;; Copyright (C) 2015-2016 Free Software Foundation, Inc.
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs 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.
+
+;; GNU Emacs 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 <http://www.gnu.org/licenses/>.
+
+;;; Code:
+
+(require 'ert)
+
+(ert-deftest casefiddle-tests-char-properties ()
+  "Sanity check of character Unicode properties."
+  (should-not
+   (let (errors)
+     ;;            character  uppercase  lowercase  titlecase
+     (dolist (test '((?A nil ?a nil)
+                     (?a ?A nil ?A)
+                     (?Ł nil ?ł nil)
+                     (?ł ?Ł nil ?Ł)
+
+                     (?DŽ nil ?dž ?Dž)
+                     (?Dž ?DŽ ?dž ?Dž)
+                     (?dž ?DŽ nil ?Dž)
+
+                     (?Σ nil ?σ nil)
+                     (?σ ?Σ nil ?Σ)
+                     (?ς ?Σ nil ?Σ)
+
+                     (?ⅷ ?Ⅷ nil ?Ⅷ)
+                     (?Ⅷ nil ?ⅷ nil)))
+       (let ((ch (car test))
+             (expected (cdr test))
+             (props '(uppercase lowercase titlecase)))
+         (while props
+           (let ((got (get-char-code-property ch (car props))))
+             (unless (equal (car expected) got)
+               (push (format "\n%c %s; expected: %s but got: %s"
+                             ch (car props) (car expected) got)
+                     errors)))
+           (setq props (cdr props) expected (cdr expected)))))
+     (when errors
+       (mapconcat (lambda (line) line) (nreverse errors) "")))))
+
+
+(defconst casefiddle-tests--characters
+  ;; character  uppercase  lowercase  titlecase
+  '((?A ?A ?a ?A)
+    (?a ?A ?a ?A)
+    (?Ł ?Ł ?ł ?Ł)
+    (?ł ?Ł ?ł ?Ł)
+
+    ;; FIXME: We should have:
+    ;;(?DŽ ?DŽ ?dž ?Dž)
+    ;; but instead we have:
+    (?DŽ ?DŽ ?dž ?DŽ)
+    ;; FIXME: Those two are broken at the moment:
+    ;;(?Dž ?DŽ ?dž ?Dž)
+    ;;(?dž ?DŽ ?dž ?Dž)
+
+    (?Σ ?Σ ?σ ?Σ)
+    (?σ ?Σ ?σ ?Σ)
+    ;; FIXME: Another broken one:
+    ;;(?ς ?Σ ?ς ?Σ)
+
+    (?Ⅷ ?Ⅷ ?ⅷ ?Ⅷ)
+    (?ⅷ ?Ⅷ ?ⅷ ?Ⅷ)))
+
+
+(ert-deftest casefiddle-tests-case-table ()
+  "Sanity check of down and up case tables."
+  (should-not
+   (let (errors
+         (up (case-table-get-table (current-case-table) 'up))
+         (down (case-table-get-table (current-case-table) 'down)))
+     (dolist (test casefiddle-tests--characters)
+       (let ((ch (car test))
+             (expected (cdr test))
+             (props '(uppercase lowercase))
+             (tabs (list up down)))
+         (while props
+           (let ((got (aref (car tabs) ch)))
+             (unless (equal (car expected) got)
+               (push (format "\n%c %s; expected: %s but got: %s"
+                             ch (car props) (car expected) got)
+                     errors)))
+           (setq props (cdr props) tabs (cdr tabs) expected (cdr expected)))))
+     (when errors
+       (mapconcat (lambda (line) line) (nreverse errors) "")))))
+
+
+(ert-deftest casefiddle-tests-casing-character ()
+  (should-not
+   (let (errors)
+     (dolist (test casefiddle-tests--characters)
+       (let ((ch (car test))
+             (expected (cdr test))
+             (funcs '(upcase downcase capitalize)))
+         (while funcs
+           (let ((got (funcall (car funcs) ch)))
+             (unless (equal (car expected) got)
+               (push (format "\n%c %s; expected: %s but got: %s"
+                             ch (car funcs) (car expected) got)
+                     errors)))
+           (setq funcs (cdr funcs) expected (cdr expected)))))
+     (when errors
+       (mapconcat (lambda (line) line) (nreverse errors) "")))))
+
+
+(ert-deftest casefiddle-tests-casing-word ()
+  (with-temp-buffer
+    (dolist (test '((upcase-word     . "FOO Bar")
+                    (downcase-word   . "foo Bar")
+                    (capitalize-word . "Foo Bar")))
+      (dolist (back '(nil t))
+        (delete-region (point-min) (point-max))
+        (insert "foO Bar")
+        (goto-char (+ (if back 4 0) (point-min)))
+        (funcall (car test) (if back -1 1))
+        (should (string-equal (cdr test) (buffer-string)))
+        (should (equal (+ (if back 4 3) (point-min)) (point)))))))
+
+
+(defun casefiddle-tests--test-casing (tests)
+  (nreverse
+   (cl-reduce
+    (lambda (errors test)
+      (let* ((input (car test))
+             (expected (cdr test))
+             (func-pairs '((upcase upcase-region)
+                           (downcase downcase-region)
+                           (capitalize capitalize-region)
+                           (upcase-initials upcase-initials-region)))
+             (get-string (lambda (func) (funcall func input)))
+             (get-region (lambda (func)
+                           (delete-region (point-min) (point-max))
+                           (unwind-protect
+                               (progn
+                                 (unless (multibyte-string-p input)
+                                   (toggle-enable-multibyte-characters))
+                                 (insert input)
+                                 (funcall func (point-min) (point-max))
+                                 (buffer-string))
+                             (unless (multibyte-string-p input)
+                               (toggle-enable-multibyte-characters)))))
+             (fmt-str (lambda (str)
+                        (format "%s  (%sbyte; %d chars; %d bytes)"
+                                str
+                                (if (multibyte-string-p str) "multi" "uni")
+                                (length str) (string-bytes str))))
+             funcs getters)
+        (while (and func-pairs expected)
+          (setq funcs (car func-pairs)
+                getters (list get-string get-region))
+          (while (and funcs getters)
+            (let ((got (funcall (car getters) (car funcs))))
+              (unless (string-equal got (car expected))
+                (let ((fmt (length (symbol-name (car funcs)))))
+                  (setq fmt (format "\n%%%ds: %%s" (max fmt 8)))
+                  (push (format (concat fmt fmt fmt)
+                                (car funcs) (funcall fmt-str input)
+                                "expected" (funcall fmt-str (car expected))
+                                "but got" (funcall fmt-str got))
+                        errors))))
+            (setq funcs (cdr funcs) getters (cdr getters)))
+          (setq func-pairs (cdr func-pairs) expected (cdr expected))))
+      errors)
+    (cons () tests))))
+
+(ert-deftest casefiddle-tests-casing ()
+  (should-not
+   (with-temp-buffer
+     (casefiddle-tests--test-casing
+      ;; input     upper     lower    capitalize up-initials
+      '(("Foo baR" "FOO BAR" "foo bar" "Foo Bar" "Foo BaR")
+        ("Ⅷ ⅷ" "Ⅷ Ⅷ" "ⅷ ⅷ" "Ⅷ Ⅷ" "Ⅷ Ⅷ")
+        ;; FIXME: Everything below is broken at the moment.  Here’s what
+        ;; should happen:
+        ;;("DŽUNGLA" "DŽUNGLA" "džungla" "Džungla" "DžUNGLA")
+        ;;("Džungla" "DŽUNGLA" "džungla" "Džungla" "Džungla")
+        ;;("džungla" "DŽUNGLA" "džungla" "Džungla" "Džungla")
+        ;;("define" "DEFINE" "define" "Define" "Define")
+        ;;("fish" "FIsh" "fish" "Fish" "Fish")
+        ;;("Straße" "STRASSE" "straße" "Straße" "Straße")
+        ;;("ΌΣΟΣ" "ΌΣΟΣ" "όσος" "Όσος" "Όσος")
+        ;;("όσος" "ΌΣΟΣ" "όσος" "Όσος" "Όσος")
+        ;; And here’s what is actually happening:
+        ("DŽUNGLA" "DŽUNGLA" "džungla" "DŽungla" "DŽUNGLA")
+        ("Džungla" "DžUNGLA" "džungla" "Džungla" "Džungla")
+        ("džungla" "DŽUNGLA" "džungla" "DŽungla" "DŽungla")
+        ("define" "DEfiNE" "define" "Define" "Define")
+        ("fish" "fiSH" "fish" "fish" "fish")
+        ("Straße" "STRAßE" "straße" "Straße" "Straße")
+        ("ΌΣΟΣ" "ΌΣΟΣ" "όσοσ" "Όσοσ" "ΌΣΟΣ")
+        ("όσος" "ΌΣΟς" "όσος" "Όσος" "Όσος"))))))
+
+(ert-deftest casefiddle-tests-casing-byte8 ()
+  (should-not
+   (with-temp-buffer
+     (casefiddle-tests--test-casing
+      '(("\xff Foo baR \xff"
+         "\xff FOO BAR \xff"
+         "\xff foo bar \xff"
+         "\xff Foo Bar \xff"
+         "\xff Foo BaR \xff")
+        ("\xff Zażółć gĘŚlą \xff"
+         "\xff ZAŻÓŁĆ GĘŚLĄ \xff"
+         "\xff zażółć gęślą \xff"
+         "\xff Zażółć Gęślą \xff"
+         "\xff Zażółć GĘŚlą \xff"))))))
+
+(ert-deftest casefiddle-tests-casing-byte8-with-changes ()
+  (let ((tab (make-char-table 'case-table))
+        (std (standard-case-table))
+        (test '("\xff\xff\xef Foo baR \xcf\xcf"
+                "\xef\xef\xef FOO BAR \xcf\xcf"
+                "\xff\xff\xff foo bar \xcf\xcf"
+                "\xef\xff\xff Foo Bar \xcf\xcf"
+                "\xef\xff\xef Foo BaR \xcf\xcf"))
+        (byte8 #x3FFF00))
+    (should-not
+     (with-temp-buffer
+       (set-case-table tab)
+       (set-char-table-parent tab std)
+       (set-char-table-extra-slot tab 0 (make-char-table 'upcase))
+       (set-char-table-parent (char-table-extra-slot tab 0)
+                              (char-table-extra-slot std 0))
+       (set-case-syntax-pair (+ byte8 #xef) (+ byte8 #xff) tab)
+       (casefiddle-tests--test-casing
+        (list test
+              (mapcar (lambda (str) (decode-coding-string str 'binary)) test)
+              '("\xff\xff\xef Zażółć gĘŚlą \xcf\xcf"
+                "\xef\xef\xef ZAŻÓŁĆ GĘŚLĄ \xcf\xcf"
+                "\xff\xff\xff zażółć gęślą \xcf\xcf"
+                "\xef\xff\xff Zażółć Gęślą \xcf\xcf"
+                "\xef\xff\xef Zażółć GĘŚlą \xcf\xcf")))))))
+
+
+;;; casefiddle-tests.el ends here
-- 
2.8.0.rc3.226.g39d4020






^ permalink raw reply related	[flat|nested] 89+ messages in thread

* bug#24603: [PATCH 2/3] Generate upcase and downcase tables from Unicode data
  2016-10-17 22:03 ` bug#24603: [PATCH 0/3] Case table updates Michal Nazarewicz
  2016-10-17 22:03   ` bug#24603: [PATCH 1/3] Add tests for casefiddle.c Michal Nazarewicz
@ 2016-10-17 22:03   ` Michal Nazarewicz
  2016-10-17 22:03   ` bug#24603: [PATCH 3/3] Don’t generate ‘X maps to X’ entries in case tables Michal Nazarewicz
  2016-10-18  6:36   ` bug#24603: [PATCH 0/3] Case table updates Eli Zaretskii
  3 siblings, 0 replies; 89+ messages in thread
From: Michal Nazarewicz @ 2016-10-17 22:03 UTC (permalink / raw)
  To: 24603

Use Unicode data to generate case tables instead of mostly repeating
them in lisp code.  Do that in a way which maps ‘Dz’ (and similar)
digraph to ‘dz’ when down- and ‘DZ’ when upcasing.

https://debbugs.gnu.org/cgi/bugreport.cgi?msg=89;bug=24603 lists all
changes to syntax table and case tables introduced by this commit.

* lisp/international/characters.el: Remove case-pairs defined with
explicit Lisp code and instead use Unicode character properties.

* test/src/casefiddle-tests.el (casefiddle-tests--characters,
casefiddle-tests-casing): Update test cases which are now working
as they should.
---
 lisp/international/characters.el | 345 ++++++++-------------------------------
 test/src/casefiddle-tests.el     |   7 +-
 2 files changed, 73 insertions(+), 279 deletions(-)

diff --git a/lisp/international/characters.el b/lisp/international/characters.el
index 1757d2b..8dd9c73 100644
--- a/lisp/international/characters.el
+++ b/lisp/international/characters.el
@@ -543,10 +543,6 @@ ?L
   (set-case-syntax ?½ "_" tbl)
   (set-case-syntax ?¾ "_" tbl)
   (set-case-syntax ?¿ "." tbl)
-  (let ((c 192))
-    (while (<= c 222)
-      (set-case-syntax-pair c (+ c 32) tbl)
-      (setq c (1+ c))))
   (set-case-syntax ?× "_" tbl)
   (set-case-syntax ?ß "w" tbl)
   (set-case-syntax ?÷ "_" tbl)
@@ -558,101 +554,8 @@ ?L
     (modify-category-entry c ?l)
     (setq c (1+ c)))
 
-  (let ((pair-ranges '((#x0100 . #x012F)
-		       (#x0132 . #x0137)
-		       (#x0139 . #x0148)
-		       (#x014a . #x0177)
-		       (#x0179 . #x017E)
-		       (#x0182 . #x0185)
-		       (#x0187 . #x0188)
-		       (#x018B . #x018C)
-		       (#x0191 . #x0192)
-		       (#x0198 . #x0199)
-		       (#x01A0 . #x01A5)
-		       (#x01A7 . #x01A8)
-		       (#x01AC . #x01AD)
-		       (#x01AF . #x01B0)
-		       (#x01B3 . #x01B6)
-		       (#x01B8 . #x01B9)
-		       (#x01BC . #x01BD)
-		       (#x01CD . #x01DC)
-		       (#x01DE . #x01EF)
-		       (#x01F4 . #x01F5)
-		       (#x01F8 . #x021F)
-		       (#x0222 . #x0233)
-		       (#x023B . #x023C)
-		       (#x0241 . #x0242)
-		       (#x0246 . #x024F))))
-    (dolist (elt pair-ranges)
-      (let ((from (car elt)) (to (cdr elt)))
-	(while (< from to)
-	  (set-case-syntax-pair from (1+ from) tbl)
-	  (setq from (+ from 2))))))
-
-  (set-case-syntax-pair ?Ÿ ?ÿ tbl)
-
-  ;; In some languages, such as Turkish, U+0049 LATIN CAPITAL LETTER I
-  ;; and U+0131 LATIN SMALL LETTER DOTLESS I make a case pair, and so
-  ;; do U+0130 LATIN CAPITAL LETTER I WITH DOT ABOVE and U+0069 LATIN
-  ;; SMALL LETTER I.
-
-  ;; We used to set up half of those correspondence unconditionally,
-  ;; but that makes searches slow.  So now we don't set up either half
-  ;; of these correspondences by default.
-
-  ;; (set-downcase-syntax  ?İ ?i tbl)
-  ;; (set-upcase-syntax    ?I ?ı tbl)
-
-  (set-case-syntax-pair ?Ɓ ?ɓ tbl)
-  (set-case-syntax-pair ?Ɔ ?ɔ tbl)
-  (set-case-syntax-pair ?Ɖ ?ɖ tbl)
-  (set-case-syntax-pair ?Ɗ ?ɗ tbl)
-  (set-case-syntax-pair ?Ǝ ?ǝ tbl)
-  (set-case-syntax-pair ?Ə ?ə tbl)
-  (set-case-syntax-pair ?Ɛ ?ɛ tbl)
-  (set-case-syntax-pair ?Ɠ ?ɠ tbl)
-  (set-case-syntax-pair ?Ɣ ?ɣ tbl)
-  (set-case-syntax-pair ?Ɩ ?ɩ tbl)
-  (set-case-syntax-pair ?Ɨ ?ɨ tbl)
-  (set-case-syntax-pair ?Ɯ ?ɯ tbl)
-  (set-case-syntax-pair ?Ɲ ?ɲ tbl)
-  (set-case-syntax-pair ?Ɵ ?ɵ tbl)
-  (set-case-syntax-pair ?Ʀ ?ʀ tbl)
-  (set-case-syntax-pair ?Ʃ ?ʃ tbl)
-  (set-case-syntax-pair ?Ʈ ?ʈ tbl)
-  (set-case-syntax-pair ?Ʊ ?ʊ tbl)
-  (set-case-syntax-pair ?Ʋ ?ʋ tbl)
-  (set-case-syntax-pair ?Ʒ ?ʒ tbl)
-  ;; We use set-downcase-syntax below, since we want upcase of dž
-  ;; return DŽ, not Dž, and the same for the rest.
-  (set-case-syntax-pair ?DŽ ?dž tbl)
-  (set-downcase-syntax ?Dž ?dž tbl)
-  (set-case-syntax-pair ?LJ ?lj tbl)
-  (set-downcase-syntax ?Lj ?lj tbl)
-  (set-case-syntax-pair ?NJ ?nj tbl)
-  (set-downcase-syntax ?Nj ?nj tbl)
-
-  ;; 01F0; F; 006A 030C; # LATIN SMALL LETTER J WITH CARON
-
-  (set-case-syntax-pair ?DZ ?dz tbl)
-  (set-downcase-syntax ?Dz ?dz tbl)
-  (set-case-syntax-pair ?Ƕ ?ƕ tbl)
-  (set-case-syntax-pair ?Ƿ ?ƿ tbl)
-  (set-case-syntax-pair ?Ⱥ ?ⱥ tbl)
-  (set-case-syntax-pair ?Ƚ ?ƚ tbl)
-  (set-case-syntax-pair ?Ⱦ ?ⱦ tbl)
-  (set-case-syntax-pair ?Ƀ ?ƀ tbl)
-  (set-case-syntax-pair ?Ʉ ?ʉ tbl)
-  (set-case-syntax-pair ?Ʌ ?ʌ tbl)
-
   ;; Latin Extended Additional
   (modify-category-entry '(#x1e00 . #x1ef9) ?l)
-  (setq c #x1e00)
-  (while (<= c #x1ef9)
-    (and (zerop (% c 2))
-	 (or (<= c #x1e94) (>= c #x1ea0))
-	 (set-case-syntax-pair c (1+ c) tbl))
-    (setq c (1+ c)))
 
   ;; Latin Extended-C
   (setq c #x2C60)
@@ -660,57 +563,12 @@ ?L
     (modify-category-entry c ?l)
     (setq c (1+ c)))
 
-  (let ((pair-ranges '((#x2C60 . #x2C61)
-                       (#x2C67 . #x2C6C)
-                       (#x2C72 . #x2C73)
-                       (#x2C75 . #x2C76))))
-    (dolist (elt pair-ranges)
-      (let ((from (car elt)) (to (cdr elt)))
-        (while (< from to)
-          (set-case-syntax-pair from (1+ from) tbl)
-          (setq from (+ from 2))))))
-
-  (set-case-syntax-pair ?Ɫ ?ɫ tbl)
-  (set-case-syntax-pair ?Ᵽ ?ᵽ tbl)
-  (set-case-syntax-pair ?Ɽ ?ɽ tbl)
-  (set-case-syntax-pair ?Ɑ ?ɑ tbl)
-  (set-case-syntax-pair ?Ɱ ?ɱ tbl)
-  (set-case-syntax-pair ?Ɐ ?ɐ tbl)
-  (set-case-syntax-pair ?Ɒ ?ɒ tbl)
-  (set-case-syntax-pair ?Ȿ ?ȿ tbl)
-  (set-case-syntax-pair ?Ɀ ?ɀ tbl)
-
   ;; Latin Extended-D
   (setq c #xA720)
   (while (<= c #xA7FF)
     (modify-category-entry c ?l)
     (setq c (1+ c)))
 
-  (let ((pair-ranges '((#xA722 . #xA72F)
-                       (#xA732 . #xA76F)
-                       (#xA779 . #xA77C)
-                       (#xA77E . #xA787)
-                       (#xA78B . #xA78E)
-                       (#xA790 . #xA793)
-                       (#xA796 . #xA7A9)
-                       (#xA7B4 . #xA7B7))))
-    (dolist (elt pair-ranges)
-      (let ((from (car elt)) (to (cdr elt)))
-        (while (< from to)
-          (set-case-syntax-pair from (1+ from) tbl)
-          (setq from (+ from 2))))))
-
-  (set-case-syntax-pair ?Ᵹ ?ᵹ tbl)
-  (set-case-syntax-pair ?Ɦ ?ɦ tbl)
-  (set-case-syntax-pair ?Ɜ ?ɜ tbl)
-  (set-case-syntax-pair ?Ɡ ?ɡ tbl)
-  (set-case-syntax-pair ?Ɬ ?ɬ tbl)
-  (set-case-syntax-pair ?Ɪ ?ɪ tbl)
-  (set-case-syntax-pair ?Ʞ ?ʞ tbl)
-  (set-case-syntax-pair ?Ʇ ?ʇ tbl)
-  (set-case-syntax-pair ?Ʝ ?ʝ tbl)
-  (set-case-syntax-pair ?Ꭓ ?ꭓ tbl)
-
   ;; Latin Extended-E
   (setq c #xAB30)
   (while (<= c #xAB64)
@@ -719,102 +577,19 @@ ?L
 
   ;; Greek
   (modify-category-entry '(#x0370 . #x03ff) ?g)
-  (setq c #x0370)
-  (while (<= c #x03ff)
-    (if (or (and (>= c #x0391) (<= c #x03a1))
-	    (and (>= c #x03a3) (<= c #x03ab)))
-	(set-case-syntax-pair c (+ c 32) tbl))
-    (and (>= c #x03da)
-	 (<= c #x03ee)
-	 (zerop (% c 2))
-	 (set-case-syntax-pair c (1+ c) tbl))
-    (setq c (1+ c)))
-  (set-case-syntax-pair ?Ά ?ά tbl)
-  (set-case-syntax-pair ?Έ ?έ tbl)
-  (set-case-syntax-pair ?Ή ?ή tbl)
-  (set-case-syntax-pair ?Ί ?ί tbl)
-  (set-case-syntax-pair ?Ό ?ό tbl)
-  (set-case-syntax-pair ?Ύ ?ύ tbl)
-  (set-case-syntax-pair ?Ώ ?ώ tbl)
 
   ;; Armenian
   (setq c #x531)
-  (while (<= c #x556)
-    (set-case-syntax-pair c (+ c #x30) tbl)
-    (setq c (1+ c)))
 
   ;; Greek Extended
   (modify-category-entry '(#x1f00 . #x1fff) ?g)
-  (setq c #x1f00)
-  (while (<= c #x1fff)
-    (and (<= (logand c #x000f) 7)
-	 (<= c #x1fa7)
-	 (not (memq c '(#x1f16 #x1f17 #x1f56 #x1f57
-			       #x1f50 #x1f52 #x1f54 #x1f56)))
-	 (/= (logand c #x00f0) #x70)
-	 (set-case-syntax-pair (+ c 8) c tbl))
-    (setq c (1+ c)))
-  (set-case-syntax-pair ?Ᾰ ?ᾰ tbl)
-  (set-case-syntax-pair ?Ᾱ ?ᾱ tbl)
-  (set-case-syntax-pair ?Ὰ ?ὰ tbl)
-  (set-case-syntax-pair ?Ά ?ά tbl)
-  (set-case-syntax-pair ?ᾼ ?ᾳ tbl)
-  (set-case-syntax-pair ?Ὲ ?ὲ tbl)
-  (set-case-syntax-pair ?Έ ?έ tbl)
-  (set-case-syntax-pair ?Ὴ ?ὴ tbl)
-  (set-case-syntax-pair ?Ή ?ή tbl)
-  (set-case-syntax-pair ?ῌ ?ῃ tbl)
-  (set-case-syntax-pair ?Ῐ ?ῐ tbl)
-  (set-case-syntax-pair ?Ῑ ?ῑ tbl)
-  (set-case-syntax-pair ?Ὶ ?ὶ tbl)
-  (set-case-syntax-pair ?Ί ?ί tbl)
-  (set-case-syntax-pair ?Ῠ ?ῠ tbl)
-  (set-case-syntax-pair ?Ῡ ?ῡ tbl)
-  (set-case-syntax-pair ?Ὺ ?ὺ tbl)
-  (set-case-syntax-pair ?Ύ ?ύ tbl)
-  (set-case-syntax-pair ?Ῥ ?ῥ tbl)
-  (set-case-syntax-pair ?Ὸ ?ὸ tbl)
-  (set-case-syntax-pair ?Ό ?ό tbl)
-  (set-case-syntax-pair ?Ὼ ?ὼ tbl)
-  (set-case-syntax-pair ?Ώ ?ώ tbl)
-  (set-case-syntax-pair ?ῼ ?ῳ tbl)
 
   ;; cyrillic
   (modify-category-entry '(#x0400 . #x04FF) ?y)
-  (setq c #x0400)
-  (while (<= c #x04ff)
-    (and (>= c #x0400)
-	 (<= c #x040f)
-	 (set-case-syntax-pair c (+ c 80) tbl))
-    (and (>= c #x0410)
-	 (<= c #x042f)
-	 (set-case-syntax-pair c (+ c 32) tbl))
-    (and (zerop (% c 2))
-	 (or (and (>= c #x0460) (<= c #x0480))
-	     (and (>= c #x048c) (<= c #x04be))
-	     (and (>= c #x04d0) (<= c #x052e)))
-	 (set-case-syntax-pair c (1+ c) tbl))
-    (setq c (1+ c)))
-  (set-case-syntax-pair ?Ӂ ?ӂ tbl)
-  (set-case-syntax-pair ?Ӄ ?ӄ tbl)
-  (set-case-syntax-pair ?Ӈ ?ӈ tbl)
-  (set-case-syntax-pair ?Ӌ ?ӌ tbl)
-
   (modify-category-entry '(#xA640 . #xA69F) ?y)
-  (setq c #xA640)
-  (while (<= c #xA66C)
-    (set-case-syntax-pair c (+ c 1) tbl)
-    (setq c (+ c 2)))
-  (setq c #xA680)
-  (while (<= c #xA69A)
-    (set-case-syntax-pair c (+ c 1) tbl)
-    (setq c (+ c 2)))
 
   ;; Georgian
   (setq c #x10A0)
-  (while (<= c #x10CD)
-    (set-case-syntax-pair c (+ c #x1C60) tbl)
-    (setq c (1+ c)))
 
   ;; Cyrillic Extended-C
   (modify-category-entry '(#x1C80 . #x1C8F) ?y)
@@ -844,12 +619,6 @@ ?L
     (set-case-syntax c "." tbl)
     (setq c (1+ c)))
 
-  ;; Roman numerals
-  (setq c #x2160)
-  (while (<= c #x216f)
-    (set-case-syntax-pair c (+ c #x10) tbl)
-    (setq c (1+ c)))
-
   ;; Fixme: The following blocks might be better as symbol rather than
   ;; punctuation.
   ;; Arrows
@@ -873,25 +642,11 @@ ?L
   ;; Circled Latin
   (setq c #x24b6)
   (while (<= c #x24cf)
-    (set-case-syntax-pair c (+ c 26) tbl)
     (modify-category-entry c ?l)
     (modify-category-entry (+ c 26) ?l)
     (setq c (1+ c)))
 
-  ;; Glagolitic
-  (setq c #x2C00)
-  (while (<= c #x2C2E)
-    (set-case-syntax-pair c (+ c 48) tbl)
-    (setq c (1+ c)))
-
   ;; Coptic
-  (let ((pair-ranges '((#x2C80 . #x2CE2)
-		       (#x2CEB . #x2CF2))))
-    (dolist (elt pair-ranges)
-      (let ((from (car elt)) (to (cdr elt)))
-	(while (< from to)
-	  (set-case-syntax-pair from (1+ from) tbl)
-	  (setq from (+ from 2))))))
   ;; There's no Coptic category.  However, Coptic letters that are
   ;; part of the Greek block above get the Greek category, and those
   ;; in this block are derived from Greek letters, so let's be
@@ -901,45 +656,85 @@ ?L
   ;; Fullwidth Latin
   (setq c #xff21)
   (while (<= c #xff3a)
-    (set-case-syntax-pair c (+ c #x20) tbl)
     (modify-category-entry c ?l)
     (modify-category-entry (+ c #x20) ?l)
     (setq c (1+ c)))
 
-  ;; Deseret
-  (setq c #x10400)
-  (while (<= c #x10427)
-    (set-case-syntax-pair c (+ c 28) tbl)
-    (setq c (1+ c)))
+  ;; Combining diacritics
+  (modify-category-entry '(#x300 . #x362) ?^)
+  ;; Combining marks
+  (modify-category-entry '(#x20d0 . #x20ff) ?^)
 
-  ;; Osage
-  (setq c #x104B0)
-  (while (<= c #x104D3)
-    (set-case-syntax-pair c (+ c 40) tbl)
-    (setq c (1+ c)))
+  ;; Set all Letter, uppercase; Letter, lowercase and Letter, titlecase syntax
+  ;; to word.
+  (let ((syn-tab (standard-syntax-table)))
+    (map-char-table
+     (lambda (ch cat)
+       (when (memq cat '(Lu Ll Lt))
+         (modify-syntax-entry ch "w   " syn-tab)))
+     (unicode-property-table-internal 'general-category))
 
-  ;; Old Hungarian
-  (setq c #x10c80)
-  (while (<= c #x10cb2)
-    (set-case-syntax-pair c (+ c #x40) tbl)
-    (setq c (1+ c)))
+    ;; Ⅰ through Ⅻ had word syntax in the past so set it here as well.
+    ;; General category of those characers is Number, Letter.
+    (modify-syntax-entry '(#x2160 . #x216b) "w   " syn-tab)
 
-  ;; Warang Citi
-  (setq c #x118a0)
-  (while (<= c #x118bf)
-    (set-case-syntax-pair c (+ c #x20) tbl)
-    (setq c (1+ c)))
+    ;; ⓐ thourgh ⓩ are symbols, other according to Unicode but Emacs set
+    ;; their syntax to word in the past so keep backwards compatibility.
+    (modify-syntax-entry '(#x24D0 . #x24E9) "w   " syn-tab))
 
-  ;; Adlam
-  (setq c #x1e900)
-  (while (<= c #x1e921)
-    (set-case-syntax-pair c (+ c #x22) tbl)
-    (setq c (1+ c)))
+  ;; Set downcase and upcase from Unicode properties
 
-  ;; Combining diacritics
-  (modify-category-entry '(#x300 . #x362) ?^)
-  ;; Combining marks
-  (modify-category-entry '(#x20d0 . #x20ff) ?^)
+  ;; In some languages, such as Turkish, U+0049 LATIN CAPITAL LETTER I and
+  ;; U+0131 LATIN SMALL LETTER DOTLESS I make a case pair, and so do U+0130
+  ;; LATIN CAPITAL LETTER I WITH DOT ABOVE and U+0069 LATIN SMALL LETTER I.
+
+  ;; We used to set up half of those correspondence unconditionally, but that
+  ;; makes searches slow.  So now we don't set up either half of these
+  ;; correspondences by default.
+
+  ;; (set-downcase-syntax  ?İ ?i tbl)
+  ;; (set-upcase-syntax    ?I ?ı tbl)
+
+  (let ((map-unicode-property
+         (lambda (property func)
+           (map-char-table
+            (lambda (ch cased)
+              ;; ASCII characters skipped due to reasons outlined above.  As of
+              ;; Unicode 9.0, this exception affects the following:
+              ;;   lc(U+0130 İ) = i
+              ;;   uc(U+0131 ı) = I
+              ;;   uc(U+017F ſ) = S
+              ;;   uc(U+212A K) = k
+              (when (> cased 127)
+                (let ((end (if (consp ch) (cdr ch) ch)))
+                  (setq ch (max 128 (if (consp ch) (car ch) ch)))
+                  (while (<= ch end)
+                    (funcall func ch cased)
+                    (setq ch (1+ ch))))))
+            (unicode-property-table-internal property))))
+        (down tbl)
+        (up (case-table-get-table tbl 'up)))
+
+    ;; This works on an assumption that if toUpper(x) != x then toLower(x) ==
+    ;; x (and the opposite for toLower/toUpper).  This doesn’t hold for title
+    ;; case characters but those incorrect mappings will be overwritten later.
+    (funcall map-unicode-property 'uppercase
+             (lambda (lc uc) (aset down lc lc) (aset up uc uc)))
+    (funcall map-unicode-property 'lowercase
+             (lambda (uc lc) (aset down lc lc) (aset up uc uc)))
+
+    ;; Now deal with the actual mapping.  This will correctly assign casing for
+    ;; title-case characters.
+    (funcall map-unicode-property 'uppercase
+             (lambda (lc uc) (aset up lc uc) (aset up uc uc)))
+    (funcall map-unicode-property 'lowercase
+             (lambda (uc lc) (aset down uc lc) (aset down lc lc))))
+
+  ;; Clear out the extra slots so that they will be recomputed from the main
+  ;; (downcase) table and upcase table.  Since we’re side-stepping the usual
+  ;; set-case-syntax-* functions, we need to do it explicitly.
+  (set-char-table-extra-slot tbl 1 nil)
+  (set-char-table-extra-slot tbl 2 nil)
 
   ;; Fixme: syntax for symbols &c
   )
diff --git a/test/src/casefiddle-tests.el b/test/src/casefiddle-tests.el
index b5a77a1..4142aa0 100644
--- a/test/src/casefiddle-tests.el
+++ b/test/src/casefiddle-tests.el
@@ -72,8 +72,7 @@ casefiddle-tests--characters
 
     (?Σ ?Σ ?σ ?Σ)
     (?σ ?Σ ?σ ?Σ)
-    ;; FIXME: Another broken one:
-    ;;(?ς ?Σ ?ς ?Σ)
+    (?ς ?Σ ?ς ?Σ)
 
     (?Ⅷ ?Ⅷ ?ⅷ ?Ⅷ)
     (?ⅷ ?Ⅷ ?ⅷ ?Ⅷ)))
@@ -195,7 +194,6 @@ casefiddle-tests--test-casing
         ;;("fish" "FIsh" "fish" "Fish" "Fish")
         ;;("Straße" "STRASSE" "straße" "Straße" "Straße")
         ;;("ΌΣΟΣ" "ΌΣΟΣ" "όσος" "Όσος" "Όσος")
-        ;;("όσος" "ΌΣΟΣ" "όσος" "Όσος" "Όσος")
         ;; And here’s what is actually happening:
         ("DŽUNGLA" "DŽUNGLA" "džungla" "DŽungla" "DŽUNGLA")
         ("Džungla" "DžUNGLA" "džungla" "Džungla" "Džungla")
@@ -204,7 +202,8 @@ casefiddle-tests--test-casing
         ("fish" "fiSH" "fish" "fish" "fish")
         ("Straße" "STRAßE" "straße" "Straße" "Straße")
         ("ΌΣΟΣ" "ΌΣΟΣ" "όσοσ" "Όσοσ" "ΌΣΟΣ")
-        ("όσος" "ΌΣΟς" "όσος" "Όσος" "Όσος"))))))
+
+        ("όσος" "ΌΣΟΣ" "όσος" "Όσος" "Όσος"))))))
 
 (ert-deftest casefiddle-tests-casing-byte8 ()
   (should-not
-- 
2.8.0.rc3.226.g39d4020






^ permalink raw reply related	[flat|nested] 89+ messages in thread

* bug#24603: [PATCH 3/3] Don’t generate ‘X maps to X’ entries in case tables
  2016-10-17 22:03 ` bug#24603: [PATCH 0/3] Case table updates Michal Nazarewicz
  2016-10-17 22:03   ` bug#24603: [PATCH 1/3] Add tests for casefiddle.c Michal Nazarewicz
  2016-10-17 22:03   ` bug#24603: [PATCH 2/3] Generate upcase and downcase tables from Unicode data Michal Nazarewicz
@ 2016-10-17 22:03   ` Michal Nazarewicz
  2016-10-18  6:36   ` bug#24603: [PATCH 0/3] Case table updates Eli Zaretskii
  3 siblings, 0 replies; 89+ messages in thread
From: Michal Nazarewicz @ 2016-10-17 22:03 UTC (permalink / raw)
  To: 24603

If character maps to itself when lower or upper casing, do not put
that information in the down or up case tables.  Such entries only
take up space without providing any additional information.  This
shrinks down and up case tables by around 500 entries each.

* lisp/international/characters.el: Don’t set case identity mappings.
---
 lisp/international/characters.el | 14 ++------------
 test/src/casefiddle-tests.el     |  2 +-
 2 files changed, 3 insertions(+), 13 deletions(-)

diff --git a/lisp/international/characters.el b/lisp/international/characters.el
index 8dd9c73..d992d08 100644
--- a/lisp/international/characters.el
+++ b/lisp/international/characters.el
@@ -715,20 +715,10 @@ ?L
         (down tbl)
         (up (case-table-get-table tbl 'up)))
 
-    ;; This works on an assumption that if toUpper(x) != x then toLower(x) ==
-    ;; x (and the opposite for toLower/toUpper).  This doesn’t hold for title
-    ;; case characters but those incorrect mappings will be overwritten later.
     (funcall map-unicode-property 'uppercase
-             (lambda (lc uc) (aset down lc lc) (aset up uc uc)))
+             (lambda (lc uc) (aset up lc uc)))
     (funcall map-unicode-property 'lowercase
-             (lambda (uc lc) (aset down lc lc) (aset up uc uc)))
-
-    ;; Now deal with the actual mapping.  This will correctly assign casing for
-    ;; title-case characters.
-    (funcall map-unicode-property 'uppercase
-             (lambda (lc uc) (aset up lc uc) (aset up uc uc)))
-    (funcall map-unicode-property 'lowercase
-             (lambda (uc lc) (aset down uc lc) (aset down lc lc))))
+             (lambda (uc lc) (aset down uc lc))))
 
   ;; Clear out the extra slots so that they will be recomputed from the main
   ;; (downcase) table and upcase table.  Since we’re side-stepping the usual
diff --git a/test/src/casefiddle-tests.el b/test/src/casefiddle-tests.el
index 4142aa0..bfc58f5 100644
--- a/test/src/casefiddle-tests.el
+++ b/test/src/casefiddle-tests.el
@@ -91,7 +91,7 @@ casefiddle-tests--characters
              (tabs (list up down)))
          (while props
            (let ((got (aref (car tabs) ch)))
-             (unless (equal (car expected) got)
+             (unless (equal (car expected) (or got ch))
                (push (format "\n%c %s; expected: %s but got: %s"
                              ch (car props) (car expected) got)
                      errors)))
-- 
2.8.0.rc3.226.g39d4020






^ permalink raw reply related	[flat|nested] 89+ messages in thread

* bug#24603: [RFC 07/18] Split up casify_region function.
  2016-10-04  7:17     ` Eli Zaretskii
@ 2016-10-18  2:27       ` Michal Nazarewicz
  0 siblings, 0 replies; 89+ messages in thread
From: Michal Nazarewicz @ 2016-10-18  2:27 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: 24603

On Tue, Oct 04 2016, Eli Zaretskii wrote:
>> From: Michal Nazarewicz <mina86@mina86.com>
>> Date: Tue,  4 Oct 2016 03:10:30 +0200
>> 
>> No functional changes at this time but splitting casify_region into
>> a function dealing with multibyte and another dealing with unibyte
>> buffers will make future code changes slightly easier.
>> 
>> * src/casefiddle.c (casify_region): Move most of the code into two
>> new functions:
>> (do_casify_multibyte_region, do_casify_unibyte_region): new functions.
>
> Please make sure the multibyte version works with embedded eight-bit
> characters, in both representations.  Some of the code you removed
> could handle those cases.

In the new version of the first patch, I’ve added bunch of tests for
unibyte and multibyte strings including byte8 characters so this all
should be covered.

-- 
Best regards
ミハウ “𝓶𝓲𝓷𝓪86” ナザレヴイツ
«If at first you don’t succeed, give up skydiving»





^ permalink raw reply	[flat|nested] 89+ messages in thread

* bug#24603: [PATCH 0/3] Case table updates
  2016-10-17 22:03 ` bug#24603: [PATCH 0/3] Case table updates Michal Nazarewicz
                     ` (2 preceding siblings ...)
  2016-10-17 22:03   ` bug#24603: [PATCH 3/3] Don’t generate ‘X maps to X’ entries in case tables Michal Nazarewicz
@ 2016-10-18  6:36   ` Eli Zaretskii
  2016-10-24 15:11     ` Michal Nazarewicz
  3 siblings, 1 reply; 89+ messages in thread
From: Eli Zaretskii @ 2016-10-18  6:36 UTC (permalink / raw)
  To: Michal Nazarewicz; +Cc: 24603

> From: Michal Nazarewicz <mina86@mina86.com>
> Cc: eliz@gnu.org
> Date: Tue, 18 Oct 2016 00:03:42 +0200
> 
> As I continue working on the patchset, it keeps on growing.  To
> somewhat limit that, I’ll start applying the patches.

This is up to you to some degree, but I'd like to point out that it is
not necessary to apply patches piecemeal.  You can merge to, or rebase
on, master everything in one go when you are done, there should be no
technical difficulties with that whatsoever.

Another potential issue I'd like us to avoid is to modify the same
parts of the code several times in related commits.  If that happens,
I'd prefer a single commit that changes them only once.

That said, separate pushes are justified if the parts you push provide
self-contained significant features or improvements.

> The first two map to the first two from original sumbission.  The
> first gained tests for byte-8 characters and the second includes
> changes requested by Eli.
> 
> The third is a new patch.
> 
> Michal Nazarewicz (3):
>   Add tests for casefiddle.c
>   Generate upcase and downcase tables from Unicode data
>   Don’t generate ‘X maps to X’ entries in case tables

Can't the 3rd patch break some code which assumes the current state of
affairs, i.e. that the case-table entries for characters with no case
variants are identical to the character itself?  IOW, this sounds like
an incompatible change, so it should be mentioned as such in NEWS, and
perhaps we should make sure we don't break too much code out there,
not sure if that is possible.

What kind of memory savings does this produce, in terms of memory
footprint of a running Emacs process?

I have no objections or comments to the other 2 patches.

Thanks.





^ permalink raw reply	[flat|nested] 89+ messages in thread

* bug#24603: [PATCH 0/3] Case table updates
  2016-10-18  6:36   ` bug#24603: [PATCH 0/3] Case table updates Eli Zaretskii
@ 2016-10-24 15:11     ` Michal Nazarewicz
  2016-10-24 15:33       ` Eli Zaretskii
  0 siblings, 1 reply; 89+ messages in thread
From: Michal Nazarewicz @ 2016-10-24 15:11 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: 24603

On Tue, Oct 18 2016, Eli Zaretskii wrote:
>> From: Michal Nazarewicz <mina86@mina86.com>
>> Cc: eliz@gnu.org
>> Date: Tue, 18 Oct 2016 00:03:42 +0200
>> 
>> As I continue working on the patchset, it keeps on growing.  To
>> somewhat limit that, I’ll start applying the patches.
>
> This is up to you to some degree, but I'd like to point out that it is
> not necessary to apply patches piecemeal.  You can merge to, or rebase
> on, master everything in one go when you are done, there should be no
> technical difficulties with that whatsoever.

Correct.  It’s more of a personal issue than technical one.  The
patchset keeps growing and it’s getting somewhat harder for me to keep
of it.

> Another potential issue I'd like us to avoid is to modify the same
> parts of the code several times in related commits.  If that happens,
> I'd prefer a single commit that changes them only once.
>
> That said, separate pushes are justified if the parts you push provide
> self-contained significant features or improvements.

The second patch does add missing entries to case-table which is
self-contained.

The first patch is somehow more tricky in that regard since it has a lot
of FIXME comments and it’s commit message mentions future patches.

>> The first two map to the first two from original sumbission.  The
>> first gained tests for byte-8 characters and the second includes
>> changes requested by Eli.
>> 
>> The third is a new patch.
>> 
>> Michal Nazarewicz (3):
>>   Add tests for casefiddle.c
>>   Generate upcase and downcase tables from Unicode data
>>   Don’t generate ‘X maps to X’ entries in case tables
>
> Can't the 3rd patch break some code which assumes the current state of
> affairs, i.e. that the case-table entries for characters with no case
> variants are identical to the character itself?  IOW, this sounds like
> an incompatible change, so it should be mentioned as such in NEWS, and
> perhaps we should make sure we don't break too much code out there,
> not sure if that is possible.

I don’t think it can.  The only place where I could find case-table’s
being used directly (as in values stored in it read) were functions in
buffer.h and those have explicit path for missing entries:

    /* Downcase a character C, or make no change if that cannot be done.  */
    INLINE int
    downcase (int c)
    {
      Lisp_Object downcase_table = BVAR (current_buffer, downcase_table);
      Lisp_Object down = CHAR_TABLE_REF (downcase_table, c);
      return NATNUMP (down) ? XFASTINT (down) : c;
    }
    
    /* Upcase a character C known to be not upper case.  */
    INLINE int
    upcase1 (int c)
    {
      Lisp_Object upcase_table = BVAR (current_buffer, upcase_table);
      Lisp_Object up = CHAR_TABLE_REF (upcase_table, c);
      return NATNUMP (up) ? XFASTINT (up) : c;
    }

> What kind of memory savings does this produce, in terms of memory
> footprint of a running Emacs process?

Actually the more I think about it, the less I’m sure the savings are
there.  Since char tables allocate memory in batches, the space may
still be allocated but simply unused.  I’ll drop the patch for now.
Maybe I’ll have time to investigate further at some future date.

> I have no objections or comments to the other 2 patches.

So yeah, I dunno how strongly you feel about it.  I certainly can wait
once the whole patchset is ready, but that may take a while.

-- 
Best regards
ミハウ “𝓶𝓲𝓷𝓪86” ナザレヴイツ
«If at first you don’t succeed, give up skydiving»





^ permalink raw reply	[flat|nested] 89+ messages in thread

* bug#24603: [PATCH 0/3] Case table updates
  2016-10-24 15:11     ` Michal Nazarewicz
@ 2016-10-24 15:33       ` Eli Zaretskii
  0 siblings, 0 replies; 89+ messages in thread
From: Eli Zaretskii @ 2016-10-24 15:33 UTC (permalink / raw)
  To: Michal Nazarewicz; +Cc: 24603

> From: Michal Nazarewicz <mina86@mina86.com>
> Cc: 24603@debbugs.gnu.org
> Date: Mon, 24 Oct 2016 17:11:22 +0200
> 
> > I have no objections or comments to the other 2 patches.
> 
> So yeah, I dunno how strongly you feel about it.  I certainly can wait
> once the whole patchset is ready, but that may take a while.

No strong feelings here, so feel free to push if that makes thinsg
easier for you in the future.

Thanks.





^ permalink raw reply	[flat|nested] 89+ messages in thread

* bug#24603: [RFC 16/18] Refactor character class checking; optimise ASCII case
  2016-10-04  7:48     ` Eli Zaretskii
  2016-10-17 13:22       ` Michal Nazarewicz
@ 2016-11-06 19:26       ` Michal Nazarewicz
  2016-11-06 19:44         ` Eli Zaretskii
  1 sibling, 1 reply; 89+ messages in thread
From: Michal Nazarewicz @ 2016-11-06 19:26 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: 24603

On Tue, Oct 04 2016, Eli Zaretskii wrote:
> Thanks.  I think this change will require a benchmark to make sure we
> don't lose too much in terms of performance.

Benchmark and its results included below.

It’s a bit noisy and as all benchmarks of that kind it doesn’t really
measure the real usage, but I think it’s safe to say that things aren’t
getting worse.

---- >8 ------------------------------------------------------------------------

Class      [[:cc:]]   no-case    [^[:cc:]]  no-case  
---------  ---------  ---------  ---------  ---------

==== Add regex character class matching benchmark ====
alnum         59.870     60.148     63.548     64.048
alpha         60.355     60.137     63.333     62.684
digit         27.835     27.648      0.513      0.488
xdigit        27.160     27.320      0.969      0.883
upper         91.027     91.572     39.423     39.595
lower         60.591     61.307     60.332     59.730
word          36.201     36.046    108.118    109.396
punct        110.987    111.683     35.110     35.200
cntrl         27.005     26.756      1.212      1.176
graph         25.694     26.097     75.872     75.711
print         24.783     24.976     76.652     74.921
space        147.210    148.431      1.261      1.252
blank         27.602     27.722      0.373      0.189
ascii         23.243     23.302      4.550      4.486
nonascii       5.448      5.407     90.733     90.410
unibyte       22.986     23.342      4.559      4.655
multibyte      5.508      5.535     92.457     91.163
...all...      1.138      1.030     93.275     93.383

==== Refactor character class checking; optimise ASCII case ====
alnum         54.643     54.301     56.668     56.898
alpha         54.654     54.558     56.134     56.281
digit         26.103     26.044      0.495      0.443
xdigit        25.606     25.690      0.815      0.806
upper         83.269     83.306     36.704     36.487
lower         56.278     55.804     54.872     54.917
word          34.820     55.092     99.577    100.618
punct        103.410    103.465     31.673     31.590
cntrl         25.509     25.274      1.119      1.101
graph         23.593     23.673     69.335     69.481
print         23.003     23.123     69.962     70.132
space        132.224    132.458      1.143      1.120
blank         26.223     26.342      0.193      0.187
ascii         22.329     22.257      4.094      4.082
nonascii       4.910      4.897     84.633     84.515
unibyte       22.866     22.385      4.094      4.078
multibyte      4.913      4.886     95.385     85.341
...all...      0.942      0.936     88.979     88.744

==== Optimise character class matching in regexes ====
alnum         53.338     53.052     56.571     56.434
alpha         53.591     53.350     56.218     56.255
digit         26.266     26.502      0.438      0.438
xdigit        25.793     25.887      0.877      0.876
upper         82.539     82.700     31.994     32.200
lower         55.280     55.040     54.615     54.429
word          33.666     33.530    100.678    101.721
punct        101.714    101.715     31.766     31.620
cntrl         25.669     25.068      1.113      1.114
graph         27.848     28.067     81.669     81.619
print         27.128     28.297     82.326     82.306
space        131.847    132.242      1.124      1.128
blank         26.493     26.607      0.190      0.188
ascii         22.332     22.315      4.379      4.358
nonascii       5.169      5.159     84.872     85.488
unibyte       22.259     22.529      4.374      4.361
multibyte      5.193      5.181     86.421     86.568
...all...      0.945      0.939     92.903     93.209

==== Fix case-fold-search character class matching ====
alnum         53.553     53.527     56.918     56.886
alpha         53.657     53.758     56.541     57.107
digit         26.616     26.641      0.467      0.510
xdigit        27.255     26.271      0.894      0.923
upper         56.608     55.073     55.792     55.422
lower         55.419     55.330     55.486     55.018
word          35.537     35.434    103.414    103.516
punct        105.810    106.618     33.454     33.322
cntrl         25.875     26.020      1.274      1.271
graph         28.011     28.185     82.239     82.245
print         26.935     27.016     99.945     83.213
space        136.774    138.135      1.170      1.159
blank         26.984     26.976      0.192      0.204
ascii         22.365     22.661      4.652      4.652
nonascii       5.759      5.524     85.805     86.403
unibyte       22.568     22.375      4.995      4.909
multibyte      5.729      5.749     84.671     84.396
...all...      0.990      0.978     89.520     89.612

All times in ms; lower is better.

---- >8 ------------------------------------------------------------------------
From 23d8fe0b093730406b64e0e20207c2fb929f707f Mon Sep 17 00:00:00 2001
From: Michal Nazarewicz <mina86@mina86.com>
Date: Fri, 7 Oct 2016 02:44:30 +0200
Subject: [PATCH] Add regex character class matching benchmark

* test/src/regex-tests.el (regex-tests-benchmark-cc-match): New function
running character class matching benchmark.
---
 test/src/regex-tests.el | 59 +++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 59 insertions(+)

diff --git a/test/src/regex-tests.el b/test/src/regex-tests.el
index fc50344..d0aad97 100644
--- a/test/src/regex-tests.el
+++ b/test/src/regex-tests.el
@@ -98,6 +98,65 @@ regex--test-cc
     (eval `(ert-deftest ,name () ,doc ,(cons 'regex--test-cc test)) t)))
 
 
+(defun regex-tests-benchmark-cc-match ()
+  "Benchmark regex character class matching."
+  (interactive)
+  (let* ((prn (if (called-interactively-p)
+                  'insert
+                (lambda (&rest args) (mapc 'princ args))))
+         (strings
+          (nconc (list
+                  (apply 'string (number-sequence 32 126))
+                  (apply 'string (number-sequence 0 127))
+                  (apply 'unibyte-string (number-sequence 128 255))
+                  (concat (apply 'string (number-sequence 0 255))
+                          (apply 'unibyte-string (number-sequence 128 255)))
+                  (make-string 10000 #x3FFF80)
+                  (make-string 10000 #x3FFFFF))
+                 (mapcar (lambda (ch) (make-string 10000 ch))
+                         (number-sequence 0 256))))
+
+         (ccs '("alnum" "alpha" "digit" "xdigit" "upper" "lower"
+                "word" "punct" "cntrl" "graph" "print" "space" "blank"
+                "ascii" "nonascii" "unibyte" "multibyte"))
+
+         (benchmark-re
+          (lambda (re)
+            (dolist (cf '(nil t))
+              ;; Compile the regex so it ends up in cache.
+              (string-match re "")
+              (let ((res (benchmark-run 10
+                           (dolist (str strings) (string-match re str)))))
+                (funcall prn (format " %10.3f"
+                                     (* (- (nth 0 res) (nth 2 res)) 100))))))))
+
+    (when (called-interactively-p)
+      (switch-to-buffer (get-buffer-create "*Regex Benchmark*"))
+      (delete-region (point-min) (point-max)))
+
+    (funcall prn (format "%-9s  %-9s  %-9s  %-9s  %-9s\n"
+                         "Class" "[[:cc:]]" "no-case"
+                         "[^[:cc:]]" "no-case")
+             (make-string 9 ?-)
+             "  " (make-string 9 ?-) "  " (make-string 9 ?-)
+             "  " (make-string 9 ?-) "  " (make-string 9 ?-) "\n")
+
+    (dolist (cc ccs)
+      (funcall prn (format "%-9s" cc))
+      (dolist (re (list (format "[[:%s:]]" cc)
+                        (format "[^[:%s:]]" cc)))
+        (funcall benchmark-re re))
+      (funcall prn "\n"))
+
+    (funcall prn (format "%-9s" "...all..."))
+    (let ((all-ccs (mapconcat (lambda (cc) (format "[:%s:]" cc)) ccs "")))
+      (funcall benchmark-re (concat "[" all-ccs "]"))
+      (funcall benchmark-re (concat "[^" all-ccs "]")))
+
+    (funcall prn "\n" (make-string 53 ?-)
+             "\nAll times in ms; lower is better.\n")))
+
+
 (defmacro regex-tests-generic-line (comment-char test-file whitelist &rest body)
   "Reads a line of the test file TEST-FILE, skipping
 comments (defined by COMMENT-CHAR), and evaluates the tests in
-- 
2.8.0.rc3.226.g39d4020





^ permalink raw reply related	[flat|nested] 89+ messages in thread

* bug#24603: [RFC 16/18] Refactor character class checking; optimise ASCII case
  2016-11-06 19:26       ` Michal Nazarewicz
@ 2016-11-06 19:44         ` Eli Zaretskii
  2016-12-20 14:32           ` Michal Nazarewicz
  0 siblings, 1 reply; 89+ messages in thread
From: Eli Zaretskii @ 2016-11-06 19:44 UTC (permalink / raw)
  To: Michal Nazarewicz; +Cc: 24603

> From: Michal Nazarewicz <mina86@mina86.com>
> Cc: 24603@debbugs.gnu.org
> Date: Sun, 06 Nov 2016 20:26:11 +0100
> 
> On Tue, Oct 04 2016, Eli Zaretskii wrote:
> > Thanks.  I think this change will require a benchmark to make sure we
> > don't lose too much in terms of performance.
> 
> Benchmark and its results included below.
> 
> It’s a bit noisy and as all benchmarks of that kind it doesn’t really
> measure the real usage, but I think it’s safe to say that things aren’t
> getting worse.

Thanks.  What happened here:

> ==== Refactor character class checking; optimise ASCII case ====
> alnum         54.643     54.301     56.668     56.898
> alpha         54.654     54.558     56.134     56.281
> digit         26.103     26.044      0.495      0.443
> xdigit        25.606     25.690      0.815      0.806
> upper         83.269     83.306     36.704     36.487
> lower         56.278     55.804     54.872     54.917
> word          34.820     55.092     99.577    100.618
                           ^^^^^^

Is this slow-down real?

The rest of the results look fine to me.





^ permalink raw reply	[flat|nested] 89+ messages in thread

* bug#24603: [RFC 16/18] Refactor character class checking; optimise ASCII case
  2016-11-06 19:44         ` Eli Zaretskii
@ 2016-12-20 14:32           ` Michal Nazarewicz
  2016-12-20 16:39             ` Eli Zaretskii
  0 siblings, 1 reply; 89+ messages in thread
From: Michal Nazarewicz @ 2016-12-20 14:32 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: 24603

Sorry about the delay.  I hope I’ll have some time during Xmas to work
on this more.

On Sun, Nov 06 2016, Eli Zaretskii wrote:
>> From: Michal Nazarewicz <mina86@mina86.com>
>> Cc: 24603@debbugs.gnu.org
>> Date: Sun, 06 Nov 2016 20:26:11 +0100
>> 
>> On Tue, Oct 04 2016, Eli Zaretskii wrote:
>> > Thanks.  I think this change will require a benchmark to make sure we
>> > don't lose too much in terms of performance.
>> 
>> Benchmark and its results included below.
>> 
>> It’s a bit noisy and as all benchmarks of that kind it doesn’t really
>> measure the real usage, but I think it’s safe to say that things aren’t
>> getting worse.
>
> Thanks.  What happened here:
>
>> ==== Refactor character class checking; optimise ASCII case ====
>> alnum         54.643     54.301     56.668     56.898
>> alpha         54.654     54.558     56.134     56.281
>> digit         26.103     26.044      0.495      0.443
>> xdigit        25.606     25.690      0.815      0.806
>> upper         83.269     83.306     36.704     36.487
>> lower         56.278     55.804     54.872     54.917
>> word          34.820     55.092     99.577    100.618
>                            ^^^^^^
> Is this slow-down real?

I’ve re-run the benchmarks five times and took averages.  Based on that,
this slow-down does not appear to be real, but it seems there are some
other which I haven’t noticed previously:

Class      [[:cc:]]              no-case               [^[:cc:]]             no-case             
---------  --------------------  --------------------  --------------------  --------------------
alnum         56.772                54.973                58.132                58.388           
              -1.385     -2.44%      0.571     +1.04%     -0.041     -0.07%     -0.346     -0.59%
              -1.539     -2.71%      0.198     +0.36%     -0.967     -1.66%     -1.272     -2.18%
              -3.017     -5.31%      2.990     +5.44%     -1.013     -1.74%     -1.681     -2.88%
              -3.850     -6.78%     -1.229     -2.24%     -0.086     -0.15%     -1.453     -2.49%
---------  --------------------  --------------------  --------------------  --------------------
alpha         54.386                54.380                56.698                58.332           
               1.135     +2.09%      0.892     +1.64%      0.587     +1.04%      0.667     +1.14%
               1.052     +1.93%      1.108     +2.04%      0.661     +1.17%     -1.555     -2.67%
              -0.338     -0.62%     -0.235     -0.43%     -0.363     -0.64%     -1.788     -3.06%
              -1.068     -1.96%     -0.541     -1.00%     -0.182     -0.32%     -2.659     -4.56%
---------  --------------------  --------------------  --------------------  --------------------
digit         26.416                26.574                 0.454                 0.455           
               0.203     +0.77%     -0.030     -0.11%     -0.010     -2.20%     -0.007     -1.58%
               0.138     +0.52%     -0.013     -0.05%     -0.006     -1.28%     -0.008     -1.71%
              -0.021     -0.08%     -0.161     -0.61%     -0.014     -3.08%     -0.018     -4.04%
              -0.293     -1.11%     -0.417     -1.57%     -0.003     -0.57%     -0.009     -2.02%
---------  --------------------  --------------------  --------------------  --------------------
xdigit        26.015                25.956                 0.902                 0.898           
               0.194     +0.75%      0.186     +0.72%     -0.074     -8.20%     -0.075     -8.33%
               1.092     +4.20%      0.191     +0.74%     -0.073     -8.13%     -0.070     -7.84%
              -0.003     -0.01%      0.239     +0.92%     -0.084     -9.35%     -0.083     -9.22%
              -0.345     -1.32%     -0.124     -0.48%     -0.069     -7.62%     -0.060     -6.64%
---------  --------------------  --------------------  --------------------  --------------------
upper         83.257                82.562                41.189                41.284           
               3.298     +3.96%      0.683     +0.83%     -4.733    -11.49%     -3.970     -9.62%
               1.791     +2.15%      3.616     +4.38%     -3.875     -9.41%     -3.845     -9.31%
               0.045     +0.05%      5.854     +7.09%     -8.977    -21.80%     -9.105    -22.05%
             -28.204    -33.88%    -27.548    -33.37%     13.052    +31.69%     12.946    +31.36%
---------  --------------------  --------------------  --------------------  --------------------
lower         64.299                64.218                61.111                62.093           
              -7.671    -11.93%     -8.443    -13.15%     -6.356    -10.40%     -7.320    -11.79%
              -7.251    -11.28%     -5.967     -9.29%     -5.593     -9.15%     -6.500    -10.47%
              -7.901    -12.29%     -8.447    -13.15%     -6.268    -10.26%     -7.304    -11.76%
              -9.213    -14.33%     -9.183    -14.30%     -4.879     -7.98%     -7.422    -11.95%
---------  --------------------  --------------------  --------------------  --------------------
word          35.618                37.086               104.661               105.706           
               0.198     +0.55%     -1.206     -3.25%      1.497     +1.43%      2.618     +2.48%
               0.614     +1.72%      0.263     +0.71%      1.618     +1.55%      2.099     +1.99%
               0.692     +1.94%     -0.403     -1.09%     -2.975     -2.84%     -3.099     -2.93%
              -1.210     -3.40%     -1.759     -4.74%     -3.491     -3.34%     -3.722     -3.52%
---------  --------------------  --------------------  --------------------  --------------------
punct        107.447               107.661                33.509                33.453           
               3.037     +2.83%      1.931     +1.79%      0.640     +1.91%      0.596     +1.78%
               3.106     +2.89%      4.309     +4.00%      0.539     +1.61%      0.680     +2.03%
              -0.588     -0.55%      3.730     +3.46%     -1.138     -3.40%     -1.046     -3.13%
               1.013     +0.94%      2.857     +2.65%      1.679     +5.01%     -1.142     -3.41%
---------  --------------------  --------------------  --------------------  --------------------
cntrl         25.770                25.718                 1.246                 1.229           
               0.115     +0.45%      0.150     +0.58%     -0.068     -5.47%     -0.063     -5.11%
               0.031     +0.12%      0.112     +0.44%     -0.087     -7.00%     -0.057     -4.64%
              -0.089     -0.35%     -0.034     -0.13%     -0.103     -8.30%     -0.088     -7.16%
              -0.410     -1.59%     -0.334     -1.30%     -0.047     -3.77%     -0.043     -3.53%
---------  --------------------  --------------------  --------------------  --------------------
graph         23.703                23.595                69.221                70.017           
               0.306     +1.29%      0.245     +1.04%      0.592     +0.85%     -0.146     -0.21%
               1.838     +7.75%      0.641     +2.71%      0.517     +0.75%     -0.316     -0.45%
               4.503    +19.00%      4.599    +19.49%     13.219    +19.10%     15.108    +21.58%
               6.628    +27.96%      4.209    +17.84%     12.004    +17.34%     11.160    +15.94%
---------  --------------------  --------------------  --------------------  --------------------
print         22.798                22.781                69.873                69.795           
               0.670     +2.94%      0.607     +2.67%      0.826     +1.18%      0.699     +1.00%
               1.225     +5.37%      1.171     +5.14%      2.049     +2.93%      1.427     +2.04%
               4.540    +19.91%      4.574    +20.08%     14.046    +20.10%     17.268    +24.74%
               4.178    +18.33%      4.188    +18.38%     12.189    +17.44%     12.351    +17.70%
---------  --------------------  --------------------  --------------------  --------------------
space        141.314               144.661                 1.130                 1.125           
               0.331     +0.23%     -3.395     -2.35%      0.011     +0.99%      0.011     +0.94%
               2.535     +1.79%      0.202     +0.14%      0.029     +2.53%      0.029     +2.60%
              -5.808     -4.11%     -6.856     -4.74%     -0.001     -0.11%      0.076     +6.79%
              -6.470     -4.58%     -9.847     -6.81%      0.010     +0.85%      0.005     +0.48%
---------  --------------------  --------------------  --------------------  --------------------
blank         26.706                26.740                 0.183                 0.181           
               0.147     +0.55%     -0.009     -0.04%      0.003     +1.74%      0.004     +2.10%
               1.461     +5.47%      0.091     +0.34%      0.006     +3.05%      0.007     +3.99%
               3.021    +11.31%      0.591     +2.21%     -0.002     -0.98%      0.000     +0.11%
              -0.305     -1.14%     -0.372     -1.39%     -0.001     -0.33%      0.000     +0.22%
---------  --------------------  --------------------  --------------------  --------------------
ascii         22.202                22.140                 4.722                 4.751           
               0.489     +2.20%      0.601     +2.71%     -0.493    -10.44%     -0.436     -9.18%
               0.625     +2.81%      0.597     +2.69%     -0.397     -8.41%     -0.436     -9.18%
               0.348     +1.57%      1.043     +4.71%      0.287     +6.08%      0.249     +5.25%
              -0.033     -0.15%      0.826     +3.73%      0.398     +8.42%      0.251     +5.29%
---------  --------------------  --------------------  --------------------  --------------------
nonascii       5.586                 5.544                85.792                83.721           
              -0.392     -7.02%     -0.405     -7.30%      5.600     +6.53%      1.420     +1.70%
              -0.459     -8.21%      0.213     +3.84%      5.553     +6.47%      3.031     +3.62%
               0.461     +8.25%     -0.144     -2.59%      4.086     +4.76%      1.803     +2.15%
              -0.368     -6.58%     -0.296     -5.35%     -0.947     -1.10%      1.088     +1.30%
---------  --------------------  --------------------  --------------------  --------------------
unibyte       22.166                22.172                 5.299                 5.403           
               0.545     +2.46%      0.533     +2.40%     -1.041    -19.65%     -1.140    -21.09%
               1.187     +5.36%      0.843     +3.80%     -1.068    -20.16%     -1.182    -21.87%
               0.429     +1.94%      0.385     +1.74%     -1.043    -19.69%     -1.163    -21.52%
               0.237     +1.07%      0.063     +0.28%     -0.915    -17.26%     -1.025    -18.98%
---------  --------------------  --------------------  --------------------  --------------------
multibyte      6.031                 5.571                83.834                85.391           
              -0.875    -14.50%     -0.432     -7.75%      1.855     +2.21%     -0.073     -0.09%
              -0.902    -14.96%     -0.440     -7.89%      7.195     +8.58%      1.665     +1.95%
              -0.904    -14.99%     -0.531     -9.53%      2.005     +2.39%      0.094     +0.11%
              -0.786    -13.03%     -0.336     -6.04%      0.692     +0.83%      1.607     +1.88%
---------  --------------------  --------------------  --------------------  --------------------
...all...      0.928                 0.927                89.115                89.857           
               0.080     +8.60%      0.076     +8.22%     -0.314     -0.35%      5.126     +5.70%
               0.058     +6.23%      0.058     +6.30%     -0.304     -0.34%      0.038     +0.04%
               0.002     +0.19%      0.001     +0.11%     -0.413     -0.46%     -1.742     -1.94%
               0.037     +3.97%      0.034     +3.64%      0.824     +0.92%     -1.253     -1.39%

(The first line in each group are absolute results with Emacs before my
changes.  The other groups show absolute and relative change to that
baseline (i.e. negative is good)).

Slow-downs in intermediate commits aren’t that big of an issue as long
as the last line shows an improvement (or at least negligible
regression) but sadly that is not always the case.

As can be seen, [[:graph:]] slows-down by almost 28% :( and I don’t
quite understand where all that can come from.

-- 
Best regards
ミハウ “𝓶𝓲𝓷𝓪86” ナザレヴイツ
«If at first you don’t succeed, give up skydiving»





^ permalink raw reply	[flat|nested] 89+ messages in thread

* bug#24603: [RFC 16/18] Refactor character class checking; optimise ASCII case
  2016-12-20 14:32           ` Michal Nazarewicz
@ 2016-12-20 16:39             ` Eli Zaretskii
  2016-12-22 14:02               ` Michal Nazarewicz
  0 siblings, 1 reply; 89+ messages in thread
From: Eli Zaretskii @ 2016-12-20 16:39 UTC (permalink / raw)
  To: Michal Nazarewicz; +Cc: 24603

> From: Michal Nazarewicz <mina86@mina86.com>
> Cc: 24603@debbugs.gnu.org
> Date: Tue, 20 Dec 2016 15:32:27 +0100
> 
> As can be seen, [[:graph:]] slows-down by almost 28% :( and I don’t
> quite understand where all that can come from.

If no ideas come up, perhaps run this under perf?





^ permalink raw reply	[flat|nested] 89+ messages in thread

* bug#24603: [RFC 16/18] Refactor character class checking; optimise ASCII case
  2016-12-20 16:39             ` Eli Zaretskii
@ 2016-12-22 14:02               ` Michal Nazarewicz
  0 siblings, 0 replies; 89+ messages in thread
From: Michal Nazarewicz @ 2016-12-22 14:02 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: 24603

On Tue, Dec 20 2016, Eli Zaretskii wrote:
>> From: Michal Nazarewicz <mina86@mina86.com>
>> Cc: 24603@debbugs.gnu.org
>> Date: Tue, 20 Dec 2016 15:32:27 +0100
>> 
>> As can be seen, [[:graph:]] slows-down by almost 28% :( and I don’t
>> quite understand where all that can come from.
>
> If no ideas come up, perhaps run this under perf?

Yes, that’s what I’m trying to do.

It gets a bit convoluted since the benchmark attempts to test C code
with Elisp code which adds various superfluous entries in the profile
(e.g. Finotify_valid_p takes third of the time but I doubt it has
anything to do with character class matching).

On top of that, the generated gmon.out doesn’t include call traces which
makes it harder to track what is actually going on.

But hopefully I’ll figure something out.

-- 
Best regards
ミハウ “𝓶𝓲𝓷𝓪86” ナザレヴイツ
«If at first you don’t succeed, give up skydiving»





^ permalink raw reply	[flat|nested] 89+ messages in thread

* bug#24603: [RFC 08/18] Support casing characters which map into multiple code points
  2016-10-07  7:46         ` Eli Zaretskii
@ 2017-01-28 23:48           ` Michal Nazarewicz
  2017-02-10  9:12             ` Eli Zaretskii
  0 siblings, 1 reply; 89+ messages in thread
From: Michal Nazarewicz @ 2017-01-28 23:48 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: 24603

On Fri, Oct 07 2016, Eli Zaretskii wrote:
> The way we deal with such augmentations is by having most of the data
> auto-generated, and some of it maintained manually.  One example is
> the current characters.el and charscript.el it loads.  Can we use a
> similar approach in this case?  Experience shows that maintaining
> everything manually is error-prone and a huge maintenance head-ache in
> the long run, what with a new version of the Unicode Standard
> available at least once a year.

The majority is handled automatically in both cases.  My approach is
that rules that are conditionals and those not included in Unicode are
manually maintained as C code.

In practice, if the Lisp data changes, C code that handles it would have
to change as well.  For example, if Unicode adds rules for Dutch ‘ij’¹,
it would be done by adding an ‘After_Uppercased_I’ condition but then
for that rule to work it’s not enough to include it in Lisp data but it
has to be coded in C.

¹ ‘ij’ at the beginning of a word should be capitalised as ‘IJ’ not ‘Ij’.

There’s also the case of ‘More_Above’:

0049; 0069 0307; 0049; 0049; lt More_Above; # LATIN CAPITAL LETTER I

The rule means that ‘I <combining chars>* <combining acute>’
(where <combining chars> is any sequence of combining characters)
should be lower-cased as ‘i <combining chars>* <combining dot
above> <combining acute>’².

The way the SpecialCasing rules are structured would want us to
scan the string from the where we encountered I to look for any
combining characters and indeed this is how some libraries
implement it.  The problem in Emacs is that the casefiddle.c
needs to work on strings as well as buffers which are different
data structures.  As a result, scanning future characters needs
two different cases.

So instead, the way I implemented it is by flipping a bit in
casing_context so that case_character_impl knows to handle
combining characters correctly.

² Without addition of the <combining dot above>, the tittle (dot
  above ‘i’) would disappear when rendering because of the
  <combining acute> and that’s apparently not how Lithuanian is
  supposed to work.

So, yeah…  Of course, I’m a bit biased by the virtue of having
the code already written and not wanting to rewrite it (which
will probably take me another few months, *sighs*) but with the
conditional casing rules I’m honestly not convinced at the moment
that trying to keep them in Lisp data would be better.

Attached below is a new version of 08/18 with the unconditional
casing rules moved from C code to a uniprop char table (I haven’t
updated commit message yet).  (Compared to previous version it’s
a bit more C code but overall 200-line AWK script is replaced by
around 50 lines of Elisp so overall the patch is shorter).

This also fixes issues with undo and cursor positioning that I’ve
mentioned before.

Both versions are available on GitHub:
- Elisp version:  git://github.com/mina86/emacs.git master-el
- C version:      git://github.com/mina86/emacs.git master

-- 
Best regards
ミハウ “𝓶𝓲𝓷𝓪86” ナザレヴイツ
«If at first you don’t succeed, give up skydiving»

From bbcf826071b158438a03ab3c9fea92528b915bc8 Mon Sep 17 00:00:00 2001
From: Michal Nazarewicz <mina86@mina86.com>
Date: Wed, 5 Oct 2016 00:06:01 +0200
Subject: [PATCH 08/19] Support casing characters which map into multiple code
 points
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Implement unconditional special casing rules defined in Unicode standard.

Among other things, they deal with cases when a single code point is
replaced by multiple ones because simple character does not exist (e.g.
fi ligature turning into FL) or is not commonly used (e.g. ß turning into
SS).

* admin/unidata/SpecialCasing.txt: New data file pulled from Unicode
standard distribution.
* admin/unidata/README: Mention SpecialCasing.txt.

* src/make-special-casing.awk: New script to generate special-casing.h
file from the SpecialCasing.txt data file.

* src/casefiddle.c: Include special-casing.h so special casing rules are
available and can be used in the translation unit.

(struct casing_str_buf): New structure for representing short strings.
It’s used to compactly encode special casing rules.

(case_character_imlp): New function which can handle one-to-many
character mappings.
(case_character, case_single_character): Wrappers for the above
functions.  The former may map one character to multiple code points
while the latter does what the former used to do (i.e. handles
one-to-one mappings only).

(do_casify_integer, do_casify_unibyte_string,
do_casify_unibyte_region): Use case_single_character.
(do_casify_multibyte_string, do_casify_multibyte_region): Support new
features of case_character.
* (do_casify_region): Updated after do_casify_multibyte_string changes.

(upcase, capitalize, upcase-initials): Update documentation to mention
limitations when working on characters.

* test/src/casefiddle-tests.el (casefiddle-tests-casing): Update test
cases which are now passing.

* test/lisp/char-fold-tests.el (char-fold--ascii-upcase,
char-fold--ascii-downcase): New functions which behave like old ‘upcase’
and ‘downcase’.
(char-fold--test-match-exactly): Use the new functions.  This is needed
because otherwise fi and similar characters are turned into their multi-
-character representation.

* doc/lispref/strings.texi: Describe issue with casing characters versus
strings.
---
 admin/unidata/README            |   4 +
 admin/unidata/SpecialCasing.txt | 281 ++++++++++++++++++++++++++++++++++++
 admin/unidata/unidata-gen.el    |  40 ++++++
 doc/lispref/strings.texi        |  23 +++
 etc/NEWS                        |  16 ++-
 src/casefiddle.c                | 305 +++++++++++++++++++++++++++++-----------
 test/lisp/char-fold-tests.el    |  12 +-
 test/src/casefiddle-tests.el    |   9 +-
 8 files changed, 591 insertions(+), 99 deletions(-)
 create mode 100644 admin/unidata/SpecialCasing.txt

diff --git a/admin/unidata/README b/admin/unidata/README
index 534670ce6db..06a66663a72 100644
--- a/admin/unidata/README
+++ b/admin/unidata/README
@@ -24,3 +24,7 @@ http://www.unicode.org/Public/8.0.0/ucd/Blocks.txt
 NormalizationTest.txt
 http://www.unicode.org/Public/UNIDATA/NormalizationTest.txt
 2016-07-16
+
+SpecialCasing.txt
+http://unicode.org/Public/UNIDATA/SpecialCasing.txt
+2016-03-03
diff --git a/admin/unidata/SpecialCasing.txt b/admin/unidata/SpecialCasing.txt
new file mode 100644
index 00000000000..b23fa7f7680
--- /dev/null
+++ b/admin/unidata/SpecialCasing.txt
@@ -0,0 +1,281 @@
+# SpecialCasing-9.0.0.txt
+# Date: 2016-03-02, 18:55:13 GMT
+# © 2016 Unicode®, Inc.
+# Unicode and the Unicode Logo are registered trademarks of Unicode, Inc. in the U.S. and other countries.
+# For terms of use, see http://www.unicode.org/terms_of_use.html
+#
+# Unicode Character Database
+#   For documentation, see http://www.unicode.org/reports/tr44/
+#
+# Special Casing
+#
+# This file is a supplement to the UnicodeData.txt file. It does not define any
+# properties, but rather provides additional information about the casing of
+# Unicode characters, for situations when casing incurs a change in string length
+# or is dependent on context or locale. For compatibility, the UnicodeData.txt
+# file only contains simple case mappings for characters where they are one-to-one
+# and independent of context and language. The data in this file, combined with
+# the simple case mappings in UnicodeData.txt, defines the full case mappings
+# Lowercase_Mapping (lc), Titlecase_Mapping (tc), and Uppercase_Mapping (uc).
+#
+# Note that the preferred mechanism for defining tailored casing operations is
+# the Unicode Common Locale Data Repository (CLDR). For more information, see the
+# discussion of case mappings and case algorithms in the Unicode Standard.
+#
+# All code points not listed in this file that do not have a simple case mappings
+# in UnicodeData.txt map to themselves.
+# ================================================================================
+# Format
+# ================================================================================
+# The entries in this file are in the following machine-readable format:
+#
+# <code>; <lower>; <title>; <upper>; (<condition_list>;)? # <comment>
+#
+# <code>, <lower>, <title>, and <upper> provide the respective full case mappings
+# of <code>, expressed as character values in hex. If there is more than one character,
+# they are separated by spaces. Other than as used to separate elements, spaces are
+# to be ignored.
+#
+# The <condition_list> is optional. Where present, it consists of one or more language IDs
+# or casing contexts, separated by spaces. In these conditions:
+# - A condition list overrides the normal behavior if all of the listed conditions are true.
+# - The casing context is always the context of the characters in the original string,
+#   NOT in the resulting string.
+# - Case distinctions in the condition list are not significant.
+# - Conditions preceded by "Not_" represent the negation of the condition.
+# The condition list is not represented in the UCD as a formal property.
+#
+# A language ID is defined by BCP 47, with '-' and '_' treated equivalently.
+#
+# A casing context for a character is defined by Section 3.13 Default Case Algorithms
+# of The Unicode Standard.
+#
+# Parsers of this file must be prepared to deal with future additions to this format:
+#  * Additional contexts
+#  * Additional fields
+# ================================================================================
+
+# ================================================================================
+# Unconditional mappings
+# ================================================================================
+
+# The German es-zed is special--the normal mapping is to SS.
+# Note: the titlecase should never occur in practice. It is equal to titlecase(uppercase(<es-zed>))
+
+00DF; 00DF; 0053 0073; 0053 0053; # LATIN SMALL LETTER SHARP S
+
+# Preserve canonical equivalence for I with dot. Turkic is handled below.
+
+0130; 0069 0307; 0130; 0130; # LATIN CAPITAL LETTER I WITH DOT ABOVE
+
+# Ligatures
+
+FB00; FB00; 0046 0066; 0046 0046; # LATIN SMALL LIGATURE FF
+FB01; FB01; 0046 0069; 0046 0049; # LATIN SMALL LIGATURE FI
+FB02; FB02; 0046 006C; 0046 004C; # LATIN SMALL LIGATURE FL
+FB03; FB03; 0046 0066 0069; 0046 0046 0049; # LATIN SMALL LIGATURE FFI
+FB04; FB04; 0046 0066 006C; 0046 0046 004C; # LATIN SMALL LIGATURE FFL
+FB05; FB05; 0053 0074; 0053 0054; # LATIN SMALL LIGATURE LONG S T
+FB06; FB06; 0053 0074; 0053 0054; # LATIN SMALL LIGATURE ST
+
+0587; 0587; 0535 0582; 0535 0552; # ARMENIAN SMALL LIGATURE ECH YIWN
+FB13; FB13; 0544 0576; 0544 0546; # ARMENIAN SMALL LIGATURE MEN NOW
+FB14; FB14; 0544 0565; 0544 0535; # ARMENIAN SMALL LIGATURE MEN ECH
+FB15; FB15; 0544 056B; 0544 053B; # ARMENIAN SMALL LIGATURE MEN INI
+FB16; FB16; 054E 0576; 054E 0546; # ARMENIAN SMALL LIGATURE VEW NOW
+FB17; FB17; 0544 056D; 0544 053D; # ARMENIAN SMALL LIGATURE MEN XEH
+
+# No corresponding uppercase precomposed character
+
+0149; 0149; 02BC 004E; 02BC 004E; # LATIN SMALL LETTER N PRECEDED BY APOSTROPHE
+0390; 0390; 0399 0308 0301; 0399 0308 0301; # GREEK SMALL LETTER IOTA WITH DIALYTIKA AND TONOS
+03B0; 03B0; 03A5 0308 0301; 03A5 0308 0301; # GREEK SMALL LETTER UPSILON WITH DIALYTIKA AND TONOS
+01F0; 01F0; 004A 030C; 004A 030C; # LATIN SMALL LETTER J WITH CARON
+1E96; 1E96; 0048 0331; 0048 0331; # LATIN SMALL LETTER H WITH LINE BELOW
+1E97; 1E97; 0054 0308; 0054 0308; # LATIN SMALL LETTER T WITH DIAERESIS
+1E98; 1E98; 0057 030A; 0057 030A; # LATIN SMALL LETTER W WITH RING ABOVE
+1E99; 1E99; 0059 030A; 0059 030A; # LATIN SMALL LETTER Y WITH RING ABOVE
+1E9A; 1E9A; 0041 02BE; 0041 02BE; # LATIN SMALL LETTER A WITH RIGHT HALF RING
+1F50; 1F50; 03A5 0313; 03A5 0313; # GREEK SMALL LETTER UPSILON WITH PSILI
+1F52; 1F52; 03A5 0313 0300; 03A5 0313 0300; # GREEK SMALL LETTER UPSILON WITH PSILI AND VARIA
+1F54; 1F54; 03A5 0313 0301; 03A5 0313 0301; # GREEK SMALL LETTER UPSILON WITH PSILI AND OXIA
+1F56; 1F56; 03A5 0313 0342; 03A5 0313 0342; # GREEK SMALL LETTER UPSILON WITH PSILI AND PERISPOMENI
+1FB6; 1FB6; 0391 0342; 0391 0342; # GREEK SMALL LETTER ALPHA WITH PERISPOMENI
+1FC6; 1FC6; 0397 0342; 0397 0342; # GREEK SMALL LETTER ETA WITH PERISPOMENI
+1FD2; 1FD2; 0399 0308 0300; 0399 0308 0300; # GREEK SMALL LETTER IOTA WITH DIALYTIKA AND VARIA
+1FD3; 1FD3; 0399 0308 0301; 0399 0308 0301; # GREEK SMALL LETTER IOTA WITH DIALYTIKA AND OXIA
+1FD6; 1FD6; 0399 0342; 0399 0342; # GREEK SMALL LETTER IOTA WITH PERISPOMENI
+1FD7; 1FD7; 0399 0308 0342; 0399 0308 0342; # GREEK SMALL LETTER IOTA WITH DIALYTIKA AND PERISPOMENI
+1FE2; 1FE2; 03A5 0308 0300; 03A5 0308 0300; # GREEK SMALL LETTER UPSILON WITH DIALYTIKA AND VARIA
+1FE3; 1FE3; 03A5 0308 0301; 03A5 0308 0301; # GREEK SMALL LETTER UPSILON WITH DIALYTIKA AND OXIA
+1FE4; 1FE4; 03A1 0313; 03A1 0313; # GREEK SMALL LETTER RHO WITH PSILI
+1FE6; 1FE6; 03A5 0342; 03A5 0342; # GREEK SMALL LETTER UPSILON WITH PERISPOMENI
+1FE7; 1FE7; 03A5 0308 0342; 03A5 0308 0342; # GREEK SMALL LETTER UPSILON WITH DIALYTIKA AND PERISPOMENI
+1FF6; 1FF6; 03A9 0342; 03A9 0342; # GREEK SMALL LETTER OMEGA WITH PERISPOMENI
+
+# IMPORTANT-when iota-subscript (0345) is uppercased or titlecased,
+#  the result will be incorrect unless the iota-subscript is moved to the end
+#  of any sequence of combining marks. Otherwise, the accents will go on the capital iota.
+#  This process can be achieved by first transforming the text to NFC before casing.
+#  E.g. <alpha><iota_subscript><acute> is uppercased to <ALPHA><acute><IOTA>
+
+# The following cases are already in the UnicodeData.txt file, so are only commented here.
+
+# 0345; 0345; 0345; 0399; # COMBINING GREEK YPOGEGRAMMENI
+
+# All letters with YPOGEGRAMMENI (iota-subscript) or PROSGEGRAMMENI (iota adscript)
+# have special uppercases.
+# Note: characters with PROSGEGRAMMENI are actually titlecase, not uppercase!
+
+1F80; 1F80; 1F88; 1F08 0399; # GREEK SMALL LETTER ALPHA WITH PSILI AND YPOGEGRAMMENI
+1F81; 1F81; 1F89; 1F09 0399; # GREEK SMALL LETTER ALPHA WITH DASIA AND YPOGEGRAMMENI
+1F82; 1F82; 1F8A; 1F0A 0399; # GREEK SMALL LETTER ALPHA WITH PSILI AND VARIA AND YPOGEGRAMMENI
+1F83; 1F83; 1F8B; 1F0B 0399; # GREEK SMALL LETTER ALPHA WITH DASIA AND VARIA AND YPOGEGRAMMENI
+1F84; 1F84; 1F8C; 1F0C 0399; # GREEK SMALL LETTER ALPHA WITH PSILI AND OXIA AND YPOGEGRAMMENI
+1F85; 1F85; 1F8D; 1F0D 0399; # GREEK SMALL LETTER ALPHA WITH DASIA AND OXIA AND YPOGEGRAMMENI
+1F86; 1F86; 1F8E; 1F0E 0399; # GREEK SMALL LETTER ALPHA WITH PSILI AND PERISPOMENI AND YPOGEGRAMMENI
+1F87; 1F87; 1F8F; 1F0F 0399; # GREEK SMALL LETTER ALPHA WITH DASIA AND PERISPOMENI AND YPOGEGRAMMENI
+1F88; 1F80; 1F88; 1F08 0399; # GREEK CAPITAL LETTER ALPHA WITH PSILI AND PROSGEGRAMMENI
+1F89; 1F81; 1F89; 1F09 0399; # GREEK CAPITAL LETTER ALPHA WITH DASIA AND PROSGEGRAMMENI
+1F8A; 1F82; 1F8A; 1F0A 0399; # GREEK CAPITAL LETTER ALPHA WITH PSILI AND VARIA AND PROSGEGRAMMENI
+1F8B; 1F83; 1F8B; 1F0B 0399; # GREEK CAPITAL LETTER ALPHA WITH DASIA AND VARIA AND PROSGEGRAMMENI
+1F8C; 1F84; 1F8C; 1F0C 0399; # GREEK CAPITAL LETTER ALPHA WITH PSILI AND OXIA AND PROSGEGRAMMENI
+1F8D; 1F85; 1F8D; 1F0D 0399; # GREEK CAPITAL LETTER ALPHA WITH DASIA AND OXIA AND PROSGEGRAMMENI
+1F8E; 1F86; 1F8E; 1F0E 0399; # GREEK CAPITAL LETTER ALPHA WITH PSILI AND PERISPOMENI AND PROSGEGRAMMENI
+1F8F; 1F87; 1F8F; 1F0F 0399; # GREEK CAPITAL LETTER ALPHA WITH DASIA AND PERISPOMENI AND PROSGEGRAMMENI
+1F90; 1F90; 1F98; 1F28 0399; # GREEK SMALL LETTER ETA WITH PSILI AND YPOGEGRAMMENI
+1F91; 1F91; 1F99; 1F29 0399; # GREEK SMALL LETTER ETA WITH DASIA AND YPOGEGRAMMENI
+1F92; 1F92; 1F9A; 1F2A 0399; # GREEK SMALL LETTER ETA WITH PSILI AND VARIA AND YPOGEGRAMMENI
+1F93; 1F93; 1F9B; 1F2B 0399; # GREEK SMALL LETTER ETA WITH DASIA AND VARIA AND YPOGEGRAMMENI
+1F94; 1F94; 1F9C; 1F2C 0399; # GREEK SMALL LETTER ETA WITH PSILI AND OXIA AND YPOGEGRAMMENI
+1F95; 1F95; 1F9D; 1F2D 0399; # GREEK SMALL LETTER ETA WITH DASIA AND OXIA AND YPOGEGRAMMENI
+1F96; 1F96; 1F9E; 1F2E 0399; # GREEK SMALL LETTER ETA WITH PSILI AND PERISPOMENI AND YPOGEGRAMMENI
+1F97; 1F97; 1F9F; 1F2F 0399; # GREEK SMALL LETTER ETA WITH DASIA AND PERISPOMENI AND YPOGEGRAMMENI
+1F98; 1F90; 1F98; 1F28 0399; # GREEK CAPITAL LETTER ETA WITH PSILI AND PROSGEGRAMMENI
+1F99; 1F91; 1F99; 1F29 0399; # GREEK CAPITAL LETTER ETA WITH DASIA AND PROSGEGRAMMENI
+1F9A; 1F92; 1F9A; 1F2A 0399; # GREEK CAPITAL LETTER ETA WITH PSILI AND VARIA AND PROSGEGRAMMENI
+1F9B; 1F93; 1F9B; 1F2B 0399; # GREEK CAPITAL LETTER ETA WITH DASIA AND VARIA AND PROSGEGRAMMENI
+1F9C; 1F94; 1F9C; 1F2C 0399; # GREEK CAPITAL LETTER ETA WITH PSILI AND OXIA AND PROSGEGRAMMENI
+1F9D; 1F95; 1F9D; 1F2D 0399; # GREEK CAPITAL LETTER ETA WITH DASIA AND OXIA AND PROSGEGRAMMENI
+1F9E; 1F96; 1F9E; 1F2E 0399; # GREEK CAPITAL LETTER ETA WITH PSILI AND PERISPOMENI AND PROSGEGRAMMENI
+1F9F; 1F97; 1F9F; 1F2F 0399; # GREEK CAPITAL LETTER ETA WITH DASIA AND PERISPOMENI AND PROSGEGRAMMENI
+1FA0; 1FA0; 1FA8; 1F68 0399; # GREEK SMALL LETTER OMEGA WITH PSILI AND YPOGEGRAMMENI
+1FA1; 1FA1; 1FA9; 1F69 0399; # GREEK SMALL LETTER OMEGA WITH DASIA AND YPOGEGRAMMENI
+1FA2; 1FA2; 1FAA; 1F6A 0399; # GREEK SMALL LETTER OMEGA WITH PSILI AND VARIA AND YPOGEGRAMMENI
+1FA3; 1FA3; 1FAB; 1F6B 0399; # GREEK SMALL LETTER OMEGA WITH DASIA AND VARIA AND YPOGEGRAMMENI
+1FA4; 1FA4; 1FAC; 1F6C 0399; # GREEK SMALL LETTER OMEGA WITH PSILI AND OXIA AND YPOGEGRAMMENI
+1FA5; 1FA5; 1FAD; 1F6D 0399; # GREEK SMALL LETTER OMEGA WITH DASIA AND OXIA AND YPOGEGRAMMENI
+1FA6; 1FA6; 1FAE; 1F6E 0399; # GREEK SMALL LETTER OMEGA WITH PSILI AND PERISPOMENI AND YPOGEGRAMMENI
+1FA7; 1FA7; 1FAF; 1F6F 0399; # GREEK SMALL LETTER OMEGA WITH DASIA AND PERISPOMENI AND YPOGEGRAMMENI
+1FA8; 1FA0; 1FA8; 1F68 0399; # GREEK CAPITAL LETTER OMEGA WITH PSILI AND PROSGEGRAMMENI
+1FA9; 1FA1; 1FA9; 1F69 0399; # GREEK CAPITAL LETTER OMEGA WITH DASIA AND PROSGEGRAMMENI
+1FAA; 1FA2; 1FAA; 1F6A 0399; # GREEK CAPITAL LETTER OMEGA WITH PSILI AND VARIA AND PROSGEGRAMMENI
+1FAB; 1FA3; 1FAB; 1F6B 0399; # GREEK CAPITAL LETTER OMEGA WITH DASIA AND VARIA AND PROSGEGRAMMENI
+1FAC; 1FA4; 1FAC; 1F6C 0399; # GREEK CAPITAL LETTER OMEGA WITH PSILI AND OXIA AND PROSGEGRAMMENI
+1FAD; 1FA5; 1FAD; 1F6D 0399; # GREEK CAPITAL LETTER OMEGA WITH DASIA AND OXIA AND PROSGEGRAMMENI
+1FAE; 1FA6; 1FAE; 1F6E 0399; # GREEK CAPITAL LETTER OMEGA WITH PSILI AND PERISPOMENI AND PROSGEGRAMMENI
+1FAF; 1FA7; 1FAF; 1F6F 0399; # GREEK CAPITAL LETTER OMEGA WITH DASIA AND PERISPOMENI AND PROSGEGRAMMENI
+1FB3; 1FB3; 1FBC; 0391 0399; # GREEK SMALL LETTER ALPHA WITH YPOGEGRAMMENI
+1FBC; 1FB3; 1FBC; 0391 0399; # GREEK CAPITAL LETTER ALPHA WITH PROSGEGRAMMENI
+1FC3; 1FC3; 1FCC; 0397 0399; # GREEK SMALL LETTER ETA WITH YPOGEGRAMMENI
+1FCC; 1FC3; 1FCC; 0397 0399; # GREEK CAPITAL LETTER ETA WITH PROSGEGRAMMENI
+1FF3; 1FF3; 1FFC; 03A9 0399; # GREEK SMALL LETTER OMEGA WITH YPOGEGRAMMENI
+1FFC; 1FF3; 1FFC; 03A9 0399; # GREEK CAPITAL LETTER OMEGA WITH PROSGEGRAMMENI
+
+# Some characters with YPOGEGRAMMENI also have no corresponding titlecases
+
+1FB2; 1FB2; 1FBA 0345; 1FBA 0399; # GREEK SMALL LETTER ALPHA WITH VARIA AND YPOGEGRAMMENI
+1FB4; 1FB4; 0386 0345; 0386 0399; # GREEK SMALL LETTER ALPHA WITH OXIA AND YPOGEGRAMMENI
+1FC2; 1FC2; 1FCA 0345; 1FCA 0399; # GREEK SMALL LETTER ETA WITH VARIA AND YPOGEGRAMMENI
+1FC4; 1FC4; 0389 0345; 0389 0399; # GREEK SMALL LETTER ETA WITH OXIA AND YPOGEGRAMMENI
+1FF2; 1FF2; 1FFA 0345; 1FFA 0399; # GREEK SMALL LETTER OMEGA WITH VARIA AND YPOGEGRAMMENI
+1FF4; 1FF4; 038F 0345; 038F 0399; # GREEK SMALL LETTER OMEGA WITH OXIA AND YPOGEGRAMMENI
+
+1FB7; 1FB7; 0391 0342 0345; 0391 0342 0399; # GREEK SMALL LETTER ALPHA WITH PERISPOMENI AND YPOGEGRAMMENI
+1FC7; 1FC7; 0397 0342 0345; 0397 0342 0399; # GREEK SMALL LETTER ETA WITH PERISPOMENI AND YPOGEGRAMMENI
+1FF7; 1FF7; 03A9 0342 0345; 03A9 0342 0399; # GREEK SMALL LETTER OMEGA WITH PERISPOMENI AND YPOGEGRAMMENI
+
+# ================================================================================
+# Conditional Mappings
+# The remainder of this file provides conditional casing data used to produce 
+# full case mappings.
+# ================================================================================
+# Language-Insensitive Mappings
+# These are characters whose full case mappings do not depend on language, but do
+# depend on context (which characters come before or after). For more information
+# see the header of this file and the Unicode Standard.
+# ================================================================================
+
+# Special case for final form of sigma
+
+03A3; 03C2; 03A3; 03A3; Final_Sigma; # GREEK CAPITAL LETTER SIGMA
+
+# Note: the following cases for non-final are already in the UnicodeData.txt file.
+
+# 03A3; 03C3; 03A3; 03A3; # GREEK CAPITAL LETTER SIGMA
+# 03C3; 03C3; 03A3; 03A3; # GREEK SMALL LETTER SIGMA
+# 03C2; 03C2; 03A3; 03A3; # GREEK SMALL LETTER FINAL SIGMA
+
+# Note: the following cases are not included, since they would case-fold in lowercasing
+
+# 03C3; 03C2; 03A3; 03A3; Final_Sigma; # GREEK SMALL LETTER SIGMA
+# 03C2; 03C3; 03A3; 03A3; Not_Final_Sigma; # GREEK SMALL LETTER FINAL SIGMA
+
+# ================================================================================
+# Language-Sensitive Mappings
+# These are characters whose full case mappings depend on language and perhaps also
+# context (which characters come before or after). For more information
+# see the header of this file and the Unicode Standard.
+# ================================================================================
+
+# Lithuanian
+
+# Lithuanian retains the dot in a lowercase i when followed by accents.
+
+# Remove DOT ABOVE after "i" with upper or titlecase
+
+0307; 0307; ; ; lt After_Soft_Dotted; # COMBINING DOT ABOVE
+
+# Introduce an explicit dot above when lowercasing capital I's and J's
+# whenever there are more accents above.
+# (of the accents used in Lithuanian: grave, acute, tilde above, and ogonek)
+
+0049; 0069 0307; 0049; 0049; lt More_Above; # LATIN CAPITAL LETTER I
+004A; 006A 0307; 004A; 004A; lt More_Above; # LATIN CAPITAL LETTER J
+012E; 012F 0307; 012E; 012E; lt More_Above; # LATIN CAPITAL LETTER I WITH OGONEK
+00CC; 0069 0307 0300; 00CC; 00CC; lt; # LATIN CAPITAL LETTER I WITH GRAVE
+00CD; 0069 0307 0301; 00CD; 00CD; lt; # LATIN CAPITAL LETTER I WITH ACUTE
+0128; 0069 0307 0303; 0128; 0128; lt; # LATIN CAPITAL LETTER I WITH TILDE
+
+# ================================================================================
+
+# Turkish and Azeri
+
+# I and i-dotless; I-dot and i are case pairs in Turkish and Azeri
+# The following rules handle those cases.
+
+0130; 0069; 0130; 0130; tr; # LATIN CAPITAL LETTER I WITH DOT ABOVE
+0130; 0069; 0130; 0130; az; # LATIN CAPITAL LETTER I WITH DOT ABOVE
+
+# When lowercasing, remove dot_above in the sequence I + dot_above, which will turn into i.
+# This matches the behavior of the canonically equivalent I-dot_above
+
+0307; ; 0307; 0307; tr After_I; # COMBINING DOT ABOVE
+0307; ; 0307; 0307; az After_I; # COMBINING DOT ABOVE
+
+# When lowercasing, unless an I is before a dot_above, it turns into a dotless i.
+
+0049; 0131; 0049; 0049; tr Not_Before_Dot; # LATIN CAPITAL LETTER I
+0049; 0131; 0049; 0049; az Not_Before_Dot; # LATIN CAPITAL LETTER I
+
+# When uppercasing, i turns into a dotted capital I
+
+0069; 0069; 0130; 0130; tr; # LATIN SMALL LETTER I
+0069; 0069; 0130; 0130; az; # LATIN SMALL LETTER I
+
+# Note: the following case is already in the UnicodeData.txt file.
+
+# 0131; 0131; 0049; 0049; tr; # LATIN SMALL LETTER DOTLESS I
+
+# EOF
+
diff --git a/admin/unidata/unidata-gen.el b/admin/unidata/unidata-gen.el
index 3c5119a8a3d..5575f0e745a 100644
--- a/admin/unidata/unidata-gen.el
+++ b/admin/unidata/unidata-gen.el
@@ -268,6 +268,20 @@ unidata-prop-alist
 The value nil means that the actual property value of a character
 is the character itself."
      string)
+    (special-casing
+     nil unidata-gen-table-special-casing "uni-special-casing.el"
+     "Unicode special casing mapping.
+
+Property value is nil or a three-element list of strings or characters.  Each
+element denotes what characters maps into when upper-casing, lower-casing or
+title-casing respectively.  String is used when the mapping is into an empty
+string or more than one character.
+
+The value nil means that no special casing rules exist for the character and
+`uppercase', `lowercase' or `titlecase' property needs to be consulted.
+
+The mapping includes only unconditional casing rules defined by Unicode."
+     nil)
     (mirroring
      unidata-gen-mirroring-list unidata-gen-table-character "uni-mirrored.el"
      "Unicode bidi-mirroring characters.
@@ -1084,6 +1098,32 @@ unidata-gen-table-decomposition
 
 
 \f
+
+(defun unidata-gen-table-special-casing (prop &rest ignore)
+  (let ((table (make-char-table 'char-code-property-table)))
+    (set-char-table-extra-slot table 0 prop)
+    (with-temp-buffer
+      (insert-file-contents (expand-file-name "SpecialCasing.txt" unidata-dir))
+      (goto-char (point-min))
+      (while (not (eobp))
+        (unless (or (eq (char-after) ?\n) (eq (char-after) ?#)) ;empty line or comment
+          (let ((line (split-string
+                       (buffer-substring (point) (progn (end-of-line) (point)))
+                       ";" "")))
+            ;; Ignore entries with conditions, i.e. those with six values.
+            (when (= (length line) 5)
+              (let ((ch (string-to-number (pop line) 16)) lo tc up)
+                (dolist (var '(lo tc up))
+                  (let ((v (mapcar (lambda (num) (string-to-number num 16))
+                                   (split-string (pop line)))))
+                    (set var (if (or (null v) (cdr v)) (apply 'string v) (car v)))))
+                ;; Order must match order of case_action enum fields defined in
+                ;; src/casefiddle.c
+                (set-char-table-range table ch (list up lo tc))))))
+        (forward-line)))
+    table))
+
+\f
 (defun unidata-describe-general-category (val)
   (cdr (assq val
 	     '((nil . "Uknown")
diff --git a/doc/lispref/strings.texi b/doc/lispref/strings.texi
index cf47db4a814..ba1cf2606ce 100644
--- a/doc/lispref/strings.texi
+++ b/doc/lispref/strings.texi
@@ -1166,6 +1166,29 @@ Case Conversion
 @end example
 @end defun
 
+  Note that case conversion is not a one-to-one mapping and the length
+of the result may differ from the length of the argument (including
+being shorter).  Furthermore, because passing a character forces
+return type to be a character, functions are unable to perform proper
+substitution and result may differ compared to treating
+a one-character string.  For example:
+
+@example
+@group
+(upcase "fi")  ; note: single character, ligature "fi"
+     @result{} "FI"
+@end group
+@group
+(upcase ?fi)
+     @result{} 64257  ; i.e. ?fi
+@end group
+@end example
+
+  To avoid this, a character must first be converted into a string,
+using @code{string} function, before being passed to one of the casing
+functions.  Of course, no assumptions on the length of the result may
+be made.
+
   @xref{Text Comparison}, for functions that compare strings; some of
 them ignore case differences, or can optionally ignore case differences.
 
diff --git a/etc/NEWS b/etc/NEWS
index 03790cac53f..bac396ecc18 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -325,13 +325,17 @@ same as in modes where the character is not whitespace.
 Instead of only checking the modification time, Emacs now also checks
 the file's actual content before prompting the user.
 
-** Title case characters are properly cased (from and into).
-'upcase', 'upcase-region' et al. convert title case characters (such
-as Dz) into their upper case form (such as DZ).
+** Various casing improvements.
 
-Similarly, 'capitalize', 'upcase-initials' et al. make use of
-title-case forms of initial characters (correctly producing for example
-Džungla instead of incorrect DŽungla).
+*** 'upcase', 'upcase-region' et al. convert title case characters
+(such as Dz) into their upper case form (such as DZ).
+
+*** 'capitalize', 'upcase-initials' et al. make use of title-case forms
+of initial characters (correctly producing for example Džungla instead
+of incorrect DŽungla).
+
+*** Characters which turn into multiple ones when cased are correctly handled.
+For example, fi ligature is converted to FI when upper cased.
 
 \f
 * Changes in Specialized Modes and Packages in Emacs 26.1
diff --git a/src/casefiddle.c b/src/casefiddle.c
index c09d0609367..8a03eaabeaf 100644
--- a/src/casefiddle.c
+++ b/src/casefiddle.c
@@ -29,6 +29,7 @@ along with GNU Emacs.  If not, see <http://www.gnu.org/licenses/>.  */
 #include "composite.h"
 #include "keymap.h"
 
+/* Order must match order in unidata-gen-table-special-casing. */
 enum case_action {CASE_UP, CASE_DOWN, CASE_CAPITALIZE, CASE_CAPITALIZE_UP};
 
 /* State for casing individual characters.  */
@@ -37,6 +38,9 @@ struct casing_context {
      implies flag being CASE_CAPITALIZE or CASE_CAPITALIZE_UP (but the reverse
      is not true).  */
   Lisp_Object titlecase_char_table;
+  /* The special-casing Unicode properties case table with unconditional special
+     casing rules defined by Unicode. */
+  Lisp_Object specialcase_char_table;
   /* User-requested action. */
   enum case_action flag;
   /* If true, function operates on a buffer as opposed to a string or character.
@@ -61,6 +65,8 @@ prepare_casing_context (struct casing_context *ctx,
   ctx->titlecase_char_table = (int)flag >= (int)CASE_CAPITALIZE
     ? uniprop_table (intern_c_string ("titlecase"))
     : Qnil;
+  ctx->specialcase_char_table =
+    uniprop_table (intern_c_string ("special-casing"));
 
   /* If the case table is flagged as modified, rescan it.  */
   if (NILP (XCHAR_TABLE (BVAR (current_buffer, downcase_table))->extras[1]))
@@ -70,25 +76,117 @@ prepare_casing_context (struct casing_context *ctx,
     SETUP_BUFFER_SYNTAX_TABLE ();	/* For syntax_prefix_flag_p.  */
 }
 
-/* Based on CTX, case character CH accordingly.  Update CTX as necessary.
-   Return cased character. */
+struct casing_str_buf {
+  unsigned char data[MAX_MULTIBYTE_LENGTH > 6 ? MAX_MULTIBYTE_LENGTH : 6];
+  unsigned char len_chars;
+  unsigned char len_bytes;
+};
+
+/* Based on CTX, case character CH.  If BUF is NULL, return cased character.
+   Otherwise, if BUF is non-NULL, save result in it and return whether the
+   character has been changed.
+
+   Since meaning of return value depends on arguments, it’s more convenient to
+   use case_single_character or case_character instead. */
 static int
-case_character (struct casing_context *ctx, int ch)
+case_character_impl (struct casing_str_buf *buf,
+		     struct casing_context *ctx, int ch)
 {
+  enum case_action flag;
   Lisp_Object prop;
+  bool was_inword;
+  int cased;
+
+  /* Update inword state */
+  was_inword = ctx->inword;
+  if ((int) ctx->flag >= (int) CASE_CAPITALIZE)
+    ctx->inword = SYNTAX (ch) == Sword &&
+      (!ctx->inbuffer || was_inword || !syntax_prefix_flag_p (ch));
+
+  /* Normalise flag so its one of CASE_UP, CASE_DOWN or CASE_CAPITALIZE. */
+  if (!was_inword)
+    flag = ctx->flag == CASE_UP ? CASE_UP : CASE_CAPITALIZE;
+  else if (ctx->flag != CASE_CAPITALIZE_UP)
+    flag = CASE_DOWN;
+  else
+    {
+      cased = ch;
+      goto done;
+    }
 
-  if (ctx->inword)
-    ch = ctx->flag == CASE_CAPITALIZE_UP ? ch : downcase (ch);
+  /* Look through the special casing entries. */
+  if (buf && !NILP(ctx->specialcase_char_table))
+    {
+      prop = CHAR_TABLE_REF(ctx->specialcase_char_table, ch);
+      switch (flag) {
+      case CASE_CAPITALIZE:
+      case CASE_CAPITALIZE_UP:
+        if (!CONSP(prop))
+          break;
+        prop = XCDR(prop);
+        /* FALL THROUGH */
+      case CASE_DOWN:
+        if (!CONSP(prop))
+          break;
+        prop = XCDR(prop);
+        /* FALL THROUGH */
+      default:
+        if (!CONSP(prop))
+          break;
+        prop = XCAR(prop);
+        if (INTEGERP(prop)) {
+          cased = XINT(prop);
+          if (0 <= cased && cased <= MAX_CHAR)
+            goto done;
+        } else if (STRINGP(prop)) {
+          struct Lisp_String *str = XSTRING(prop);
+          if (STRING_BYTES(str) <= sizeof buf->data) {
+            buf->len_chars = str->size;
+            buf->len_bytes = STRING_BYTES(str);
+            memcpy(buf->data, str->data, buf->len_bytes);
+            return 1;
+          }
+        }
+      }
+    }
+
+  /* Handle simple, one-to-one case. */
+  if (flag == CASE_DOWN)
+    cased = downcase (ch);
   else if (!NILP (ctx->titlecase_char_table) &&
 	   CHARACTERP (prop = CHAR_TABLE_REF (ctx->titlecase_char_table, ch)))
-    ch = XFASTINT (prop);
+    cased = XFASTINT (prop);
   else
-    ch = upcase(ch);
+    cased = upcase(ch);
+
+  /* And we’re done. */
+ done:
+  if (!buf)
+    return cased;
+  buf->len_chars = 1;
+  buf->len_bytes = CHAR_STRING (cased, buf->data);
+  return cased != ch;
+}
 
-  if ((int) ctx->flag >= (int) CASE_CAPITALIZE)
-    ctx->inword = SYNTAX (ch) == Sword &&
-      (!ctx->inbuffer || ctx->inword || !syntax_prefix_flag_p (ch));
-  return ch;
+/* Based on CTX, case character CH accordingly.  Update CTX as necessary.
+   Return cased character.
+
+   Special casing rules (such as upcase(fi) = FI) are not handled.  For
+   characters whose casing results in multiple code points, the character is
+   returned unchanged. */
+static inline int
+case_single_character (struct casing_context *ctx, int ch)
+{
+  return case_character_impl (NULL, ctx, ch);
+}
+
+/* Save in BUF result of casing character CH.  Return whether casing changed the
+   character.  This is like case_single_character but also handles one-to-many
+   casing rules. */
+static inline bool
+case_character (struct casing_str_buf *buf, struct casing_context *ctx, int ch)
+{
+  return case_character_impl (buf, ctx, ch);
 }
 \f
 static Lisp_Object
@@ -115,7 +213,7 @@ do_casify_integer (struct casing_context *ctx, Lisp_Object obj)
 	       !NILP (BVAR (current_buffer, enable_multibyte_characters)));
   if (! multibyte)
     MAKE_CHAR_MULTIBYTE (ch);
-  cased = case_character (ctx, ch);
+  cased = case_single_character (ctx, ch);
   if (cased == ch)
     return obj;
 
@@ -128,25 +226,34 @@ do_casify_integer (struct casing_context *ctx, Lisp_Object obj)
 static Lisp_Object
 do_casify_multibyte_string (struct casing_context *ctx, Lisp_Object obj)
 {
-  ptrdiff_t i, i_byte, size = SCHARS (obj);
-  int len, ch, cased;
+  /* We assume data is the first member of casing_str_buf structure so that if
+     we cast a (char *) into (struct casing_str_buf *) the representation of the
+     character is at the beginning of the buffer.  This is why we don’t need
+     separate struct casing_str_buf object but rather write directly to o. */
+  typedef char static_assertion[offsetof(struct casing_str_buf, data) ? -1 : 1];
+
+  ptrdiff_t size = SCHARS (obj), n;
+  int ch;
   USE_SAFE_ALLOCA;
-  ptrdiff_t o_size;
-  if (INT_MULTIPLY_WRAPV (size, MAX_MULTIBYTE_LENGTH, &o_size))
-    o_size = PTRDIFF_MAX;
-  unsigned char *dst = SAFE_ALLOCA (o_size);
+  if (INT_MULTIPLY_WRAPV (size, MAX_MULTIBYTE_LENGTH, &n) ||
+      INT_ADD_WRAPV (n, sizeof(struct casing_str_buf), &n))
+    n = PTRDIFF_MAX;
+  unsigned char *const dst = SAFE_ALLOCA (n), *const dst_end = dst + n;
   unsigned char *o = dst;
 
-  for (i = i_byte = 0; i < size; i++, i_byte += len)
+  const unsigned char *src = SDATA (obj);
+
+  for (n = 0; size; --size)
     {
-      if (o_size - MAX_MULTIBYTE_LENGTH < o - dst)
+      if (dst_end - o < sizeof(struct casing_str_buf))
 	string_overflow ();
-      ch = STRING_CHAR_AND_LENGTH (SDATA (obj) + i_byte, len);
-      cased = case_character (ctx, ch);
-      o += CHAR_STRING (cased, o);
+      ch = STRING_CHAR_ADVANCE (src);
+      case_character ((void *)o, ctx, ch);
+      n += ((struct casing_str_buf *)o)->len_chars;
+      o += ((struct casing_str_buf *)o)->len_bytes;
     }
-  eassert (o - dst <= o_size);
-  obj = make_multibyte_string ((char *) dst, size, o - dst);
+  eassert (o <= dst_end);
+  obj = make_multibyte_string ((char *) dst, n, o - dst);
   SAFE_FREE ();
   return obj;
 }
@@ -162,7 +269,7 @@ do_casify_unibyte_string (struct casing_context *ctx, Lisp_Object obj)
     {
       ch = SREF (obj, i);
       MAKE_CHAR_MULTIBYTE (ch);
-      cased = case_character (ctx, ch);
+      cased = case_single_character (ctx, ch);
       if (ch == cased)
 	continue;
       MAKE_CHAR_UNIBYTE (cased);
@@ -194,7 +301,9 @@ casify_object (enum case_action flag, Lisp_Object obj)
 DEFUN ("upcase", Fupcase, Supcase, 1, 1, 0,
        doc: /* Convert argument to upper case and return that.
 The argument may be a character or string.  The result has the same type.
-The argument object is not altered--the value is a copy.
+The argument object is not altered--the value is a copy.  If argument
+is a character, characters which map to multiple code points when
+cased, e.g. fi, are returned unchanged.
 See also `capitalize', `downcase' and `upcase-initials'.  */)
   (Lisp_Object obj)
 {
@@ -215,7 +324,9 @@ DEFUN ("capitalize", Fcapitalize, Scapitalize, 1, 1, 0,
 This means that each word's first character is upper case (more
 precisely, if available, title case) and the rest is lower case.
 The argument may be a character or string.  The result has the same type.
-The argument object is not altered--the value is a copy.  */)
+The argument object is not altered--the value is a copy.  If argument
+is a character, characters which map to multiple code points when
+cased, e.g. fi, are returned unchanged.  */)
   (Lisp_Object obj)
 {
   return casify_object (CASE_CAPITALIZE, obj);
@@ -228,21 +339,28 @@ DEFUN ("upcase-initials", Fupcase_initials, Supcase_initials, 1, 1, 0,
 (More precisely, if available, initial of each word is converted to
 title-case).  Do not change the other letters of each word.
 The argument may be a character or string.  The result has the same type.
-The argument object is not altered--the value is a copy.  */)
+The argument object is not altered--the value is a copy.  If argument
+is a character, characters which map to multiple code points when
+cased, e.g. fi, are returned unchanged.  */)
   (Lisp_Object obj)
 {
   return casify_object (CASE_CAPITALIZE_UP, obj);
 }
 \f
-/* Based on CTX, case region in a unibyte buffer from POS to *ENDP.  Return
-   first position that has changed and save last position in *ENDP.  If no
-   characters were changed, return -1 and *ENDP is unspecified. */
+/* Based on CTX, case region in a unibyte buffer from *STARTP to *ENDP.
+
+   Save first and last positions that has changed in *STARTP and *ENDP
+   respectively.  If no characters were changed, save -1 to *STARTP and leave
+   *ENDP unspecified.
+
+   Always return 0.  This is so that interface of this function is the same as
+   do_casify_multibyte_region. */
 static ptrdiff_t
 do_casify_unibyte_region (struct casing_context *ctx,
-			  ptrdiff_t pos, ptrdiff_t *endp)
+			  ptrdiff_t *startp, ptrdiff_t *endp)
 {
   ptrdiff_t first = -1, last = -1;  /* Position of first and last changes. */
-  ptrdiff_t end = *endp;
+  ptrdiff_t pos = *startp, end = *endp;
   int ch, cased;
 
   for (; pos < end; ++pos)
@@ -250,11 +368,11 @@ do_casify_unibyte_region (struct casing_context *ctx,
       ch = FETCH_BYTE (pos);
       MAKE_CHAR_MULTIBYTE (ch);
 
-      cased = case_character (ctx, ch);
+      cased = case_single_character (ctx, ch);
       if (cased == ch)
 	continue;
 
-      last = pos;
+      last = pos + 1;
       if (first < 0)
 	first = pos;
 
@@ -262,88 +380,107 @@ do_casify_unibyte_region (struct casing_context *ctx,
       FETCH_BYTE (pos) = cased;
     }
 
-  *endp = last + 1;
-  return first;
+  *startp = first;
+  *endp = last;
+  return 0;
 }
 
-/* Based on CTX, case region in a multibyte buffer from POS to *ENDP.  Return
-   first position that has changed and save last position in *ENDP.  If no
-   characters were changed, return -1 and *ENDP is unspecified. */
+/* Based on CTX, case region in a multibyte buffer from *STARTP to *ENDP.
+
+   Return number of added characters (may be negative if more characters were
+   deleted then inserted), save first and last positions that has changed in
+   *STARTP and *ENDP respectively.  If no characters were changed, return 0,
+   save -1 to *STARTP and leave *ENDP unspecified. */
 static ptrdiff_t
 do_casify_multibyte_region (struct casing_context *ctx,
-                           ptrdiff_t pos, ptrdiff_t *endp)
+			    ptrdiff_t *startp, ptrdiff_t *endp)
 {
   ptrdiff_t first = -1, last = -1;  /* Position of first and last changes. */
-  ptrdiff_t pos_byte = CHAR_TO_BYTE (pos), end = *endp;
-  ptrdiff_t opoint = PT;
+  ptrdiff_t pos = *startp, pos_byte = CHAR_TO_BYTE (pos), size = *endp - pos;
+  ptrdiff_t opoint = PT, added = 0;
+  struct casing_str_buf buf;
   int ch, cased, len;
 
-  while (pos < end)
+  for (; size; --size)
     {
       ch = STRING_CHAR_AND_LENGTH (BYTE_POS_ADDR (pos_byte), len);
-      cased = case_character (ctx, ch);
-      if (cased != ch)
+      if (!case_character (&buf, ctx, ch))
+	{
+	  pos_byte += len;
+	  ++pos;
+	  continue;
+	}
+
+      last = pos + buf.len_chars;
+      if (first < 0)
+	first = pos;
+
+      if (buf.len_chars == 1 && buf.len_bytes == len)
+	memcpy (BYTE_POS_ADDR (pos_byte), buf.data, len);
+      else
 	{
-	  last = pos;
-	  if (first < 0)
-	    first = pos;
-
-	  if (ASCII_CHAR_P (cased) && ASCII_CHAR_P (ch))
-	    FETCH_BYTE (pos_byte) = cased;
-	  else
-	    {
-	      unsigned char str[MAX_MULTIBYTE_LENGTH];
-	      int totlen = CHAR_STRING (cased, str);
-	      if (len == totlen)
-		memcpy (BYTE_POS_ADDR (pos_byte), str, len);
-	      else
-		/* Replace one character with the other(s), keeping text
-		   properties the same.  */
-		replace_range_2 (pos, pos_byte, pos + 1, pos_byte + len,
-				 (char *) str, 9, totlen, 0);
-	      len = totlen;
-	    }
+	  /* Replace one character with the other(s), keeping text
+	     properties the same.  */
+	  replace_range_2 (pos, pos_byte, pos + 1, pos_byte + len,
+			   (const char *) buf.data, buf.len_chars,
+			   buf.len_bytes,
+			   0);
+	  added += (ptrdiff_t) buf.len_chars - 1;
+	  if (opoint > pos)
+	    opoint += (ptrdiff_t) buf.len_chars - 1;
 	}
-      pos++;
-      pos_byte += len;
+
+      pos_byte += buf.len_bytes;
+      pos += buf.len_chars;
     }
 
   if (PT != opoint)
     TEMP_SET_PT_BOTH (opoint, CHAR_TO_BYTE (opoint));
 
+  *startp = first;
   *endp = last;
-  return first;
+  return added;
 }
 
-/* flag is CASE_UP, CASE_DOWN or CASE_CAPITALIZE or CASE_CAPITALIZE_UP.
-   b and e specify range of buffer to operate on. */
-static void
+/* flag is CASE_UP, CASE_DOWN or CASE_CAPITALIZE or CASE_CAPITALIZE_UP.  b and
+   e specify range of buffer to operate on.  Return character position of the
+   end of the region after changes.  */
+static ptrdiff_t
 casify_region (enum case_action flag, Lisp_Object b, Lisp_Object e)
 {
+  ptrdiff_t start, end, orig_end, added;
   struct casing_context ctx;
-  ptrdiff_t start, end;
-
-  if (EQ (b, e))
-    /* Not modifying because nothing marked */
-    return;
 
   validate_region (&b, &e);
   start = XFASTINT (b);
   end = XFASTINT (e);
+  if (start == end)
+    /* Not modifying because nothing marked */
+    return end;
   modify_text (start, end);
-  record_change (start, end - start);
   prepare_casing_context (&ctx, flag, true);
 
+  orig_end = end;
+  record_delete (start, make_buffer_string (start, end, true), false);
   if (NILP (BVAR (current_buffer, enable_multibyte_characters)))
-    start = do_casify_unibyte_region (&ctx, start, &end);
+    {
+      record_insert (start, end - start);
+      added = do_casify_unibyte_region (&ctx, &start, &end);
+    }
   else
-    start = do_casify_multibyte_region (&ctx, start, &end);
+    {
+      ptrdiff_t len = end - start, ostart = start;
+      added = do_casify_multibyte_region (&ctx, &start, &end);
+      record_insert (ostart, len + added);
+    }
 
   if (start >= 0)
     {
-      signal_after_change (start, end + 1 - start, end + 1 - start);
-      update_compositions (start, end + 1, CHECK_ALL);
+      signal_after_change (start, end - start - added, end - start);
+      update_compositions (start, end, CHECK_ALL);
     }
+
+  return orig_end + added;
 }
 
 DEFUN ("upcase-region", Fupcase_region, Supcase_region, 2, 3,
@@ -435,9 +572,7 @@ casify_word (enum case_action flag, Lisp_Object arg)
   ptrdiff_t farend = scan_words (PT, XINT (arg));
   if (!farend)
     farend = XINT (arg) <= 0 ? BEGV : ZV;
-  ptrdiff_t newpoint = max (PT, farend);
-  casify_region (flag, make_number (PT), make_number (farend));
-  SET_PT (newpoint);
+  SET_PT (casify_region (flag, make_number (PT), make_number (farend)));
   return Qnil;
 }
 
diff --git a/test/lisp/char-fold-tests.el b/test/lisp/char-fold-tests.el
index d86c731b6e3..00bc3c83d05 100644
--- a/test/lisp/char-fold-tests.el
+++ b/test/lisp/char-fold-tests.el
@@ -54,6 +54,14 @@ char-fold--test-search-with-contents
        (concat w1 "\s\n\s\t\f\t\n\r\t" w2)
        (concat w1 (make-string 10 ?\s) w2)))))
 
+(defun char-fold--ascii-upcase (string)
+  "Like `upcase' but acts on ASCII characters only."
+  (replace-regexp-in-string "[a-z]+" 'upcase string))
+
+(defun char-fold--ascii-downcase (string)
+  "Like `downcase' but acts on ASCII characters only."
+  (replace-regexp-in-string "[a-z]+" 'downcase string))
+
 (defun char-fold--test-match-exactly (string &rest strings-to-match)
   (let ((re (concat "\\`" (char-fold-to-regexp string) "\\'")))
     (dolist (it strings-to-match)
@@ -61,8 +69,8 @@ char-fold--test-match-exactly
     ;; Case folding
     (let ((case-fold-search t))
       (dolist (it strings-to-match)
-        (should (string-match (upcase re) (downcase it)))
-        (should (string-match (downcase re) (upcase it)))))))
+        (should (string-match (char-fold--ascii-upcase re) (downcase it)))
+        (should (string-match (char-fold--ascii-downcase re) (upcase it)))))))
 
 (ert-deftest char-fold--test-some-defaults ()
   (dolist (it '(("ffl" . "ffl") ("ffi" . "ffi")
diff --git a/test/src/casefiddle-tests.el b/test/src/casefiddle-tests.el
index d7fe55f97d7..e347ed7b875 100644
--- a/test/src/casefiddle-tests.el
+++ b/test/src/casefiddle-tests.el
@@ -188,16 +188,13 @@ casefiddle-tests--test-casing
         ("DŽUNGLA" "DŽUNGLA" "džungla" "Džungla" "DžUNGLA")
         ("Džungla" "DŽUNGLA" "džungla" "Džungla" "Džungla")
         ("džungla" "DŽUNGLA" "džungla" "Džungla" "Džungla")
+        ("define" "DEFINE" "define" "Define" "Define")
+        ("fish" "FISH" "fish" "Fish" "Fish")
+        ("Straße" "STRASSE" "straße" "Straße" "Straße")
         ;; FIXME: Everything below is broken at the moment.  Here’s what
         ;; should happen:
-        ;;("define" "DEFINE" "define" "Define" "Define")
-        ;;("fish" "FIsh" "fish" "Fish" "Fish")
-        ;;("Straße" "STRASSE" "straße" "Straße" "Straße")
         ;;("ΌΣΟΣ" "ΌΣΟΣ" "όσος" "Όσος" "Όσος")
         ;; And here’s what is actually happening:
-        ("define" "DEfiNE" "define" "Define" "Define")
-        ("fish" "fiSH" "fish" "fish" "fish")
-        ("Straße" "STRAßE" "straße" "Straße" "Straße")
         ("ΌΣΟΣ" "ΌΣΟΣ" "όσοσ" "Όσοσ" "ΌΣΟΣ")
 
         ("όσος" "ΌΣΟΣ" "όσος" "Όσος" "Όσος"))))))
-- 
2.11.0.483.g087da7b7c-goog






^ permalink raw reply related	[flat|nested] 89+ messages in thread

* bug#24603: [RFC 08/18] Support casing characters which map into multiple code points
  2017-01-28 23:48           ` Michal Nazarewicz
@ 2017-02-10  9:12             ` Eli Zaretskii
  0 siblings, 0 replies; 89+ messages in thread
From: Eli Zaretskii @ 2017-02-10  9:12 UTC (permalink / raw)
  To: Michal Nazarewicz; +Cc: 24603

> From: Michal Nazarewicz <mina86@mina86.com>
> Cc: 24603@debbugs.gnu.org
> Date: Sun, 29 Jan 2017 00:48:02 +0100
> 
> Attached below is a new version of 08/18 with the unconditional
> casing rules moved from C code to a uniprop char table (I haven’t
> updated commit message yet).  (Compared to previous version it’s
> a bit more C code but overall 200-line AWK script is replaced by
> around 50 lines of Elisp so overall the patch is shorter).

Thanks, this looks good.

It seems you've forgotten a log entry for the change in
unidata-gen.el.  Also, the new uni-special-casing.el file defines a
new uniprop table, whose user-visible manifestation -- the new
char-code property, I think should to be mentioned in the ELisp
manual.

Thanks again for working on this.





^ permalink raw reply	[flat|nested] 89+ messages in thread

* bug#24603: [PATCHv5 00/11] Casing improvements
  2016-10-04  1:05 bug#24603: [RFC 00/18] Improvement to casing Michal Nazarewicz
  2016-10-04  1:10 ` bug#24603: [RFC 01/18] Add tests for casefiddle.c Michal Nazarewicz
  2016-10-17 22:03 ` bug#24603: [PATCH 0/3] Case table updates Michal Nazarewicz
@ 2017-03-09 21:51 ` Michal Nazarewicz
  2017-03-09 21:51   ` bug#24603: [PATCHv5 01/11] Split casify_object into multiple functions Michal Nazarewicz
                     ` (12 more replies)
  2 siblings, 13 replies; 89+ messages in thread
From: Michal Nazarewicz @ 2017-03-09 21:51 UTC (permalink / raw)
  To: 24603, eliz

I figured that I should probably start versioning the patchsets;
starting from v5 is as good as any.

The first six patches (up to sigma casing rule) should be
uncontroversial and unless there are objections I would like to get
them submitted soon:

  Split casify_object into multiple functions
  Introduce case_character function
  Add support for title-casing letters  (bug#24603)
  Split up casify_region function  (bug#24603)
  Support casing characters which map into multiple code points 
  Implement special sigma casing rule  (bug#24603)

The next patch adds ‘buffer-language’ buffer-local variable.  This
seems to me as a sensible way of dealing with language-dependent rules
and in the future I imagine the variable might be used for more
cases, e.g. spell checking should automatically choose a dictionary
based on it.  But perhaps there is another way which integrates with
the rest of Emacs better:

  Introduce ‘buffer-language’ buffer-local variable

The rest are just implementation of various language-specific rules.
The implementation seems to be valid but it’s done purely in C which
I guess still is a point of contention between me and Eli.

Compared to previous versions of the patches, the new implementation
is, I believe, a bit cleaner:

  Implement rules for title-casing Dutch ij ‘letter’  (bug#24603)
  Implement Turkic dotless and dotted i casing rules  (bug#24603)
  Implement casing rules for Lithuanian  (bug#24603)
  Implement Irish casing rules  (bug#24603)

The whole thing (plus regex changes not included in this patchset) are
available at:

  git fetch git://github.com/mina86/emacs master

 admin/unidata/README                               |    4 +
 admin/unidata/SpecialCasing.txt                    |  281 +++++
 admin/unidata/unidata-gen.el                       |   40 +
 doc/lispref/strings.texi                           |   23 +
 etc/NEWS                                           |   22 +-
 lisp/international/mule-cmds.el                    |    8 +-
 src/buffer.c                                       |    8 +
 src/buffer.h                                       |    8 +
 src/casefiddle.c                                   | 1269 +++++++++++++++++---
 test/lisp/char-fold-tests.el                       |   12 +-
 .../casefiddle-resources/irish-lowercase-1-ref.txt |  211 ++++
 .../src/casefiddle-resources/irish-lowercase-1.txt |  211 ++++
 .../casefiddle-resources/irish-uppercase-1-ref.txt |  105 ++
 .../src/casefiddle-resources/irish-uppercase-1.txt |  105 ++
 test/src/casefiddle-tests.el                       |  193 ++-
 15 files changed, 2260 insertions(+), 240 deletions(-)
 create mode 100644 admin/unidata/SpecialCasing.txt
 create mode 100644 test/src/casefiddle-resources/irish-lowercase-1-ref.txt
 create mode 100644 test/src/casefiddle-resources/irish-lowercase-1.txt
 create mode 100644 test/src/casefiddle-resources/irish-uppercase-1-ref.txt
 create mode 100644 test/src/casefiddle-resources/irish-uppercase-1.txt

-- 
2.12.0.246.ga2ecc84866-goog






^ permalink raw reply	[flat|nested] 89+ messages in thread

* bug#24603: [PATCHv5 01/11] Split casify_object into multiple functions
  2017-03-09 21:51 ` bug#24603: [PATCHv5 00/11] Casing improvements Michal Nazarewicz
@ 2017-03-09 21:51   ` Michal Nazarewicz
  2017-03-10  9:00     ` Andreas Schwab
  2017-03-09 21:51   ` bug#24603: [PATCHv5 02/11] Introduce case_character function Michal Nazarewicz
                     ` (11 subsequent siblings)
  12 siblings, 1 reply; 89+ messages in thread
From: Michal Nazarewicz @ 2017-03-09 21:51 UTC (permalink / raw)
  To: 24603, eliz

casify_object had three major cases to cover and those were mostly
independent of each other.  Move those branches to separate function
so it’s easier to comprehend each individual case.

While at it, use somewhat more descriptive ch and cased variable names
rather than c and c1.

This commit introduces no functional changes.

* src/casefiddle.c (casify_object): Split into…
(do_casify_integer, do_casify_multibyte_string,
do_casify_unibyte_string): …new functions.
---
 src/casefiddle.c | 196 +++++++++++++++++++++++++++++--------------------------
 1 file changed, 104 insertions(+), 92 deletions(-)

diff --git a/src/casefiddle.c b/src/casefiddle.c
index 11d59444916..e32910fa8aa 100644
--- a/src/casefiddle.c
+++ b/src/casefiddle.c
@@ -32,108 +32,120 @@ along with GNU Emacs.  If not, see <http://www.gnu.org/licenses/>.  */
 enum case_action {CASE_UP, CASE_DOWN, CASE_CAPITALIZE, CASE_CAPITALIZE_UP};
 \f
 static Lisp_Object
-casify_object (enum case_action flag, Lisp_Object obj)
+do_casify_natnum (enum case_action flag, Lisp_Object obj)
+{
+  int flagbits = (CHAR_ALT | CHAR_SUPER | CHAR_HYPER
+		  | CHAR_SHIFT | CHAR_CTL | CHAR_META);
+  int flags, ch = XFASTINT (obj), cased;
+  bool multibyte;
+
+  /* If the character has higher bits set above the flags, return it unchanged.
+     It is not a real character.  */
+  if (UNSIGNED_CMP (ch, >, flagbits))
+    return obj;
+
+  flags = ch & flagbits;
+  ch = ch & ~flagbits;
+
+  /* FIXME: Even if enable-multibyte-characters is nil, we may manipulate
+     multibyte chars.  This means we have a bug for latin-1 chars since when we
+     receive an int 128-255 we can't tell whether it's an eight-bit byte or
+     a latin-1 char.  */
+  multibyte = (ch >= 256 ||
+	       !NILP (BVAR (current_buffer, enable_multibyte_characters)));
+  if (! multibyte)
+    MAKE_CHAR_MULTIBYTE (ch);
+  cased = flag == CASE_DOWN ? downcase (ch) : upcase (ch);
+  if (cased == ch)
+    return obj;
+
+  if (! multibyte)
+    MAKE_CHAR_UNIBYTE (cased);
+  XSETFASTINT (obj, cased | flags);
+  return obj;
+}
+
+static Lisp_Object
+do_casify_multibyte_string (enum case_action flag, Lisp_Object obj)
+{
+  ptrdiff_t i, i_byte, size = SCHARS (obj);
+  bool inword = flag == CASE_DOWN;
+  int len, ch, cased;
+  USE_SAFE_ALLOCA;
+  ptrdiff_t o_size;
+  if (INT_MULTIPLY_WRAPV (size, MAX_MULTIBYTE_LENGTH, &o_size))
+    o_size = PTRDIFF_MAX;
+  unsigned char *dst = SAFE_ALLOCA (o_size);
+  unsigned char *o = dst;
+
+  for (i = i_byte = 0; i < size; i++, i_byte += len)
+    {
+      if (o_size - MAX_MULTIBYTE_LENGTH < o - dst)
+	string_overflow ();
+      ch = STRING_CHAR_AND_LENGTH (SDATA (obj) + i_byte, len);
+      if (inword && flag != CASE_CAPITALIZE_UP)
+	cased = downcase (ch);
+      else if (!inword || flag != CASE_CAPITALIZE_UP)
+	cased = upcase (ch);
+      else
+	cased = ch;
+      if ((int) flag >= (int) CASE_CAPITALIZE)
+	inword = (SYNTAX (ch) == Sword);
+      o += CHAR_STRING (cased, o);
+    }
+  eassert (o - dst <= o_size);
+  obj = make_multibyte_string ((char *) dst, size, o - dst);
+  SAFE_FREE ();
+  return obj;
+}
+
+static Lisp_Object
+do_casify_unibyte_string (enum case_action flag, Lisp_Object obj)
 {
-  int c, c1;
+  ptrdiff_t i, size = SCHARS (obj);
   bool inword = flag == CASE_DOWN;
+  int ch, cased;
+
+  obj = Fcopy_sequence (obj);
+  for (i = 0; i < size; i++)
+    {
+      ch = SREF (obj, i);
+      MAKE_CHAR_MULTIBYTE (ch);
+      cased = ch;
+      if (inword && flag != CASE_CAPITALIZE_UP)
+	ch = downcase (ch);
+      else if (!uppercasep (ch)
+	       && (!inword || flag != CASE_CAPITALIZE_UP))
+	ch = upcase (cased);
+      if ((int) flag >= (int) CASE_CAPITALIZE)
+	inword = (SYNTAX (ch) == Sword);
+      if (ch == cased)
+	continue;
+      MAKE_CHAR_UNIBYTE (ch);
+      /* If the char can't be converted to a valid byte, just don't change it */
+      if (ch >= 0 && ch < 256)
+	SSET (obj, i, ch);
+    }
+  return obj;
+}
 
+static Lisp_Object
+casify_object (enum case_action flag, Lisp_Object obj)
+{
   /* If the case table is flagged as modified, rescan it.  */
   if (NILP (XCHAR_TABLE (BVAR (current_buffer, downcase_table))->extras[1]))
     Fset_case_table (BVAR (current_buffer, downcase_table));
 
   if (NATNUMP (obj))
-    {
-      int flagbits = (CHAR_ALT | CHAR_SUPER | CHAR_HYPER
-		      | CHAR_SHIFT | CHAR_CTL | CHAR_META);
-      int flags = XINT (obj) & flagbits;
-      bool multibyte = ! NILP (BVAR (current_buffer,
-				     enable_multibyte_characters));
-
-      /* If the character has higher bits set
-	 above the flags, return it unchanged.
-	 It is not a real character.  */
-      if (UNSIGNED_CMP (XFASTINT (obj), >, flagbits))
-	return obj;
-
-      c1 = XFASTINT (obj) & ~flagbits;
-      /* FIXME: Even if enable-multibyte-characters is nil, we may
-	 manipulate multibyte chars.  This means we have a bug for latin-1
-	 chars since when we receive an int 128-255 we can't tell whether
-	 it's an eight-bit byte or a latin-1 char.  */
-      if (c1 >= 256)
-	multibyte = 1;
-      if (! multibyte)
-	MAKE_CHAR_MULTIBYTE (c1);
-      c = flag == CASE_DOWN ? downcase (c1) : upcase (c1);
-      if (c != c1)
-	{
-	  if (! multibyte)
-	    MAKE_CHAR_UNIBYTE (c);
-	  XSETFASTINT (obj, c | flags);
-	}
-      return obj;
-    }
-
-  if (!STRINGP (obj))
+    return do_casify_natnum (flag, obj);
+  else if (!STRINGP (obj))
     wrong_type_argument (Qchar_or_string_p, obj);
-  else if (!STRING_MULTIBYTE (obj))
-    {
-      ptrdiff_t i;
-      ptrdiff_t size = SCHARS (obj);
-
-      obj = Fcopy_sequence (obj);
-      for (i = 0; i < size; i++)
-	{
-	  c = SREF (obj, i);
-	  MAKE_CHAR_MULTIBYTE (c);
-	  c1 = c;
-	  if (inword && flag != CASE_CAPITALIZE_UP)
-	    c = downcase (c);
-	  else if (!uppercasep (c)
-		   && (!inword || flag != CASE_CAPITALIZE_UP))
-	    c = upcase (c1);
-	  if ((int) flag >= (int) CASE_CAPITALIZE)
-	    inword = (SYNTAX (c) == Sword);
-	  if (c != c1)
-	    {
-	      MAKE_CHAR_UNIBYTE (c);
-	      /* If the char can't be converted to a valid byte, just don't
-		 change it.  */
-	      if (c >= 0 && c < 256)
-		SSET (obj, i, c);
-	    }
-	}
-      return obj;
-    }
+  else if (!SCHARS (obj))
+    return obj;
+  else if (STRING_MULTIBYTE (obj))
+    return do_casify_multibyte_string (flag, obj);
   else
-    {
-      ptrdiff_t i, i_byte, size = SCHARS (obj);
-      int len;
-      USE_SAFE_ALLOCA;
-      ptrdiff_t o_size;
-      if (INT_MULTIPLY_WRAPV (size, MAX_MULTIBYTE_LENGTH, &o_size))
-	o_size = PTRDIFF_MAX;
-      unsigned char *dst = SAFE_ALLOCA (o_size);
-      unsigned char *o = dst;
-
-      for (i = i_byte = 0; i < size; i++, i_byte += len)
-	{
-	  if (o_size - MAX_MULTIBYTE_LENGTH < o - dst)
-	    string_overflow ();
-	  c = STRING_CHAR_AND_LENGTH (SDATA (obj) + i_byte, len);
-	  if (inword && flag != CASE_CAPITALIZE_UP)
-	    c = downcase (c);
-	  else if (!inword || flag != CASE_CAPITALIZE_UP)
-	    c = upcase (c);
-	  if ((int) flag >= (int) CASE_CAPITALIZE)
-	    inword = (SYNTAX (c) == Sword);
-	  o += CHAR_STRING (c, o);
-	}
-      eassert (o - dst <= o_size);
-      obj = make_multibyte_string ((char *) dst, size, o - dst);
-      SAFE_FREE ();
-      return obj;
-    }
+    return do_casify_unibyte_string (flag, obj);
 }
 
 DEFUN ("upcase", Fupcase, Supcase, 1, 1, 0,
-- 
2.12.0.246.ga2ecc84866-goog






^ permalink raw reply related	[flat|nested] 89+ messages in thread

* bug#24603: [PATCHv5 02/11] Introduce case_character function
  2017-03-09 21:51 ` bug#24603: [PATCHv5 00/11] Casing improvements Michal Nazarewicz
  2017-03-09 21:51   ` bug#24603: [PATCHv5 01/11] Split casify_object into multiple functions Michal Nazarewicz
@ 2017-03-09 21:51   ` Michal Nazarewicz
  2017-03-09 21:51   ` bug#24603: [PATCHv5 03/11] Add support for title-casing letters (bug#24603) Michal Nazarewicz
                     ` (10 subsequent siblings)
  12 siblings, 0 replies; 89+ messages in thread
From: Michal Nazarewicz @ 2017-03-09 21:51 UTC (permalink / raw)
  To: 24603, eliz

Move single-character casing logic into a separate function so that
it is collected in a single place.  This will make future changes to
the logic easier.  This commit introduces no functionality changes.

* src/casefiddle.c (struct casing_context, prepare_casing_context): New
sturcture for saving casing context and function to initialise it.
(case_character): New function which cases character base on provided
context.
(do_casify_integer, do_casify_multibyte_string,
do_casify_unibyte_string, casify_object, casify_region): Convert to
use casing_context and case_character.
---
 src/casefiddle.c | 135 +++++++++++++++++++++++++++++++------------------------
 1 file changed, 77 insertions(+), 58 deletions(-)

diff --git a/src/casefiddle.c b/src/casefiddle.c
index e32910fa8aa..8129d376a5a 100644
--- a/src/casefiddle.c
+++ b/src/casefiddle.c
@@ -30,9 +30,56 @@ along with GNU Emacs.  If not, see <http://www.gnu.org/licenses/>.  */
 #include "keymap.h"
 
 enum case_action {CASE_UP, CASE_DOWN, CASE_CAPITALIZE, CASE_CAPITALIZE_UP};
+
+/* State for casing individual characters.  */
+struct casing_context {
+  /* User-requested action. */
+  enum case_action flag;
+  /* If true, function operates on a buffer as opposed to a string or character.
+     When run on a buffer, syntax_prefix_flag_p is taken into account when
+     determined inword flag. */
+  bool inbuffer;
+  /* Conceptually, this denotes whether we are inside of a word except
+     that if flag is CASE_UP it’s always false and if flag is CASE_DOWN
+     this is always true. */
+  bool inword;
+};
+
+/* Initialise CTX structure and prepares related global data for casing
+   characters. */
+static void
+prepare_casing_context (struct casing_context *ctx,
+			enum case_action flag, bool inbuffer)
+{
+  ctx->flag = flag;
+  ctx->inbuffer = inbuffer;
+  ctx->inword = flag == CASE_DOWN;
+
+  /* If the case table is flagged as modified, rescan it.  */
+  if (NILP (XCHAR_TABLE (BVAR (current_buffer, downcase_table))->extras[1]))
+    Fset_case_table (BVAR (current_buffer, downcase_table));
+
+  if (inbuffer && (int) flag >= (int) CASE_CAPITALIZE)
+    SETUP_BUFFER_SYNTAX_TABLE ();	/* For syntax_prefix_flag_p.  */
+}
+
+/* Based on CTX, case character CH accordingly.  Update CTX as necessary.
+   Return cased character. */
+static int
+case_character (struct casing_context *ctx, int ch)
+{
+  if (ctx->inword)
+    ch = ctx->flag == CASE_CAPITALIZE_UP ? ch : downcase (ch);
+  else
+    ch = upcase(ch);
+  if ((int) ctx->flag >= (int) CASE_CAPITALIZE)
+    ctx->inword = SYNTAX (ch) == Sword &&
+      (!ctx->inbuffer || ctx->inword || !syntax_prefix_flag_p (ch));
+  return ch;
+}
 \f
 static Lisp_Object
-do_casify_natnum (enum case_action flag, Lisp_Object obj)
+do_casify_natnum (struct casing_context *ctx, Lisp_Object obj)
 {
   int flagbits = (CHAR_ALT | CHAR_SUPER | CHAR_HYPER
 		  | CHAR_SHIFT | CHAR_CTL | CHAR_META);
@@ -55,7 +102,7 @@ do_casify_natnum (enum case_action flag, Lisp_Object obj)
 	       !NILP (BVAR (current_buffer, enable_multibyte_characters)));
   if (! multibyte)
     MAKE_CHAR_MULTIBYTE (ch);
-  cased = flag == CASE_DOWN ? downcase (ch) : upcase (ch);
+  cased = case_character (ctx, ch);
   if (cased == ch)
     return obj;
 
@@ -66,10 +113,9 @@ do_casify_natnum (enum case_action flag, Lisp_Object obj)
 }
 
 static Lisp_Object
-do_casify_multibyte_string (enum case_action flag, Lisp_Object obj)
+do_casify_multibyte_string (struct casing_context *ctx, Lisp_Object obj)
 {
   ptrdiff_t i, i_byte, size = SCHARS (obj);
-  bool inword = flag == CASE_DOWN;
   int len, ch, cased;
   USE_SAFE_ALLOCA;
   ptrdiff_t o_size;
@@ -83,14 +129,7 @@ do_casify_multibyte_string (enum case_action flag, Lisp_Object obj)
       if (o_size - MAX_MULTIBYTE_LENGTH < o - dst)
 	string_overflow ();
       ch = STRING_CHAR_AND_LENGTH (SDATA (obj) + i_byte, len);
-      if (inword && flag != CASE_CAPITALIZE_UP)
-	cased = downcase (ch);
-      else if (!inword || flag != CASE_CAPITALIZE_UP)
-	cased = upcase (ch);
-      else
-	cased = ch;
-      if ((int) flag >= (int) CASE_CAPITALIZE)
-	inword = (SYNTAX (ch) == Sword);
+      cased = case_character (ctx, ch);
       o += CHAR_STRING (cased, o);
     }
   eassert (o - dst <= o_size);
@@ -100,10 +139,9 @@ do_casify_multibyte_string (enum case_action flag, Lisp_Object obj)
 }
 
 static Lisp_Object
-do_casify_unibyte_string (enum case_action flag, Lisp_Object obj)
+do_casify_unibyte_string (struct casing_context *ctx, Lisp_Object obj)
 {
   ptrdiff_t i, size = SCHARS (obj);
-  bool inword = flag == CASE_DOWN;
   int ch, cased;
 
   obj = Fcopy_sequence (obj);
@@ -111,20 +149,13 @@ do_casify_unibyte_string (enum case_action flag, Lisp_Object obj)
     {
       ch = SREF (obj, i);
       MAKE_CHAR_MULTIBYTE (ch);
-      cased = ch;
-      if (inword && flag != CASE_CAPITALIZE_UP)
-	ch = downcase (ch);
-      else if (!uppercasep (ch)
-	       && (!inword || flag != CASE_CAPITALIZE_UP))
-	ch = upcase (cased);
-      if ((int) flag >= (int) CASE_CAPITALIZE)
-	inword = (SYNTAX (ch) == Sword);
+      cased = case_character (ctx, ch);
       if (ch == cased)
 	continue;
-      MAKE_CHAR_UNIBYTE (ch);
+      MAKE_CHAR_UNIBYTE (cased);
       /* If the char can't be converted to a valid byte, just don't change it */
-      if (ch >= 0 && ch < 256)
-	SSET (obj, i, ch);
+      if (cased >= 0 && cased < 256)
+	SSET (obj, i, cased);
     }
   return obj;
 }
@@ -132,20 +163,19 @@ do_casify_unibyte_string (enum case_action flag, Lisp_Object obj)
 static Lisp_Object
 casify_object (enum case_action flag, Lisp_Object obj)
 {
-  /* If the case table is flagged as modified, rescan it.  */
-  if (NILP (XCHAR_TABLE (BVAR (current_buffer, downcase_table))->extras[1]))
-    Fset_case_table (BVAR (current_buffer, downcase_table));
+  struct casing_context ctx;
+  prepare_casing_context (&ctx, flag, false);
 
   if (NATNUMP (obj))
-    return do_casify_natnum (flag, obj);
+    return do_casify_natnum (&ctx, obj);
   else if (!STRINGP (obj))
     wrong_type_argument (Qchar_or_string_p, obj);
   else if (!SCHARS (obj))
     return obj;
   else if (STRING_MULTIBYTE (obj))
-    return do_casify_multibyte_string (flag, obj);
+    return do_casify_multibyte_string (&ctx, obj);
   else
-    return do_casify_unibyte_string (flag, obj);
+    return do_casify_unibyte_string (&ctx, obj);
 }
 
 DEFUN ("upcase", Fupcase, Supcase, 1, 1, 0,
@@ -196,8 +226,6 @@ The argument object is not altered--the value is a copy.  */)
 static void
 casify_region (enum case_action flag, Lisp_Object b, Lisp_Object e)
 {
-  int c;
-  bool inword = flag == CASE_DOWN;
   bool multibyte = !NILP (BVAR (current_buffer, enable_multibyte_characters));
   ptrdiff_t start, end;
   ptrdiff_t start_byte;
@@ -208,14 +236,12 @@ casify_region (enum case_action flag, Lisp_Object b, Lisp_Object e)
   ptrdiff_t opoint = PT;
   ptrdiff_t opoint_byte = PT_BYTE;
 
+  struct casing_context ctx;
+
   if (EQ (b, e))
     /* Not modifying because nothing marked */
     return;
 
-  /* If the case table is flagged as modified, rescan it.  */
-  if (NILP (XCHAR_TABLE (BVAR (current_buffer, downcase_table))->extras[1]))
-    Fset_case_table (BVAR (current_buffer, downcase_table));
-
   validate_region (&b, &e);
   start = XFASTINT (b);
   end = XFASTINT (e);
@@ -223,32 +249,25 @@ casify_region (enum case_action flag, Lisp_Object b, Lisp_Object e)
   record_change (start, end - start);
   start_byte = CHAR_TO_BYTE (start);
 
-  SETUP_BUFFER_SYNTAX_TABLE ();	/* For syntax_prefix_flag_p.  */
+  prepare_casing_context (&ctx, flag, true);
 
   while (start < end)
     {
-      int c2, len;
+      int ch, cased, len;
 
       if (multibyte)
 	{
-	  c = FETCH_MULTIBYTE_CHAR (start_byte);
-	  len = CHAR_BYTES (c);
+	  ch = FETCH_MULTIBYTE_CHAR (start_byte);
+	  len = CHAR_BYTES (ch);
 	}
       else
 	{
-	  c = FETCH_BYTE (start_byte);
-	  MAKE_CHAR_MULTIBYTE (c);
+	  ch = FETCH_BYTE (start_byte);
+	  MAKE_CHAR_MULTIBYTE (ch);
 	  len = 1;
 	}
-      c2 = c;
-      if (inword && flag != CASE_CAPITALIZE_UP)
-	c = downcase (c);
-      else if (!inword || flag != CASE_CAPITALIZE_UP)
-	c = upcase (c);
-      if ((int) flag >= (int) CASE_CAPITALIZE)
-	inword = ((SYNTAX (c) == Sword)
-		  && (inword || !syntax_prefix_flag_p (c)));
-      if (c != c2)
+      cased = case_character (&ctx, ch);
+      if (ch != cased)
 	{
 	  last = start;
 	  if (first < 0)
@@ -256,18 +275,18 @@ casify_region (enum case_action flag, Lisp_Object b, Lisp_Object e)
 
 	  if (! multibyte)
 	    {
-	      MAKE_CHAR_UNIBYTE (c);
-	      FETCH_BYTE (start_byte) = c;
+	      MAKE_CHAR_UNIBYTE (cased);
+	      FETCH_BYTE (start_byte) = cased;
 	    }
-	  else if (ASCII_CHAR_P (c2) && ASCII_CHAR_P (c))
-	    FETCH_BYTE (start_byte) = c;
+	  else if (ASCII_CHAR_P (cased) && ASCII_CHAR_P (ch))
+	    FETCH_BYTE (start_byte) = cased;
 	  else
 	    {
-	      int tolen = CHAR_BYTES (c);
+	      int tolen = CHAR_BYTES (cased);
 	      int j;
 	      unsigned char str[MAX_MULTIBYTE_LENGTH];
 
-	      CHAR_STRING (c, str);
+	      CHAR_STRING (cased, str);
 	      if (len == tolen)
 		{
 		  /* Length is unchanged.  */
-- 
2.12.0.246.ga2ecc84866-goog






^ permalink raw reply related	[flat|nested] 89+ messages in thread

* bug#24603: [PATCHv5 03/11] Add support for title-casing letters (bug#24603)
  2017-03-09 21:51 ` bug#24603: [PATCHv5 00/11] Casing improvements Michal Nazarewicz
  2017-03-09 21:51   ` bug#24603: [PATCHv5 01/11] Split casify_object into multiple functions Michal Nazarewicz
  2017-03-09 21:51   ` bug#24603: [PATCHv5 02/11] Introduce case_character function Michal Nazarewicz
@ 2017-03-09 21:51   ` Michal Nazarewicz
  2017-03-11  9:03     ` Eli Zaretskii
  2017-03-09 21:51   ` bug#24603: [PATCHv5 04/11] Split up casify_region function (bug#24603) Michal Nazarewicz
                     ` (9 subsequent siblings)
  12 siblings, 1 reply; 89+ messages in thread
From: Michal Nazarewicz @ 2017-03-09 21:51 UTC (permalink / raw)
  To: 24603, eliz

* src/casefiddle.c (struct casing_context, prepare_casing_context): Add
titlecase_char_table member.  It’s set to the ‘titlecase’ Unicode
property table if capitalisation has been requested.
(case_character): Make use of the titlecase_char_table to title-case
initial characters when capitalising.

* test/src/casefiddle-tests.el (casefiddle-tests--characters,
casefiddle-tests-casing): Update test cases which are now passing.
---
 etc/NEWS                     |  2 +-
 src/casefiddle.c             | 27 +++++++++++++++++++++------
 test/src/casefiddle-tests.el | 39 ++++++++++++++++++++++++++-------------
 3 files changed, 48 insertions(+), 20 deletions(-)

diff --git a/etc/NEWS b/etc/NEWS
index 32137a79da6..715764accf1 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -351,7 +351,7 @@ same as in modes where the character is not whitespace.
 Instead of only checking the modification time, Emacs now also checks
 the file's actual content before prompting the user.
 
-** Title case characters are properly converted to upper case.
+** Title case characters are properly cased (from and into).
 'upcase', 'upcase-region' et al. convert title case characters (such
 as the single character "Dz") into their upper case form (such as "DZ").
 As a downside, 'capitalize' and 'upcase-initials' produce awkward
diff --git a/src/casefiddle.c b/src/casefiddle.c
index 8129d376a5a..01e35194e0e 100644
--- a/src/casefiddle.c
+++ b/src/casefiddle.c
@@ -33,6 +33,10 @@ enum case_action {CASE_UP, CASE_DOWN, CASE_CAPITALIZE, CASE_CAPITALIZE_UP};
 
 /* State for casing individual characters.  */
 struct casing_context {
+  /* A char-table with title-case character mappings or nil.  It being non-nil
+     implies flag being CASE_CAPITALIZE or CASE_CAPITALIZE_UP (but the reverse
+     is not true).  */
+  Lisp_Object titlecase_char_table;
   /* User-requested action. */
   enum case_action flag;
   /* If true, function operates on a buffer as opposed to a string or character.
@@ -54,6 +58,9 @@ prepare_casing_context (struct casing_context *ctx,
   ctx->flag = flag;
   ctx->inbuffer = inbuffer;
   ctx->inword = flag == CASE_DOWN;
+  ctx->titlecase_char_table = (int)flag >= (int)CASE_CAPITALIZE
+    ? uniprop_table (intern_c_string ("titlecase"))
+    : Qnil;
 
   /* If the case table is flagged as modified, rescan it.  */
   if (NILP (XCHAR_TABLE (BVAR (current_buffer, downcase_table))->extras[1]))
@@ -68,10 +75,16 @@ prepare_casing_context (struct casing_context *ctx,
 static int
 case_character (struct casing_context *ctx, int ch)
 {
+  Lisp_Object prop;
+
   if (ctx->inword)
     ch = ctx->flag == CASE_CAPITALIZE_UP ? ch : downcase (ch);
+  else if (!NILP (ctx->titlecase_char_table) &&
+	   CHARACTERP (prop = CHAR_TABLE_REF (ctx->titlecase_char_table, ch)))
+    ch = XFASTINT (prop);
   else
     ch = upcase(ch);
+
   if ((int) ctx->flag >= (int) CASE_CAPITALIZE)
     ctx->inword = SYNTAX (ch) == Sword &&
       (!ctx->inbuffer || ctx->inword || !syntax_prefix_flag_p (ch));
@@ -199,8 +212,8 @@ The argument object is not altered--the value is a copy.  */)
 
 DEFUN ("capitalize", Fcapitalize, Scapitalize, 1, 1, 0,
        doc: /* Convert argument to capitalized form and return that.
-This means that each word's first character is upper case
-and the rest is lower case.
+This means that each word's first character is upper case (more
+precisely, if available, title case) and the rest is lower case.
 The argument may be a character or string.  The result has the same type.
 The argument object is not altered--the value is a copy.  */)
   (Lisp_Object obj)
@@ -212,7 +225,8 @@ The argument object is not altered--the value is a copy.  */)
 
 DEFUN ("upcase-initials", Fupcase_initials, Supcase_initials, 1, 1, 0,
        doc: /* Convert the initial of each word in the argument to upper case.
-Do not change the other letters of each word.
+(More precisely, if available, initial of each word is converted to
+title-case).  Do not change the other letters of each word.
 The argument may be a character or string.  The result has the same type.
 The argument object is not altered--the value is a copy.  */)
   (Lisp_Object obj)
@@ -376,8 +390,8 @@ point and the mark is operated on.  */)
 
 DEFUN ("capitalize-region", Fcapitalize_region, Scapitalize_region, 2, 2, "r",
        doc: /* Convert the region to capitalized form.
-Capitalized form means each word's first character is upper case
-and the rest of it is lower case.
+Capitalized form means each word's first character is upper case (more
+precisely, if available, title case) and the rest of it is lower case.
 In programs, give two arguments, the starting and ending
 character positions to operate on.  */)
   (Lisp_Object beg, Lisp_Object end)
@@ -391,7 +405,8 @@ character positions to operate on.  */)
 DEFUN ("upcase-initials-region", Fupcase_initials_region,
        Supcase_initials_region, 2, 2, "r",
        doc: /* Upcase the initial of each word in the region.
-Subsequent letters of each word are not changed.
+(More precisely, if available, initial of each word is converted to
+title-case).  Subsequent letters of each word are not changed.
 In programs, give two arguments, the starting and ending
 character positions to operate on.  */)
   (Lisp_Object beg, Lisp_Object end)
diff --git a/test/src/casefiddle-tests.el b/test/src/casefiddle-tests.el
index 152d85de006..e83cb00059b 100644
--- a/test/src/casefiddle-tests.el
+++ b/test/src/casefiddle-tests.el
@@ -63,13 +63,9 @@ casefiddle-tests--characters
     (?Ł ?Ł ?ł ?Ł)
     (?ł ?Ł ?ł ?Ł)
 
-    ;; FIXME(bug#24603): Commented ones are what we want.
-    ;;(?DŽ ?DŽ ?dž ?Dž)
-    (?DŽ ?DŽ ?dž ?DŽ)
-    ;;(?Dž ?DŽ ?dž ?Dž)
-    (?Dž ?DŽ ?dž ?DŽ)
-    ;;(?dž ?DŽ ?dž ?Dž)
-    (?dž ?DŽ ?dž ?DŽ)
+    (?DŽ ?DŽ ?dž ?Dž)
+    (?Dž ?DŽ ?dž ?Dž)
+    (?dž ?DŽ ?dž ?Dž)
 
     (?Σ ?Σ ?σ ?Σ)
     (?σ ?Σ ?σ ?Σ)
@@ -186,19 +182,19 @@ casefiddle-tests--test-casing
       ;; input     upper     lower    capitalize up-initials
       '(("Foo baR" "FOO BAR" "foo bar" "Foo Bar" "Foo BaR")
         ("Ⅷ ⅷ" "Ⅷ Ⅷ" "ⅷ ⅷ" "Ⅷ Ⅷ" "Ⅷ Ⅷ")
+        ;; "DžUNGLA" is an unfortunate result but it’s really best we can
+        ;; do while still being consistent.  Hopefully, users only ever
+        ;; use upcase-initials on camelCase identifiers not real words.
+        ("DŽUNGLA" "DŽUNGLA" "džungla" "Džungla" "DžUNGLA")
+        ("Džungla" "DŽUNGLA" "džungla" "Džungla" "Džungla")
+        ("džungla" "DŽUNGLA" "džungla" "Džungla" "Džungla")
         ;; FIXME(bug#24603): Everything below is broken at the moment.
         ;; Here’s what should happen:
-        ;;("DŽUNGLA" "DŽUNGLA" "džungla" "Džungla" "DžUNGLA")
-        ;;("Džungla" "DŽUNGLA" "džungla" "Džungla" "Džungla")
-        ;;("džungla" "DŽUNGLA" "džungla" "Džungla" "Džungla")
         ;;("define" "DEFINE" "define" "Define" "Define")
         ;;("fish" "FIsh" "fish" "Fish" "Fish")
         ;;("Straße" "STRASSE" "straße" "Straße" "Straße")
         ;;("ΌΣΟΣ" "ΌΣΟΣ" "όσος" "Όσος" "Όσος")
         ;; And here’s what is actually happening:
-        ("DŽUNGLA" "DŽUNGLA" "džungla" "DŽungla" "DŽUNGLA")
-        ("Džungla" "DŽUNGLA" "džungla" "DŽungla" "DŽungla")
-        ("džungla" "DŽUNGLA" "džungla" "DŽungla" "DŽungla")
         ("define" "DEfiNE" "define" "Define" "Define")
         ("fish" "fiSH" "fish" "fish" "fish")
         ("Straße" "STRAßE" "straße" "Straße" "Straße")
@@ -243,4 +239,21 @@ casefiddle-tests--test-casing
                 "\xef\xff\xef Zażółć GĘŚlą \xcf\xcf")))))))
 
 
+(ert-deftest casefiddle-tests-char-casing ()
+  ;;             input upcase downcase [titlecase]
+  (dolist (test '((?a ?A ?a) (?A ?A ?a)
+                  (?ł ?Ł ?ł) (?Ł ?Ł ?ł)
+                  (?ß ?ß ?ß) (?ẞ ?ẞ ?ß)
+                  (?ⅷ ?Ⅷ ?ⅷ) (?Ⅷ ?Ⅷ ?ⅷ)
+                  (?DŽ ?DŽ ?dž ?Dž) (?Dž ?DŽ ?dž ?Dž) (?dž ?DŽ ?dž ?Dž)))
+    (let ((ch (car test))
+          (up (nth 1 test))
+          (lo (nth 2 test))
+          (tc (or (nth 3 test) (nth 1 test))))
+      (should (eq up (upcase ch)))
+      (should (eq lo (downcase ch)))
+      (should (eq tc (capitalize ch)))
+      (should (eq tc (upcase-initials ch))))))
+
+
 ;;; casefiddle-tests.el ends here
-- 
2.12.0.246.ga2ecc84866-goog






^ permalink raw reply related	[flat|nested] 89+ messages in thread

* bug#24603: [PATCHv5 04/11] Split up casify_region function (bug#24603)
  2017-03-09 21:51 ` bug#24603: [PATCHv5 00/11] Casing improvements Michal Nazarewicz
                     ` (2 preceding siblings ...)
  2017-03-09 21:51   ` bug#24603: [PATCHv5 03/11] Add support for title-casing letters (bug#24603) Michal Nazarewicz
@ 2017-03-09 21:51   ` Michal Nazarewicz
  2017-03-09 21:51   ` bug#24603: [PATCHv5 05/11] Support casing characters which map into multiple code points (bug#24603) Michal Nazarewicz
                     ` (8 subsequent siblings)
  12 siblings, 0 replies; 89+ messages in thread
From: Michal Nazarewicz @ 2017-03-09 21:51 UTC (permalink / raw)
  To: 24603, eliz

No functional changes at this time but splitting casify_region into
a function dealing with multibyte and another dealing with unibyte
buffers will make future code changes slightly easier.

* src/casefiddle.c (casify_region): Move most of the code into two
new functions:
(do_casify_multibyte_region, do_casify_unibyte_region): new functions.
---
 src/casefiddle.c | 159 ++++++++++++++++++++++++++++++-------------------------
 1 file changed, 86 insertions(+), 73 deletions(-)

diff --git a/src/casefiddle.c b/src/casefiddle.c
index 01e35194e0e..88da609b746 100644
--- a/src/casefiddle.c
+++ b/src/casefiddle.c
@@ -234,102 +234,115 @@ The argument object is not altered--the value is a copy.  */)
   return casify_object (CASE_CAPITALIZE_UP, obj);
 }
 \f
-/* flag is CASE_UP, CASE_DOWN or CASE_CAPITALIZE or CASE_CAPITALIZE_UP.
-   b and e specify range of buffer to operate on. */
-
-static void
-casify_region (enum case_action flag, Lisp_Object b, Lisp_Object e)
+/* Based on CTX, case region in a unibyte buffer from POS to *ENDP.  Return
+   first position that has changed and save last position in *ENDP.  If no
+   characters were changed, return -1 and *ENDP is unspecified. */
+static ptrdiff_t
+do_casify_unibyte_region (struct casing_context *ctx,
+			  ptrdiff_t pos, ptrdiff_t *endp)
 {
-  bool multibyte = !NILP (BVAR (current_buffer, enable_multibyte_characters));
-  ptrdiff_t start, end;
-  ptrdiff_t start_byte;
+  ptrdiff_t first = -1, last = -1;  /* Position of first and last changes. */
+  ptrdiff_t end = *endp;
+  int ch, cased;
 
-  /* Position of first and last changes.  */
-  ptrdiff_t first = -1, last;
+  for (; pos < end; ++pos)
+    {
+      ch = FETCH_BYTE (pos);
+      MAKE_CHAR_MULTIBYTE (ch);
 
-  ptrdiff_t opoint = PT;
-  ptrdiff_t opoint_byte = PT_BYTE;
+      cased = case_character (ctx, ch);
+      if (cased == ch)
+	continue;
 
-  struct casing_context ctx;
+      last = pos;
+      if (first < 0)
+	first = pos;
 
-  if (EQ (b, e))
-    /* Not modifying because nothing marked */
-    return;
+      MAKE_CHAR_UNIBYTE (cased);
+      FETCH_BYTE (pos) = cased;
+    }
 
-  validate_region (&b, &e);
-  start = XFASTINT (b);
-  end = XFASTINT (e);
-  modify_text (start, end);
-  record_change (start, end - start);
-  start_byte = CHAR_TO_BYTE (start);
+  *endp = last + 1;
+  return first;
+}
 
-  prepare_casing_context (&ctx, flag, true);
+/* Based on CTX, case region in a multibyte buffer from POS to *ENDP.  Return
+   first position that has changed and save last position in *ENDP.  If no
+   characters were changed, return -1 and *ENDP is unspecified. */
+static ptrdiff_t
+do_casify_multibyte_region (struct casing_context *ctx,
+                           ptrdiff_t pos, ptrdiff_t *endp)
+{
+  ptrdiff_t first = -1, last = -1;  /* Position of first and last changes. */
+  ptrdiff_t pos_byte = CHAR_TO_BYTE (pos), end = *endp;
+  ptrdiff_t opoint = PT;
+  int ch, cased, len;
 
-  while (start < end)
+  while (pos < end)
     {
-      int ch, cased, len;
-
-      if (multibyte)
-	{
-	  ch = FETCH_MULTIBYTE_CHAR (start_byte);
-	  len = CHAR_BYTES (ch);
-	}
-      else
-	{
-	  ch = FETCH_BYTE (start_byte);
-	  MAKE_CHAR_MULTIBYTE (ch);
-	  len = 1;
-	}
-      cased = case_character (&ctx, ch);
-      if (ch != cased)
+      ch = STRING_CHAR_AND_LENGTH (BYTE_POS_ADDR (pos_byte), len);
+      cased = case_character (ctx, ch);
+      if (cased != ch)
 	{
-	  last = start;
+	  last = pos;
 	  if (first < 0)
-	    first = start;
+	    first = pos;
 
-	  if (! multibyte)
-	    {
-	      MAKE_CHAR_UNIBYTE (cased);
-	      FETCH_BYTE (start_byte) = cased;
-	    }
-	  else if (ASCII_CHAR_P (cased) && ASCII_CHAR_P (ch))
-	    FETCH_BYTE (start_byte) = cased;
+	  if (ASCII_CHAR_P (cased) && ASCII_CHAR_P (ch))
+	    FETCH_BYTE (pos_byte) = cased;
 	  else
 	    {
-	      int tolen = CHAR_BYTES (cased);
-	      int j;
 	      unsigned char str[MAX_MULTIBYTE_LENGTH];
-
-	      CHAR_STRING (cased, str);
-	      if (len == tolen)
-		{
-		  /* Length is unchanged.  */
-		  for (j = 0; j < len; ++j)
-		    FETCH_BYTE (start_byte + j) = str[j];
-		}
+	      int totlen = CHAR_STRING (cased, str);
+	      if (len == totlen)
+		memcpy (BYTE_POS_ADDR (pos_byte), str, len);
 	      else
-		{
-		  /* Replace one character with the other,
-		     keeping text properties the same.  */
-		  replace_range_2 (start, start_byte,
-				   start + 1, start_byte + len,
-				   (char *) str, 1, tolen,
-				   0);
-		  len = tolen;
-		}
+		/* Replace one character with the other(s), keeping text
+		   properties the same.  */
+		replace_range_2 (pos, pos_byte, pos + 1, pos_byte + len,
+				 (char *) str, 9, totlen, 0);
+	      len = totlen;
 	    }
 	}
-      start++;
-      start_byte += len;
+      pos++;
+      pos_byte += len;
     }
 
   if (PT != opoint)
-    TEMP_SET_PT_BOTH (opoint, opoint_byte);
+    TEMP_SET_PT_BOTH (opoint, CHAR_TO_BYTE (opoint));
+
+  *endp = last;
+  return first;
+}
+
+/* flag is CASE_UP, CASE_DOWN or CASE_CAPITALIZE or CASE_CAPITALIZE_UP.
+   b and e specify range of buffer to operate on. */
+static void
+casify_region (enum case_action flag, Lisp_Object b, Lisp_Object e)
+{
+  struct casing_context ctx;
+  ptrdiff_t start, end;
+
+  if (EQ (b, e))
+    /* Not modifying because nothing marked */
+    return;
+
+  validate_region (&b, &e);
+  start = XFASTINT (b);
+  end = XFASTINT (e);
+  modify_text (start, end);
+  record_change (start, end - start);
+  prepare_casing_context (&ctx, flag, true);
+
+  if (NILP (BVAR (current_buffer, enable_multibyte_characters)))
+    start = do_casify_unibyte_region (&ctx, start, &end);
+  else
+    start = do_casify_multibyte_region (&ctx, start, &end);
 
-  if (first >= 0)
+  if (start >= 0)
     {
-      signal_after_change (first, last + 1 - first, last + 1 - first);
-      update_compositions (first, last + 1, CHECK_ALL);
+      signal_after_change (start, end + 1 - start, end + 1 - start);
+      update_compositions (start, end + 1, CHECK_ALL);
     }
 }
 
-- 
2.12.0.246.ga2ecc84866-goog






^ permalink raw reply related	[flat|nested] 89+ messages in thread

* bug#24603: [PATCHv5 05/11] Support casing characters which map into multiple code points (bug#24603)
  2017-03-09 21:51 ` bug#24603: [PATCHv5 00/11] Casing improvements Michal Nazarewicz
                     ` (3 preceding siblings ...)
  2017-03-09 21:51   ` bug#24603: [PATCHv5 04/11] Split up casify_region function (bug#24603) Michal Nazarewicz
@ 2017-03-09 21:51   ` Michal Nazarewicz
  2017-03-11  9:14     ` Eli Zaretskii
  2017-03-09 21:51   ` bug#24603: [PATCHv5 06/11] Implement special sigma casing rule (bug#24603) Michal Nazarewicz
                     ` (7 subsequent siblings)
  12 siblings, 1 reply; 89+ messages in thread
From: Michal Nazarewicz @ 2017-03-09 21:51 UTC (permalink / raw)
  To: 24603, eliz

Implement unconditional special casing rules defined in Unicode standard.

Among other things, they deal with cases when a single code point is
replaced by multiple ones because single character does not exist (e.g.
‘fi’ ligature turning into ‘FL’) or is not commonly used (e.g. ß turning
into SS).

* admin/unidata/SpecialCasing.txt: New data file pulled from Unicode
standard distribution.
* admin/unidata/README: Mention SpecialCasing.txt.

* admin/unidata/unidata-get.el (unidata-gen-table-special-casing): New
function for generating ‘special-casing’ character Unicode property
built from the SpecialCasing.txt Unicode data file.

* src/casefiddle.c (struct casing_str_buf): New structure for
representing short strings used to handle one-to-many character
mappings.

(case_character_imlp): New function which can handle one-to-many
character mappings.
(case_character, case_single_character): Wrappers for the above
functions.  The former may map one character to multiple (or no)
code points while the latter does what the former used to do (i.e.
handles one-to-one mappings only).

(do_casify_natnum, do_casify_unibyte_string,
do_casify_unibyte_region): Use case_single_character.
(do_casify_multibyte_string, do_casify_multibyte_region): Support new
features of case_character.
* (do_casify_region): Updated to reflact do_casify_multibyte_string
changes.

(casify_word): Handle situation when one character-length of a word
can change affecting where end of the word is.

(upcase, capitalize, upcase-initials): Update documentation to mention
limitations when working on characters.

* test/src/casefiddle-tests.el (casefiddle-tests-casing): Update test
cases which are now passing.

* test/lisp/char-fold-tests.el (char-fold--ascii-upcase,
char-fold--ascii-downcase): New functions which behave like old ‘upcase’
and ‘downcase’.
(char-fold--test-match-exactly): Use the new functions.  This is needed
because otherwise fi and similar characters are turned into their multi-
-character representation.

* doc/lispref/strings.texi: Describe issue with casing characters versus
strings.
---
 admin/unidata/README            |   4 +
 admin/unidata/SpecialCasing.txt | 281 ++++++++++++++++++++++++++++++++++++
 admin/unidata/unidata-gen.el    |  40 ++++++
 doc/lispref/strings.texi        |  23 +++
 etc/NEWS                        |  17 ++-
 src/casefiddle.c                | 305 +++++++++++++++++++++++++++++-----------
 test/lisp/char-fold-tests.el    |  12 +-
 test/src/casefiddle-tests.el    |   9 +-
 8 files changed, 592 insertions(+), 99 deletions(-)
 create mode 100644 admin/unidata/SpecialCasing.txt

diff --git a/admin/unidata/README b/admin/unidata/README
index 534670ce6db..06a66663a72 100644
--- a/admin/unidata/README
+++ b/admin/unidata/README
@@ -24,3 +24,7 @@ http://www.unicode.org/Public/8.0.0/ucd/Blocks.txt
 NormalizationTest.txt
 http://www.unicode.org/Public/UNIDATA/NormalizationTest.txt
 2016-07-16
+
+SpecialCasing.txt
+http://unicode.org/Public/UNIDATA/SpecialCasing.txt
+2016-03-03
diff --git a/admin/unidata/SpecialCasing.txt b/admin/unidata/SpecialCasing.txt
new file mode 100644
index 00000000000..b23fa7f7680
--- /dev/null
+++ b/admin/unidata/SpecialCasing.txt
@@ -0,0 +1,281 @@
+# SpecialCasing-9.0.0.txt
+# Date: 2016-03-02, 18:55:13 GMT
+# © 2016 Unicode®, Inc.
+# Unicode and the Unicode Logo are registered trademarks of Unicode, Inc. in the U.S. and other countries.
+# For terms of use, see http://www.unicode.org/terms_of_use.html
+#
+# Unicode Character Database
+#   For documentation, see http://www.unicode.org/reports/tr44/
+#
+# Special Casing
+#
+# This file is a supplement to the UnicodeData.txt file. It does not define any
+# properties, but rather provides additional information about the casing of
+# Unicode characters, for situations when casing incurs a change in string length
+# or is dependent on context or locale. For compatibility, the UnicodeData.txt
+# file only contains simple case mappings for characters where they are one-to-one
+# and independent of context and language. The data in this file, combined with
+# the simple case mappings in UnicodeData.txt, defines the full case mappings
+# Lowercase_Mapping (lc), Titlecase_Mapping (tc), and Uppercase_Mapping (uc).
+#
+# Note that the preferred mechanism for defining tailored casing operations is
+# the Unicode Common Locale Data Repository (CLDR). For more information, see the
+# discussion of case mappings and case algorithms in the Unicode Standard.
+#
+# All code points not listed in this file that do not have a simple case mappings
+# in UnicodeData.txt map to themselves.
+# ================================================================================
+# Format
+# ================================================================================
+# The entries in this file are in the following machine-readable format:
+#
+# <code>; <lower>; <title>; <upper>; (<condition_list>;)? # <comment>
+#
+# <code>, <lower>, <title>, and <upper> provide the respective full case mappings
+# of <code>, expressed as character values in hex. If there is more than one character,
+# they are separated by spaces. Other than as used to separate elements, spaces are
+# to be ignored.
+#
+# The <condition_list> is optional. Where present, it consists of one or more language IDs
+# or casing contexts, separated by spaces. In these conditions:
+# - A condition list overrides the normal behavior if all of the listed conditions are true.
+# - The casing context is always the context of the characters in the original string,
+#   NOT in the resulting string.
+# - Case distinctions in the condition list are not significant.
+# - Conditions preceded by "Not_" represent the negation of the condition.
+# The condition list is not represented in the UCD as a formal property.
+#
+# A language ID is defined by BCP 47, with '-' and '_' treated equivalently.
+#
+# A casing context for a character is defined by Section 3.13 Default Case Algorithms
+# of The Unicode Standard.
+#
+# Parsers of this file must be prepared to deal with future additions to this format:
+#  * Additional contexts
+#  * Additional fields
+# ================================================================================
+
+# ================================================================================
+# Unconditional mappings
+# ================================================================================
+
+# The German es-zed is special--the normal mapping is to SS.
+# Note: the titlecase should never occur in practice. It is equal to titlecase(uppercase(<es-zed>))
+
+00DF; 00DF; 0053 0073; 0053 0053; # LATIN SMALL LETTER SHARP S
+
+# Preserve canonical equivalence for I with dot. Turkic is handled below.
+
+0130; 0069 0307; 0130; 0130; # LATIN CAPITAL LETTER I WITH DOT ABOVE
+
+# Ligatures
+
+FB00; FB00; 0046 0066; 0046 0046; # LATIN SMALL LIGATURE FF
+FB01; FB01; 0046 0069; 0046 0049; # LATIN SMALL LIGATURE FI
+FB02; FB02; 0046 006C; 0046 004C; # LATIN SMALL LIGATURE FL
+FB03; FB03; 0046 0066 0069; 0046 0046 0049; # LATIN SMALL LIGATURE FFI
+FB04; FB04; 0046 0066 006C; 0046 0046 004C; # LATIN SMALL LIGATURE FFL
+FB05; FB05; 0053 0074; 0053 0054; # LATIN SMALL LIGATURE LONG S T
+FB06; FB06; 0053 0074; 0053 0054; # LATIN SMALL LIGATURE ST
+
+0587; 0587; 0535 0582; 0535 0552; # ARMENIAN SMALL LIGATURE ECH YIWN
+FB13; FB13; 0544 0576; 0544 0546; # ARMENIAN SMALL LIGATURE MEN NOW
+FB14; FB14; 0544 0565; 0544 0535; # ARMENIAN SMALL LIGATURE MEN ECH
+FB15; FB15; 0544 056B; 0544 053B; # ARMENIAN SMALL LIGATURE MEN INI
+FB16; FB16; 054E 0576; 054E 0546; # ARMENIAN SMALL LIGATURE VEW NOW
+FB17; FB17; 0544 056D; 0544 053D; # ARMENIAN SMALL LIGATURE MEN XEH
+
+# No corresponding uppercase precomposed character
+
+0149; 0149; 02BC 004E; 02BC 004E; # LATIN SMALL LETTER N PRECEDED BY APOSTROPHE
+0390; 0390; 0399 0308 0301; 0399 0308 0301; # GREEK SMALL LETTER IOTA WITH DIALYTIKA AND TONOS
+03B0; 03B0; 03A5 0308 0301; 03A5 0308 0301; # GREEK SMALL LETTER UPSILON WITH DIALYTIKA AND TONOS
+01F0; 01F0; 004A 030C; 004A 030C; # LATIN SMALL LETTER J WITH CARON
+1E96; 1E96; 0048 0331; 0048 0331; # LATIN SMALL LETTER H WITH LINE BELOW
+1E97; 1E97; 0054 0308; 0054 0308; # LATIN SMALL LETTER T WITH DIAERESIS
+1E98; 1E98; 0057 030A; 0057 030A; # LATIN SMALL LETTER W WITH RING ABOVE
+1E99; 1E99; 0059 030A; 0059 030A; # LATIN SMALL LETTER Y WITH RING ABOVE
+1E9A; 1E9A; 0041 02BE; 0041 02BE; # LATIN SMALL LETTER A WITH RIGHT HALF RING
+1F50; 1F50; 03A5 0313; 03A5 0313; # GREEK SMALL LETTER UPSILON WITH PSILI
+1F52; 1F52; 03A5 0313 0300; 03A5 0313 0300; # GREEK SMALL LETTER UPSILON WITH PSILI AND VARIA
+1F54; 1F54; 03A5 0313 0301; 03A5 0313 0301; # GREEK SMALL LETTER UPSILON WITH PSILI AND OXIA
+1F56; 1F56; 03A5 0313 0342; 03A5 0313 0342; # GREEK SMALL LETTER UPSILON WITH PSILI AND PERISPOMENI
+1FB6; 1FB6; 0391 0342; 0391 0342; # GREEK SMALL LETTER ALPHA WITH PERISPOMENI
+1FC6; 1FC6; 0397 0342; 0397 0342; # GREEK SMALL LETTER ETA WITH PERISPOMENI
+1FD2; 1FD2; 0399 0308 0300; 0399 0308 0300; # GREEK SMALL LETTER IOTA WITH DIALYTIKA AND VARIA
+1FD3; 1FD3; 0399 0308 0301; 0399 0308 0301; # GREEK SMALL LETTER IOTA WITH DIALYTIKA AND OXIA
+1FD6; 1FD6; 0399 0342; 0399 0342; # GREEK SMALL LETTER IOTA WITH PERISPOMENI
+1FD7; 1FD7; 0399 0308 0342; 0399 0308 0342; # GREEK SMALL LETTER IOTA WITH DIALYTIKA AND PERISPOMENI
+1FE2; 1FE2; 03A5 0308 0300; 03A5 0308 0300; # GREEK SMALL LETTER UPSILON WITH DIALYTIKA AND VARIA
+1FE3; 1FE3; 03A5 0308 0301; 03A5 0308 0301; # GREEK SMALL LETTER UPSILON WITH DIALYTIKA AND OXIA
+1FE4; 1FE4; 03A1 0313; 03A1 0313; # GREEK SMALL LETTER RHO WITH PSILI
+1FE6; 1FE6; 03A5 0342; 03A5 0342; # GREEK SMALL LETTER UPSILON WITH PERISPOMENI
+1FE7; 1FE7; 03A5 0308 0342; 03A5 0308 0342; # GREEK SMALL LETTER UPSILON WITH DIALYTIKA AND PERISPOMENI
+1FF6; 1FF6; 03A9 0342; 03A9 0342; # GREEK SMALL LETTER OMEGA WITH PERISPOMENI
+
+# IMPORTANT-when iota-subscript (0345) is uppercased or titlecased,
+#  the result will be incorrect unless the iota-subscript is moved to the end
+#  of any sequence of combining marks. Otherwise, the accents will go on the capital iota.
+#  This process can be achieved by first transforming the text to NFC before casing.
+#  E.g. <alpha><iota_subscript><acute> is uppercased to <ALPHA><acute><IOTA>
+
+# The following cases are already in the UnicodeData.txt file, so are only commented here.
+
+# 0345; 0345; 0345; 0399; # COMBINING GREEK YPOGEGRAMMENI
+
+# All letters with YPOGEGRAMMENI (iota-subscript) or PROSGEGRAMMENI (iota adscript)
+# have special uppercases.
+# Note: characters with PROSGEGRAMMENI are actually titlecase, not uppercase!
+
+1F80; 1F80; 1F88; 1F08 0399; # GREEK SMALL LETTER ALPHA WITH PSILI AND YPOGEGRAMMENI
+1F81; 1F81; 1F89; 1F09 0399; # GREEK SMALL LETTER ALPHA WITH DASIA AND YPOGEGRAMMENI
+1F82; 1F82; 1F8A; 1F0A 0399; # GREEK SMALL LETTER ALPHA WITH PSILI AND VARIA AND YPOGEGRAMMENI
+1F83; 1F83; 1F8B; 1F0B 0399; # GREEK SMALL LETTER ALPHA WITH DASIA AND VARIA AND YPOGEGRAMMENI
+1F84; 1F84; 1F8C; 1F0C 0399; # GREEK SMALL LETTER ALPHA WITH PSILI AND OXIA AND YPOGEGRAMMENI
+1F85; 1F85; 1F8D; 1F0D 0399; # GREEK SMALL LETTER ALPHA WITH DASIA AND OXIA AND YPOGEGRAMMENI
+1F86; 1F86; 1F8E; 1F0E 0399; # GREEK SMALL LETTER ALPHA WITH PSILI AND PERISPOMENI AND YPOGEGRAMMENI
+1F87; 1F87; 1F8F; 1F0F 0399; # GREEK SMALL LETTER ALPHA WITH DASIA AND PERISPOMENI AND YPOGEGRAMMENI
+1F88; 1F80; 1F88; 1F08 0399; # GREEK CAPITAL LETTER ALPHA WITH PSILI AND PROSGEGRAMMENI
+1F89; 1F81; 1F89; 1F09 0399; # GREEK CAPITAL LETTER ALPHA WITH DASIA AND PROSGEGRAMMENI
+1F8A; 1F82; 1F8A; 1F0A 0399; # GREEK CAPITAL LETTER ALPHA WITH PSILI AND VARIA AND PROSGEGRAMMENI
+1F8B; 1F83; 1F8B; 1F0B 0399; # GREEK CAPITAL LETTER ALPHA WITH DASIA AND VARIA AND PROSGEGRAMMENI
+1F8C; 1F84; 1F8C; 1F0C 0399; # GREEK CAPITAL LETTER ALPHA WITH PSILI AND OXIA AND PROSGEGRAMMENI
+1F8D; 1F85; 1F8D; 1F0D 0399; # GREEK CAPITAL LETTER ALPHA WITH DASIA AND OXIA AND PROSGEGRAMMENI
+1F8E; 1F86; 1F8E; 1F0E 0399; # GREEK CAPITAL LETTER ALPHA WITH PSILI AND PERISPOMENI AND PROSGEGRAMMENI
+1F8F; 1F87; 1F8F; 1F0F 0399; # GREEK CAPITAL LETTER ALPHA WITH DASIA AND PERISPOMENI AND PROSGEGRAMMENI
+1F90; 1F90; 1F98; 1F28 0399; # GREEK SMALL LETTER ETA WITH PSILI AND YPOGEGRAMMENI
+1F91; 1F91; 1F99; 1F29 0399; # GREEK SMALL LETTER ETA WITH DASIA AND YPOGEGRAMMENI
+1F92; 1F92; 1F9A; 1F2A 0399; # GREEK SMALL LETTER ETA WITH PSILI AND VARIA AND YPOGEGRAMMENI
+1F93; 1F93; 1F9B; 1F2B 0399; # GREEK SMALL LETTER ETA WITH DASIA AND VARIA AND YPOGEGRAMMENI
+1F94; 1F94; 1F9C; 1F2C 0399; # GREEK SMALL LETTER ETA WITH PSILI AND OXIA AND YPOGEGRAMMENI
+1F95; 1F95; 1F9D; 1F2D 0399; # GREEK SMALL LETTER ETA WITH DASIA AND OXIA AND YPOGEGRAMMENI
+1F96; 1F96; 1F9E; 1F2E 0399; # GREEK SMALL LETTER ETA WITH PSILI AND PERISPOMENI AND YPOGEGRAMMENI
+1F97; 1F97; 1F9F; 1F2F 0399; # GREEK SMALL LETTER ETA WITH DASIA AND PERISPOMENI AND YPOGEGRAMMENI
+1F98; 1F90; 1F98; 1F28 0399; # GREEK CAPITAL LETTER ETA WITH PSILI AND PROSGEGRAMMENI
+1F99; 1F91; 1F99; 1F29 0399; # GREEK CAPITAL LETTER ETA WITH DASIA AND PROSGEGRAMMENI
+1F9A; 1F92; 1F9A; 1F2A 0399; # GREEK CAPITAL LETTER ETA WITH PSILI AND VARIA AND PROSGEGRAMMENI
+1F9B; 1F93; 1F9B; 1F2B 0399; # GREEK CAPITAL LETTER ETA WITH DASIA AND VARIA AND PROSGEGRAMMENI
+1F9C; 1F94; 1F9C; 1F2C 0399; # GREEK CAPITAL LETTER ETA WITH PSILI AND OXIA AND PROSGEGRAMMENI
+1F9D; 1F95; 1F9D; 1F2D 0399; # GREEK CAPITAL LETTER ETA WITH DASIA AND OXIA AND PROSGEGRAMMENI
+1F9E; 1F96; 1F9E; 1F2E 0399; # GREEK CAPITAL LETTER ETA WITH PSILI AND PERISPOMENI AND PROSGEGRAMMENI
+1F9F; 1F97; 1F9F; 1F2F 0399; # GREEK CAPITAL LETTER ETA WITH DASIA AND PERISPOMENI AND PROSGEGRAMMENI
+1FA0; 1FA0; 1FA8; 1F68 0399; # GREEK SMALL LETTER OMEGA WITH PSILI AND YPOGEGRAMMENI
+1FA1; 1FA1; 1FA9; 1F69 0399; # GREEK SMALL LETTER OMEGA WITH DASIA AND YPOGEGRAMMENI
+1FA2; 1FA2; 1FAA; 1F6A 0399; # GREEK SMALL LETTER OMEGA WITH PSILI AND VARIA AND YPOGEGRAMMENI
+1FA3; 1FA3; 1FAB; 1F6B 0399; # GREEK SMALL LETTER OMEGA WITH DASIA AND VARIA AND YPOGEGRAMMENI
+1FA4; 1FA4; 1FAC; 1F6C 0399; # GREEK SMALL LETTER OMEGA WITH PSILI AND OXIA AND YPOGEGRAMMENI
+1FA5; 1FA5; 1FAD; 1F6D 0399; # GREEK SMALL LETTER OMEGA WITH DASIA AND OXIA AND YPOGEGRAMMENI
+1FA6; 1FA6; 1FAE; 1F6E 0399; # GREEK SMALL LETTER OMEGA WITH PSILI AND PERISPOMENI AND YPOGEGRAMMENI
+1FA7; 1FA7; 1FAF; 1F6F 0399; # GREEK SMALL LETTER OMEGA WITH DASIA AND PERISPOMENI AND YPOGEGRAMMENI
+1FA8; 1FA0; 1FA8; 1F68 0399; # GREEK CAPITAL LETTER OMEGA WITH PSILI AND PROSGEGRAMMENI
+1FA9; 1FA1; 1FA9; 1F69 0399; # GREEK CAPITAL LETTER OMEGA WITH DASIA AND PROSGEGRAMMENI
+1FAA; 1FA2; 1FAA; 1F6A 0399; # GREEK CAPITAL LETTER OMEGA WITH PSILI AND VARIA AND PROSGEGRAMMENI
+1FAB; 1FA3; 1FAB; 1F6B 0399; # GREEK CAPITAL LETTER OMEGA WITH DASIA AND VARIA AND PROSGEGRAMMENI
+1FAC; 1FA4; 1FAC; 1F6C 0399; # GREEK CAPITAL LETTER OMEGA WITH PSILI AND OXIA AND PROSGEGRAMMENI
+1FAD; 1FA5; 1FAD; 1F6D 0399; # GREEK CAPITAL LETTER OMEGA WITH DASIA AND OXIA AND PROSGEGRAMMENI
+1FAE; 1FA6; 1FAE; 1F6E 0399; # GREEK CAPITAL LETTER OMEGA WITH PSILI AND PERISPOMENI AND PROSGEGRAMMENI
+1FAF; 1FA7; 1FAF; 1F6F 0399; # GREEK CAPITAL LETTER OMEGA WITH DASIA AND PERISPOMENI AND PROSGEGRAMMENI
+1FB3; 1FB3; 1FBC; 0391 0399; # GREEK SMALL LETTER ALPHA WITH YPOGEGRAMMENI
+1FBC; 1FB3; 1FBC; 0391 0399; # GREEK CAPITAL LETTER ALPHA WITH PROSGEGRAMMENI
+1FC3; 1FC3; 1FCC; 0397 0399; # GREEK SMALL LETTER ETA WITH YPOGEGRAMMENI
+1FCC; 1FC3; 1FCC; 0397 0399; # GREEK CAPITAL LETTER ETA WITH PROSGEGRAMMENI
+1FF3; 1FF3; 1FFC; 03A9 0399; # GREEK SMALL LETTER OMEGA WITH YPOGEGRAMMENI
+1FFC; 1FF3; 1FFC; 03A9 0399; # GREEK CAPITAL LETTER OMEGA WITH PROSGEGRAMMENI
+
+# Some characters with YPOGEGRAMMENI also have no corresponding titlecases
+
+1FB2; 1FB2; 1FBA 0345; 1FBA 0399; # GREEK SMALL LETTER ALPHA WITH VARIA AND YPOGEGRAMMENI
+1FB4; 1FB4; 0386 0345; 0386 0399; # GREEK SMALL LETTER ALPHA WITH OXIA AND YPOGEGRAMMENI
+1FC2; 1FC2; 1FCA 0345; 1FCA 0399; # GREEK SMALL LETTER ETA WITH VARIA AND YPOGEGRAMMENI
+1FC4; 1FC4; 0389 0345; 0389 0399; # GREEK SMALL LETTER ETA WITH OXIA AND YPOGEGRAMMENI
+1FF2; 1FF2; 1FFA 0345; 1FFA 0399; # GREEK SMALL LETTER OMEGA WITH VARIA AND YPOGEGRAMMENI
+1FF4; 1FF4; 038F 0345; 038F 0399; # GREEK SMALL LETTER OMEGA WITH OXIA AND YPOGEGRAMMENI
+
+1FB7; 1FB7; 0391 0342 0345; 0391 0342 0399; # GREEK SMALL LETTER ALPHA WITH PERISPOMENI AND YPOGEGRAMMENI
+1FC7; 1FC7; 0397 0342 0345; 0397 0342 0399; # GREEK SMALL LETTER ETA WITH PERISPOMENI AND YPOGEGRAMMENI
+1FF7; 1FF7; 03A9 0342 0345; 03A9 0342 0399; # GREEK SMALL LETTER OMEGA WITH PERISPOMENI AND YPOGEGRAMMENI
+
+# ================================================================================
+# Conditional Mappings
+# The remainder of this file provides conditional casing data used to produce 
+# full case mappings.
+# ================================================================================
+# Language-Insensitive Mappings
+# These are characters whose full case mappings do not depend on language, but do
+# depend on context (which characters come before or after). For more information
+# see the header of this file and the Unicode Standard.
+# ================================================================================
+
+# Special case for final form of sigma
+
+03A3; 03C2; 03A3; 03A3; Final_Sigma; # GREEK CAPITAL LETTER SIGMA
+
+# Note: the following cases for non-final are already in the UnicodeData.txt file.
+
+# 03A3; 03C3; 03A3; 03A3; # GREEK CAPITAL LETTER SIGMA
+# 03C3; 03C3; 03A3; 03A3; # GREEK SMALL LETTER SIGMA
+# 03C2; 03C2; 03A3; 03A3; # GREEK SMALL LETTER FINAL SIGMA
+
+# Note: the following cases are not included, since they would case-fold in lowercasing
+
+# 03C3; 03C2; 03A3; 03A3; Final_Sigma; # GREEK SMALL LETTER SIGMA
+# 03C2; 03C3; 03A3; 03A3; Not_Final_Sigma; # GREEK SMALL LETTER FINAL SIGMA
+
+# ================================================================================
+# Language-Sensitive Mappings
+# These are characters whose full case mappings depend on language and perhaps also
+# context (which characters come before or after). For more information
+# see the header of this file and the Unicode Standard.
+# ================================================================================
+
+# Lithuanian
+
+# Lithuanian retains the dot in a lowercase i when followed by accents.
+
+# Remove DOT ABOVE after "i" with upper or titlecase
+
+0307; 0307; ; ; lt After_Soft_Dotted; # COMBINING DOT ABOVE
+
+# Introduce an explicit dot above when lowercasing capital I's and J's
+# whenever there are more accents above.
+# (of the accents used in Lithuanian: grave, acute, tilde above, and ogonek)
+
+0049; 0069 0307; 0049; 0049; lt More_Above; # LATIN CAPITAL LETTER I
+004A; 006A 0307; 004A; 004A; lt More_Above; # LATIN CAPITAL LETTER J
+012E; 012F 0307; 012E; 012E; lt More_Above; # LATIN CAPITAL LETTER I WITH OGONEK
+00CC; 0069 0307 0300; 00CC; 00CC; lt; # LATIN CAPITAL LETTER I WITH GRAVE
+00CD; 0069 0307 0301; 00CD; 00CD; lt; # LATIN CAPITAL LETTER I WITH ACUTE
+0128; 0069 0307 0303; 0128; 0128; lt; # LATIN CAPITAL LETTER I WITH TILDE
+
+# ================================================================================
+
+# Turkish and Azeri
+
+# I and i-dotless; I-dot and i are case pairs in Turkish and Azeri
+# The following rules handle those cases.
+
+0130; 0069; 0130; 0130; tr; # LATIN CAPITAL LETTER I WITH DOT ABOVE
+0130; 0069; 0130; 0130; az; # LATIN CAPITAL LETTER I WITH DOT ABOVE
+
+# When lowercasing, remove dot_above in the sequence I + dot_above, which will turn into i.
+# This matches the behavior of the canonically equivalent I-dot_above
+
+0307; ; 0307; 0307; tr After_I; # COMBINING DOT ABOVE
+0307; ; 0307; 0307; az After_I; # COMBINING DOT ABOVE
+
+# When lowercasing, unless an I is before a dot_above, it turns into a dotless i.
+
+0049; 0131; 0049; 0049; tr Not_Before_Dot; # LATIN CAPITAL LETTER I
+0049; 0131; 0049; 0049; az Not_Before_Dot; # LATIN CAPITAL LETTER I
+
+# When uppercasing, i turns into a dotted capital I
+
+0069; 0069; 0130; 0130; tr; # LATIN SMALL LETTER I
+0069; 0069; 0130; 0130; az; # LATIN SMALL LETTER I
+
+# Note: the following case is already in the UnicodeData.txt file.
+
+# 0131; 0131; 0049; 0049; tr; # LATIN SMALL LETTER DOTLESS I
+
+# EOF
+
diff --git a/admin/unidata/unidata-gen.el b/admin/unidata/unidata-gen.el
index 3c5119a8a3d..5575f0e745a 100644
--- a/admin/unidata/unidata-gen.el
+++ b/admin/unidata/unidata-gen.el
@@ -268,6 +268,20 @@ unidata-prop-alist
 The value nil means that the actual property value of a character
 is the character itself."
      string)
+    (special-casing
+     nil unidata-gen-table-special-casing "uni-special-casing.el"
+     "Unicode special casing mapping.
+
+Property value is nil or a three-element list of strings or characters.  Each
+element denotes what characters maps into when upper-casing, lower-casing or
+title-casing respectively.  String is used when the mapping is into an empty
+string or more than one character.
+
+The value nil means that no special casing rules exist for the character and
+`uppercase', `lowercase' or `titlecase' property needs to be consulted.
+
+The mapping includes only unconditional casing rules defined by Unicode."
+     nil)
     (mirroring
      unidata-gen-mirroring-list unidata-gen-table-character "uni-mirrored.el"
      "Unicode bidi-mirroring characters.
@@ -1084,6 +1098,32 @@ unidata-gen-table-decomposition
 
 
 \f
+
+(defun unidata-gen-table-special-casing (prop &rest ignore)
+  (let ((table (make-char-table 'char-code-property-table)))
+    (set-char-table-extra-slot table 0 prop)
+    (with-temp-buffer
+      (insert-file-contents (expand-file-name "SpecialCasing.txt" unidata-dir))
+      (goto-char (point-min))
+      (while (not (eobp))
+        (unless (or (eq (char-after) ?\n) (eq (char-after) ?#)) ;empty line or comment
+          (let ((line (split-string
+                       (buffer-substring (point) (progn (end-of-line) (point)))
+                       ";" "")))
+            ;; Ignore entries with conditions, i.e. those with six values.
+            (when (= (length line) 5)
+              (let ((ch (string-to-number (pop line) 16)) lo tc up)
+                (dolist (var '(lo tc up))
+                  (let ((v (mapcar (lambda (num) (string-to-number num 16))
+                                   (split-string (pop line)))))
+                    (set var (if (or (null v) (cdr v)) (apply 'string v) (car v)))))
+                ;; Order must match order of case_action enum fields defined in
+                ;; src/casefiddle.c
+                (set-char-table-range table ch (list up lo tc))))))
+        (forward-line)))
+    table))
+
+\f
 (defun unidata-describe-general-category (val)
   (cdr (assq val
 	     '((nil . "Uknown")
diff --git a/doc/lispref/strings.texi b/doc/lispref/strings.texi
index cf47db4a814..ba1cf2606ce 100644
--- a/doc/lispref/strings.texi
+++ b/doc/lispref/strings.texi
@@ -1166,6 +1166,29 @@ Case Conversion
 @end example
 @end defun
 
+  Note that case conversion is not a one-to-one mapping and the length
+of the result may differ from the length of the argument (including
+being shorter).  Furthermore, because passing a character forces
+return type to be a character, functions are unable to perform proper
+substitution and result may differ compared to treating
+a one-character string.  For example:
+
+@example
+@group
+(upcase "fi")  ; note: single character, ligature "fi"
+     @result{} "FI"
+@end group
+@group
+(upcase ?fi)
+     @result{} 64257  ; i.e. ?fi
+@end group
+@end example
+
+  To avoid this, a character must first be converted into a string,
+using @code{string} function, before being passed to one of the casing
+functions.  Of course, no assumptions on the length of the result may
+be made.
+
   @xref{Text Comparison}, for functions that compare strings; some of
 them ignore case differences, or can optionally ignore case differences.
 
diff --git a/etc/NEWS b/etc/NEWS
index 715764accf1..902b91a2f1a 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -351,12 +351,17 @@ same as in modes where the character is not whitespace.
 Instead of only checking the modification time, Emacs now also checks
 the file's actual content before prompting the user.
 
-** Title case characters are properly cased (from and into).
-'upcase', 'upcase-region' et al. convert title case characters (such
-as the single character "Dz") into their upper case form (such as "DZ").
-As a downside, 'capitalize' and 'upcase-initials' produce awkward
-words where first character is upper rather than title case, e.g.,
-"DŽungla" instead of "Džungla".
+** Various casing improvements.
+
+*** 'upcase', 'upcase-region' et al. convert title case characters
+(such as Dz) into their upper case form (such as DZ).
+
+*** 'capitalize', 'upcase-initials' et al. make use of title-case forms
+of initial characters (correctly producing for example Džungla instead
+of incorrect DŽungla).
+
+*** Characters which turn into multiple ones when cased are correctly handled.
+For example, fi ligature is converted to FI when upper cased.
 
 \f
 * Changes in Specialized Modes and Packages in Emacs 26.1
diff --git a/src/casefiddle.c b/src/casefiddle.c
index 88da609b746..36b9de3ba90 100644
--- a/src/casefiddle.c
+++ b/src/casefiddle.c
@@ -29,6 +29,7 @@ along with GNU Emacs.  If not, see <http://www.gnu.org/licenses/>.  */
 #include "composite.h"
 #include "keymap.h"
 
+/* Order must match order in unidata-gen-table-special-casing. */
 enum case_action {CASE_UP, CASE_DOWN, CASE_CAPITALIZE, CASE_CAPITALIZE_UP};
 
 /* State for casing individual characters.  */
@@ -37,6 +38,9 @@ struct casing_context {
      implies flag being CASE_CAPITALIZE or CASE_CAPITALIZE_UP (but the reverse
      is not true).  */
   Lisp_Object titlecase_char_table;
+  /* The special-casing Unicode properties case table with unconditional special
+     casing rules defined by Unicode. */
+  Lisp_Object specialcase_char_table;
   /* User-requested action. */
   enum case_action flag;
   /* If true, function operates on a buffer as opposed to a string or character.
@@ -61,6 +65,8 @@ prepare_casing_context (struct casing_context *ctx,
   ctx->titlecase_char_table = (int)flag >= (int)CASE_CAPITALIZE
     ? uniprop_table (intern_c_string ("titlecase"))
     : Qnil;
+  ctx->specialcase_char_table =
+    uniprop_table (intern_c_string ("special-casing"));
 
   /* If the case table is flagged as modified, rescan it.  */
   if (NILP (XCHAR_TABLE (BVAR (current_buffer, downcase_table))->extras[1]))
@@ -70,25 +76,117 @@ prepare_casing_context (struct casing_context *ctx,
     SETUP_BUFFER_SYNTAX_TABLE ();	/* For syntax_prefix_flag_p.  */
 }
 
-/* Based on CTX, case character CH accordingly.  Update CTX as necessary.
-   Return cased character. */
+struct casing_str_buf {
+  unsigned char data[MAX_MULTIBYTE_LENGTH > 6 ? MAX_MULTIBYTE_LENGTH : 6];
+  unsigned char len_chars;
+  unsigned char len_bytes;
+};
+
+/* Based on CTX, case character CH.  If BUF is NULL, return cased character.
+   Otherwise, if BUF is non-NULL, save result in it and return whether the
+   character has been changed.
+
+   Since meaning of return value depends on arguments, it’s more convenient to
+   use case_single_character or case_character instead. */
 static int
-case_character (struct casing_context *ctx, int ch)
+case_character_impl (struct casing_str_buf *buf,
+		     struct casing_context *ctx, int ch)
 {
+  enum case_action flag;
   Lisp_Object prop;
+  bool was_inword;
+  int cased;
+
+  /* Update inword state */
+  was_inword = ctx->inword;
+  if ((int) ctx->flag >= (int) CASE_CAPITALIZE)
+    ctx->inword = SYNTAX (ch) == Sword &&
+      (!ctx->inbuffer || was_inword || !syntax_prefix_flag_p (ch));
+
+  /* Normalise flag so its one of CASE_UP, CASE_DOWN or CASE_CAPITALIZE. */
+  if (!was_inword)
+    flag = ctx->flag == CASE_UP ? CASE_UP : CASE_CAPITALIZE;
+  else if (ctx->flag != CASE_CAPITALIZE_UP)
+    flag = CASE_DOWN;
+  else
+    {
+      cased = ch;
+      goto done;
+    }
 
-  if (ctx->inword)
-    ch = ctx->flag == CASE_CAPITALIZE_UP ? ch : downcase (ch);
+  /* Look through the special casing entries. */
+  if (buf && !NILP(ctx->specialcase_char_table))
+    {
+      prop = CHAR_TABLE_REF(ctx->specialcase_char_table, ch);
+      switch (flag) {
+      case CASE_CAPITALIZE:
+      case CASE_CAPITALIZE_UP:
+        if (!CONSP(prop))
+          break;
+        prop = XCDR(prop);
+        /* FALL THROUGH */
+      case CASE_DOWN:
+        if (!CONSP(prop))
+          break;
+        prop = XCDR(prop);
+        /* FALL THROUGH */
+      default:
+        if (!CONSP(prop))
+          break;
+        prop = XCAR(prop);
+        if (INTEGERP(prop)) {
+          cased = XINT(prop);
+          if (0 <= cased && cased <= MAX_CHAR)
+            goto done;
+        } else if (STRINGP(prop)) {
+          struct Lisp_String *str = XSTRING(prop);
+          if (STRING_BYTES(str) <= sizeof buf->data) {
+            buf->len_chars = str->size;
+            buf->len_bytes = STRING_BYTES(str);
+            memcpy(buf->data, str->data, buf->len_bytes);
+            return 1;
+          }
+        }
+      }
+    }
+
+  /* Handle simple, one-to-one case. */
+  if (flag == CASE_DOWN)
+    cased = downcase (ch);
   else if (!NILP (ctx->titlecase_char_table) &&
 	   CHARACTERP (prop = CHAR_TABLE_REF (ctx->titlecase_char_table, ch)))
-    ch = XFASTINT (prop);
+    cased = XFASTINT (prop);
   else
-    ch = upcase(ch);
+    cased = upcase(ch);
+
+  /* And we’re done. */
+ done:
+  if (!buf)
+    return cased;
+  buf->len_chars = 1;
+  buf->len_bytes = CHAR_STRING (cased, buf->data);
+  return cased != ch;
+}
 
-  if ((int) ctx->flag >= (int) CASE_CAPITALIZE)
-    ctx->inword = SYNTAX (ch) == Sword &&
-      (!ctx->inbuffer || ctx->inword || !syntax_prefix_flag_p (ch));
-  return ch;
+/* Based on CTX, case character CH accordingly.  Update CTX as necessary.
+   Return cased character.
+
+   Special casing rules (such as upcase(fi) = FI) are not handled.  For
+   characters whose casing results in multiple code points, the character is
+   returned unchanged. */
+static inline int
+case_single_character (struct casing_context *ctx, int ch)
+{
+  return case_character_impl (NULL, ctx, ch);
+}
+
+/* Save in BUF result of casing character CH.  Return whether casing changed the
+   character.  This is like case_single_character but also handles one-to-many
+   casing rules. */
+static inline bool
+case_character (struct casing_str_buf *buf, struct casing_context *ctx, int ch)
+{
+  return case_character_impl (buf, ctx, ch);
 }
 \f
 static Lisp_Object
@@ -115,7 +213,7 @@ do_casify_natnum (struct casing_context *ctx, Lisp_Object obj)
 	       !NILP (BVAR (current_buffer, enable_multibyte_characters)));
   if (! multibyte)
     MAKE_CHAR_MULTIBYTE (ch);
-  cased = case_character (ctx, ch);
+  cased = case_single_character (ctx, ch);
   if (cased == ch)
     return obj;
 
@@ -128,25 +226,34 @@ do_casify_natnum (struct casing_context *ctx, Lisp_Object obj)
 static Lisp_Object
 do_casify_multibyte_string (struct casing_context *ctx, Lisp_Object obj)
 {
-  ptrdiff_t i, i_byte, size = SCHARS (obj);
-  int len, ch, cased;
+  /* We assume data is the first member of casing_str_buf structure so that if
+     we cast a (char *) into (struct casing_str_buf *) the representation of the
+     character is at the beginning of the buffer.  This is why we don’t need
+     separate struct casing_str_buf object but rather write directly to o. */
+  typedef char static_assertion[offsetof(struct casing_str_buf, data) ? -1 : 1];
+
+  ptrdiff_t size = SCHARS (obj), n;
+  int ch;
   USE_SAFE_ALLOCA;
-  ptrdiff_t o_size;
-  if (INT_MULTIPLY_WRAPV (size, MAX_MULTIBYTE_LENGTH, &o_size))
-    o_size = PTRDIFF_MAX;
-  unsigned char *dst = SAFE_ALLOCA (o_size);
+  if (INT_MULTIPLY_WRAPV (size, MAX_MULTIBYTE_LENGTH, &n) ||
+      INT_ADD_WRAPV (n, sizeof(struct casing_str_buf), &n))
+    n = PTRDIFF_MAX;
+  unsigned char *const dst = SAFE_ALLOCA (n), *const dst_end = dst + n;
   unsigned char *o = dst;
 
-  for (i = i_byte = 0; i < size; i++, i_byte += len)
+  const unsigned char *src = SDATA (obj);
+
+  for (n = 0; size; --size)
     {
-      if (o_size - MAX_MULTIBYTE_LENGTH < o - dst)
+      if (dst_end - o < sizeof(struct casing_str_buf))
 	string_overflow ();
-      ch = STRING_CHAR_AND_LENGTH (SDATA (obj) + i_byte, len);
-      cased = case_character (ctx, ch);
-      o += CHAR_STRING (cased, o);
+      ch = STRING_CHAR_ADVANCE (src);
+      case_character ((void *)o, ctx, ch);
+      n += ((struct casing_str_buf *)o)->len_chars;
+      o += ((struct casing_str_buf *)o)->len_bytes;
     }
-  eassert (o - dst <= o_size);
-  obj = make_multibyte_string ((char *) dst, size, o - dst);
+  eassert (o <= dst_end);
+  obj = make_multibyte_string ((char *) dst, n, o - dst);
   SAFE_FREE ();
   return obj;
 }
@@ -162,7 +269,7 @@ do_casify_unibyte_string (struct casing_context *ctx, Lisp_Object obj)
     {
       ch = SREF (obj, i);
       MAKE_CHAR_MULTIBYTE (ch);
-      cased = case_character (ctx, ch);
+      cased = case_single_character (ctx, ch);
       if (ch == cased)
 	continue;
       MAKE_CHAR_UNIBYTE (cased);
@@ -194,7 +301,9 @@ casify_object (enum case_action flag, Lisp_Object obj)
 DEFUN ("upcase", Fupcase, Supcase, 1, 1, 0,
        doc: /* Convert argument to upper case and return that.
 The argument may be a character or string.  The result has the same type.
-The argument object is not altered--the value is a copy.
+The argument object is not altered--the value is a copy.  If argument
+is a character, characters which map to multiple code points when
+cased, e.g. fi, are returned unchanged.
 See also `capitalize', `downcase' and `upcase-initials'.  */)
   (Lisp_Object obj)
 {
@@ -215,7 +324,9 @@ DEFUN ("capitalize", Fcapitalize, Scapitalize, 1, 1, 0,
 This means that each word's first character is upper case (more
 precisely, if available, title case) and the rest is lower case.
 The argument may be a character or string.  The result has the same type.
-The argument object is not altered--the value is a copy.  */)
+The argument object is not altered--the value is a copy.  If argument
+is a character, characters which map to multiple code points when
+cased, e.g. fi, are returned unchanged.  */)
   (Lisp_Object obj)
 {
   return casify_object (CASE_CAPITALIZE, obj);
@@ -228,21 +339,28 @@ DEFUN ("upcase-initials", Fupcase_initials, Supcase_initials, 1, 1, 0,
 (More precisely, if available, initial of each word is converted to
 title-case).  Do not change the other letters of each word.
 The argument may be a character or string.  The result has the same type.
-The argument object is not altered--the value is a copy.  */)
+The argument object is not altered--the value is a copy.  If argument
+is a character, characters which map to multiple code points when
+cased, e.g. fi, are returned unchanged.  */)
   (Lisp_Object obj)
 {
   return casify_object (CASE_CAPITALIZE_UP, obj);
 }
 \f
-/* Based on CTX, case region in a unibyte buffer from POS to *ENDP.  Return
-   first position that has changed and save last position in *ENDP.  If no
-   characters were changed, return -1 and *ENDP is unspecified. */
+/* Based on CTX, case region in a unibyte buffer from *STARTP to *ENDP.
+
+   Save first and last positions that has changed in *STARTP and *ENDP
+   respectively.  If no characters were changed, save -1 to *STARTP and leave
+   *ENDP unspecified.
+
+   Always return 0.  This is so that interface of this function is the same as
+   do_casify_multibyte_region. */
 static ptrdiff_t
 do_casify_unibyte_region (struct casing_context *ctx,
-			  ptrdiff_t pos, ptrdiff_t *endp)
+			  ptrdiff_t *startp, ptrdiff_t *endp)
 {
   ptrdiff_t first = -1, last = -1;  /* Position of first and last changes. */
-  ptrdiff_t end = *endp;
+  ptrdiff_t pos = *startp, end = *endp;
   int ch, cased;
 
   for (; pos < end; ++pos)
@@ -250,11 +368,11 @@ do_casify_unibyte_region (struct casing_context *ctx,
       ch = FETCH_BYTE (pos);
       MAKE_CHAR_MULTIBYTE (ch);
 
-      cased = case_character (ctx, ch);
+      cased = case_single_character (ctx, ch);
       if (cased == ch)
 	continue;
 
-      last = pos;
+      last = pos + 1;
       if (first < 0)
 	first = pos;
 
@@ -262,88 +380,107 @@ do_casify_unibyte_region (struct casing_context *ctx,
       FETCH_BYTE (pos) = cased;
     }
 
-  *endp = last + 1;
-  return first;
+  *startp = first;
+  *endp = last;
+  return 0;
 }
 
-/* Based on CTX, case region in a multibyte buffer from POS to *ENDP.  Return
-   first position that has changed and save last position in *ENDP.  If no
-   characters were changed, return -1 and *ENDP is unspecified. */
+/* Based on CTX, case region in a multibyte buffer from *STARTP to *ENDP.
+
+   Return number of added characters (may be negative if more characters were
+   deleted then inserted), save first and last positions that has changed in
+   *STARTP and *ENDP respectively.  If no characters were changed, return 0,
+   save -1 to *STARTP and leave *ENDP unspecified. */
 static ptrdiff_t
 do_casify_multibyte_region (struct casing_context *ctx,
-                           ptrdiff_t pos, ptrdiff_t *endp)
+			    ptrdiff_t *startp, ptrdiff_t *endp)
 {
   ptrdiff_t first = -1, last = -1;  /* Position of first and last changes. */
-  ptrdiff_t pos_byte = CHAR_TO_BYTE (pos), end = *endp;
-  ptrdiff_t opoint = PT;
+  ptrdiff_t pos = *startp, pos_byte = CHAR_TO_BYTE (pos), size = *endp - pos;
+  ptrdiff_t opoint = PT, added = 0;
+  struct casing_str_buf buf;
   int ch, cased, len;
 
-  while (pos < end)
+  for (; size; --size)
     {
       ch = STRING_CHAR_AND_LENGTH (BYTE_POS_ADDR (pos_byte), len);
-      cased = case_character (ctx, ch);
-      if (cased != ch)
+      if (!case_character (&buf, ctx, ch))
+	{
+	  pos_byte += len;
+	  ++pos;
+	  continue;
+	}
+
+      last = pos + buf.len_chars;
+      if (first < 0)
+	first = pos;
+
+      if (buf.len_chars == 1 && buf.len_bytes == len)
+	memcpy (BYTE_POS_ADDR (pos_byte), buf.data, len);
+      else
 	{
-	  last = pos;
-	  if (first < 0)
-	    first = pos;
-
-	  if (ASCII_CHAR_P (cased) && ASCII_CHAR_P (ch))
-	    FETCH_BYTE (pos_byte) = cased;
-	  else
-	    {
-	      unsigned char str[MAX_MULTIBYTE_LENGTH];
-	      int totlen = CHAR_STRING (cased, str);
-	      if (len == totlen)
-		memcpy (BYTE_POS_ADDR (pos_byte), str, len);
-	      else
-		/* Replace one character with the other(s), keeping text
-		   properties the same.  */
-		replace_range_2 (pos, pos_byte, pos + 1, pos_byte + len,
-				 (char *) str, 9, totlen, 0);
-	      len = totlen;
-	    }
+	  /* Replace one character with the other(s), keeping text
+	     properties the same.  */
+	  replace_range_2 (pos, pos_byte, pos + 1, pos_byte + len,
+			   (const char *) buf.data, buf.len_chars,
+			   buf.len_bytes,
+			   0);
+	  added += (ptrdiff_t) buf.len_chars - 1;
+	  if (opoint > pos)
+	    opoint += (ptrdiff_t) buf.len_chars - 1;
 	}
-      pos++;
-      pos_byte += len;
+
+      pos_byte += buf.len_bytes;
+      pos += buf.len_chars;
     }
 
   if (PT != opoint)
     TEMP_SET_PT_BOTH (opoint, CHAR_TO_BYTE (opoint));
 
+  *startp = first;
   *endp = last;
-  return first;
+  return added;
 }
 
-/* flag is CASE_UP, CASE_DOWN or CASE_CAPITALIZE or CASE_CAPITALIZE_UP.
-   b and e specify range of buffer to operate on. */
-static void
+/* flag is CASE_UP, CASE_DOWN or CASE_CAPITALIZE or CASE_CAPITALIZE_UP.  b and
+   e specify range of buffer to operate on.  Return character position of the
+   end of the region after changes.  */
+static ptrdiff_t
 casify_region (enum case_action flag, Lisp_Object b, Lisp_Object e)
 {
+  ptrdiff_t start, end, orig_end, added;
   struct casing_context ctx;
-  ptrdiff_t start, end;
-
-  if (EQ (b, e))
-    /* Not modifying because nothing marked */
-    return;
 
   validate_region (&b, &e);
   start = XFASTINT (b);
   end = XFASTINT (e);
+  if (start == end)
+    /* Not modifying because nothing marked */
+    return end;
   modify_text (start, end);
-  record_change (start, end - start);
   prepare_casing_context (&ctx, flag, true);
 
+  orig_end = end;
+  record_delete (start, make_buffer_string (start, end, true), false);
   if (NILP (BVAR (current_buffer, enable_multibyte_characters)))
-    start = do_casify_unibyte_region (&ctx, start, &end);
+    {
+      record_insert (start, end - start);
+      added = do_casify_unibyte_region (&ctx, &start, &end);
+    }
   else
-    start = do_casify_multibyte_region (&ctx, start, &end);
+    {
+      ptrdiff_t len = end - start, ostart = start;
+      added = do_casify_multibyte_region (&ctx, &start, &end);
+      record_insert (ostart, len + added);
+    }
 
   if (start >= 0)
     {
-      signal_after_change (start, end + 1 - start, end + 1 - start);
-      update_compositions (start, end + 1, CHECK_ALL);
+      signal_after_change (start, end - start - added, end - start);
+      update_compositions (start, end, CHECK_ALL);
     }
+
+  return orig_end + added;
 }
 
 DEFUN ("upcase-region", Fupcase_region, Supcase_region, 2, 3,
@@ -435,9 +572,7 @@ casify_word (enum case_action flag, Lisp_Object arg)
   ptrdiff_t farend = scan_words (PT, XINT (arg));
   if (!farend)
     farend = XINT (arg) <= 0 ? BEGV : ZV;
-  ptrdiff_t newpoint = max (PT, farend);
-  casify_region (flag, make_number (PT), make_number (farend));
-  SET_PT (newpoint);
+  SET_PT (casify_region (flag, make_number (PT), make_number (farend)));
   return Qnil;
 }
 
diff --git a/test/lisp/char-fold-tests.el b/test/lisp/char-fold-tests.el
index d86c731b6e3..00bc3c83d05 100644
--- a/test/lisp/char-fold-tests.el
+++ b/test/lisp/char-fold-tests.el
@@ -54,6 +54,14 @@ char-fold--test-search-with-contents
        (concat w1 "\s\n\s\t\f\t\n\r\t" w2)
        (concat w1 (make-string 10 ?\s) w2)))))
 
+(defun char-fold--ascii-upcase (string)
+  "Like `upcase' but acts on ASCII characters only."
+  (replace-regexp-in-string "[a-z]+" 'upcase string))
+
+(defun char-fold--ascii-downcase (string)
+  "Like `downcase' but acts on ASCII characters only."
+  (replace-regexp-in-string "[a-z]+" 'downcase string))
+
 (defun char-fold--test-match-exactly (string &rest strings-to-match)
   (let ((re (concat "\\`" (char-fold-to-regexp string) "\\'")))
     (dolist (it strings-to-match)
@@ -61,8 +69,8 @@ char-fold--test-match-exactly
     ;; Case folding
     (let ((case-fold-search t))
       (dolist (it strings-to-match)
-        (should (string-match (upcase re) (downcase it)))
-        (should (string-match (downcase re) (upcase it)))))))
+        (should (string-match (char-fold--ascii-upcase re) (downcase it)))
+        (should (string-match (char-fold--ascii-downcase re) (upcase it)))))))
 
 (ert-deftest char-fold--test-some-defaults ()
   (dolist (it '(("ffl" . "ffl") ("ffi" . "ffi")
diff --git a/test/src/casefiddle-tests.el b/test/src/casefiddle-tests.el
index e83cb00059b..8a88292bd78 100644
--- a/test/src/casefiddle-tests.el
+++ b/test/src/casefiddle-tests.el
@@ -188,16 +188,13 @@ casefiddle-tests--test-casing
         ("DŽUNGLA" "DŽUNGLA" "džungla" "Džungla" "DžUNGLA")
         ("Džungla" "DŽUNGLA" "džungla" "Džungla" "Džungla")
         ("džungla" "DŽUNGLA" "džungla" "Džungla" "Džungla")
+        ("define" "DEFINE" "define" "Define" "Define")
+        ("fish" "FISH" "fish" "Fish" "Fish")
+        ("Straße" "STRASSE" "straße" "Straße" "Straße")
         ;; FIXME(bug#24603): Everything below is broken at the moment.
         ;; Here’s what should happen:
-        ;;("define" "DEFINE" "define" "Define" "Define")
-        ;;("fish" "FIsh" "fish" "Fish" "Fish")
-        ;;("Straße" "STRASSE" "straße" "Straße" "Straße")
         ;;("ΌΣΟΣ" "ΌΣΟΣ" "όσος" "Όσος" "Όσος")
         ;; And here’s what is actually happening:
-        ("define" "DEfiNE" "define" "Define" "Define")
-        ("fish" "fiSH" "fish" "fish" "fish")
-        ("Straße" "STRAßE" "straße" "Straße" "Straße")
         ("ΌΣΟΣ" "ΌΣΟΣ" "όσοσ" "Όσοσ" "ΌΣΟΣ")
 
         ("όσος" "ΌΣΟΣ" "όσος" "Όσος" "Όσος"))))))
-- 
2.12.0.246.ga2ecc84866-goog






^ permalink raw reply related	[flat|nested] 89+ messages in thread

* bug#24603: [PATCHv5 06/11] Implement special sigma casing rule (bug#24603)
  2017-03-09 21:51 ` bug#24603: [PATCHv5 00/11] Casing improvements Michal Nazarewicz
                     ` (4 preceding siblings ...)
  2017-03-09 21:51   ` bug#24603: [PATCHv5 05/11] Support casing characters which map into multiple code points (bug#24603) Michal Nazarewicz
@ 2017-03-09 21:51   ` Michal Nazarewicz
  2017-03-09 21:51   ` bug#24603: [PATCHv5 07/11] Introduce ‘buffer-language’ buffer-locar variable Michal Nazarewicz
                     ` (6 subsequent siblings)
  12 siblings, 0 replies; 89+ messages in thread
From: Michal Nazarewicz @ 2017-03-09 21:51 UTC (permalink / raw)
  To: 24603, eliz

In Greek, a sigma character has two lower case forms which depend on
their position in the word.  Implement logic determining it.

* src/casefiddle.c (struct casing_context, case_character_impl): Don’t
assume inword is true when flag is CASE_UP and false when flag is
CASE_DOWN.  For final sigma detection we need this information tracked
reliably;.
(CAPITAL_SIGMA, SMALL_SIGMA, SMALL_FINAL_SIGMA): New macros defining
Unicode code point of different forms of sigma letter.
(case_character): Implement support for final sigma casing.
(do_casify_multibyte_string, do_casify_multibyte_region): Update after
changes to case_character.

* test/src/casefiddle-tests.el (casefiddle-tests-casing): Add test
cases for final sigma.
---
 etc/NEWS                     |  5 +++
 src/casefiddle.c             | 73 +++++++++++++++++++++++++++++++++-----------
 test/src/casefiddle-tests.el | 15 +++++----
 3 files changed, 69 insertions(+), 24 deletions(-)

diff --git a/etc/NEWS b/etc/NEWS
index 902b91a2f1a..a080a95e276 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -363,6 +363,11 @@ of incorrect DŽungla).
 *** Characters which turn into multiple ones when cased are correctly handled.
 For example, fi ligature is converted to FI when upper cased.
 
+*** Greek small sigma is correctly handled when at the end of the word.
+Strings such as ΌΣΟΣ are now correctly converted to Όσος when
+capitalized instead of incorrect Όσοσ (compare lowercase sigma at the
+end of the word).
+
 \f
 * Changes in Specialized Modes and Packages in Emacs 26.1
 
diff --git a/src/casefiddle.c b/src/casefiddle.c
index 36b9de3ba90..2f573782115 100644
--- a/src/casefiddle.c
+++ b/src/casefiddle.c
@@ -47,9 +47,7 @@ struct casing_context {
      When run on a buffer, syntax_prefix_flag_p is taken into account when
      determined inword flag. */
   bool inbuffer;
-  /* Conceptually, this denotes whether we are inside of a word except
-     that if flag is CASE_UP it’s always false and if flag is CASE_DOWN
-     this is always true. */
+  /* Whether we are inside of a word. */
   bool inword;
 };
 
@@ -61,7 +59,7 @@ prepare_casing_context (struct casing_context *ctx,
 {
   ctx->flag = flag;
   ctx->inbuffer = inbuffer;
-  ctx->inword = flag == CASE_DOWN;
+  ctx->inword = false;
   ctx->titlecase_char_table = (int)flag >= (int)CASE_CAPITALIZE
     ? uniprop_table (intern_c_string ("titlecase"))
     : Qnil;
@@ -99,15 +97,16 @@ case_character_impl (struct casing_str_buf *buf,
 
   /* Update inword state */
   was_inword = ctx->inword;
-  if ((int) ctx->flag >= (int) CASE_CAPITALIZE)
-    ctx->inword = SYNTAX (ch) == Sword &&
-      (!ctx->inbuffer || was_inword || !syntax_prefix_flag_p (ch));
+  ctx->inword = SYNTAX (ch) == Sword &&
+    (!ctx->inbuffer || was_inword || !syntax_prefix_flag_p (ch));
 
   /* Normalise flag so its one of CASE_UP, CASE_DOWN or CASE_CAPITALIZE. */
-  if (!was_inword)
-    flag = ctx->flag == CASE_UP ? CASE_UP : CASE_CAPITALIZE;
+  if (ctx->flag == CASE_CAPITALIZE)
+    flag = (enum case_action)((int)ctx->flag - was_inword);
   else if (ctx->flag != CASE_CAPITALIZE_UP)
-    flag = CASE_DOWN;
+    flag = ctx->flag;
+  else if (!was_inword)
+    flag = CASE_CAPITALIZE;
   else
     {
       cased = ch;
@@ -167,7 +166,18 @@ case_character_impl (struct casing_str_buf *buf,
   buf->len_bytes = CHAR_STRING (cased, buf->data);
   return cased != ch;
 }
+\f
+/* In Greek, lower case sigma has two forms: one when used in the middle and one
+   when used at the end of a word.  Below is to help handle those cases when
+   casing.
+
+   The rule does not conflict with any other casing rules so while it is
+   a conditional one, it is independent on language. */
 
+#define CAPITAL_SIGMA     0x03A3
+#define SMALL_SIGMA       0x03C3
+#define SMALL_FINAL_SIGMA 0x03C2
+\f
 /* Based on CTX, case character CH accordingly.  Update CTX as necessary.
    Return cased character.
 
@@ -181,12 +191,34 @@ case_single_character (struct casing_context *ctx, int ch)
 }
 
 /* Save in BUF result of casing character CH.  Return whether casing changed the
-   character.  This is like case_single_character but also handles one-to-many
-   casing rules. */
-static inline bool
-case_character (struct casing_str_buf *buf, struct casing_context *ctx, int ch)
+   character.
+
+   If not-NULL, NEXT points to the next character in the cased string.  If NULL,
+   it is assumed current character is the last one being cased.  This is used to
+   apply some rules which depend on proceeding state.
+
+   This is like case_single_character but also handles one-to-many casing
+   rules. */
+static bool
+case_character (struct casing_str_buf *buf, struct casing_context *ctx,
+		int ch, const unsigned char *next)
 {
-  return case_character_impl (buf, ctx, ch);
+  bool changed, was_inword;
+
+  was_inword = ctx->inword;
+  changed = case_character_impl (buf, ctx, ch);
+
+  /* If we have just down-cased a capital sigma and the next character no longer
+     has a word syntax (i.e. current character is end of word), use final
+     sigma. */
+  if (was_inword && ch == CAPITAL_SIGMA && changed &&
+      (!next || SYNTAX (STRING_CHAR (next)) != Sword))
+    {
+      buf->len_bytes = CHAR_STRING (SMALL_FINAL_SIGMA, buf->data);
+      buf->len_chars = 1;
+    }
+
+  return changed;
 }
 \f
 static Lisp_Object
@@ -248,7 +280,7 @@ do_casify_multibyte_string (struct casing_context *ctx, Lisp_Object obj)
       if (dst_end - o < sizeof(struct casing_str_buf))
 	string_overflow ();
       ch = STRING_CHAR_ADVANCE (src);
-      case_character ((void *)o, ctx, ch);
+      case_character ((void *)o, ctx, ch, size > 1 ? src : NULL);
       n += ((struct casing_str_buf *)o)->len_chars;
       o += ((struct casing_str_buf *)o)->len_bytes;
     }
@@ -399,12 +431,17 @@ do_casify_multibyte_region (struct casing_context *ctx,
   ptrdiff_t pos = *startp, pos_byte = CHAR_TO_BYTE (pos), size = *endp - pos;
   ptrdiff_t opoint = PT, added = 0;
   struct casing_str_buf buf;
-  int ch, cased, len;
+  bool changed;
+  int ch, len;
 
   for (; size; --size)
     {
       ch = STRING_CHAR_AND_LENGTH (BYTE_POS_ADDR (pos_byte), len);
-      if (!case_character (&buf, ctx, ch))
+      changed = case_character (
+	  &buf, ctx, ch,
+	  size > 1 ? BYTE_POS_ADDR (pos_byte + len) : NULL);
+
+      if (!changed)
 	{
 	  pos_byte += len;
 	  ++pos;
diff --git a/test/src/casefiddle-tests.el b/test/src/casefiddle-tests.el
index 8a88292bd78..10450360eab 100644
--- a/test/src/casefiddle-tests.el
+++ b/test/src/casefiddle-tests.el
@@ -191,13 +191,16 @@ casefiddle-tests--test-casing
         ("define" "DEFINE" "define" "Define" "Define")
         ("fish" "FISH" "fish" "Fish" "Fish")
         ("Straße" "STRASSE" "straße" "Straße" "Straße")
-        ;; FIXME(bug#24603): Everything below is broken at the moment.
-        ;; Here’s what should happen:
-        ;;("ΌΣΟΣ" "ΌΣΟΣ" "όσος" "Όσος" "Όσος")
-        ;; And here’s what is actually happening:
-        ("ΌΣΟΣ" "ΌΣΟΣ" "όσοσ" "Όσοσ" "ΌΣΟΣ")
 
-        ("όσος" "ΌΣΟΣ" "όσος" "Όσος" "Όσος"))))))
+        ;; The word repeated twice to test behaviour at the end of a word
+        ;; inside of an input string as well as at the end of the string.
+        ("ΌΣΟΣ ΌΣΟΣ" "ΌΣΟΣ ΌΣΟΣ" "όσος όσος" "Όσος Όσος" "ΌΣΟΣ ΌΣΟΣ")
+        ;; What should be done with sole sigma?  It is ‘final’ but on the
+        ;; other hand it does not form a word.  We’re using regular sigma.
+        ("Σ Σ" "Σ Σ" "σ σ" "Σ Σ" "Σ Σ")
+        ("όσος" "ΌΣΟΣ" "όσος" "Όσος" "Όσος")
+        ;; If sigma is already lower case, we don’t want to change it.
+        ("όσοσ" "ΌΣΟΣ" "όσοσ" "Όσοσ" "Όσοσ"))))))
 
 (ert-deftest casefiddle-tests-casing-byte8 ()
   (should-not
-- 
2.12.0.246.ga2ecc84866-goog






^ permalink raw reply related	[flat|nested] 89+ messages in thread

* bug#24603: [PATCHv5 07/11] Introduce ‘buffer-language’ buffer-locar variable
  2017-03-09 21:51 ` bug#24603: [PATCHv5 00/11] Casing improvements Michal Nazarewicz
                     ` (5 preceding siblings ...)
  2017-03-09 21:51   ` bug#24603: [PATCHv5 06/11] Implement special sigma casing rule (bug#24603) Michal Nazarewicz
@ 2017-03-09 21:51   ` Michal Nazarewicz
  2017-03-11  9:29     ` Eli Zaretskii
  2017-03-09 21:51   ` bug#24603: [PATCHv5 08/11] Implement rules for title-casing Dutch ij ‘letter’ (bug#24603) Michal Nazarewicz
                     ` (5 subsequent siblings)
  12 siblings, 1 reply; 89+ messages in thread
From: Michal Nazarewicz @ 2017-03-09 21:51 UTC (permalink / raw)
  To: 24603, eliz

At the time unused, ‘buffer-language’ variable denotes the language
buffer is in.

* src/buffer.h (struct buffer): New ‘buffer-language’ buffer-local
variable.
* lisp/international/mule-cmds.el (set-locale-environment): Set
‘buffer-language’ on locale change.
---
 lisp/international/mule-cmds.el | 8 ++++++--
 src/buffer.c                    | 8 ++++++++
 src/buffer.h                    | 8 ++++++++
 3 files changed, 22 insertions(+), 2 deletions(-)

diff --git a/lisp/international/mule-cmds.el b/lisp/international/mule-cmds.el
index bdba8eeb112..d883e8c2298 100644
--- a/lisp/international/mule-cmds.el
+++ b/lisp/international/mule-cmds.el
@@ -2601,7 +2601,7 @@ set-locale-environment
 	(setq system-messages-locale locale)
 	(setq system-time-locale locale))
 
-      (if (string-match "^[a-z][a-z]" locale)
+      (when (string-match "^[a-z][a-z]" locale)
           ;; The value of 'current-iso639-language' is matched against
           ;; the ':lang' property of font-spec objects when selecting
           ;; and prioritizing available fonts for displaying
@@ -2613,7 +2613,11 @@ set-locale-environment
                 ;; match mentioned above uses memq, and ':lang'
                 ;; properties have lower-case values, the letter-case
                 ;; must match exactly.
-                (intern (downcase (match-string 0 locale))))))
+                (intern (downcase (match-string 0 locale))))
+
+          ;; ‘buffer-language’ is used to identify language buffer is in for the
+          ;; purpose of performing language-specific operations.
+          (setq-default buffer-language locale)))
 
     (setq woman-locale
           (or system-messages-locale
diff --git a/src/buffer.c b/src/buffer.c
index c00cc40d6f2..7a56ace20d9 100644
--- a/src/buffer.c
+++ b/src/buffer.c
@@ -934,6 +934,7 @@ reset_buffer (register struct buffer *b)
   bset_display_time (b, Qnil);
   bset_enable_multibyte_characters
     (b, BVAR (&buffer_defaults, enable_multibyte_characters));
+  bset_language (b, BVAR (&buffer_defaults, language));
   bset_cursor_type (b, BVAR (&buffer_defaults, cursor_type));
   bset_extra_line_spacing (b, BVAR (&buffer_defaults, extra_line_spacing));
 
@@ -5113,6 +5114,7 @@ init_buffer_once (void)
   XSETFASTINT (BVAR (&buffer_local_flags, scroll_up_aggressively), idx); ++idx;
   XSETFASTINT (BVAR (&buffer_local_flags, scroll_down_aggressively), idx); ++idx;
   XSETFASTINT (BVAR (&buffer_local_flags, header_line_format), idx); ++idx;
+  XSETFASTINT (BVAR (&buffer_local_flags, language), idx); ++idx;
   XSETFASTINT (BVAR (&buffer_local_flags, cursor_type), idx); ++idx;
   XSETFASTINT (BVAR (&buffer_local_flags, extra_line_spacing), idx); ++idx;
   XSETFASTINT (BVAR (&buffer_local_flags, cursor_in_non_selected_windows), idx); ++idx;
@@ -5175,6 +5177,7 @@ init_buffer_once (void)
   bset_ctl_arrow (&buffer_defaults, Qt);
   bset_bidi_display_reordering (&buffer_defaults, Qt);
   bset_bidi_paragraph_direction (&buffer_defaults, Qnil);
+  bset_language (&buffer_defaults, Qnil);
   bset_cursor_type (&buffer_defaults, Qt);
   bset_extra_line_spacing (&buffer_defaults, Qnil);
   bset_cursor_in_non_selected_windows (&buffer_defaults, Qt);
@@ -6096,6 +6099,11 @@ and disregard a `read-only' text property if the property value
 is a member of the list.  */);
   Vinhibit_read_only = Qnil;
 
+  DEFVAR_PER_BUFFER ("buffer-language", &BVAR (current_buffer, language), Qnil,
+		     doc: /* A locale string describing what natural language text in the buffer is in.
+Nil indicates the buffer does not have natural language text or the language
+is not specified.  */);
+
   DEFVAR_PER_BUFFER ("cursor-type", &BVAR (current_buffer, cursor_type), Qnil,
 		     doc: /* Cursor to use when this buffer is in the selected window.
 Values are interpreted as follows:
diff --git a/src/buffer.h b/src/buffer.h
index a2bdc4e7294..d86bca888a9 100644
--- a/src/buffer.h
+++ b/src/buffer.h
@@ -735,6 +735,9 @@ struct buffer
      window.  Nil means that scrolling method isn't used.  */
   Lisp_Object scroll_down_aggressively_;
 
+  /* A locale string describing what (natural) language current buffer is in. */
+  Lisp_Object language_;
+
   /* Desired cursor type in this buffer.  See the doc string of
      per-buffer variable `cursor-type'.  */
   Lisp_Object cursor_type_;
@@ -994,6 +997,11 @@ bset_undo_list (struct buffer *b, Lisp_Object val)
   b->undo_list_ = val;
 }
 INLINE void
+bset_language (struct buffer *b, Lisp_Object val)
+{
+  b->language_ = val;
+}
+INLINE void
 bset_upcase_table (struct buffer *b, Lisp_Object val)
 {
   b->upcase_table_ = val;
-- 
2.12.0.246.ga2ecc84866-goog






^ permalink raw reply related	[flat|nested] 89+ messages in thread

* bug#24603: [PATCHv5 08/11] Implement rules for title-casing Dutch ij ‘letter’ (bug#24603)
  2017-03-09 21:51 ` bug#24603: [PATCHv5 00/11] Casing improvements Michal Nazarewicz
                     ` (6 preceding siblings ...)
  2017-03-09 21:51   ` bug#24603: [PATCHv5 07/11] Introduce ‘buffer-language’ buffer-locar variable Michal Nazarewicz
@ 2017-03-09 21:51   ` Michal Nazarewicz
  2017-03-11  9:40     ` Eli Zaretskii
  2017-03-09 21:51   ` bug#24603: [PATCHv5 09/11] Implement Turkic dotless and dotted i casing rules (bug#24603) Michal Nazarewicz
                     ` (4 subsequent siblings)
  12 siblings, 1 reply; 89+ messages in thread
From: Michal Nazarewicz @ 2017-03-09 21:51 UTC (permalink / raw)
  To: 24603, eliz

Dutch treats ‘ij’ as a single letter and when capitalising a word it
should be capitalised as such (i.e. ‘ij’ becomes ‘IJ’).  Implement that.

* src/casefiddle.c (struct casing_context): Add a ‘special’ field which
determines if any special casing rules are in effect.
(prepare_casing_context): Interpret ‘buffer-language’ variable and set
ctx->special accordingly.  This allows for per-language special rules.
For now only Dutch (‘nl’) is handled specially.
(case_character_impl): Add handling of a Dutch ‘ij’ letter.

* test/src/casefiddle-tests.el (casefiddle-tests--test-casing): Add
test cases for Dutch ‘ij’.
---
 src/casefiddle.c             | 56 ++++++++++++++++++++++++++++++++++++++++++++
 test/src/casefiddle-tests.el |  7 +++++-
 2 files changed, 62 insertions(+), 1 deletion(-)

diff --git a/src/casefiddle.c b/src/casefiddle.c
index 2f573782115..d59684c7b8e 100644
--- a/src/casefiddle.c
+++ b/src/casefiddle.c
@@ -49,6 +49,32 @@ struct casing_context {
   bool inbuffer;
   /* Whether we are inside of a word. */
   bool inword;
+
+  /* Determines which special casing rules need to be applied as well as tracks
+     state for some of the transformations.*/
+  enum {
+    /* No special casing rules need to be applied. */
+    SPECIAL_NONE,
+
+    /* In Dutch, ‘ij’ is a digraph and when capitalised the whole thing is upper
+       cased.  Unicode has ‘ij’ and ‘IJ’ (with proper casing mappings) but they
+       aren’t always used so we cannot/should not rely on them.
+
+       Note that rule for capitalising ‘ij’ as a single letter is not present in
+       Unicode 9.0’s SpecialCasing.txt.  On the flip side, Firefox implements
+       this as well so we’re not completely alone.
+
+       There are words where ‘ij’ are two separate letters (such as bijectie or
+       bijoux) in which case the capitalisation rules do not apply.  I (mina86)
+       have googled this a little and couldn’t find a Dutch word which beings
+       with ‘ij’ that is not a digraph so we should be in the clear since we
+       only care about the initial. */
+    /* Apply Dutch rules for capitalising ‘ij’. */
+    SPECIAL_NL,
+    /* As above and the previous character was upcased ‘i’ so if we now see ‘j’
+       it needs to be upcased as well. */
+    SPECIAL_NL_UPCASE_J
+  } special;
 };
 
 /* Initialise CTX structure and prepares related global data for casing
@@ -57,6 +83,8 @@ static void
 prepare_casing_context (struct casing_context *ctx,
 			enum case_action flag, bool inbuffer)
 {
+  Lisp_Object lang;
+
   ctx->flag = flag;
   ctx->inbuffer = inbuffer;
   ctx->inword = false;
@@ -65,6 +93,7 @@ prepare_casing_context (struct casing_context *ctx,
     : Qnil;
   ctx->specialcase_char_table =
     uniprop_table (intern_c_string ("special-casing"));
+  ctx->special = SPECIAL_NONE;
 
   /* If the case table is flagged as modified, rescan it.  */
   if (NILP (XCHAR_TABLE (BVAR (current_buffer, downcase_table))->extras[1]))
@@ -72,6 +101,14 @@ prepare_casing_context (struct casing_context *ctx,
 
   if (inbuffer && (int) flag >= (int) CASE_CAPITALIZE)
     SETUP_BUFFER_SYNTAX_TABLE ();	/* For syntax_prefix_flag_p.  */
+
+  lang = BVAR(current_buffer, language);
+  if (STRINGP (lang) && SCHARS (lang) >= 2)
+    switch ((SREF(lang, 0) << 8) | SREF(lang, 1) | 0x2020u) {
+    case ('n' << 8) | 'l':  /* Dutch */
+      if ((int) flag >= (int) CASE_CAPITALIZE)
+	ctx->special = SPECIAL_NL;
+    }
 }
 
 struct casing_str_buf {
@@ -95,6 +132,25 @@ case_character_impl (struct casing_str_buf *buf,
   bool was_inword;
   int cased;
 
+  /* Handle Dutch ij.  Note that SPECIAL_NL and SPECIAL_NL_UPCASE_J implies that
+     ctx->flag ≥ CASE_CAPITALIZE. */
+  if (ctx->special == SPECIAL_NL && ch == 'i' && !ctx->inword)
+    {
+      ctx->special = SPECIAL_NL_UPCASE_J;
+      ctx->inword = true;
+      cased = 'I';
+      goto done;
+    }
+  else if (ctx->special == SPECIAL_NL_UPCASE_J)
+    {
+      ctx->special = SPECIAL_NL;
+      if (ch == 'j')
+	{
+	  cased = 'J';
+	  goto done;
+	}
+    }
+
   /* Update inword state */
   was_inword = ctx->inword;
   ctx->inword = SYNTAX (ch) == Sword &&
diff --git a/test/src/casefiddle-tests.el b/test/src/casefiddle-tests.el
index 10450360eab..5e38a97d256 100644
--- a/test/src/casefiddle-tests.el
+++ b/test/src/casefiddle-tests.el
@@ -135,6 +135,7 @@ casefiddle-tests--test-casing
     (lambda (errors test)
       (let* ((input (car test))
              (expected (cdr test))
+             (buffer-language (or (nth 5 test) "en_GB"))
              (func-pairs '((upcase upcase-region)
                            (downcase downcase-region)
                            (capitalize capitalize-region)
@@ -200,7 +201,11 @@ casefiddle-tests--test-casing
         ("Σ Σ" "Σ Σ" "σ σ" "Σ Σ" "Σ Σ")
         ("όσος" "ΌΣΟΣ" "όσος" "Όσος" "Όσος")
         ;; If sigma is already lower case, we don’t want to change it.
-        ("όσοσ" "ΌΣΟΣ" "όσοσ" "Όσοσ" "Όσοσ"))))))
+        ("όσοσ" "ΌΣΟΣ" "όσοσ" "Όσοσ" "Όσοσ")
+
+        ;; Dutch 'ij' is capitalised as single digraph.
+        ("ijsland" "IJSLAND" "ijsland" "Ijsland" "Ijsland")
+        ("ijsland" "IJSLAND" "ijsland" "IJsland" "IJsland" "nl"))))))
 
 (ert-deftest casefiddle-tests-casing-byte8 ()
   (should-not
-- 
2.12.0.246.ga2ecc84866-goog






^ permalink raw reply related	[flat|nested] 89+ messages in thread

* bug#24603: [PATCHv5 09/11] Implement Turkic dotless and dotted i casing rules (bug#24603)
  2017-03-09 21:51 ` bug#24603: [PATCHv5 00/11] Casing improvements Michal Nazarewicz
                     ` (7 preceding siblings ...)
  2017-03-09 21:51   ` bug#24603: [PATCHv5 08/11] Implement rules for title-casing Dutch ij ‘letter’ (bug#24603) Michal Nazarewicz
@ 2017-03-09 21:51   ` Michal Nazarewicz
  2017-03-09 21:51   ` bug#24603: [PATCHv5 10/11] Implement casing rules for Lithuanian (bug#24603) Michal Nazarewicz
                     ` (3 subsequent siblings)
  12 siblings, 0 replies; 89+ messages in thread
From: Michal Nazarewicz @ 2017-03-09 21:51 UTC (permalink / raw)
  To: 24603, eliz

Implement part of Unicode special handling rules for Azerbaijani and
Turkish languages, namely ‘i’ is paired with ‘İ’ while ‘ı’ is paired
with ‘I’.

* src/casefiddle.c (enum case_action): Add ‘CASE_NO_ACTION’ enum value
which is used when in word while CASE_CAPITALIZE_UP requested.
(struct casing_context): Add SPECIAL_TR enum value to the special flag.
(prepare_casing_context): Set SPECIAL_TR special flag if buffer is in
Turkish and Azerbaijani.
(maybe_case_greek): Extract handling of sigma from case_character to
a new function.
(maybe_case_turkic): New function handling Turkic dotted and dotless
‘i’ casing.
(case_character_impl): Extract flag normalisation to a new function:
(normalise_flag): New function.
(case_single_character): Update after above changes.
(case_character): Rename to:
(case_characters): Make use of maybe_case_greek and maybe_case_turkic.
(do_casify_multibyte_string, do_casify_multibyte_region): Update to use
renamed case_characters.

* test/src/casefiddle-tests.el (casefiddle-tests-casing): Add test
cases for dotless and dotted i’s.
---
 src/casefiddle.c             | 305 ++++++++++++++++++++++++++++++-------------
 test/src/casefiddle-tests.el |  42 +++++-
 2 files changed, 255 insertions(+), 92 deletions(-)

diff --git a/src/casefiddle.c b/src/casefiddle.c
index d59684c7b8e..4785ebaddc4 100644
--- a/src/casefiddle.c
+++ b/src/casefiddle.c
@@ -30,7 +30,8 @@ along with GNU Emacs.  If not, see <http://www.gnu.org/licenses/>.  */
 #include "keymap.h"
 
 /* Order must match order in unidata-gen-table-special-casing. */
-enum case_action {CASE_UP, CASE_DOWN, CASE_CAPITALIZE, CASE_CAPITALIZE_UP};
+enum case_action {CASE_UP, CASE_DOWN, CASE_CAPITALIZE, CASE_CAPITALIZE_UP,
+		  /* Only for internal use: */ CASE_NO_ACTION};
 
 /* State for casing individual characters.  */
 struct casing_context {
@@ -73,7 +74,10 @@ struct casing_context {
     SPECIAL_NL,
     /* As above and the previous character was upcased ‘i’ so if we now see ‘j’
        it needs to be upcased as well. */
-    SPECIAL_NL_UPCASE_J
+    SPECIAL_NL_UPCASE_J,
+
+    /* Handle Azerbaijani and Turkish dotted and dotless i. */
+    SPECIAL_TR
   } special;
 };
 
@@ -108,32 +112,62 @@ prepare_casing_context (struct casing_context *ctx,
     case ('n' << 8) | 'l':  /* Dutch */
       if ((int) flag >= (int) CASE_CAPITALIZE)
 	ctx->special = SPECIAL_NL;
+      break;
+    case ('t' << 8) | 'r':  /* Turkish */
+    case ('a' << 8) | 'z':  /* Azerbaijani */
+      ctx->special = SPECIAL_TR;
     }
 }
 
+/* Normalise CFG->flag and return CASE_UP, CASE_DOWN, CASE_CAPITALIZE or
+   CASE_NO_ACTION.  The latter if CFG->flag is CASE_CAPITALIZE_UP and we are
+   inside of a word. */
+static enum case_action
+normalise_flag (struct casing_context *ctx)
+{
+  /* Normalise flag so its one of CASE_UP, CASE_DOWN or CASE_CAPITALIZE. */
+  switch (ctx->flag) {
+  case CASE_CAPITALIZE:
+    return ctx->inword ? CASE_DOWN : CASE_CAPITALIZE;
+  case CASE_CAPITALIZE_UP:
+    return ctx->inword ? CASE_NO_ACTION : CASE_CAPITALIZE;
+  default:
+    return ctx->flag;
+  }
+}
+
 struct casing_str_buf {
   unsigned char data[MAX_MULTIBYTE_LENGTH > 6 ? MAX_MULTIBYTE_LENGTH : 6];
   unsigned char len_chars;
   unsigned char len_bytes;
 };
 
-/* Based on CTX, case character CH.  If BUF is NULL, return cased character.
-   Otherwise, if BUF is non-NULL, save result in it and return whether the
-   character has been changed.
+#define RES_NOT_TOUCHED -2
+#define RES_NO_CHANGE   -1
+#define RES_CHANGED      0
+
+/* Based on CTX and FLAG, case character CH.  If BUF is NULL, return cased
+   character.  Otherwise, if BUF is non-NULL, save result in it and return
+   RES_CHANGED if the character changed or RES_NO_CHANGE if it didn’t.
+
+   FLAG may be one of CASE_UP, CASE_DOWN, CASE_CAPITALIZE (title-case if
+   possible, upper-aces otherwise) or CASE_NO_ACTION.  CTX->inword is not taken
+   into account when interpreting FLAG (it may be taken into account for other
+   decisions though).
 
    Since meaning of return value depends on arguments, it’s more convenient to
-   use case_single_character or case_character instead. */
+   use case_single_character or case_characters instead. */
 static int
 case_character_impl (struct casing_str_buf *buf,
-		     struct casing_context *ctx, int ch)
+		     struct casing_context *ctx, enum case_action flag, int ch)
 {
-  enum case_action flag;
   Lisp_Object prop;
-  bool was_inword;
   int cased;
 
   /* Handle Dutch ij.  Note that SPECIAL_NL and SPECIAL_NL_UPCASE_J implies that
-     ctx->flag ≥ CASE_CAPITALIZE. */
+     ctx->flag ≥ CASE_CAPITALIZE.  This has to be handled prior to flag ==
+     CASE_NO_ACTION check or otherwise we won’t handle ctx->flag ==
+     CASE_CAPITALIZE_UP correctly. */
   if (ctx->special == SPECIAL_NL && ch == 'i' && !ctx->inword)
     {
       ctx->special = SPECIAL_NL_UPCASE_J;
@@ -152,18 +186,10 @@ case_character_impl (struct casing_str_buf *buf,
     }
 
   /* Update inword state */
-  was_inword = ctx->inword;
   ctx->inword = SYNTAX (ch) == Sword &&
-    (!ctx->inbuffer || was_inword || !syntax_prefix_flag_p (ch));
+    (!ctx->inbuffer || ctx->inword || !syntax_prefix_flag_p (ch));
 
-  /* Normalise flag so its one of CASE_UP, CASE_DOWN or CASE_CAPITALIZE. */
-  if (ctx->flag == CASE_CAPITALIZE)
-    flag = (enum case_action)((int)ctx->flag - was_inword);
-  else if (ctx->flag != CASE_CAPITALIZE_UP)
-    flag = ctx->flag;
-  else if (!was_inword)
-    flag = CASE_CAPITALIZE;
-  else
+  if (flag == CASE_NO_ACTION)
     {
       cased = ch;
       goto done;
@@ -199,7 +225,7 @@ case_character_impl (struct casing_str_buf *buf,
             buf->len_chars = str->size;
             buf->len_bytes = STRING_BYTES(str);
             memcpy(buf->data, str->data, buf->len_bytes);
-            return 1;
+            return 0;
           }
         }
       }
@@ -220,7 +246,20 @@ case_character_impl (struct casing_str_buf *buf,
     return cased;
   buf->len_chars = 1;
   buf->len_bytes = CHAR_STRING (cased, buf->data);
-  return cased != ch;
+  return cased == ch ? -1 : 0;
+}
+
+/* Based on CTX, case character CH accordingly.  Update CTX as necessary.
+   Return cased character.
+
+   Special casing rules (such as upcase(fi) = FI) are not handled.  For
+   characters whose casing results in multiple code points, the character is
+   returned unchanged. */
+static inline int
+case_single_character (struct casing_context *ctx, int ch)
+{
+  enum case_action flag = normalise_flag (ctx);
+  return case_character_impl (NULL, ctx, flag, ch);
 }
 \f
 /* In Greek, lower case sigma has two forms: one when used in the middle and one
@@ -233,48 +272,126 @@ case_character_impl (struct casing_str_buf *buf,
 #define CAPITAL_SIGMA     0x03A3
 #define SMALL_SIGMA       0x03C3
 #define SMALL_FINAL_SIGMA 0x03C2
-\f
-/* Based on CTX, case character CH accordingly.  Update CTX as necessary.
-   Return cased character.
 
-   Special casing rules (such as upcase(fi) = FI) are not handled.  For
-   characters whose casing results in multiple code points, the character is
-   returned unchanged. */
-static inline int
-case_single_character (struct casing_context *ctx, int ch)
-{
-  return case_character_impl (NULL, ctx, ch);
+/* Save in BUF result of casing character CH if Greek casing rules apply.
+
+   If not-NULL, NEXT points to the next character in the cased string.  If NULL,
+   it is assumed current character is the last one being cased.  This is used to
+   apply some rules which depend on proceeding state.
+
+   FLAG is a normalised flag (as returned by normalise_flag function).
+
+   Return -2 (RES_NOT_TOUCHED) if Greek rules did not apply, no changes were
+   made and other casing rules should be tried.  Otherwise, meaning of return
+   values is the same as in case_characters function. */
+static int
+maybe_case_greek (struct casing_str_buf *buf, struct casing_context *ctx,
+		  enum case_action flag, int ch, const char *next) {
+  if (flag != CASE_DOWN || ch != CAPITAL_SIGMA)
+    return RES_NOT_TOUCHED;
+
+  ch = (ctx->inword && (!next || SYNTAX (STRING_CHAR (next)) != Sword))
+    ? SMALL_FINAL_SIGMA : SMALL_SIGMA;
+  buf->len_bytes = CHAR_STRING (ch, buf->data);
+  buf->len_chars = 1;
+  ctx->inword = true;
+  return RES_CHANGED;
 }
+\f
+/* Azerbaijani and Turkish have dotless and dotted i.  An upper case of i is
+   İ while lower case of I is ı. */
+
+#define CAPITAL_DOTTED_I    0x130
+#define SMALL_DOTLESS_I     0x131
+#define COMBINING_DOT_ABOVE 0x307
 
-/* Save in BUF result of casing character CH.  Return whether casing changed the
-   character.
+/* Save in BUF result of casing character CH if Turkic casing rules apply.
 
    If not-NULL, NEXT points to the next character in the cased string.  If NULL,
    it is assumed current character is the last one being cased.  This is used to
    apply some rules which depend on proceeding state.
 
-   This is like case_single_character but also handles one-to-many casing
-   rules. */
-static bool
-case_character (struct casing_str_buf *buf, struct casing_context *ctx,
-		int ch, const unsigned char *next)
+   FLAG is a normalised flag (as returned by normalise_flag function).
+
+   Return -2 (RES_NOT_TOUCHED) if Turkic rules did not apply, no changes were
+   made and other casing rules should be tried.  Otherwise, meaning of return
+   values is the same as in case_characters function. */
+static int
+maybe_case_turkic (struct casing_str_buf *buf, struct casing_context *ctx,
+		   enum case_action flag, int ch, const char *next) {
+  bool dot_above = false;
+  int cased = ch;
+
+  if (flag == CASE_NO_ACTION || ctx->special != SPECIAL_TR)
+    return RES_NOT_TOUCHED;
+
+  switch (ch) {
+  case 'I':
+    if (flag == CASE_DOWN)
+      {
+	dot_above = next && STRING_CHAR (next) == COMBINING_DOT_ABOVE;
+	cased = dot_above ? 'i' : SMALL_DOTLESS_I;
+      }
+    break;
+
+  case 'i':
+    if (flag == CASE_UP || flag == CASE_CAPITALIZE)
+      cased = CAPITAL_DOTTED_I;
+    break;
+
+  case CAPITAL_DOTTED_I:
+    if (flag == CASE_DOWN)
+      cased = 'i';
+    break;
+
+  case SMALL_DOTLESS_I:
+    if (flag == CASE_UP || flag == CASE_CAPITALIZE)
+      cased = 'I';
+    break;
+
+  default:
+    return RES_NOT_TOUCHED;
+  }
+
+  ctx->inword = true;
+  buf->len_chars = 1;
+  buf->len_bytes = CHAR_STRING (cased, buf->data);
+  if (dot_above)
+    return CHAR_BYTES (COMBINING_DOT_ABOVE);
+  else
+    return ch == cased ? RES_NO_CHANGE : RES_CHANGED;
+}
+\f
+/* Save in BUF result of casing character CH.
+
+   If not-NULL, NEXT points to the next character in the cased string.  If NULL,
+   it is assumed current character is the last one being cased.  This is used to
+   apply some rules which depend on proceeding state.
+
+   Return:
+   • -1 (RES_NO_CHANGE) if character has not been changed,
+   • 0 (RES_CHANGED) if the character has changed or
+   • a positive number if the character CH and the one following it (pointed by
+     NEXT) map to character saved in BUF.  Returned value is the length in bytes
+     of the next character.
+
+   This is like case_single_character but also many-to-many casing rules. */
+static int
+case_characters (struct casing_str_buf *buf, struct casing_context *ctx,
+		 int ch, const unsigned char *next)
 {
-  bool changed, was_inword;
+  enum case_action flag = normalise_flag (ctx);
+  int ret;
 
-  was_inword = ctx->inword;
-  changed = case_character_impl (buf, ctx, ch);
+  ret = maybe_case_turkic (buf, ctx, flag, ch, next);
+  if (ret != RES_NOT_TOUCHED)
+    return ret;
 
-  /* If we have just down-cased a capital sigma and the next character no longer
-     has a word syntax (i.e. current character is end of word), use final
-     sigma. */
-  if (was_inword && ch == CAPITAL_SIGMA && changed &&
-      (!next || SYNTAX (STRING_CHAR (next)) != Sword))
-    {
-      buf->len_bytes = CHAR_STRING (SMALL_FINAL_SIGMA, buf->data);
-      buf->len_chars = 1;
-    }
+  ret = maybe_case_greek (buf, ctx, flag, ch, next);
+  if (ret != RES_NOT_TOUCHED)
+    return ret;
 
-  return changed;
+  return case_character_impl (buf, ctx, flag, ch);
 }
 \f
 static Lisp_Object
@@ -321,7 +438,6 @@ do_casify_multibyte_string (struct casing_context *ctx, Lisp_Object obj)
   typedef char static_assertion[offsetof(struct casing_str_buf, data) ? -1 : 1];
 
   ptrdiff_t size = SCHARS (obj), n;
-  int ch;
   USE_SAFE_ALLOCA;
   if (INT_MULTIPLY_WRAPV (size, MAX_MULTIBYTE_LENGTH, &n) ||
       INT_ADD_WRAPV (n, sizeof(struct casing_str_buf), &n))
@@ -331,12 +447,17 @@ do_casify_multibyte_string (struct casing_context *ctx, Lisp_Object obj)
 
   const unsigned char *src = SDATA (obj);
 
-  for (n = 0; size; --size)
+  n = 0;
+  while (size)
     {
+      int ch, ret;
       if (dst_end - o < sizeof(struct casing_str_buf))
 	string_overflow ();
       ch = STRING_CHAR_ADVANCE (src);
-      case_character ((void *)o, ctx, ch, size > 1 ? src : NULL);
+      ret = case_characters ((void *)o, ctx, ch, size > 1 ? src : NULL);
+      if (ret > RES_CHANGED)
+	src += ret;
+      size -= ret > RES_CHANGED ? 2 : 1;
       n += ((struct casing_str_buf *)o)->len_chars;
       o += ((struct casing_str_buf *)o)->len_bytes;
     }
@@ -487,44 +608,50 @@ do_casify_multibyte_region (struct casing_context *ctx,
   ptrdiff_t pos = *startp, pos_byte = CHAR_TO_BYTE (pos), size = *endp - pos;
   ptrdiff_t opoint = PT, added = 0;
   struct casing_str_buf buf;
-  bool changed;
-  int ch, len;
+  int ch, len_bytes, len_chars, ret;
 
-  for (; size; --size)
+  while (size)
     {
-      ch = STRING_CHAR_AND_LENGTH (BYTE_POS_ADDR (pos_byte), len);
-      changed = case_character (
+      ch = STRING_CHAR_AND_LENGTH (BYTE_POS_ADDR (pos_byte), len_bytes);
+      ret = case_characters (
 	  &buf, ctx, ch,
-	  size > 1 ? BYTE_POS_ADDR (pos_byte + len) : NULL);
-
-      if (!changed)
-	{
-	  pos_byte += len;
-	  ++pos;
-	  continue;
-	}
+	  size > 1 ? BYTE_POS_ADDR (pos_byte + len_bytes) : NULL);
+      len_chars = 1;
 
-      last = pos + buf.len_chars;
-      if (first < 0)
-	first = pos;
-
-      if (buf.len_chars == 1 && buf.len_bytes == len)
-	memcpy (BYTE_POS_ADDR (pos_byte), buf.data, len);
-      else
-	{
-	  /* Replace one character with the other(s), keeping text
-	     properties the same.  */
-	  replace_range_2 (pos, pos_byte, pos + 1, pos_byte + len,
-			   (const char *) buf.data, buf.len_chars,
-			   buf.len_bytes,
-			   0);
-	  added += (ptrdiff_t) buf.len_chars - 1;
-	  if (opoint > pos)
-	    opoint += (ptrdiff_t) buf.len_chars - 1;
-	}
-
-      pos_byte += buf.len_bytes;
-      pos += buf.len_chars;
+      switch (ret) {
+      default:
+	len_chars += 1;
+	/* FALL THROUGH */
+
+      case RES_CHANGED:
+	len_bytes += ret;
+	len_chars = ret ? 2 : 1;
+
+	last = pos + buf.len_chars;
+	if (first < 0)
+	  first = pos;
+
+	if (ret == 0 && buf.len_chars == 1 && buf.len_bytes == len_bytes)
+	  memcpy (BYTE_POS_ADDR (pos_byte), buf.data, len_bytes);
+	else
+	  {
+	    /* Replace one character with the other(s), keeping text
+	       properties the same.  */
+	    replace_range_2 (pos, pos_byte, pos + len_chars, pos_byte + len_bytes,
+			     (const char *) buf.data, buf.len_chars,
+			     buf.len_bytes,
+			     0);
+	    added += buf.len_chars - len_chars;
+	    if (opoint > pos)
+	      opoint += buf.len_chars - len_chars;
+	  }
+
+	/* FALL THOUGH */
+      case RES_NO_CHANGE:
+	size -= len_chars;
+	pos += buf.len_chars;
+	pos_byte += buf.len_bytes;
+      }
     }
 
   if (PT != opoint)
diff --git a/test/src/casefiddle-tests.el b/test/src/casefiddle-tests.el
index 5e38a97d256..ce1bb18dd40 100644
--- a/test/src/casefiddle-tests.el
+++ b/test/src/casefiddle-tests.el
@@ -180,8 +180,8 @@ casefiddle-tests--test-casing
   (should-not
    (with-temp-buffer
      (casefiddle-tests--test-casing
-      ;; input     upper     lower    capitalize up-initials
-      '(("Foo baR" "FOO BAR" "foo bar" "Foo Bar" "Foo BaR")
+      ;; input     upper     lower    capitalize up-initials  [locale]
+      `(("Foo baR" "FOO BAR" "foo bar" "Foo Bar" "Foo BaR")
         ("Ⅷ ⅷ" "Ⅷ Ⅷ" "ⅷ ⅷ" "Ⅷ Ⅷ" "Ⅷ Ⅷ")
         ;; "DžUNGLA" is an unfortunate result but it’s really best we can
         ;; do while still being consistent.  Hopefully, users only ever
@@ -205,7 +205,43 @@ casefiddle-tests--test-casing
 
         ;; Dutch 'ij' is capitalised as single digraph.
         ("ijsland" "IJSLAND" "ijsland" "Ijsland" "Ijsland")
-        ("ijsland" "IJSLAND" "ijsland" "IJsland" "IJsland" "nl"))))))
+        ("ijsland" "IJSLAND" "ijsland" "IJsland" "IJsland" "nl")
+
+        ;; There is a language-independent special casing rule which
+        ;; converts İ into i followed by combining dot above that’s why we
+        ;; get the weird \u0307.  Conceptually, it converts i with
+        ;; a soft-dot into an i with a hard-dot so it makes some doze of
+        ;; sense.
+        ("İstanbul" "İSTANBUL" "i\u0307stanbul" "İstanbul" "İstanbul")
+        ("İstanbul" "İSTANBUL" "istanbul" "İstanbul" "İstanbul" "tr")
+        ("İstanbul" "İSTANBUL" "istanbul" "İstanbul" "İstanbul" "az")
+        (,(decode-coding-string "istanbul" 'no-conversion-multibyte) ; make it multibyte
+         "ISTANBUL" "istanbul" "Istanbul" "Istanbul")
+        (,(decode-coding-string "istanbul" 'no-conversion-multibyte)
+         "İSTANBUL" "istanbul" "İstanbul" "İstanbul" "tr")
+        (,(decode-coding-string "istanbul" 'no-conversion-multibyte)
+         "İSTANBUL" "istanbul" "İstanbul" "İstanbul" "az")
+        (,(decode-coding-string "Irmak" 'no-conversion-multibyte)
+         "IRMAK" "irmak" "Irmak" "Irmak")
+        (,(decode-coding-string "Irmak" 'no-conversion-multibyte)
+         "IRMAK" "ırmak" "Irmak" "Irmak" "tr")
+        (,(decode-coding-string "Irmak" 'no-conversion-multibyte)
+         "IRMAK" "ırmak" "Irmak" "Irmak" "az")
+        ;; FIXME: We explicitly exclude ı→I mapping from the case tables
+        ;; in characters.el which is why instead of:
+        ;;("ırmak" "IRMAK" "ırmak" "Irmak" "Irmak")
+        ;; we actually get:
+        ("ırmak" "ıRMAK" "ırmak" "Irmak" "Irmak")
+        ;; ‘But wait,’ you ask, ‘why capitalisation works’?  This is
+        ;; because those bypass case-table and use character’s Unicode
+        ;; titlecase property.
+        ("ırmak" "IRMAK" "ırmak" "Irmak" "Irmak" "tr")
+        ("ırmak" "IRMAK" "ırmak" "Irmak" "Irmak" "az")
+        ;; And for some combining dot above removal.
+        ("I\u0307si\u0307s" "I\u0307Sİ\u0307S" "isi\u0307s"
+         "I\u0307si\u0307s" "I\u0307si\u0307s" "tr")
+        ("I\u0307sI\u0307s" "I\u0307SI\u0307S" "isis"
+         "I\u0307sis" "I\u0307sI\u0307s" "tr"))))))
 
 (ert-deftest casefiddle-tests-casing-byte8 ()
   (should-not
-- 
2.12.0.246.ga2ecc84866-goog






^ permalink raw reply related	[flat|nested] 89+ messages in thread

* bug#24603: [PATCHv5 10/11] Implement casing rules for Lithuanian (bug#24603)
  2017-03-09 21:51 ` bug#24603: [PATCHv5 00/11] Casing improvements Michal Nazarewicz
                     ` (8 preceding siblings ...)
  2017-03-09 21:51   ` bug#24603: [PATCHv5 09/11] Implement Turkic dotless and dotted i casing rules (bug#24603) Michal Nazarewicz
@ 2017-03-09 21:51   ` Michal Nazarewicz
  2017-03-09 21:51   ` bug#24603: [PATCHv5 11/11] Implement Irish casing rules (bug#24603) Michal Nazarewicz
                     ` (2 subsequent siblings)
  12 siblings, 0 replies; 89+ messages in thread
From: Michal Nazarewicz @ 2017-03-09 21:51 UTC (permalink / raw)
  To: 24603, eliz

In Lithuanian, tittle above lower case i and j are retained even if
there are other diacritics above present.  For that to work, an explicit
combining dot above must be added after i and j or otherwise the
rendering engine will remove the tittle.

* src/casefiddle.c (struct casing_context, prepare_casing_context): Add
SPECIAL_LT, SPECIAL_LT_DEL_DOT_ABOVE and SPECIAL_LT_INS_DOT_ABOVE
special flag valus for handling Lithuanian.  Set the flag to SPECIAL_LT
if buffer is in Lithuanian.
(maybe_case_lithuanian): New function which implements Lithuanian rules.
(case_characters): Make use of maybe_case_lithuanian.

* test/src/casefiddle-tests.el (casefiddle-tests-casing): Add test cases
for Lithuanian rules.
---
 src/casefiddle.c             | 178 ++++++++++++++++++++++++++++++++++++++++---
 test/src/casefiddle-tests.el |  27 ++++++-
 2 files changed, 195 insertions(+), 10 deletions(-)

diff --git a/src/casefiddle.c b/src/casefiddle.c
index 4785ebaddc4..a33bac7d21e 100644
--- a/src/casefiddle.c
+++ b/src/casefiddle.c
@@ -77,7 +77,15 @@ struct casing_context {
     SPECIAL_NL_UPCASE_J,
 
     /* Handle Azerbaijani and Turkish dotted and dotless i. */
-    SPECIAL_TR
+    SPECIAL_TR,
+
+    /* Apply Lithuanian rules for i’s and j’s tittle. */
+    SPECIAL_LT,
+    /* As above plus look out for combining dot above to delete. */
+    SPECIAL_LT_DEL_DOT_ABOVE,
+    /* As above plus look out for diacritics combining above because
+       we may need to inject dot above before them. */
+    SPECIAL_LT_INS_DOT_ABOVE,
   } special;
 };
 
@@ -116,6 +124,9 @@ prepare_casing_context (struct casing_context *ctx,
     case ('t' << 8) | 'r':  /* Turkish */
     case ('a' << 8) | 'z':  /* Azerbaijani */
       ctx->special = SPECIAL_TR;
+      break;
+    case ('l' << 8) | 't':  /* Lithuania */
+      ctx->special = SPECIAL_LT;
     }
 }
 
@@ -362,6 +373,142 @@ maybe_case_turkic (struct casing_str_buf *buf, struct casing_context *ctx,
     return ch == cased ? RES_NO_CHANGE : RES_CHANGED;
 }
 \f
+/* Lithuanian retains tittle in lower case i and j when there are more
+   accents above those letters. */
+
+#define CAPITAL_I_WITH_GRAVE  0x0CC
+#define CAPITAL_I_WITH_ACUTE  0x0CD
+#define CAPITAL_I_WITH_TILDE  0x128
+#define CAPITAL_I_WITH_OGONEK 0x12E
+#define SMALL_I_WITH_OGONEK   0x12F
+#define COMBINING_GRAVE_ABOVE 0x300
+#define COMBINING_ACUTE_ABOVE 0x301
+#define COMBINING_TILDE_ABOVE 0x303
+#define COMBINING_OGONEK      0x328
+
+/* Save in BUF result of casing character CH if Lithuanian casing rules apply.
+
+   If not-NULL, NEXT points to the next character in the cased string.  If NULL,
+   it is assumed current character is the last one being cased.  This is used to
+   apply some rules which depend on proceeding state.
+
+   FLAG is a normalised flag (as returned by normalise_flag function).
+
+   Return -2 (RES_NOT_TOUCHED) if Lithuanian rules did not apply, no changes
+   were made and other casing rules should be tried.  Otherwise, meaning of
+   return values is the same as in case_characters function. */
+static int
+maybe_case_lithuanian (struct casing_str_buf *buf, struct casing_context *ctx,
+		       enum case_action flag, int ch)
+{
+  switch (ctx->special) {
+  case SPECIAL_LT:
+    break;
+
+  case SPECIAL_LT_DEL_DOT_ABOVE:
+    /* When upper-casing i or j, a combining dot above that follows it must be
+       removed.  This is true even if there’s a combining ogonek in between.
+       But, if there’s another character combining above in between, combining
+       dot needs to stay (since the dot will be rendered above the other
+       diacritic). */
+    switch (ch) {
+    case COMBINING_DOT_ABOVE:
+      buf->len_chars = buf->len_bytes = 0;
+      ctx->special = SPECIAL_LT;
+      return RES_CHANGED;
+    case COMBINING_GRAVE_ABOVE:
+    case COMBINING_ACUTE_ABOVE:
+    case COMBINING_TILDE_ABOVE:
+      ctx->special = SPECIAL_LT;
+      return RES_NOT_TOUCHED;
+    case COMBINING_OGONEK:
+      return RES_NOT_TOUCHED;
+    default:
+      ctx->special = SPECIAL_LT;
+    }
+    break;
+
+  case SPECIAL_LT_INS_DOT_ABOVE:
+    /* When lower-casing I or J, if the letter has any accents above,
+       a combining dot above must be added before them.  If we are here, it
+       means that we have lower cased I or J and we’re now on the lookout for
+       accents combining above. */
+    switch (ch) {
+    case COMBINING_GRAVE_ABOVE:
+    case COMBINING_ACUTE_ABOVE:
+    case COMBINING_TILDE_ABOVE:
+      buf->len_chars = 2;
+      buf->len_bytes = CHAR_STRING (COMBINING_DOT_ABOVE, buf->data);
+      buf->len_bytes += CHAR_STRING (ch, buf->data + buf->len_bytes);
+      ctx->special = SPECIAL_LT;
+      return RES_CHANGED;
+    case COMBINING_OGONEK:
+      return RES_NOT_TOUCHED;
+    default:
+      ctx->special = SPECIAL_LT;
+    }
+    break;
+
+  default:
+    return RES_NOT_TOUCHED;
+  }
+
+  switch (flag) {
+  case CASE_UP:
+  case CASE_CAPITALIZE:
+    if (ch == 'i' || ch == 'j')
+      {
+	buf->data[0] = ch ^ ('i' ^ 'I');
+	buf->len_bytes = 1;
+      }
+    else if (ch == SMALL_I_WITH_OGONEK)
+      buf->len_bytes = CHAR_STRING (CAPITAL_I_WITH_OGONEK, buf->data);
+    else
+      break;
+    buf->len_chars = 1;
+    /* Change the state so we’re on the lookout for combining dot above. */
+    ctx->special = SPECIAL_LT_DEL_DOT_ABOVE;
+    return RES_CHANGED;
+
+  case CASE_DOWN:
+    /* Turning I or J to lower case requires combining dot above to be included
+       IF there are any other characters combining above present.  This is so
+       that the tittle is preserved. */
+    switch (ch) {
+    case CAPITAL_I_WITH_GRAVE:
+      ch = 0x80;  /* U+300, "\xCC\x80", combining grave accent */
+      goto has_accent;
+    case CAPITAL_I_WITH_ACUTE:
+      ch = 0x81;  /* U+301, "\xCC \x81", combining acute accent */
+      goto has_accent;
+    case CAPITAL_I_WITH_TILDE:
+      ch = 0x83;  /* U+303, "\xCC\x83", combining tilde */
+    has_accent:
+      memcpy (buf->data, "i\xCC\x87\xCC", 4);
+      buf->data[4] = ch;
+      buf->len_chars = 3;
+      buf->len_bytes = 5;
+      return RES_CHANGED;
+
+    case 'I':
+    case 'J':
+      buf->data[0] = ch ^ ('i' ^ 'I');
+      buf->len_bytes = 1;
+      if (false)
+    case CAPITAL_I_WITH_OGONEK:
+	buf->len_bytes = CHAR_STRING (SMALL_I_WITH_OGONEK, buf->data);
+      buf->len_chars = 1;
+      /* Change the state so we’re on the lookout for diacritics combining
+	 above.  If one is found, we need to add combining dot above. */
+      ctx->special = SPECIAL_LT_INS_DOT_ABOVE;
+      return RES_CHANGED;
+    }
+    break;
+  }
+
+  return RES_NOT_TOUCHED;
+}
+\f
 /* Save in BUF result of casing character CH.
 
    If not-NULL, NEXT points to the next character in the cased string.  If NULL,
@@ -381,17 +528,30 @@ case_characters (struct casing_str_buf *buf, struct casing_context *ctx,
 		 int ch, const unsigned char *next)
 {
   enum case_action flag = normalise_flag (ctx);
-  int ret;
+  int ret = RES_NOT_TOUCHED;
+
+  switch (ctx->special) {
+  case SPECIAL_NONE:
+    break;
+
+  case SPECIAL_TR:
+    ret = maybe_case_turkic (buf, ctx, flag, ch, next);
+    break;
+
+  default:
+  /* case SPECIAL_LT: */
+  /* case SPECIAL_LT_DEL_DOT_ABOVE: */
+  /* case SPECIAL_LT_INS_DOT_ABOVE: */
+    ret = maybe_case_lithuanian (buf, ctx, flag, ch);
+  }
 
-  ret = maybe_case_turkic (buf, ctx, flag, ch, next);
-  if (ret != RES_NOT_TOUCHED)
-    return ret;
+  if (ret == RES_NOT_TOUCHED)
+    ret = maybe_case_greek (buf, ctx, flag, ch, next);
 
-  ret = maybe_case_greek (buf, ctx, flag, ch, next);
-  if (ret != RES_NOT_TOUCHED)
-    return ret;
+  if (ret == RES_NOT_TOUCHED)
+    ret = case_character_impl (buf, ctx, flag, ch);
 
-  return case_character_impl (buf, ctx, flag, ch);
+  return ret;
 }
 \f
 static Lisp_Object
diff --git a/test/src/casefiddle-tests.el b/test/src/casefiddle-tests.el
index ce1bb18dd40..f7b0da41029 100644
--- a/test/src/casefiddle-tests.el
+++ b/test/src/casefiddle-tests.el
@@ -241,7 +241,32 @@ casefiddle-tests--test-casing
         ("I\u0307si\u0307s" "I\u0307Sİ\u0307S" "isi\u0307s"
          "I\u0307si\u0307s" "I\u0307si\u0307s" "tr")
         ("I\u0307sI\u0307s" "I\u0307SI\u0307S" "isis"
-         "I\u0307sis" "I\u0307sI\u0307s" "tr"))))))
+         "I\u0307sis" "I\u0307sI\u0307s" "tr")
+
+        ;; Test combining dot above in inserted when needed when lower
+        ;; casing I or J.
+        ("I\u0328\u0300"          ; I + ogonek + grave
+         "I\u0328\u0300" "i\u0328\u0307\u0300"
+         "I\u0328\u0300" "I\u0328\u0300" "lt")
+
+        ("J\u0328\u0300"          ; J + ogonek + grave
+         "J\u0328\u0300" "j\u0328\u0307\u0300"
+         "J\u0328\u0300" "J\u0328\u0300" "lt")
+
+        ("Į\u0300"          ; I-ogonek + grave
+         "Į\u0300" "į\u0307\u0300" "Į\u0300" "Į\u0300" "lt")
+
+        ("Ì Í Ĩ"
+         "Ì Í Ĩ" "i\u0307\u0300 i\u0307\u0301 i\u0307\u0303"
+         "Ì Í Ĩ" "Ì Í Ĩ" "lt")
+
+        ;; Test combining dot above in removed when upper casing i or j.
+        ("i\u0328\u0307"          ; i + ogonek + dot above
+         "I\u0328" "i\u0328\u0307" "I\u0328" "I\u0328" "lt")
+        ("j\u0328\u0307"          ; j + ogonek + dot above
+         "J\u0328" "j\u0328\u0307" "J\u0328" "J\u0328" "lt")
+        ("į\u0307"                ; i-ogonek + dot above
+         "Į" "į\u0307" "Į" "Į" "lt"))))))
 
 (ert-deftest casefiddle-tests-casing-byte8 ()
   (should-not
-- 
2.12.0.246.ga2ecc84866-goog






^ permalink raw reply related	[flat|nested] 89+ messages in thread

* bug#24603: [PATCHv5 11/11] Implement Irish casing rules  (bug#24603)
  2017-03-09 21:51 ` bug#24603: [PATCHv5 00/11] Casing improvements Michal Nazarewicz
                     ` (9 preceding siblings ...)
  2017-03-09 21:51   ` bug#24603: [PATCHv5 10/11] Implement casing rules for Lithuanian (bug#24603) Michal Nazarewicz
@ 2017-03-09 21:51   ` Michal Nazarewicz
  2017-03-11  9:44     ` Eli Zaretskii
  2017-03-11 10:00   ` bug#24603: [PATCHv5 00/11] Casing improvements Eli Zaretskii
  2017-03-21  1:27   ` bug#24603: [PATCHv6 0/6] Casing improvements, language-independent part Michal Nazarewicz
  12 siblings, 1 reply; 89+ messages in thread
From: Michal Nazarewicz @ 2017-03-09 21:51 UTC (permalink / raw)
  To: 24603, eliz

Add rules for casing Irish words whose rules are quite complicated
and require some letters to remain lower case when upper casing,
involve insertion of dashes and various other transformations.

* src/casefiddle.c (struct casing_context): Add flags for denoting
Irish casing rules are in effect.
(prepare_casing_context): Detect Irish language and set ctx->special
accordingly.
(irish_upcase, irish_downcase): New functions for upcasing and
downcasing Irish letters.
(is_irish_vowel, is_irish_uc_vowel, is_irish_lc_vowel): New functions
detecting whether a letter is an Irish vowel.
(maybe_case_irish): New function implementing Irish casing rules.
(case_characters): Make use of maybe_case_irish.

* test/src/casefiddle-resources/irish-lowercase-1.txt,
test/src/casefiddle-resources/irish-lowercase-1-ref.txt,
test/src/casefiddle-resources/irish-uppercase-1.txt,
test/src/casefiddle-resources/irish-uppercase-1-ref.txt: New files
with test cases for Irish capitalisation.  The files are copied from
Mozilla’s test suite.

* test/src/casefiddle-tests.el (casefiddle-tests--resources-dir): New
variable to point to aforementioned test case files.
(casefiddle-tests--test-casing): Support missing expected strings.
(casefiddle--read-lines): New helper functions for reading lines from
a file.
(casefiddle-test-irish): Apply test cases reada from the Irish test
case files.
---
 src/casefiddle.c                                   | 313 +++++++++++++++++++++
 .../casefiddle-resources/irish-lowercase-1-ref.txt | 211 ++++++++++++++
 .../src/casefiddle-resources/irish-lowercase-1.txt | 211 ++++++++++++++
 .../casefiddle-resources/irish-uppercase-1-ref.txt | 105 +++++++
 .../src/casefiddle-resources/irish-uppercase-1.txt | 105 +++++++
 test/src/casefiddle-tests.el                       |  58 +++-
 6 files changed, 992 insertions(+), 11 deletions(-)
 create mode 100644 test/src/casefiddle-resources/irish-lowercase-1-ref.txt
 create mode 100644 test/src/casefiddle-resources/irish-lowercase-1.txt
 create mode 100644 test/src/casefiddle-resources/irish-uppercase-1-ref.txt
 create mode 100644 test/src/casefiddle-resources/irish-uppercase-1.txt

diff --git a/src/casefiddle.c b/src/casefiddle.c
index a33bac7d21e..3352fb6795a 100644
--- a/src/casefiddle.c
+++ b/src/casefiddle.c
@@ -86,6 +86,24 @@ struct casing_context {
     /* As above plus look out for diacritics combining above because
        we may need to inject dot above before them. */
     SPECIAL_LT_INS_DOT_ABOVE,
+
+    /* Flags for Irish word capitalising rules.  Those are insane, see
+       https://bugzilla.mozilla.org/show_bug.cgi?id=1018805 and
+       https://bugzilla.mozilla.org/show_bug.cgi?id=1014639 for reference. */
+    /* Irish handling enabled; we are lower-casing words. */
+    SPECIAL_GA_LC,
+    /* Irish handling enabled; we are upper-casing words or capitalising. */
+    SPECIAL_GA_UC,
+    /* Upper-case next character. */
+    SPECIAL_GA_UC_NEXT,
+    /* We’re in the middle of a potential ‘bhf’ or ‘bhF’ triplet. */
+    SPECIAL_GA_TRIPLET_B = 'b',
+    /* We’re in the middle of a potential ‘n-{v}’ triplet. */
+    SPECIAL_GA_TRIPLET_N = 'n',
+    /* We’re in the middle of a potential triplet starting with ‘ts’ or ‘tS’. */
+    SPECIAL_GA_TRIPLET_T = 't'
+    /* Yes, it matters that the last three flags equal the characters for b,
+       n or t. */
   } special;
 };
 
@@ -127,6 +145,9 @@ prepare_casing_context (struct casing_context *ctx,
       break;
     case ('l' << 8) | 't':  /* Lithuania */
       ctx->special = SPECIAL_LT;
+      break;
+    case ('g' << 8) | 'a':  /* Irish */
+      ctx->special = flag == CASE_DOWN ? SPECIAL_GA_LC : SPECIAL_GA_UC;
     }
 }
 
@@ -509,6 +530,289 @@ maybe_case_lithuanian (struct casing_str_buf *buf, struct casing_context *ctx,
   return RES_NOT_TOUCHED;
 }
 \f
+/* The naïve method works for ASCII letters but also non-ASCII Irish
+   vowels and second byte in UTF-8 representation of those vowels. */
+static int irish_upcase(int ch) { return ch & ~('a' ^ 'A'); }
+static int irish_downcase(int ch) { return ch | ('a' ^ 'A'); }
+
+/* A byte starting two-byte sequence of non-ASCII Irish vowels. */
+#define GA_UTF8_FIRST_BYTE 0xC3
+
+/* Classifies UTF-8 encoded character at *CH as Irish vowel or not. */
+static enum {
+  GA_NOT_VOWEL = 0,
+  GA_UC_VOWEL = 1,
+  GA_LC_VOWEL = 2
+} is_irish_vowel(const unsigned char *ch) {
+  switch (*ch) {
+  case 'A': case 'E': case 'I': case 'O': case 'U':
+    return GA_UC_VOWEL;
+  case 'a': case 'e': case 'i': case 'o': case 'u':
+    return GA_LC_VOWEL;
+  case GA_UTF8_FIRST_BYTE:
+    switch (ch[1]) {
+    case 0x81:  /* C3 81 → U+00C1 → Á */
+    case 0x89:  /* C3 89 → U+00C9 → É */
+    case 0x8D:  /* C3 8D → U+00CD → Í */
+    case 0x93:  /* C3 93 → U+00D3 → Ó */
+    case 0x9A:  /* C3 9A → U+00DA → Ú */
+      return GA_UC_VOWEL;
+    case 0xA1:  /* C3 A1 → U+00E1 → á */
+    case 0xA9:  /* C3 A9 → U+00E9 → é */
+    case 0xAD:  /* C3 AD → U+00ED → í */
+    case 0xB3:  /* C3 B3 → U+00F3 → ó */
+    case 0xBA:  /* C3 BA → U+00FA → ú */
+      return GA_LC_VOWEL;
+    }
+  }
+  return GA_NOT_VOWEL;
+}
+
+/* Return whether *CH is an upper-case Irish vowel encoded in UTF-8. */
+static bool
+is_irish_uc_vowel(const char *ch) {
+  return is_irish_vowel(ch) == GA_UC_VOWEL;
+}
+
+/* Return whether *CH is an lower-case Irish vowel encoded in UTF-8. */
+static bool
+is_irish_lc_vowel(const char *ch) {
+  return is_irish_vowel(ch) == GA_LC_VOWEL;
+}
+
+/* Save in BUF result of casing character CH if Irish casing rules apply.
+
+   If not-NULL, NEXT points to the next character in the cased string.  If NULL,
+   it is assumed current character is the last one being cased.  This is used to
+   apply some rules which depend on proceeding state.
+
+   FLAG is a normalised flag (as returned by normalise_flag function).
+
+   Return -2 (RES_NOT_TOUCHED) if Irish rules did not apply, no changes
+   were made and other casing rules should be tried.  Otherwise, meaning of
+   return values is the same as in case_characters function. */
+static int
+maybe_case_irish (struct casing_str_buf *buf, struct casing_context *ctx,
+		  enum case_action flag, int ch, const unsigned char *next) {
+  unsigned n;
+
+  switch (ctx->special) {
+  case SPECIAL_GA_LC:
+    /* We are lower-casing words and Irish rules are enabled.  See
+       https://bugzilla.mozilla.org/show_bug.cgi?id=1018805 for reference.  The
+       rules are:
+
+         t{V} → t-{v}
+         n{V} → n-{v}
+
+       {V} denotes upper-case Irish vowel and {v} denotes a lower-case one. */
+
+    if (!next ||
+	ctx->inword ||
+	(ch != 't' && ch != 'n') ||
+	!is_irish_uc_vowel(next))
+      return RES_NOT_TOUCHED;
+
+    ctx->inword = true;
+    buf->len_chars = 3;
+    buf->data[0] = ch;
+    buf->data[1] = '-';
+    if (*next < 0x80) {
+      buf->data[2] = irish_downcase(next[0]);
+      buf->len_bytes = 3;
+      return 1;
+    } else {
+      buf->data[2] = next[0];
+      buf->data[3] = irish_downcase(next[1]);
+      buf->len_bytes = 4;
+      return 2;
+    }
+
+  case SPECIAL_GA_UC:
+    /* We are upper-casing or capitalising words and Irish rules are enabled.
+       See https://bugzilla.mozilla.org/show_bug.cgi?id=1014639 for
+       reference. The rules are:
+
+	 h{V}  → h{V}
+	 n{V}  → n{V}
+	 t{V}  → t{V}
+
+	 bp    → bP
+	 bP    → bP
+	 dt    → dT
+	 dT    → dT
+	 gc    → gC
+	 gC    → gC
+	 mb    → mB
+	 mB    → mB
+	 nd    → nD
+	 nD    → nD
+	 ng    → nG
+	 nG    → nG
+
+	 bhf   → bhF
+	 bhF   → bhF
+
+	 n-{v} → n{V}
+	 t-{v} → t{V}
+
+	 tSL   → tSL
+	 tSl   → tSL
+	 tsl   → tSL
+	 tSN   → tSN
+	 tsn   → tSN
+	 tSn   → tSN
+	 tSR   → tSR
+	 tSr   → tSR
+	 tsr   → tSR
+	 tS{V} → tS{V}
+	 tS{v} → tS{V}
+	 ts{v} → tS{V}
+
+       {V} denotes upper-case Irish vowel and {v} denotes a lower-case one. */
+
+    if (!next || ctx->inword || ch < 'a' || ch > 'z')
+      return RES_NOT_TOUCHED;
+
+    n = irish_upcase(*next);
+    if (((ch == 'h' || ch == 'n' || ch == 't') && is_irish_uc_vowel(next)) ||
+	(ch == 'b' && n == 'P') ||
+	(ch == 'd' && n == 'T') ||
+	(ch == 'g' && n == 'C') ||
+	(ch == 'm' && n == 'B') ||
+	(ch == 'n' && n == 'D') ||
+	(ch == 'n' && n == 'G'))
+      {
+	ctx->inword = true;
+	ctx->special = SPECIAL_GA_UC_NEXT;
+	buf->data[0] = ch;
+	buf->len_chars = 1;
+	buf->len_bytes = 1;
+	return RES_NO_CHANGE;
+      }
+
+    if ((ch == 'b' && *next == 'h') || (ch == 't' && n == 'S') ||
+	(ch == 't' && *next == '-') || (ch == 'n' && *next == '-'))
+      {
+	/* We can only look at two characters at a time but here we need to make
+	   a decision based on a 3-character sequence.
+
+	   Let’s return empty string for now, remember the current character and
+	   when we’ll be dealing with the next character we’ll be able to see
+	   three characters.
+
+	   Downside of this approach is that we cannot always correctly mark
+	   buffer as changed.  Namely, ‘bhF’ triplet does not need to be changed
+	   but sadly we'll mark this as modified. */
+	ctx->inword = true;
+	ctx->special = ch;
+	buf->len_chars = 0;
+	buf->len_bytes = 0;
+	return RES_CHANGED;
+      }
+
+    return RES_NOT_TOUCHED;
+
+  case SPECIAL_GA_UC_NEXT:
+    ctx->special = SPECIAL_GA_UC;
+    n = irish_upcase(ch);
+    buf->len_bytes = CHAR_STRING (n, buf->data);
+    buf->len_chars = 1;
+    return n == ch ? RES_NO_CHANGE : RES_CHANGED;
+
+  case SPECIAL_GA_TRIPLET_B:
+  case SPECIAL_GA_TRIPLET_N:
+  case SPECIAL_GA_TRIPLET_T:
+    /* We’re here after encountering a possible beginning of a three-character
+       sequence that needs to be handled.  Those are:
+
+	 bhf   → bhF
+	 bhF   → bhF
+
+	 n-{v} → n{V}
+	 t-{v} → t{V}
+
+	 tSL   → tSL
+	 tSl   → tSL
+	 tsl   → tSL
+	 tSN   → tSN
+	 tsn   → tSN
+	 tSn   → tSN
+	 tSR   → tSR
+	 tSr   → tSR
+	 tsr   → tSR
+	 tS{V} → tS{V}
+	 tS{v} → tS{V}
+	 ts{v} → tS{V} */
+
+    if (*next)
+      switch (ch) {
+      case '-':  /* ‘n-’ or ‘t-’ prefix. */
+	if (is_irish_lc_vowel(next))
+	  {
+	    buf->data[0] = ctx->special;
+	    buf->len_chars = 1;
+	    buf->len_bytes = 1;
+	    ctx->special = SPECIAL_GA_UC_NEXT;
+	    return RES_CHANGED;
+	  }
+	break;
+
+      case 'h':  /* ‘bh’ prefix */
+	if (irish_upcase(*next) == 'F') {
+	  ctx->special = SPECIAL_GA_UC;
+	  buf->data[0] = 'b';
+	  buf->data[1] = 'h';
+	  buf->data[2] = 'F';
+	  buf->len_chars = 3;
+	  buf->len_bytes = 3;
+	  return 1;
+	}
+	break;
+
+      case 's':  /* ‘ts’ prefix. */
+	if (*next == 'l' || *next == 'n' || *next == 'r' ||
+	    is_irish_lc_vowel(next))
+	  goto tSU;
+	break;
+
+      case 'S':  /* ‘tS’ prefix. */
+	if (*next == 'l' || *next == 'n' || *next == 'r' ||
+	    *next == 'L' || *next == 'N' || *next == 'R' ||
+	    is_irish_vowel(next))
+	  {
+	  tSU:
+	    /* t{s}{x} → tS{X} */
+	    ctx->special = SPECIAL_GA_UC_NEXT;
+	    buf->data[0] = 't';
+	    buf->data[1] = 'S';
+	    buf->len_chars = 2;
+	    buf->len_bytes = 2;
+	    return RES_CHANGED;
+	  }
+	break;
+      }
+
+    /* Recover from a incorrect guess that it was a triplet. */
+    if (ch == '-') {
+      ctx->inword = false;
+    } else if (ctx->flag == CASE_UP) {
+      ch = irish_upcase(ch);
+    } else if (ctx->flag == CASE_CAPITALIZE) {
+      ch = irish_downcase(ch);
+    }
+
+    buf->data[0] = irish_upcase(ctx->special);
+    buf->data[1] = ch;
+    buf->len_chars = 2;
+    buf->len_bytes = 2;
+    ctx->special = SPECIAL_GA_UC;
+    return RES_CHANGED;
+  }
+
+  return RES_NOT_TOUCHED;
+}
+\f
 /* Save in BUF result of casing character CH.
 
    If not-NULL, NEXT points to the next character in the cased string.  If NULL,
@@ -543,6 +847,15 @@ case_characters (struct casing_str_buf *buf, struct casing_context *ctx,
   /* case SPECIAL_LT_DEL_DOT_ABOVE: */
   /* case SPECIAL_LT_INS_DOT_ABOVE: */
     ret = maybe_case_lithuanian (buf, ctx, flag, ch);
+    break;
+
+  case SPECIAL_GA_LC:
+  case SPECIAL_GA_UC:
+  case SPECIAL_GA_UC_NEXT:
+  case SPECIAL_GA_TRIPLET_B:
+  case SPECIAL_GA_TRIPLET_N:
+  case SPECIAL_GA_TRIPLET_T:
+    ret = maybe_case_irish (buf, ctx, flag, ch, next);
   }
 
   if (ret == RES_NOT_TOUCHED)
diff --git a/test/src/casefiddle-resources/irish-lowercase-1-ref.txt b/test/src/casefiddle-resources/irish-lowercase-1-ref.txt
new file mode 100644
index 00000000000..cbe9f601e9c
--- /dev/null
+++ b/test/src/casefiddle-resources/irish-lowercase-1-ref.txt
@@ -0,0 +1,211 @@
+ár n-acmhainní uisce
+ár n-acmhainní uisce
+ár n-acmhainní uisce
+ár n-acmhainní uisce
+ár n-acmhainní uisce
+ár nathair
+ár nathair
+ár nathair
+n-a shaighdiúir
+gan dul as aca ach le n-a chabhair
+eolaíocht na n-ábhar
+eolaíocht na n-ábhar
+eolaíocht na n-ábhar
+eolaíocht na n-ábhar
+eolaíocht na n-ábhar
+amhrán náisiúnta
+amhrán náisiúnta
+amhrán náisiúnta
+lucht na n-ealaíon
+lucht na n-ealaíon
+lucht na n-ealaíon
+lucht na n-ealaíon
+lucht na n-ealaíon
+neart daoine
+neart daoine
+neart daoine
+ceol na n-éan
+ceol na n-éan
+ceol na n-éan
+ceol na n-éan
+ceol na n-éan
+sa néal
+sa néal
+sa néal
+ord na n-imeachtaí
+ord na n-imeachtaí
+ord na n-imeachtaí
+ord na n-imeachtaí
+ord na n-imeachtaí
+nathair nimhe
+nathair nimhe
+nathair nimhe
+lucht adhartha na n-íomhánna
+lucht adhartha na n-íomhánna
+lucht adhartha na n-íomhánna
+lucht adhartha na n-íomhánna
+lucht adhartha na n-íomhánna
+níos measa
+níos measa
+níos measa
+gnéithe dár n-oidhreacht
+gnéithe dár n-oidhreacht
+gnéithe dár n-oidhreacht
+gnéithe dár n-oidhreacht
+gnéithe dár n-oidhreacht
+duine nochta
+duine nochta
+duine nochta
+cultúr na n-óg
+cultúr na n-óg
+cultúr na n-óg
+cultúr na n-óg
+cultúr na n-óg
+dhá nóiméad
+dhá nóiméad
+dhá nóiméad
+ocht n-uaire sa lá
+ocht n-uaire sa lá
+ocht n-uaire sa lá
+ocht n-uaire sa lá
+ocht n-uaire sa lá
+gúna nua
+gúna nua
+gúna nua
+formhór na n-údarás
+formhór na n-údarás
+formhór na n-údarás
+formhór na n-údarás
+formhór na n-údarás
+imoibreoir núicléach
+imoibreoir núicléach
+imoibreoir núicléach
+sean-airteagal
+seanairteagal
+bunioncaim
+bun-ioncaim
+buanorduithe
+buan-orduithe
+ár n-athair
+ár n-athair
+clár na n-ábhar
+clár na n-ábhar
+ceol na ndaoine
+ceol na ndaoine
+táim i ngrá leat
+táim i ngrá leat
+cén t-am é?
+cén t-am é?
+cén t-am é?
+cén t-am é?
+cén t-am é?
+tar ar ais!
+tar ar ais!
+tá an t-ádh orm inniu!
+tá an t-ádh orm inniu!
+tá an t-ádh orm inniu!
+tá an t-ádh orm inniu!
+tá an t-ádh orm inniu!
+rud tábhachtach
+rud tábhachtach
+rud tábhachtach
+den obair an t-eolas
+den obair an t-eolas
+den obair an t-eolas
+den obair an t-eolas
+den obair an t-eolas
+an t-éileamh a íoc
+an t-éileamh a íoc
+an t-éileamh a íoc
+an t-éileamh a íoc
+an t-éileamh a íoc
+an t-inneall cuardaigh is fearr
+an t-inneall cuardaigh is fearr
+an t-inneall cuardaigh is fearr
+an t-inneall cuardaigh is fearr
+an t-inneall cuardaigh is fearr
+an t-íochtar a chur in uachtar
+an t-íochtar a chur in uachtar
+an t-íochtar a chur in uachtar
+an t-íochtar a chur in uachtar
+an t-íochtar a chur in uachtar
+tabhair an t-ordú seo dó!
+tabhair an t-ordú seo dó!
+tabhair an t-ordú seo dó!
+tabhair an t-ordú seo dó!
+tabhair an t-ordú seo dó!
+tá an t-ór buí aige.
+tá an t-ór buí aige.
+tá an t-ór buí aige.
+tá an t-ór buí aige.
+tá an t-ór buí aige.
+an t-uisce beatha ar an tábla.
+an t-uisce beatha ar an tábla.
+an t-uisce beatha ar an tábla.
+an t-uisce beatha ar an tábla.
+an t-uisce beatha ar an tábla.
+an t-úrscéal is deireanaí
+an t-úrscéal is deireanaí
+an t-úrscéal is deireanaí
+an t-úrscéal is deireanaí
+an t-úrscéal is deireanaí
+dréacht-acht
+dréachtphlean
+dréacht-phlean
+dréacht-íocaíocht
+áitainmneacha
+áit-ainmneacha
+státurraithe
+stát-urraithe
+ar aon tslí
+ar aon tslí
+amach ón tsnáthaid
+amach ón tsnáthaid
+ar an tsráid
+ar an tsráid
+caint an tsráidbhaile
+caint an tsráidbhaile
+cora crua an tsaoil
+cora crua an tsaoil
+bholadh an tsáile
+bholadh an tsáile
+uair sa tseachtain
+uair sa tseachtain
+deireadh an tséasúir
+deireadh an tséasúir
+fear an tsiopa
+fear an tsiopa
+an tsíocháin a choimeád
+an tsíocháin a choimeád
+an tsochaí faisnéise
+an tsochaí faisnéise
+gaoth an tsóláis
+gaoth an tsóláis
+is beag an tsuim iad
+is beag an tsuim iad
+infheicthe ag an tsúil
+infheicthe ag an tsúil
+scríobhfaidh
+scríobhfaidh
+preabphas
+preabphas
+úsáidtear
+úsáidtear
+snagcheol
+snagcheol
+in-athnuaite agatsa
+in-athnuaite agatsa
+teanga dhomhanda
+teanga dhomhanda
+réaltsruth
+réaltsruth
+na hataí
+na hataí
+t-léine
+t-léine
+t-léine
+t-léine
+torc allta
+torc allta
+tsk tsk tsk a chara
+tsk tsk tsk a chara
diff --git a/test/src/casefiddle-resources/irish-lowercase-1.txt b/test/src/casefiddle-resources/irish-lowercase-1.txt
new file mode 100644
index 00000000000..dcb3454b96d
--- /dev/null
+++ b/test/src/casefiddle-resources/irish-lowercase-1.txt
@@ -0,0 +1,211 @@
+ÁR nACMHAINNÍ UISCE
+ÁR N-ACMHAINNÍ UISCE
+Ár nAcmhainní Uisce
+Ár n-Acmhainní Uisce
+ár n-acmhainní uisce
+Ár nathair
+ÁR NATHAIR
+Ár Nathair
+N-a shaighdiúir
+gan dul as aca ach le nA chabhair
+EOLAÍOCHT NA nÁBHAR
+EOLAÍOCHT NA n-ÁBHAR
+Eolaíocht na nÁbhar
+Eolaíocht na n-Ábhar
+eolaíocht na n-ábhar
+Amhrán náisiúnta
+Amhrán Náisiúnta
+AMHRÁN NÁISIÚNTA
+LUCHT NA nEALAÍON
+LUCHT NA n-EALAÍON
+Lucht na nEalaíon
+Lucht na n-Ealaíon
+lucht na n-ealaíon
+Neart Daoine
+neart daoine
+NEART DAOINE
+CEOL NA nÉAN
+CEOL NA n-ÉAN
+Ceol na nÉan
+Ceol na n-Éan
+ceol na n-éan
+Sa Néal
+Sa néal
+SA NÉAL
+ORD NA nIMEACHTAÍ
+ORD NA n-IMEACHTAÍ
+Ord na nImeachtaí
+Ord na n-Imeachtaí
+ord na n-imeachtaí
+Nathair Nimhe
+Nathair nimhe
+NATHAIR NIMHE
+LUCHT ADHARTHA NA nÍOMHÁNNA
+LUCHT ADHARTHA NA n-ÍOMHÁNNA
+Lucht Adhartha na nÍomhánna
+Lucht Adhartha na n-Íomhánna
+lucht adhartha na n-íomhánna
+Níos Measa
+níos measa
+NÍOS MEASA
+GNÉITHE DÁR nOIDHREACHT
+GNÉITHE DÁR n-OIDHREACHT
+Gnéithe Dár nOidhreacht
+Gnéithe Dár n-Oidhreacht
+gnéithe dár n-oidhreacht
+Duine Nochta
+Duine nochta
+DUINE NOCHTA
+CULTÚR NA nÓG
+CULTÚR NA n-ÓG
+Cultúr na nÓg
+Cultúr na n-Óg
+cultúr na n-óg
+Dhá Nóiméad
+Dhá nóiméad
+DHÁ NÓIMÉAD
+OCHT nUAIRE SA LÁ
+OCHT n-UAIRE SA LÁ
+Ocht nUaire Sa Lá
+Ocht n-Uaire Sa Lá
+ocht n-uaire sa lá
+Gúna Nua
+gúna nua
+GÚNA NUA
+FORMHÓR NA nÚDARÁS
+FORMHÓR NA n-ÚDARÁS
+Formhór na nÚdarás
+Formhór na n-Údarás
+formhór na n-údarás
+Imoibreoir Núicléach
+Imoibreoir núicléach
+IMOIBREOIR NÚICLÉACH
+sean-Airteagal
+SeanAirteagal
+BunIoncaim
+Bun-Ioncaim
+BuanOrduithe
+Buan-Orduithe
+ÁR nATHAIR
+Ár nAthair
+CLÁR NA nÁBHAR
+Clár na nÁbhar
+CEOL NA nDAOINE
+Ceol na nDaoine
+TÁIM I nGRÁ LEAT
+Táim i nGrá Leat
+CÉN tAM É?
+CÉN t-AM É?
+Cén tAm É?
+Cén t-Am É?
+cén t-am é?
+Tar Ar Ais!
+tar ar ais!
+TÁ AN tÁDH ORM INNIU!
+TÁ AN t-ÁDH ORM INNIU!
+Tá An tÁdh Orm Inniu!
+Tá An t-Ádh Orm Inniu!
+tá an t-ádh orm inniu!
+Rud Tábhachtach
+Rud tábhachtach
+rud tábhachtach
+DEN OBAIR AN tEOLAS
+DEN OBAIR AN t-EOLAS
+Den Obair an tEolas
+Den Obair an t-Eolas
+den obair an t-eolas
+AN tÉILEAMH A ÍOC
+AN t-ÉILEAMH A ÍOC
+An tÉileamh a Íoc
+An t-Éileamh a Íoc
+an t-éileamh a íoc
+AN tINNEALL CUARDAIGH IS FEARR
+AN t-INNEALL CUARDAIGH IS FEARR
+An tInneall Cuardaigh Is Fearr
+An t-Inneall Cuardaigh Is Fearr
+an t-inneall cuardaigh is fearr
+AN tÍOCHTAR A CHUR IN UACHTAR
+AN t-ÍOCHTAR A CHUR IN UACHTAR
+An tÍochtar a Chur In Uachtar
+An t-Íochtar a Chur In Uachtar
+an t-íochtar a chur in uachtar
+TABHAIR AN tORDÚ SEO DÓ!
+TABHAIR AN t-ORDÚ SEO DÓ!
+Tabhair An tOrdú Seo Dó!
+Tabhair An t-Ordú Seo Dó!
+tabhair an t-ordú seo dó!
+TÁ AN tÓR BUÍ AIGE.
+TÁ AN t-ÓR BUÍ AIGE.
+Tá An tÓr Buí Aige.
+Tá An t-Ór Buí Aige.
+tá an t-ór buí aige.
+AN tUISCE BEATHA AR AN TÁBLA.
+AN t-UISCE BEATHA AR AN TÁBLA.
+An tUisce Beatha Ar An Tábla.
+An t-Uisce Beatha Ar An Tábla.
+an t-uisce beatha ar an tábla.
+AN tÚRSCÉAL IS DEIREANAÍ
+AN t-ÚRSCÉAL IS DEIREANAÍ
+An tÚrscéal Is Deireanaí
+An t-Úrscéal Is Deireanaí
+an t-úrscéal is deireanaí
+Dréacht-Acht
+DréachtPhlean
+Dréacht-Phlean
+Dréacht-Íocaíocht
+ÁitAinmneacha
+Áit-Ainmneacha
+StátUrraithe
+Stát-Urraithe
+AR AON tSLÍ
+Ar Aon tSlí
+AMACH ÓN tSNÁTHAID
+Amach Ón tSnáthaid
+AR AN tSRÁID
+Ar An tSráid
+CAINT AN tSRÁIDBHAILE
+Caint An tSráidbhaile
+CORA CRUA AN tSAOIL
+Cora Crua An tSaoil
+BHOLADH AN tSÁILE
+Bholadh An tSáile
+UAIR SA tSEACHTAIN
+Uair Sa tSeachtain
+DEIREADH AN tSÉASÚIR
+Deireadh An tSéasúir
+FEAR AN tSIOPA
+Fear an tSiopa
+AN tSÍOCHÁIN A CHOIMEÁD
+An tSíocháin a Choimeád
+AN tSOCHAÍ FAISNÉISE
+An tSochaí Faisnéise
+GAOTH AN tSÓLÁIS
+Gaoth aN tSóláis
+IS BEAG AN tSUIM IAD
+Is Beag An tSuim Iad
+INFHEICTHE AG AN tSÚIL
+Infheicthe Ag An tSúil
+SCRÍOBHFAIDH
+Scríobhfaidh
+PREABPHAS
+Preabphas
+ÚSÁIDTEAR
+Úsáidtear
+SNAGCHEOL
+Snagcheol
+IN-ATHNUAITE AGATSA
+In-Athnuaite AGATSA
+TEANGA DHOMHANDA
+Teanga Dhomhanda
+RÉALTSRUTH
+Réaltsruth
+NA HATAÍ
+Na Hataí
+T-LÉINE
+T-Léine
+t-Léine
+t-léine
+TORC ALLTA
+Torc Allta
+TSK TSK TSK A CHARA
+Tsk Tsk Tsk a Chara
diff --git a/test/src/casefiddle-resources/irish-uppercase-1-ref.txt b/test/src/casefiddle-resources/irish-uppercase-1-ref.txt
new file mode 100644
index 00000000000..21d3e4a6126
--- /dev/null
+++ b/test/src/casefiddle-resources/irish-uppercase-1-ref.txt
@@ -0,0 +1,105 @@
+ORD NA bhFOCAL
+COSÁN NA bhFILÍ
+ÁR bPOBAL
+NÓRA NA bPORTACH
+I dTOSACH BÁIRE
+AN GHAEILGE I dTUAISCEART NA hÉIREANN
+AS AN gCEANTAR SIN
+I gCONTAE NA MÍ AGUS I gCONAMARA
+DÉ hAOINE
+OIRTHEAR NA hÁISE
+PARLAIMINT NA hEORPA
+POBLACHT NA hÉIREANN
+EALAÍN NA hIODÁILE
+NA hÍOSÁNAIGH
+ACADAMH NA hOLLSCOLAÍOCHTA
+TÍR NA hÓIGE
+TOGHCHÁN NA hUACHTARÁNACHTA
+NA hÚDARÁIS CHÁNACH
+I mBUN MO MHACHNAMH
+I mBÉAL FEIRSTE AGUS I mBAILE ÁTHA CLIATH
+ÁR nACMHAINNÍ UISCE
+EOLAÍOCHT NA nÁBHAR
+LUCHT NA nEALAÍON
+CEOL NA nÉAN
+ORD NA nIMEACHTAÍ
+LUCHT ADHARTHA NA nÍOMHÁNNA
+GNÉITHE DÁR nOIDHREACHT
+CULTÚR NA nÓG
+OCHT nUAIRE SA LÁ
+FORMHÓR NA nÚDARÁS
+ÁR nATHAIR
+CLÁR NA nÁBHAR
+LOCH nEATHACH
+CUMANN NA nÉIREANNACH AONTAITHE
+GRÉASÁN NA nIONTAS
+NÓIBHÍSEACHT NA nÍOSÁNACH
+I gCEANTAR NA nOILEÁN
+TÍR NA nÓG
+BAILE NA nULTACH
+GORT NA nÚLL
+CEOL NA nDAOINE
+I nDÚN NA nGALL
+TÁIM I nGRÁ LEAT
+LABHAIR SÉ I nGAEILGE!
+CÉN tAM É?
+TÁ AN tÁDH ORM INNIU!
+DEN OBAIR AN tEOLAS
+AN tÉILEAMH A ÍOC
+AN tINNEALL CUARDAIGH IS FEARR
+AN tÍOCHTAR A CHUR IN UACHTAR
+TABHAIR AN tORDÚ SEO DÓ!
+TÁ AN tÓR BUÍ AIGE.
+AN tUISCE BEATHA AR AN TÁBLA.
+AN tÚRSCÉAL IS DEIREANAÍ
+AN tACHT OIDEACHAIS
+AN tÁIVÉ MÁIRIA
+AN tEARRACH ARABACH
+AN tÉIRÍ AMACH
+AN tIMEALL
+AN tÍOSÁNACH PEADAR CANISIUS
+AN tOILEÁNACH
+AN tÓR MUIRE
+AN tUASAL ÉAMON Ó CUÍV
+AN tÚDARÁS UM BÓITHRE NÁISIÚNTA
+AR AON tSLÍ
+BÉAL ÁTHA AN tSLÉIBHE
+AMACH ÓN tSNÁTHAID
+BANRÍON AN tSNEACHTA
+AR AN tSRÁID
+CAINT AN tSRÁIDBHAILE
+CORA CRUA AN tSAOIL
+BHOLADH AN tSÁILE
+UAIR SA tSEACHTAIN
+DEIREADH AN tSÉASÚIR
+FEAR AN tSIOPA
+AN tSÍOCHÁIN A CHOIMEÁD
+AN tSOCHAÍ FAISNÉISE
+GAOTH AN tSÓLÁIS
+IS BEAG AN tSUIM IAD
+INFHEICTHE AG AN tSÚIL
+CNOC AN tSAMHRAIDH
+CIONN tSÁILE
+AN tSEIRBHÍS PHOIBLÍ
+BAILE AN tSÉIPÉIL
+AN tSIRIA
+AN tSÍN
+OIFIG AN tSOLÁTHAIR
+POLL AN tSÓMAIS
+EOLAIRE AN tSUÍMH
+CASADH AN tSÚGÁIN
+SCRÍOBHFAIDH
+PREABPHAS
+ÚSÁIDTEAR
+SNAGCHEOL
+STÁITSE IMBOLC
+IN-ATHNUAITE AGATSA
+TEANGA DHOMHANDA
+RÉALTSRUTH
+NA HATAÍ
+NA HATAÍ
+ÁR NATHAIR
+ÁR NATHAIR
+T-LÉINE
+TORC ALLTA
+TSK TSK TSK A CHARA
diff --git a/test/src/casefiddle-resources/irish-uppercase-1.txt b/test/src/casefiddle-resources/irish-uppercase-1.txt
new file mode 100644
index 00000000000..b95e0aa04df
--- /dev/null
+++ b/test/src/casefiddle-resources/irish-uppercase-1.txt
@@ -0,0 +1,105 @@
+ord na bhfocal
+Cosán na bhFilí
+ár bpobal
+Nóra na bPortach
+i dtosach báire
+An Ghaeilge i dTuaisceart na hÉireann
+as an gceantar sin
+I gContae na Mí agus i gConamara
+Dé hAoine
+Oirthear na hÁise
+Parlaimint na hEorpa
+Poblacht na hÉireann
+Ealaín na hIodáile
+na hÍosánaigh
+Acadamh na hOllscolaíochta
+Tír na hÓige
+toghchán na hUachtaránachta
+na hÚdaráis Chánach
+I mbun mo mhachnamh
+I mBéal Feirste agus i mBaile Átha Cliath
+ár n-acmhainní uisce
+eolaíocht na n-ábhar
+lucht na n-ealaíon
+ceol na n-éan
+ord na n-imeachtaí
+lucht adhartha na n-íomhánna
+gnéithe dár n-oidhreacht
+cultúr na n-óg
+ocht n-uaire sa lá
+formhór na n-údarás
+Ár nAthair
+Clár na nÁbhar
+Loch nEathach
+Cumann na nÉireannach Aontaithe
+Gréasán na nIontas
+nóibhíseacht na nÍosánach
+i gCeantar na nOileán
+Tír na nÓg
+Baile na nUltach
+Gort na nÚll
+ceol na ndaoine
+i nDún na nGall
+táim i ngrá leat
+labhair sé i nGaeilge!
+cén t-am é?
+tá an t-ádh orm inniu!
+Den obair an t-eolas
+An t-éileamh a íoc
+an t-inneall cuardaigh is fearr
+an t-íochtar a chur in uachtar
+Tabhair an t-ordú seo dó!
+Tá an t-ór buí aige.
+an t-uisce beatha ar an tábla.
+an t-úrscéal is deireanaí
+An tAcht Oideachais
+an tÁivé Máiria
+An tEarrach Arabach
+An tÉirí Amach
+An tImeall
+An tÍosánach Peadar Canisius
+An tOileánach
+An tÓr Muire
+an tUasal Éamon Ó Cuív
+An tÚdarás um Bóithre Náisiúnta
+ar aon tslí
+Béal Átha an tSléibhe
+Amach ón tsnáthaid
+Banríon an tSneachta
+ar an tsráid
+Caint an tSráidbhaile
+cora crua an tsaoil
+bholadh an tsáile
+uair sa tseachtain
+deireadh an tséasúir
+fear an tsiopa
+an tsíocháin a choimeád
+an tsochaí faisnéise
+gaoth an tsóláis
+Is beag an tsuim iad
+infheicthe ag an tsúil
+Cnoc an tSamhraidh
+Cionn tSáile
+an tSeirbhís Phoiblí
+Baile an tSéipéil
+An tSiria
+An tSín
+Oifig an tSoláthair
+Poll an tSómais
+Eolaire an tSuímh
+Casadh an tSúgáin
+scríobhfaidh
+preabphas
+úsáidtear
+snagcheol
+Stáitse Imbolc
+in-athnuaite agatsa
+Teanga Dhomhanda
+Réaltsruth
+na hataí
+Na Hataí
+ár nathair
+Ár Nathair
+t-léine
+torc allta
+tsk tsk tsk a chara
diff --git a/test/src/casefiddle-tests.el b/test/src/casefiddle-tests.el
index f7b0da41029..e5309066c9c 100644
--- a/test/src/casefiddle-tests.el
+++ b/test/src/casefiddle-tests.el
@@ -22,6 +22,11 @@
 (require 'case-table)
 (require 'ert)
 
+(defvar casefiddle-tests--resources-dir
+  (concat (concat (file-name-directory (or load-file-name buffer-file-name))
+                  "/casefiddle-resources/"))
+  "Path to casefiddle-resources directory next to the \"casefiddle-tests.el\" file.")
+
 (ert-deftest casefiddle-tests-char-properties ()
   "Sanity check of character Unicode properties."
   (should-not
@@ -161,17 +166,18 @@ casefiddle-tests--test-casing
         (while (and func-pairs expected)
           (setq funcs (car func-pairs)
                 getters (list get-string get-region))
-          (while (and funcs getters)
-            (let ((got (funcall (car getters) (car funcs))))
-              (unless (string-equal got (car expected))
-                (let ((fmt (length (symbol-name (car funcs)))))
-                  (setq fmt (format "\n%%%ds: %%s" (max fmt 8)))
-                  (push (format (concat fmt fmt fmt)
-                                (car funcs) (funcall fmt-str input)
-                                "expected" (funcall fmt-str (car expected))
-                                "but got" (funcall fmt-str got))
-                        errors))))
-            (setq funcs (cdr funcs) getters (cdr getters)))
+          (when (car expected)
+            (while (and funcs getters)
+              (let ((got (funcall (car getters) (car funcs))))
+                (unless (string-equal got (car expected))
+                  (let ((fmt (length (symbol-name (car funcs)))))
+                    (setq fmt (format "\n%%%ds: %%s" (max fmt 8)))
+                    (push (format (concat fmt fmt fmt)
+                                  (car funcs) (funcall fmt-str input)
+                                  "expected" (funcall fmt-str (car expected))
+                                  "but got" (funcall fmt-str got))
+                          errors))))
+              (setq funcs (cdr funcs) getters (cdr getters))))
           (setq func-pairs (cdr func-pairs) expected (cdr expected))))
       errors)
     (cons () tests))))
@@ -268,6 +274,36 @@ casefiddle-tests--test-casing
         ("į\u0307"                ; i-ogonek + dot above
          "Į" "į\u0307" "Į" "Į" "lt"))))))
 
+
+(defun casefiddle--read-lines (test-file)
+  (with-temp-buffer
+    (insert-file-contents (concat casefiddle-tests--resources-dir test-file))
+    (split-string (buffer-string) "\n" nil " +")))
+
+(ert-deftest casefiddle-test-irish ()
+  (let (tests)
+    ;; Read upcase test cases
+    (let ((input    (casefiddle--read-lines "irish-uppercase-1.txt"))
+          (expected (casefiddle--read-lines "irish-uppercase-1-ref.txt")))
+      (while (and input expected)
+        (push (list (car input) (car expected) nil nil nil "ga") tests)
+        (setq input (cdr input) expected (cdr expected)))
+      (should-not (or input expected)))
+
+    ;; Read downcase test cases
+    (let ((input    (casefiddle--read-lines "irish-lowercase-1.txt"))
+          (expected (casefiddle--read-lines "irish-lowercase-1-ref.txt")))
+      (while (and input expected)
+        (let ((test (assoc (car input) tests)))
+          (if test
+              (setcar (cddr test) (car expected))
+            (push (list (car input) nil (car expected) nil nil "ga") tests)))
+        (setq input (cdr input) expected (cdr expected)))
+      (should-not (or input expected)))
+
+    (should-not (with-temp-buffer (casefiddle-tests--test-casing tests)))))
+
+
 (ert-deftest casefiddle-tests-casing-byte8 ()
   (should-not
    (with-temp-buffer
-- 
2.12.0.246.ga2ecc84866-goog






^ permalink raw reply related	[flat|nested] 89+ messages in thread

* bug#24603: [PATCHv5 01/11] Split casify_object into multiple functions
  2017-03-09 21:51   ` bug#24603: [PATCHv5 01/11] Split casify_object into multiple functions Michal Nazarewicz
@ 2017-03-10  9:00     ` Andreas Schwab
  0 siblings, 0 replies; 89+ messages in thread
From: Andreas Schwab @ 2017-03-10  9:00 UTC (permalink / raw)
  To: Michal Nazarewicz; +Cc: 24603

On Mär 09 2017, Michal Nazarewicz <mina86@mina86.com> wrote:

> +  multibyte = (ch >= 256 ||
> +	       !NILP (BVAR (current_buffer, enable_multibyte_characters)));

Style: linebreak before operator, not after.

Andreas.

-- 
Andreas Schwab, schwab@linux-m68k.org
GPG Key fingerprint = 58CA 54C7 6D53 942B 1756  01D3 44D5 214B 8276 4ED5
"And now for something completely different."





^ permalink raw reply	[flat|nested] 89+ messages in thread

* bug#24603: [PATCHv5 03/11] Add support for title-casing letters (bug#24603)
  2017-03-09 21:51   ` bug#24603: [PATCHv5 03/11] Add support for title-casing letters (bug#24603) Michal Nazarewicz
@ 2017-03-11  9:03     ` Eli Zaretskii
  0 siblings, 0 replies; 89+ messages in thread
From: Eli Zaretskii @ 2017-03-11  9:03 UTC (permalink / raw)
  To: Michal Nazarewicz; +Cc: 24603

> From: Michal Nazarewicz <mina86@mina86.com>
> Date: Thu,  9 Mar 2017 22:51:42 +0100
> 
>  DEFUN ("capitalize", Fcapitalize, Scapitalize, 1, 1, 0,
>         doc: /* Convert argument to capitalized form and return that.
> -This means that each word's first character is upper case
> -and the rest is lower case.
> +This means that each word's first character is upper case (more
> +precisely, if available, title case) and the rest is lower case.

I'd rephrase this:

  Convert argument to capitalized form and return that.
  This means that each word's first character is converted to either
  title case or upper case, and the rest to lower case.

IOW, I wouldn't risk confusing the reader with "more precisely" and
"if available".  I think this is not really needed.  If you think that
"title case" might not be understood, perhaps add a pointer to the
relevant manual node.

This comment is relevant, with minor variations, to all of the other
doc string changes you proposed.

Thanks.





^ permalink raw reply	[flat|nested] 89+ messages in thread

* bug#24603: [PATCHv5 05/11] Support casing characters which map into multiple code points (bug#24603)
  2017-03-09 21:51   ` bug#24603: [PATCHv5 05/11] Support casing characters which map into multiple code points (bug#24603) Michal Nazarewicz
@ 2017-03-11  9:14     ` Eli Zaretskii
  2017-03-21  2:09       ` Michal Nazarewicz
  0 siblings, 1 reply; 89+ messages in thread
From: Eli Zaretskii @ 2017-03-11  9:14 UTC (permalink / raw)
  To: Michal Nazarewicz; +Cc: 24603

> From: Michal Nazarewicz <mina86@mina86.com>
> Date: Thu,  9 Mar 2017 22:51:44 +0100
> 
> Implement unconditional special casing rules defined in Unicode standard.
> 
> Among other things, they deal with cases when a single code point is
> replaced by multiple ones because single character does not exist (e.g.
> ‘fi’ ligature turning into ‘FL’) or is not commonly used (e.g. ß turning
> into SS).
> 
> * admin/unidata/SpecialCasing.txt: New data file pulled from Unicode
> standard distribution.
> * admin/unidata/README: Mention SpecialCasing.txt.
> 
> * admin/unidata/unidata-get.el (unidata-gen-table-special-casing): New
> function for generating ‘special-casing’ character Unicode property
> built from the SpecialCasing.txt Unicode data file.

This new property is attainable via get-char-code-property, right?  If
so, it should be documented in the Elisp manual, in the "Character
Properties" node.

I think I'd also like to see a few simple tests for this property.

> diff --git a/doc/lispref/strings.texi b/doc/lispref/strings.texi
> index cf47db4a814..ba1cf2606ce 100644
> --- a/doc/lispref/strings.texi
> +++ b/doc/lispref/strings.texi
> @@ -1166,6 +1166,29 @@ Case Conversion
>  @end example
>  @end defun
>  
> +  Note that case conversion is not a one-to-one mapping and the length
> +of the result may differ from the length of the argument (including
> +being shorter).  Furthermore, because passing a character forces
> +return type to be a character, functions are unable to perform proper
> +substitution and result may differ compared to treating
> +a one-character string.  For example:
> +
> +@example
> +@group
> +(upcase "fi")  ; note: single character, ligature "fi"
> +     @result{} "FI"
> +@end group
> +@group
> +(upcase ?fi)
> +     @result{} 64257  ; i.e. ?fi
> +@end group
> +@end example
> +
> +  To avoid this, a character must first be converted into a string,
> +using @code{string} function, before being passed to one of the casing
> +functions.  Of course, no assumptions on the length of the result may
> +be made.

Once the ELisp manual describes the new special-casing property, the
above text should include a cross-reference to that description.

>  DEFUN ("upcase", Fupcase, Supcase, 1, 1, 0,
>         doc: /* Convert argument to upper case and return that.
>  The argument may be a character or string.  The result has the same type.
> -The argument object is not altered--the value is a copy.
> +The argument object is not altered--the value is a copy.  If argument
> +is a character, characters which map to multiple code points when
> +cased, e.g. fi, are returned unchanged.
>  See also `capitalize', `downcase' and `upcase-initials'.  */)

This (and other similar doc strings) should mention the special-casing
property as the way to know in advance which characters will remain
unchanged due to that.





^ permalink raw reply	[flat|nested] 89+ messages in thread

* bug#24603: [PATCHv5 07/11] Introduce ‘buffer-language’ buffer-locar variable
  2017-03-09 21:51   ` bug#24603: [PATCHv5 07/11] Introduce ‘buffer-language’ buffer-locar variable Michal Nazarewicz
@ 2017-03-11  9:29     ` Eli Zaretskii
  0 siblings, 0 replies; 89+ messages in thread
From: Eli Zaretskii @ 2017-03-11  9:29 UTC (permalink / raw)
  To: Michal Nazarewicz; +Cc: 24603

> From: Michal Nazarewicz <mina86@mina86.com>
> Date: Thu,  9 Mar 2017 22:51:46 +0100
> 
> diff --git a/lisp/international/mule-cmds.el b/lisp/international/mule-cmds.el
> index bdba8eeb112..d883e8c2298 100644
> --- a/lisp/international/mule-cmds.el
> +++ b/lisp/international/mule-cmds.el
> @@ -2601,7 +2601,7 @@ set-locale-environment
>  	(setq system-messages-locale locale)
>  	(setq system-time-locale locale))
>  
> -      (if (string-match "^[a-z][a-z]" locale)
> +      (when (string-match "^[a-z][a-z]" locale)
>            ;; The value of 'current-iso639-language' is matched against
>            ;; the ':lang' property of font-spec objects when selecting
>            ;; and prioritizing available fonts for displaying
> @@ -2613,7 +2613,11 @@ set-locale-environment
>                  ;; match mentioned above uses memq, and ':lang'
>                  ;; properties have lower-case values, the letter-case
>                  ;; must match exactly.
> -                (intern (downcase (match-string 0 locale))))))
> +                (intern (downcase (match-string 0 locale))))
> +
> +          ;; ‘buffer-language’ is used to identify language buffer is in for the
> +          ;; purpose of performing language-specific operations.
> +          (setq-default buffer-language locale)))

This make buffer-language's value include the locale's codeset, which
is unnecessary and might get in the way later.  I think we should
remove the codeset part, if present.

In addition, there's the case of Windows, where languages use a
different, 3-letter notation (e.g., "ENU" instead of the Posix
"en_US").  So I think we need a function to generate the portable
language name from the locale, which will need to use a database of
conversions for Windows (this could be added later, if you like, but
we need at least a FIXME comment to that effect).

> +  bset_language (&buffer_defaults, Qnil);

It's hard to see if this default is TRT, since the practical meaning
of nil here is not yet clear, not even after reading the doc string of
buffer-language.  The nil value is of course okay for special casing
rules, but you propose this feature for more than that.  For example,
for spell-checking purposes, it is not clear what nil means.

I have a higher-level comments on this feature, which I will post
elsewhere.

Last, but not least: can we have some tests for this?

Thanks.





^ permalink raw reply	[flat|nested] 89+ messages in thread

* bug#24603: [PATCHv5 08/11] Implement rules for title-casing Dutch ij ‘letter’ (bug#24603)
  2017-03-09 21:51   ` bug#24603: [PATCHv5 08/11] Implement rules for title-casing Dutch ij ‘letter’ (bug#24603) Michal Nazarewicz
@ 2017-03-11  9:40     ` Eli Zaretskii
  2017-03-16 21:30       ` Michal Nazarewicz
  0 siblings, 1 reply; 89+ messages in thread
From: Eli Zaretskii @ 2017-03-11  9:40 UTC (permalink / raw)
  To: Michal Nazarewicz; +Cc: 24603

> From: Michal Nazarewicz <mina86@mina86.com>
> Date: Thu,  9 Mar 2017 22:51:47 +0100
> 
> +    /* In Dutch, ‘ij’ is a digraph and when capitalised the whole thing is upper
> +       cased.  Unicode has ‘ij’ and ‘IJ’ (with proper casing mappings) but they
> +       aren’t always used so we cannot/should not rely on them.
> +
> +       Note that rule for capitalising ‘ij’ as a single letter is not present in
> +       Unicode 9.0’s SpecialCasing.txt.  On the flip side, Firefox implements
> +       this as well so we’re not completely alone.

If this is not mandated by Unicode 9.0 (and not by the latest draft of
10.0, AFAICS), shouldn't we have a user option for this, by default
off?

> +       There are words where ‘ij’ are two separate letters (such as bijectie or
> +       bijoux) in which case the capitalisation rules do not apply.  I (mina86)
> +       have googled this a little and couldn’t find a Dutch word which beings
> +       with ‘ij’ that is not a digraph so we should be in the clear since we
> +       only care about the initial. */

I'm not sure I get this right: does this mean that writing in English
(or any other non-Dutch language) in a Dutch locale will automatically
capitalize "ij" to "IJ", just because the default value of
buffer-language is "nl_NL" or somesuch, and no specific language was
set for the buffer?  Wouldn't that surprise users?





^ permalink raw reply	[flat|nested] 89+ messages in thread

* bug#24603: [PATCHv5 11/11] Implement Irish casing rules  (bug#24603)
  2017-03-09 21:51   ` bug#24603: [PATCHv5 11/11] Implement Irish casing rules (bug#24603) Michal Nazarewicz
@ 2017-03-11  9:44     ` Eli Zaretskii
  2017-03-16 22:16       ` Michal Nazarewicz
  0 siblings, 1 reply; 89+ messages in thread
From: Eli Zaretskii @ 2017-03-11  9:44 UTC (permalink / raw)
  To: Michal Nazarewicz; +Cc: 24603

> From: Michal Nazarewicz <mina86@mina86.com>
> Date: Thu,  9 Mar 2017 22:51:50 +0100
> 
> * test/src/casefiddle-resources/irish-lowercase-1.txt,
> test/src/casefiddle-resources/irish-lowercase-1-ref.txt,
> test/src/casefiddle-resources/irish-uppercase-1.txt,
> test/src/casefiddle-resources/irish-uppercase-1-ref.txt: New files
> with test cases for Irish capitalisation.  The files are copied from
> Mozilla’s test suite.

What is the license and copyright status of these files?  We cannot
redistribute files unless they have GPL-compatible licenses.





^ permalink raw reply	[flat|nested] 89+ messages in thread

* bug#24603: [PATCHv5 00/11] Casing improvements
  2017-03-09 21:51 ` bug#24603: [PATCHv5 00/11] Casing improvements Michal Nazarewicz
                     ` (10 preceding siblings ...)
  2017-03-09 21:51   ` bug#24603: [PATCHv5 11/11] Implement Irish casing rules (bug#24603) Michal Nazarewicz
@ 2017-03-11 10:00   ` Eli Zaretskii
  2017-03-21  1:27   ` bug#24603: [PATCHv6 0/6] Casing improvements, language-independent part Michal Nazarewicz
  12 siblings, 0 replies; 89+ messages in thread
From: Eli Zaretskii @ 2017-03-11 10:00 UTC (permalink / raw)
  To: Michal Nazarewicz; +Cc: 24603

> From: Michal Nazarewicz <mina86@mina86.com>
> Date: Thu,  9 Mar 2017 22:51:39 +0100
> 
> The first six patches (up to sigma casing rule) should be
> uncontroversial and unless there are objections I would like to get
> them submitted soon:
> 
>   Split casify_object into multiple functions
>   Introduce case_character function
>   Add support for title-casing letters  (bug#24603)
>   Split up casify_region function  (bug#24603)
>   Support casing characters which map into multiple code points 
>   Implement special sigma casing rule  (bug#24603)

Fine with me, modulo a few comments I posted to these 6 patches.

> The next patch adds ‘buffer-language’ buffer-local variable.  This
> seems to me as a sensible way of dealing with language-dependent rules
> and in the future I imagine the variable might be used for more
> cases, e.g. spell checking should automatically choose a dictionary
> based on it.  But perhaps there is another way which integrates with
> the rest of Emacs better:
> 
>   Introduce ‘buffer-language’ buffer-local variable

I think we should rather introduce a _function_ named buffer-language,
so that it's easier to extend this mechanism in the future to more
sophisticated and more fine-grained methods of determining the
language, such as text properties and/or overlays with special
properties.  The function could for now just return the value of a
buffer-specific variable, but I wouldn't expose and advertise that
variable so much as your code does.

In addition, your implementation seems to assume that the language
rules are independent of the country where that language is used,
i.e. that nl_NL and nl_BE will necessarily use the same rules for case
conversions.  Is this a good assumption?  Collation rules definitely
do depend on the country as well, AFAIK.

> The rest are just implementation of various language-specific rules.
> The implementation seems to be valid but it’s done purely in C which
> I guess still is a point of contention between me and Eli.

Yes, I'd still prefer that as much of the rules as possible be
specified in Lisp, thus avoiding the need to hard-code Unicode
codepoints and the associated rules in C.  I understand that the
support for each kind of rule should be available in C before the
rules can be used, but once such support is there, having the spec in
Lisp will allow us easier maintenance in the future, easier expansion
of this to cover additional languages that use the same types of
rules, and, with time, perhaps also automatic derivation of the rules
from the Unicode data files, thus providing for easier updates when a
new version of Unicode is incorporated.

So I'd still urge you to try to refactor the code so that as much as
is feasible of the rules is implemented as a Lisp database.  But I
won't reject these patches if you don't want to do such refactoring

Thanks.





^ permalink raw reply	[flat|nested] 89+ messages in thread

* bug#24603: [PATCHv5 08/11] Implement rules for title-casing Dutch ij ‘letter’ (bug#24603)
  2017-03-11  9:40     ` Eli Zaretskii
@ 2017-03-16 21:30       ` Michal Nazarewicz
  2017-03-17 13:43         ` Eli Zaretskii
  0 siblings, 1 reply; 89+ messages in thread
From: Michal Nazarewicz @ 2017-03-16 21:30 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: 24603

On Sat, Mar 11 2017, Eli Zaretskii wrote:
>> From: Michal Nazarewicz <mina86@mina86.com>
>> Date: Thu,  9 Mar 2017 22:51:47 +0100
>> 
>> +    /* In Dutch, ‘ij’ is a digraph and when capitalised the whole thing is upper
>> +       cased.  Unicode has ‘ij’ and ‘IJ’ (with proper casing mappings) but they
>> +       aren’t always used so we cannot/should not rely on them.
>> +
>> +       Note that rule for capitalising ‘ij’ as a single letter is not present in
>> +       Unicode 9.0’s SpecialCasing.txt.  On the flip side, Firefox implements
>> +       this as well so we’re not completely alone.
>
> If this is not mandated by Unicode 9.0 (and not by the latest draft of
> 10.0, AFAICS), shouldn't we have a user option for this, by default
> off?

I don’t really see why.

If the goal is to implement Unicode then ‘ij’ handling should not be
implemented at all and Unicode-mandated behaviour should not be
configurable, but implementing Unicode is a mean, not a goal in itself.

Rather, the goal is to properly case strings and while Unicode is
helpful in that it’s not the whole story.

And if user are allowed to disable ‘ij’ handling, they should also be
allowed to disable Turkish ‘i’ handling.

>> +       There are words where ‘ij’ are two separate letters (such as bijectie or
>> +       bijoux) in which case the capitalisation rules do not apply.  I (mina86)
>> +       have googled this a little and couldn’t find a Dutch word which beings
>> +       with ‘ij’ that is not a digraph so we should be in the clear since we
>> +       only care about the initial. */
>
> I'm not sure I get this right: does this mean that writing in English
> (or any other non-Dutch language) in a Dutch locale will automatically
> capitalize "ij" to "IJ", just because the default value of
> buffer-language is "nl_NL" or somesuch, and no specific language was
> set for the buffer?  Wouldn't that surprise users?

Yes it does.  And yes it would.

This is currently the biggest blocker/concern for all the patches past
07/11 and I’m still wondering what would be the best solution.

I thought about having a ‘language’ string property so that programming
major modes would mark everything outside of comments as a ‘nil’
language.  This would require support from multiple major modes and
likely complicate them.¹

Or perhaps have off-by-default ‘special-casing-mode’ which enables
language-dependent casing rules.  Similar effect could be accomplished
by replacing the ‘buffer-language’ with nil-by-default ‘casing-locale’
variable applicable only to casing, but I would miss ‘buffer-language’
since I believe it might get used for other things.

¹ Having string property could still be an option in the future of
  course and it might allow fancy things like: <p lang=en>Iceland’s name
  in Dutch is <span lang=nl>Ijsland</span></p>.

-- 
Best regards
ミハウ “𝓶𝓲𝓷𝓪86” ナザレヴイツ
«If at first you don’t succeed, give up skydiving»





^ permalink raw reply	[flat|nested] 89+ messages in thread

* bug#24603: [PATCHv5 11/11] Implement Irish casing rules (bug#24603)
  2017-03-11  9:44     ` Eli Zaretskii
@ 2017-03-16 22:16       ` Michal Nazarewicz
  2017-03-17  8:20         ` Eli Zaretskii
  0 siblings, 1 reply; 89+ messages in thread
From: Michal Nazarewicz @ 2017-03-16 22:16 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: 24603

On Sat, Mar 11 2017, Eli Zaretskii wrote:
>> From: Michal Nazarewicz <mina86@mina86.com>
>> Date: Thu,  9 Mar 2017 22:51:50 +0100
>> 
>> * test/src/casefiddle-resources/irish-lowercase-1.txt,
>> test/src/casefiddle-resources/irish-lowercase-1-ref.txt,
>> test/src/casefiddle-resources/irish-uppercase-1.txt,
>> test/src/casefiddle-resources/irish-uppercase-1-ref.txt: New files
>> with test cases for Irish capitalisation.  The files are copied from
>> Mozilla’s test suite.
>
> What is the license and copyright status of these files?  We cannot
> redistribute files unless they have GPL-compatible licenses.

MPL 2.0.  mozilla-central/LICENSE¹ points to another file² which
mentions that:

    <p>All of the <b>source code</b> to this product is
       available under licenses which are both
       <a href="http://www.gnu.org/philosophy/free-sw.html">free</a> and
       <a href="http://www.opensource.org/docs/definition.php">open source</a>.
       A URL identifying the specific source code used to create this copy can be found
       on the <a href="about:buildconfig">build configuration page</a>, and you can read
       <a href="https://developer.mozilla.org/en/Mozilla_Source_Code_%28Mercurial%29">instructions
       on how to download and build the code for yourself</a>.
    </p>

    <p>More specifically, most of the source code is available under the
       <a href="about:license#mpl">Mozilla Public License 2.0</a> (MPL).
       The MPL has a
       <a href="http://www.mozilla.org/MPL/2.0/FAQ.html">FAQ</a> to help
       you understand it. The remainder of the software which is not
       under the MPL is available under one of a variety of other
       free and open source licenses. Those that require reproduction
       of the license text in the distribution are given below.
       (Note: your copy of this product may not contain code covered by one
       or more of the licenses listed here, depending on the exact product
       and version you choose.)
    </p>

Admittedly, the wording is a bit weird but if you open ‘about:license’
in Mozilla-provided Firefox you can read:

	Binaries of this product have been made available to you by the
	Mozilla Project under the Mozilla Public License 2.0 (MPL). Know
	your rights.

With ‘Know your rights’ leading to a page claiming that:

	Mozilla Firefox is free and open source software, built by
	a community of thousands from all over the world. There are
	a few things you should know:

	* Firefox is made available to you under the terms of the
          Mozilla Public Licence.  This means you may use, copy and
          distribute Firefox to others. You are also welcome to modify
          the source code of Firefox as you want to meet your needs. The
          Mozilla Public Licence also gives you the right to distribute
          your modified versions.

MPL 2.0, in contrast to MPL 1.1, is compatible with GPL.

I’ll add mention of the license to the README in the directory.

¹ https://hg.mozilla.org/mozilla-central/file/tip/LICENSE
² https://hg.mozilla.org/mozilla-central/file/tip/toolkit/content/license.html

-- 
Best regards
ミハウ “𝓶𝓲𝓷𝓪86” ナザレヴイツ
«If at first you don’t succeed, give up skydiving»





^ permalink raw reply	[flat|nested] 89+ messages in thread

* bug#24603: [PATCHv5 11/11] Implement Irish casing rules (bug#24603)
  2017-03-16 22:16       ` Michal Nazarewicz
@ 2017-03-17  8:20         ` Eli Zaretskii
  0 siblings, 0 replies; 89+ messages in thread
From: Eli Zaretskii @ 2017-03-17  8:20 UTC (permalink / raw)
  To: Michal Nazarewicz, Richard Stallman; +Cc: 24603

> From: Michal Nazarewicz <mina86@mina86.com>
> Cc: 24603@debbugs.gnu.org
> Date: Thu, 16 Mar 2017 23:16:15 +0100
> 
> On Sat, Mar 11 2017, Eli Zaretskii wrote:
> >> From: Michal Nazarewicz <mina86@mina86.com>
> >> Date: Thu,  9 Mar 2017 22:51:50 +0100
> >> 
> >> * test/src/casefiddle-resources/irish-lowercase-1.txt,
> >> test/src/casefiddle-resources/irish-lowercase-1-ref.txt,
> >> test/src/casefiddle-resources/irish-uppercase-1.txt,
> >> test/src/casefiddle-resources/irish-uppercase-1-ref.txt: New files
> >> with test cases for Irish capitalisation.  The files are copied from
> >> Mozilla’s test suite.
> >
> > What is the license and copyright status of these files?  We cannot
> > redistribute files unless they have GPL-compatible licenses.
> 
> MPL 2.0.  mozilla-central/LICENSE¹ points to another file² which
> mentions that:
> 
>     <p>All of the <b>source code</b> to this product is
>        available under licenses which are both
>        <a href="http://www.gnu.org/philosophy/free-sw.html">free</a> and
>        <a href="http://www.opensource.org/docs/definition.php">open source</a>.
>        A URL identifying the specific source code used to create this copy can be found
>        on the <a href="about:buildconfig">build configuration page</a>, and you can read
>        <a href="https://developer.mozilla.org/en/Mozilla_Source_Code_%28Mercurial%29">instructions
>        on how to download and build the code for yourself</a>.
>     </p>
> 
>     <p>More specifically, most of the source code is available under the
>        <a href="about:license#mpl">Mozilla Public License 2.0</a> (MPL).
>        The MPL has a
>        <a href="http://www.mozilla.org/MPL/2.0/FAQ.html">FAQ</a> to help
>        you understand it. The remainder of the software which is not
>        under the MPL is available under one of a variety of other
>        free and open source licenses. Those that require reproduction
>        of the license text in the distribution are given below.
>        (Note: your copy of this product may not contain code covered by one
>        or more of the licenses listed here, depending on the exact product
>        and version you choose.)
>     </p>
> 
> Admittedly, the wording is a bit weird but if you open ‘about:license’
> in Mozilla-provided Firefox you can read:
> 
> 	Binaries of this product have been made available to you by the
> 	Mozilla Project under the Mozilla Public License 2.0 (MPL). Know
> 	your rights.
> 
> With ‘Know your rights’ leading to a page claiming that:
> 
> 	Mozilla Firefox is free and open source software, built by
> 	a community of thousands from all over the world. There are
> 	a few things you should know:
> 
> 	* Firefox is made available to you under the terms of the
>           Mozilla Public Licence.  This means you may use, copy and
>           distribute Firefox to others. You are also welcome to modify
>           the source code of Firefox as you want to meet your needs. The
>           Mozilla Public Licence also gives you the right to distribute
>           your modified versions.
> 
> MPL 2.0, in contrast to MPL 1.1, is compatible with GPL.
> 
> I’ll add mention of the license to the README in the directory.
> 
> ¹ https://hg.mozilla.org/mozilla-central/file/tip/LICENSE
> ² https://hg.mozilla.org/mozilla-central/file/tip/toolkit/content/license.html

Thanks.

Richard, is it okay for us to include these files in Emacs, given
these licenses?





^ permalink raw reply	[flat|nested] 89+ messages in thread

* bug#24603: [PATCHv5 08/11] Implement rules for title-casing Dutch ij ‘letter’ (bug#24603)
  2017-03-16 21:30       ` Michal Nazarewicz
@ 2017-03-17 13:43         ` Eli Zaretskii
  0 siblings, 0 replies; 89+ messages in thread
From: Eli Zaretskii @ 2017-03-17 13:43 UTC (permalink / raw)
  To: Michal Nazarewicz; +Cc: 24603

> From: Michal Nazarewicz <mina86@mina86.com>
> Cc: 24603@debbugs.gnu.org
> Date: Thu, 16 Mar 2017 22:30:52 +0100
> 
> > If this is not mandated by Unicode 9.0 (and not by the latest draft of
> > 10.0, AFAICS), shouldn't we have a user option for this, by default
> > off?
> 
> I don’t really see why.
> 
> If the goal is to implement Unicode then ‘ij’ handling should not be
> implemented at all and Unicode-mandated behaviour should not be
> configurable, but implementing Unicode is a mean, not a goal in itself.

That's true, but my thinking was that where Unicode says something
shall be done we have more "moral authority" to implement what they
say.  Whereas where there's no Unicode-mandated behavior, we should
consider the possibility that the behavior will be more controversial,
and let users decide.

> > I'm not sure I get this right: does this mean that writing in English
> > (or any other non-Dutch language) in a Dutch locale will automatically
> > capitalize "ij" to "IJ", just because the default value of
> > buffer-language is "nl_NL" or somesuch, and no specific language was
> > set for the buffer?  Wouldn't that surprise users?
> 
> Yes it does.  And yes it would.
> 
> This is currently the biggest blocker/concern for all the patches past
> 07/11 and I’m still wondering what would be the best solution.
> 
> I thought about having a ‘language’ string property so that programming
> major modes would mark everything outside of comments as a ‘nil’
> language.  This would require support from multiple major modes and
> likely complicate them.¹
> 
> Or perhaps have off-by-default ‘special-casing-mode’ which enables
> language-dependent casing rules.  Similar effect could be accomplished
> by replacing the ‘buffer-language’ with nil-by-default ‘casing-locale’
> variable applicable only to casing, but I would miss ‘buffer-language’
> since I believe it might get used for other things.

I think buffer-language is a more broad issue, so if we want to let
users control whether casing follows language rules, that should be a
separate setting, independent of the language, and it shouldn't be a
reason for not introducing buffer-language or language properties.  So
with this in mind, I think your second proposal is better.

Btw, if we do introduce such properties, I think their values should
be symbols, not strings, like we already do, for example, with
'charset' property we put on text decoded by some coding-systems.  And
such a property will indeed allow more fine-grained language-dependent
behavior, provided that we find good ways of computing this property
according to user expectations.





^ permalink raw reply	[flat|nested] 89+ messages in thread

* bug#24603: [PATCHv6 0/6] Casing improvements, language-independent part
  2017-03-09 21:51 ` bug#24603: [PATCHv5 00/11] Casing improvements Michal Nazarewicz
                     ` (11 preceding siblings ...)
  2017-03-11 10:00   ` bug#24603: [PATCHv5 00/11] Casing improvements Eli Zaretskii
@ 2017-03-21  1:27   ` Michal Nazarewicz
  2017-03-21  1:27     ` bug#24603: [PATCHv6 1/6] Split casify_object into multiple functions Michal Nazarewicz
                       ` (5 more replies)
  12 siblings, 6 replies; 89+ messages in thread
From: Michal Nazarewicz @ 2017-03-21  1:27 UTC (permalink / raw)
  To: Eli Zaretskii, Andreas Schwab, 24603

I think this is now ready to be submited.  I’ll test the code for
a week and unless there are any objections will submit it next Monday
or thenabouts.

The biggest change was to the fifth patch where I’ve split
special-casing property into three separate ones: special-uppercase,
special-lowercase and special-titlecase.  This felt more in line with
how other properties were defined.

Michal Nazarewicz (6):
  Split casify_object into multiple functions
  Introduce case_character function
  Add support for title-casing letters  (bug#24603)
  Split up casify_region function  (bug#24603)
  Support casing characters which map into multiple code points 
    (bug#24603)
  Implement special sigma casing rule  (bug#24603)

 admin/unidata/README            |   4 +
 admin/unidata/SpecialCasing.txt | 281 +++++++++++++++++++
 admin/unidata/unidata-gen.el    |  72 +++++
 doc/lispref/nonascii.texi       |  22 ++
 doc/lispref/strings.texi        |  23 ++
 etc/NEWS                        |  22 +-
 src/casefiddle.c                | 583 +++++++++++++++++++++++++++-------------
 test/lisp/char-fold-tests.el    |  12 +-
 test/src/casefiddle-tests.el    | 129 +++++----
 9 files changed, 899 insertions(+), 249 deletions(-)
 create mode 100644 admin/unidata/SpecialCasing.txt

-- 
2.12.0.367.g23dc2f6d3c-goog






^ permalink raw reply	[flat|nested] 89+ messages in thread

* bug#24603: [PATCHv6 1/6] Split casify_object into multiple functions
  2017-03-21  1:27   ` bug#24603: [PATCHv6 0/6] Casing improvements, language-independent part Michal Nazarewicz
@ 2017-03-21  1:27     ` Michal Nazarewicz
  2017-03-21  1:27     ` bug#24603: [PATCHv6 2/6] Introduce case_character function Michal Nazarewicz
                       ` (4 subsequent siblings)
  5 siblings, 0 replies; 89+ messages in thread
From: Michal Nazarewicz @ 2017-03-21  1:27 UTC (permalink / raw)
  To: Eli Zaretskii, Andreas Schwab, 24603

casify_object had three major cases to cover and those were mostly
independent of each other.  Move those branches to separate function
so it’s easier to comprehend each individual case.

While at it, use somewhat more descriptive ch and cased variable names
rather than c and c1.

This commit introduces no functional changes.

* src/casefiddle.c (casify_object): Split into…
(do_casify_integer, do_casify_multibyte_string,
do_casify_unibyte_string): …new functions.
---
 src/casefiddle.c | 196 +++++++++++++++++++++++++++++--------------------------
 1 file changed, 104 insertions(+), 92 deletions(-)

diff --git a/src/casefiddle.c b/src/casefiddle.c
index 11d59444916..72661979b4d 100644
--- a/src/casefiddle.c
+++ b/src/casefiddle.c
@@ -32,108 +32,120 @@ along with GNU Emacs.  If not, see <http://www.gnu.org/licenses/>.  */
 enum case_action {CASE_UP, CASE_DOWN, CASE_CAPITALIZE, CASE_CAPITALIZE_UP};
 \f
 static Lisp_Object
-casify_object (enum case_action flag, Lisp_Object obj)
+do_casify_natnum (enum case_action flag, Lisp_Object obj)
+{
+  int flagbits = (CHAR_ALT | CHAR_SUPER | CHAR_HYPER
+		  | CHAR_SHIFT | CHAR_CTL | CHAR_META);
+  int flags, ch = XFASTINT (obj), cased;
+  bool multibyte;
+
+  /* If the character has higher bits set above the flags, return it unchanged.
+     It is not a real character.  */
+  if (UNSIGNED_CMP (ch, >, flagbits))
+    return obj;
+
+  flags = ch & flagbits;
+  ch = ch & ~flagbits;
+
+  /* FIXME: Even if enable-multibyte-characters is nil, we may manipulate
+     multibyte chars.  This means we have a bug for latin-1 chars since when we
+     receive an int 128-255 we can't tell whether it's an eight-bit byte or
+     a latin-1 char.  */
+  multibyte = ch >= 256
+    || !NILP (BVAR (current_buffer, enable_multibyte_characters));
+  if (! multibyte)
+    MAKE_CHAR_MULTIBYTE (ch);
+  cased = flag == CASE_DOWN ? downcase (ch) : upcase (ch);
+  if (cased == ch)
+    return obj;
+
+  if (! multibyte)
+    MAKE_CHAR_UNIBYTE (cased);
+  XSETFASTINT (obj, cased | flags);
+  return obj;
+}
+
+static Lisp_Object
+do_casify_multibyte_string (enum case_action flag, Lisp_Object obj)
+{
+  ptrdiff_t i, i_byte, size = SCHARS (obj);
+  bool inword = flag == CASE_DOWN;
+  int len, ch, cased;
+  USE_SAFE_ALLOCA;
+  ptrdiff_t o_size;
+  if (INT_MULTIPLY_WRAPV (size, MAX_MULTIBYTE_LENGTH, &o_size))
+    o_size = PTRDIFF_MAX;
+  unsigned char *dst = SAFE_ALLOCA (o_size);
+  unsigned char *o = dst;
+
+  for (i = i_byte = 0; i < size; i++, i_byte += len)
+    {
+      if (o_size - MAX_MULTIBYTE_LENGTH < o - dst)
+	string_overflow ();
+      ch = STRING_CHAR_AND_LENGTH (SDATA (obj) + i_byte, len);
+      if (inword && flag != CASE_CAPITALIZE_UP)
+	cased = downcase (ch);
+      else if (!inword || flag != CASE_CAPITALIZE_UP)
+	cased = upcase (ch);
+      else
+	cased = ch;
+      if ((int) flag >= (int) CASE_CAPITALIZE)
+	inword = (SYNTAX (ch) == Sword);
+      o += CHAR_STRING (cased, o);
+    }
+  eassert (o - dst <= o_size);
+  obj = make_multibyte_string ((char *) dst, size, o - dst);
+  SAFE_FREE ();
+  return obj;
+}
+
+static Lisp_Object
+do_casify_unibyte_string (enum case_action flag, Lisp_Object obj)
 {
-  int c, c1;
+  ptrdiff_t i, size = SCHARS (obj);
   bool inword = flag == CASE_DOWN;
+  int ch, cased;
+
+  obj = Fcopy_sequence (obj);
+  for (i = 0; i < size; i++)
+    {
+      ch = SREF (obj, i);
+      MAKE_CHAR_MULTIBYTE (ch);
+      cased = ch;
+      if (inword && flag != CASE_CAPITALIZE_UP)
+	ch = downcase (ch);
+      else if (!uppercasep (ch)
+	       && (!inword || flag != CASE_CAPITALIZE_UP))
+	ch = upcase (cased);
+      if ((int) flag >= (int) CASE_CAPITALIZE)
+	inword = (SYNTAX (ch) == Sword);
+      if (ch == cased)
+	continue;
+      MAKE_CHAR_UNIBYTE (ch);
+      /* If the char can't be converted to a valid byte, just don't change it */
+      if (ch >= 0 && ch < 256)
+	SSET (obj, i, ch);
+    }
+  return obj;
+}
 
+static Lisp_Object
+casify_object (enum case_action flag, Lisp_Object obj)
+{
   /* If the case table is flagged as modified, rescan it.  */
   if (NILP (XCHAR_TABLE (BVAR (current_buffer, downcase_table))->extras[1]))
     Fset_case_table (BVAR (current_buffer, downcase_table));
 
   if (NATNUMP (obj))
-    {
-      int flagbits = (CHAR_ALT | CHAR_SUPER | CHAR_HYPER
-		      | CHAR_SHIFT | CHAR_CTL | CHAR_META);
-      int flags = XINT (obj) & flagbits;
-      bool multibyte = ! NILP (BVAR (current_buffer,
-				     enable_multibyte_characters));
-
-      /* If the character has higher bits set
-	 above the flags, return it unchanged.
-	 It is not a real character.  */
-      if (UNSIGNED_CMP (XFASTINT (obj), >, flagbits))
-	return obj;
-
-      c1 = XFASTINT (obj) & ~flagbits;
-      /* FIXME: Even if enable-multibyte-characters is nil, we may
-	 manipulate multibyte chars.  This means we have a bug for latin-1
-	 chars since when we receive an int 128-255 we can't tell whether
-	 it's an eight-bit byte or a latin-1 char.  */
-      if (c1 >= 256)
-	multibyte = 1;
-      if (! multibyte)
-	MAKE_CHAR_MULTIBYTE (c1);
-      c = flag == CASE_DOWN ? downcase (c1) : upcase (c1);
-      if (c != c1)
-	{
-	  if (! multibyte)
-	    MAKE_CHAR_UNIBYTE (c);
-	  XSETFASTINT (obj, c | flags);
-	}
-      return obj;
-    }
-
-  if (!STRINGP (obj))
+    return do_casify_natnum (flag, obj);
+  else if (!STRINGP (obj))
     wrong_type_argument (Qchar_or_string_p, obj);
-  else if (!STRING_MULTIBYTE (obj))
-    {
-      ptrdiff_t i;
-      ptrdiff_t size = SCHARS (obj);
-
-      obj = Fcopy_sequence (obj);
-      for (i = 0; i < size; i++)
-	{
-	  c = SREF (obj, i);
-	  MAKE_CHAR_MULTIBYTE (c);
-	  c1 = c;
-	  if (inword && flag != CASE_CAPITALIZE_UP)
-	    c = downcase (c);
-	  else if (!uppercasep (c)
-		   && (!inword || flag != CASE_CAPITALIZE_UP))
-	    c = upcase (c1);
-	  if ((int) flag >= (int) CASE_CAPITALIZE)
-	    inword = (SYNTAX (c) == Sword);
-	  if (c != c1)
-	    {
-	      MAKE_CHAR_UNIBYTE (c);
-	      /* If the char can't be converted to a valid byte, just don't
-		 change it.  */
-	      if (c >= 0 && c < 256)
-		SSET (obj, i, c);
-	    }
-	}
-      return obj;
-    }
+  else if (!SCHARS (obj))
+    return obj;
+  else if (STRING_MULTIBYTE (obj))
+    return do_casify_multibyte_string (flag, obj);
   else
-    {
-      ptrdiff_t i, i_byte, size = SCHARS (obj);
-      int len;
-      USE_SAFE_ALLOCA;
-      ptrdiff_t o_size;
-      if (INT_MULTIPLY_WRAPV (size, MAX_MULTIBYTE_LENGTH, &o_size))
-	o_size = PTRDIFF_MAX;
-      unsigned char *dst = SAFE_ALLOCA (o_size);
-      unsigned char *o = dst;
-
-      for (i = i_byte = 0; i < size; i++, i_byte += len)
-	{
-	  if (o_size - MAX_MULTIBYTE_LENGTH < o - dst)
-	    string_overflow ();
-	  c = STRING_CHAR_AND_LENGTH (SDATA (obj) + i_byte, len);
-	  if (inword && flag != CASE_CAPITALIZE_UP)
-	    c = downcase (c);
-	  else if (!inword || flag != CASE_CAPITALIZE_UP)
-	    c = upcase (c);
-	  if ((int) flag >= (int) CASE_CAPITALIZE)
-	    inword = (SYNTAX (c) == Sword);
-	  o += CHAR_STRING (c, o);
-	}
-      eassert (o - dst <= o_size);
-      obj = make_multibyte_string ((char *) dst, size, o - dst);
-      SAFE_FREE ();
-      return obj;
-    }
+    return do_casify_unibyte_string (flag, obj);
 }
 
 DEFUN ("upcase", Fupcase, Supcase, 1, 1, 0,
-- 
2.12.0.367.g23dc2f6d3c-goog






^ permalink raw reply related	[flat|nested] 89+ messages in thread

* bug#24603: [PATCHv6 2/6] Introduce case_character function
  2017-03-21  1:27   ` bug#24603: [PATCHv6 0/6] Casing improvements, language-independent part Michal Nazarewicz
  2017-03-21  1:27     ` bug#24603: [PATCHv6 1/6] Split casify_object into multiple functions Michal Nazarewicz
@ 2017-03-21  1:27     ` Michal Nazarewicz
  2017-03-21  1:27     ` bug#24603: [PATCHv6 3/6] Add support for title-casing letters (bug#24603) Michal Nazarewicz
                       ` (3 subsequent siblings)
  5 siblings, 0 replies; 89+ messages in thread
From: Michal Nazarewicz @ 2017-03-21  1:27 UTC (permalink / raw)
  To: Eli Zaretskii, Andreas Schwab, 24603

Move single-character casing logic into a separate function so that
it is collected in a single place.  This will make future changes to
the logic easier.  This commit introduces no functionality changes.

* src/casefiddle.c (struct casing_context, prepare_casing_context): New
sturcture for saving casing context and function to initialise it.
(case_character): New function which cases character base on provided
context.
(do_casify_integer, do_casify_multibyte_string,
do_casify_unibyte_string, casify_object, casify_region): Convert to
use casing_context and case_character.
---
 src/casefiddle.c | 134 +++++++++++++++++++++++++++++++------------------------
 1 file changed, 76 insertions(+), 58 deletions(-)

diff --git a/src/casefiddle.c b/src/casefiddle.c
index 72661979b4d..dfbb5c3e172 100644
--- a/src/casefiddle.c
+++ b/src/casefiddle.c
@@ -30,9 +30,55 @@ along with GNU Emacs.  If not, see <http://www.gnu.org/licenses/>.  */
 #include "keymap.h"
 
 enum case_action {CASE_UP, CASE_DOWN, CASE_CAPITALIZE, CASE_CAPITALIZE_UP};
+
+/* State for casing individual characters.  */
+struct casing_context {
+  /* User-requested action. */
+  enum case_action flag;
+  /* If true, function operates on a buffer as opposed to a string or character.
+     When run on a buffer, syntax_prefix_flag_p is taken into account when
+     determined inword flag. */
+  bool inbuffer;
+  /* Conceptually, this denotes whether we are inside of a word except
+     that if flag is CASE_UP it’s always false and if flag is CASE_DOWN
+     this is always true. */
+  bool inword;
+};
+
+/* Initialise CTX structure for casing characters. */
+static void
+prepare_casing_context (struct casing_context *ctx,
+			enum case_action flag, bool inbuffer)
+{
+  ctx->flag = flag;
+  ctx->inbuffer = inbuffer;
+  ctx->inword = flag == CASE_DOWN;
+
+  /* If the case table is flagged as modified, rescan it.  */
+  if (NILP (XCHAR_TABLE (BVAR (current_buffer, downcase_table))->extras[1]))
+    Fset_case_table (BVAR (current_buffer, downcase_table));
+
+  if (inbuffer && (int) flag >= (int) CASE_CAPITALIZE)
+    SETUP_BUFFER_SYNTAX_TABLE ();	/* For syntax_prefix_flag_p.  */
+}
+
+/* Based on CTX, case character CH accordingly.  Update CTX as necessary.
+   Return cased character. */
+static int
+case_character (struct casing_context *ctx, int ch)
+{
+  if (ctx->inword)
+    ch = ctx->flag == CASE_CAPITALIZE_UP ? ch : downcase (ch);
+  else
+    ch = upcase(ch);
+  if ((int) ctx->flag >= (int) CASE_CAPITALIZE)
+    ctx->inword = SYNTAX (ch) == Sword &&
+      (!ctx->inbuffer || ctx->inword || !syntax_prefix_flag_p (ch));
+  return ch;
+}
 \f
 static Lisp_Object
-do_casify_natnum (enum case_action flag, Lisp_Object obj)
+do_casify_natnum (struct casing_context *ctx, Lisp_Object obj)
 {
   int flagbits = (CHAR_ALT | CHAR_SUPER | CHAR_HYPER
 		  | CHAR_SHIFT | CHAR_CTL | CHAR_META);
@@ -55,7 +101,7 @@ do_casify_natnum (enum case_action flag, Lisp_Object obj)
     || !NILP (BVAR (current_buffer, enable_multibyte_characters));
   if (! multibyte)
     MAKE_CHAR_MULTIBYTE (ch);
-  cased = flag == CASE_DOWN ? downcase (ch) : upcase (ch);
+  cased = case_character (ctx, ch);
   if (cased == ch)
     return obj;
 
@@ -66,10 +112,9 @@ do_casify_natnum (enum case_action flag, Lisp_Object obj)
 }
 
 static Lisp_Object
-do_casify_multibyte_string (enum case_action flag, Lisp_Object obj)
+do_casify_multibyte_string (struct casing_context *ctx, Lisp_Object obj)
 {
   ptrdiff_t i, i_byte, size = SCHARS (obj);
-  bool inword = flag == CASE_DOWN;
   int len, ch, cased;
   USE_SAFE_ALLOCA;
   ptrdiff_t o_size;
@@ -83,14 +128,7 @@ do_casify_multibyte_string (enum case_action flag, Lisp_Object obj)
       if (o_size - MAX_MULTIBYTE_LENGTH < o - dst)
 	string_overflow ();
       ch = STRING_CHAR_AND_LENGTH (SDATA (obj) + i_byte, len);
-      if (inword && flag != CASE_CAPITALIZE_UP)
-	cased = downcase (ch);
-      else if (!inword || flag != CASE_CAPITALIZE_UP)
-	cased = upcase (ch);
-      else
-	cased = ch;
-      if ((int) flag >= (int) CASE_CAPITALIZE)
-	inword = (SYNTAX (ch) == Sword);
+      cased = case_character (ctx, ch);
       o += CHAR_STRING (cased, o);
     }
   eassert (o - dst <= o_size);
@@ -100,10 +138,9 @@ do_casify_multibyte_string (enum case_action flag, Lisp_Object obj)
 }
 
 static Lisp_Object
-do_casify_unibyte_string (enum case_action flag, Lisp_Object obj)
+do_casify_unibyte_string (struct casing_context *ctx, Lisp_Object obj)
 {
   ptrdiff_t i, size = SCHARS (obj);
-  bool inword = flag == CASE_DOWN;
   int ch, cased;
 
   obj = Fcopy_sequence (obj);
@@ -111,20 +148,13 @@ do_casify_unibyte_string (enum case_action flag, Lisp_Object obj)
     {
       ch = SREF (obj, i);
       MAKE_CHAR_MULTIBYTE (ch);
-      cased = ch;
-      if (inword && flag != CASE_CAPITALIZE_UP)
-	ch = downcase (ch);
-      else if (!uppercasep (ch)
-	       && (!inword || flag != CASE_CAPITALIZE_UP))
-	ch = upcase (cased);
-      if ((int) flag >= (int) CASE_CAPITALIZE)
-	inword = (SYNTAX (ch) == Sword);
+      cased = case_character (ctx, ch);
       if (ch == cased)
 	continue;
-      MAKE_CHAR_UNIBYTE (ch);
+      MAKE_CHAR_UNIBYTE (cased);
       /* If the char can't be converted to a valid byte, just don't change it */
-      if (ch >= 0 && ch < 256)
-	SSET (obj, i, ch);
+      if (cased >= 0 && cased < 256)
+	SSET (obj, i, cased);
     }
   return obj;
 }
@@ -132,20 +162,19 @@ do_casify_unibyte_string (enum case_action flag, Lisp_Object obj)
 static Lisp_Object
 casify_object (enum case_action flag, Lisp_Object obj)
 {
-  /* If the case table is flagged as modified, rescan it.  */
-  if (NILP (XCHAR_TABLE (BVAR (current_buffer, downcase_table))->extras[1]))
-    Fset_case_table (BVAR (current_buffer, downcase_table));
+  struct casing_context ctx;
+  prepare_casing_context (&ctx, flag, false);
 
   if (NATNUMP (obj))
-    return do_casify_natnum (flag, obj);
+    return do_casify_natnum (&ctx, obj);
   else if (!STRINGP (obj))
     wrong_type_argument (Qchar_or_string_p, obj);
   else if (!SCHARS (obj))
     return obj;
   else if (STRING_MULTIBYTE (obj))
-    return do_casify_multibyte_string (flag, obj);
+    return do_casify_multibyte_string (&ctx, obj);
   else
-    return do_casify_unibyte_string (flag, obj);
+    return do_casify_unibyte_string (&ctx, obj);
 }
 
 DEFUN ("upcase", Fupcase, Supcase, 1, 1, 0,
@@ -196,8 +225,6 @@ The argument object is not altered--the value is a copy.  */)
 static void
 casify_region (enum case_action flag, Lisp_Object b, Lisp_Object e)
 {
-  int c;
-  bool inword = flag == CASE_DOWN;
   bool multibyte = !NILP (BVAR (current_buffer, enable_multibyte_characters));
   ptrdiff_t start, end;
   ptrdiff_t start_byte;
@@ -208,14 +235,12 @@ casify_region (enum case_action flag, Lisp_Object b, Lisp_Object e)
   ptrdiff_t opoint = PT;
   ptrdiff_t opoint_byte = PT_BYTE;
 
+  struct casing_context ctx;
+
   if (EQ (b, e))
     /* Not modifying because nothing marked */
     return;
 
-  /* If the case table is flagged as modified, rescan it.  */
-  if (NILP (XCHAR_TABLE (BVAR (current_buffer, downcase_table))->extras[1]))
-    Fset_case_table (BVAR (current_buffer, downcase_table));
-
   validate_region (&b, &e);
   start = XFASTINT (b);
   end = XFASTINT (e);
@@ -223,32 +248,25 @@ casify_region (enum case_action flag, Lisp_Object b, Lisp_Object e)
   record_change (start, end - start);
   start_byte = CHAR_TO_BYTE (start);
 
-  SETUP_BUFFER_SYNTAX_TABLE ();	/* For syntax_prefix_flag_p.  */
+  prepare_casing_context (&ctx, flag, true);
 
   while (start < end)
     {
-      int c2, len;
+      int ch, cased, len;
 
       if (multibyte)
 	{
-	  c = FETCH_MULTIBYTE_CHAR (start_byte);
-	  len = CHAR_BYTES (c);
+	  ch = FETCH_MULTIBYTE_CHAR (start_byte);
+	  len = CHAR_BYTES (ch);
 	}
       else
 	{
-	  c = FETCH_BYTE (start_byte);
-	  MAKE_CHAR_MULTIBYTE (c);
+	  ch = FETCH_BYTE (start_byte);
+	  MAKE_CHAR_MULTIBYTE (ch);
 	  len = 1;
 	}
-      c2 = c;
-      if (inword && flag != CASE_CAPITALIZE_UP)
-	c = downcase (c);
-      else if (!inword || flag != CASE_CAPITALIZE_UP)
-	c = upcase (c);
-      if ((int) flag >= (int) CASE_CAPITALIZE)
-	inword = ((SYNTAX (c) == Sword)
-		  && (inword || !syntax_prefix_flag_p (c)));
-      if (c != c2)
+      cased = case_character (&ctx, ch);
+      if (ch != cased)
 	{
 	  last = start;
 	  if (first < 0)
@@ -256,18 +274,18 @@ casify_region (enum case_action flag, Lisp_Object b, Lisp_Object e)
 
 	  if (! multibyte)
 	    {
-	      MAKE_CHAR_UNIBYTE (c);
-	      FETCH_BYTE (start_byte) = c;
+	      MAKE_CHAR_UNIBYTE (cased);
+	      FETCH_BYTE (start_byte) = cased;
 	    }
-	  else if (ASCII_CHAR_P (c2) && ASCII_CHAR_P (c))
-	    FETCH_BYTE (start_byte) = c;
+	  else if (ASCII_CHAR_P (cased) && ASCII_CHAR_P (ch))
+	    FETCH_BYTE (start_byte) = cased;
 	  else
 	    {
-	      int tolen = CHAR_BYTES (c);
+	      int tolen = CHAR_BYTES (cased);
 	      int j;
 	      unsigned char str[MAX_MULTIBYTE_LENGTH];
 
-	      CHAR_STRING (c, str);
+	      CHAR_STRING (cased, str);
 	      if (len == tolen)
 		{
 		  /* Length is unchanged.  */
-- 
2.12.0.367.g23dc2f6d3c-goog






^ permalink raw reply related	[flat|nested] 89+ messages in thread

* bug#24603: [PATCHv6 3/6] Add support for title-casing letters (bug#24603)
  2017-03-21  1:27   ` bug#24603: [PATCHv6 0/6] Casing improvements, language-independent part Michal Nazarewicz
  2017-03-21  1:27     ` bug#24603: [PATCHv6 1/6] Split casify_object into multiple functions Michal Nazarewicz
  2017-03-21  1:27     ` bug#24603: [PATCHv6 2/6] Introduce case_character function Michal Nazarewicz
@ 2017-03-21  1:27     ` Michal Nazarewicz
  2017-03-21  1:27     ` bug#24603: [PATCHv6 4/6] Split up casify_region function (bug#24603) Michal Nazarewicz
                       ` (2 subsequent siblings)
  5 siblings, 0 replies; 89+ messages in thread
From: Michal Nazarewicz @ 2017-03-21  1:27 UTC (permalink / raw)
  To: Eli Zaretskii, Andreas Schwab, 24603

* src/casefiddle.c (struct casing_context, prepare_casing_context): Add
titlecase_char_table member.  It’s set to the ‘titlecase’ Unicode
property table if capitalisation has been requested.
(case_character): Make use of the titlecase_char_table to title-case
initial characters when capitalising.

* test/src/casefiddle-tests.el (casefiddle-tests--characters,
casefiddle-tests-casing): Update test cases which are now passing.
---
 etc/NEWS                     |  2 +-
 src/casefiddle.c             | 25 +++++++++++++++++++------
 test/src/casefiddle-tests.el | 39 ++++++++++++++++++++++++++-------------
 3 files changed, 46 insertions(+), 20 deletions(-)

diff --git a/etc/NEWS b/etc/NEWS
index cd98f533998..ff9e5c058be 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -355,7 +355,7 @@ same as in modes where the character is not whitespace.
 Instead of only checking the modification time, Emacs now also checks
 the file's actual content before prompting the user.
 
-** Title case characters are properly converted to upper case.
+** Title case characters are properly cased (from and into).
 'upcase', 'upcase-region' et al. convert title case characters (such
 as the single character "Dz") into their upper case form (such as "DZ").
 As a downside, 'capitalize' and 'upcase-initials' produce awkward
diff --git a/src/casefiddle.c b/src/casefiddle.c
index dfbb5c3e172..a83469cbdc7 100644
--- a/src/casefiddle.c
+++ b/src/casefiddle.c
@@ -33,6 +33,9 @@ enum case_action {CASE_UP, CASE_DOWN, CASE_CAPITALIZE, CASE_CAPITALIZE_UP};
 
 /* State for casing individual characters.  */
 struct casing_context {
+  /* A char-table with title-case character mappings or nil.  Non-nil implies
+     flag is CASE_CAPITALIZE or CASE_CAPITALIZE_UP.  */
+  Lisp_Object titlecase_char_table;
   /* User-requested action. */
   enum case_action flag;
   /* If true, function operates on a buffer as opposed to a string or character.
@@ -53,6 +56,8 @@ prepare_casing_context (struct casing_context *ctx,
   ctx->flag = flag;
   ctx->inbuffer = inbuffer;
   ctx->inword = flag == CASE_DOWN;
+  ctx->titlecase_char_table = (int)flag < (int)CASE_CAPITALIZE ? Qnil :
+    uniprop_table (intern_c_string ("titlecase"));
 
   /* If the case table is flagged as modified, rescan it.  */
   if (NILP (XCHAR_TABLE (BVAR (current_buffer, downcase_table))->extras[1]))
@@ -67,10 +72,16 @@ prepare_casing_context (struct casing_context *ctx,
 static int
 case_character (struct casing_context *ctx, int ch)
 {
+  Lisp_Object prop;
+
   if (ctx->inword)
     ch = ctx->flag == CASE_CAPITALIZE_UP ? ch : downcase (ch);
+  else if (!NILP (ctx->titlecase_char_table) &&
+	   CHARACTERP (prop = CHAR_TABLE_REF (ctx->titlecase_char_table, ch)))
+    ch = XFASTINT (prop);
   else
     ch = upcase(ch);
+
   if ((int) ctx->flag >= (int) CASE_CAPITALIZE)
     ctx->inword = SYNTAX (ch) == Sword &&
       (!ctx->inbuffer || ctx->inword || !syntax_prefix_flag_p (ch));
@@ -198,8 +209,8 @@ The argument object is not altered--the value is a copy.  */)
 
 DEFUN ("capitalize", Fcapitalize, Scapitalize, 1, 1, 0,
        doc: /* Convert argument to capitalized form and return that.
-This means that each word's first character is upper case
-and the rest is lower case.
+This means that each word's first character is converted to either
+title case or upper case, and the rest to lower case.
 The argument may be a character or string.  The result has the same type.
 The argument object is not altered--the value is a copy.  */)
   (Lisp_Object obj)
@@ -211,7 +222,8 @@ The argument object is not altered--the value is a copy.  */)
 
 DEFUN ("upcase-initials", Fupcase_initials, Supcase_initials, 1, 1, 0,
        doc: /* Convert the initial of each word in the argument to upper case.
-Do not change the other letters of each word.
+This means that each word's first character is converted to either
+title case or upper case, and the rest are left unchanged.
 The argument may be a character or string.  The result has the same type.
 The argument object is not altered--the value is a copy.  */)
   (Lisp_Object obj)
@@ -375,8 +387,8 @@ point and the mark is operated on.  */)
 
 DEFUN ("capitalize-region", Fcapitalize_region, Scapitalize_region, 2, 2, "r",
        doc: /* Convert the region to capitalized form.
-Capitalized form means each word's first character is upper case
-and the rest of it is lower case.
+This means that each word's first character is converted to either
+title case or upper case, and the rest to lower case.
 In programs, give two arguments, the starting and ending
 character positions to operate on.  */)
   (Lisp_Object beg, Lisp_Object end)
@@ -390,7 +402,8 @@ character positions to operate on.  */)
 DEFUN ("upcase-initials-region", Fupcase_initials_region,
        Supcase_initials_region, 2, 2, "r",
        doc: /* Upcase the initial of each word in the region.
-Subsequent letters of each word are not changed.
+This means that each word's first character is converted to either
+title case or upper case, and the rest are left unchanged.
 In programs, give two arguments, the starting and ending
 character positions to operate on.  */)
   (Lisp_Object beg, Lisp_Object end)
diff --git a/test/src/casefiddle-tests.el b/test/src/casefiddle-tests.el
index 152d85de006..e83cb00059b 100644
--- a/test/src/casefiddle-tests.el
+++ b/test/src/casefiddle-tests.el
@@ -63,13 +63,9 @@ casefiddle-tests--characters
     (?Ł ?Ł ?ł ?Ł)
     (?ł ?Ł ?ł ?Ł)
 
-    ;; FIXME(bug#24603): Commented ones are what we want.
-    ;;(?DŽ ?DŽ ?dž ?Dž)
-    (?DŽ ?DŽ ?dž ?DŽ)
-    ;;(?Dž ?DŽ ?dž ?Dž)
-    (?Dž ?DŽ ?dž ?DŽ)
-    ;;(?dž ?DŽ ?dž ?Dž)
-    (?dž ?DŽ ?dž ?DŽ)
+    (?DŽ ?DŽ ?dž ?Dž)
+    (?Dž ?DŽ ?dž ?Dž)
+    (?dž ?DŽ ?dž ?Dž)
 
     (?Σ ?Σ ?σ ?Σ)
     (?σ ?Σ ?σ ?Σ)
@@ -186,19 +182,19 @@ casefiddle-tests--test-casing
       ;; input     upper     lower    capitalize up-initials
       '(("Foo baR" "FOO BAR" "foo bar" "Foo Bar" "Foo BaR")
         ("Ⅷ ⅷ" "Ⅷ Ⅷ" "ⅷ ⅷ" "Ⅷ Ⅷ" "Ⅷ Ⅷ")
+        ;; "DžUNGLA" is an unfortunate result but it’s really best we can
+        ;; do while still being consistent.  Hopefully, users only ever
+        ;; use upcase-initials on camelCase identifiers not real words.
+        ("DŽUNGLA" "DŽUNGLA" "džungla" "Džungla" "DžUNGLA")
+        ("Džungla" "DŽUNGLA" "džungla" "Džungla" "Džungla")
+        ("džungla" "DŽUNGLA" "džungla" "Džungla" "Džungla")
         ;; FIXME(bug#24603): Everything below is broken at the moment.
         ;; Here’s what should happen:
-        ;;("DŽUNGLA" "DŽUNGLA" "džungla" "Džungla" "DžUNGLA")
-        ;;("Džungla" "DŽUNGLA" "džungla" "Džungla" "Džungla")
-        ;;("džungla" "DŽUNGLA" "džungla" "Džungla" "Džungla")
         ;;("define" "DEFINE" "define" "Define" "Define")
         ;;("fish" "FIsh" "fish" "Fish" "Fish")
         ;;("Straße" "STRASSE" "straße" "Straße" "Straße")
         ;;("ΌΣΟΣ" "ΌΣΟΣ" "όσος" "Όσος" "Όσος")
         ;; And here’s what is actually happening:
-        ("DŽUNGLA" "DŽUNGLA" "džungla" "DŽungla" "DŽUNGLA")
-        ("Džungla" "DŽUNGLA" "džungla" "DŽungla" "DŽungla")
-        ("džungla" "DŽUNGLA" "džungla" "DŽungla" "DŽungla")
         ("define" "DEfiNE" "define" "Define" "Define")
         ("fish" "fiSH" "fish" "fish" "fish")
         ("Straße" "STRAßE" "straße" "Straße" "Straße")
@@ -243,4 +239,21 @@ casefiddle-tests--test-casing
                 "\xef\xff\xef Zażółć GĘŚlą \xcf\xcf")))))))
 
 
+(ert-deftest casefiddle-tests-char-casing ()
+  ;;             input upcase downcase [titlecase]
+  (dolist (test '((?a ?A ?a) (?A ?A ?a)
+                  (?ł ?Ł ?ł) (?Ł ?Ł ?ł)
+                  (?ß ?ß ?ß) (?ẞ ?ẞ ?ß)
+                  (?ⅷ ?Ⅷ ?ⅷ) (?Ⅷ ?Ⅷ ?ⅷ)
+                  (?DŽ ?DŽ ?dž ?Dž) (?Dž ?DŽ ?dž ?Dž) (?dž ?DŽ ?dž ?Dž)))
+    (let ((ch (car test))
+          (up (nth 1 test))
+          (lo (nth 2 test))
+          (tc (or (nth 3 test) (nth 1 test))))
+      (should (eq up (upcase ch)))
+      (should (eq lo (downcase ch)))
+      (should (eq tc (capitalize ch)))
+      (should (eq tc (upcase-initials ch))))))
+
+
 ;;; casefiddle-tests.el ends here
-- 
2.12.0.367.g23dc2f6d3c-goog






^ permalink raw reply related	[flat|nested] 89+ messages in thread

* bug#24603: [PATCHv6 4/6] Split up casify_region function (bug#24603)
  2017-03-21  1:27   ` bug#24603: [PATCHv6 0/6] Casing improvements, language-independent part Michal Nazarewicz
                       ` (2 preceding siblings ...)
  2017-03-21  1:27     ` bug#24603: [PATCHv6 3/6] Add support for title-casing letters (bug#24603) Michal Nazarewicz
@ 2017-03-21  1:27     ` Michal Nazarewicz
  2017-03-21  1:27     ` bug#24603: [PATCHv6 5/6] Support casing characters which map into multiple code points (bug#24603) Michal Nazarewicz
  2017-03-21  1:27     ` bug#24603: [PATCHv6 6/6] Implement special sigma casing rule (bug#24603) Michal Nazarewicz
  5 siblings, 0 replies; 89+ messages in thread
From: Michal Nazarewicz @ 2017-03-21  1:27 UTC (permalink / raw)
  To: Eli Zaretskii, Andreas Schwab, 24603

No functional changes at this time but splitting casify_region into
a function dealing with multibyte and another dealing with unibyte
buffers will make future code changes slightly easier.

* src/casefiddle.c (casify_region): Move most of the code into two
new functions:
(do_casify_multibyte_region, do_casify_unibyte_region): new functions.
---
 src/casefiddle.c | 159 ++++++++++++++++++++++++++++++-------------------------
 1 file changed, 86 insertions(+), 73 deletions(-)

diff --git a/src/casefiddle.c b/src/casefiddle.c
index a83469cbdc7..b1a5f8e236e 100644
--- a/src/casefiddle.c
+++ b/src/casefiddle.c
@@ -231,102 +231,115 @@ The argument object is not altered--the value is a copy.  */)
   return casify_object (CASE_CAPITALIZE_UP, obj);
 }
 \f
-/* flag is CASE_UP, CASE_DOWN or CASE_CAPITALIZE or CASE_CAPITALIZE_UP.
-   b and e specify range of buffer to operate on. */
-
-static void
-casify_region (enum case_action flag, Lisp_Object b, Lisp_Object e)
+/* Based on CTX, case region in a unibyte buffer from POS to *ENDP.  Return
+   first position that has changed and save last position in *ENDP.  If no
+   characters were changed, return -1 and *ENDP is unspecified. */
+static ptrdiff_t
+do_casify_unibyte_region (struct casing_context *ctx,
+			  ptrdiff_t pos, ptrdiff_t *endp)
 {
-  bool multibyte = !NILP (BVAR (current_buffer, enable_multibyte_characters));
-  ptrdiff_t start, end;
-  ptrdiff_t start_byte;
+  ptrdiff_t first = -1, last = -1;  /* Position of first and last changes. */
+  ptrdiff_t end = *endp;
+  int ch, cased;
 
-  /* Position of first and last changes.  */
-  ptrdiff_t first = -1, last;
+  for (; pos < end; ++pos)
+    {
+      ch = FETCH_BYTE (pos);
+      MAKE_CHAR_MULTIBYTE (ch);
 
-  ptrdiff_t opoint = PT;
-  ptrdiff_t opoint_byte = PT_BYTE;
+      cased = case_character (ctx, ch);
+      if (cased == ch)
+	continue;
 
-  struct casing_context ctx;
+      last = pos;
+      if (first < 0)
+	first = pos;
 
-  if (EQ (b, e))
-    /* Not modifying because nothing marked */
-    return;
+      MAKE_CHAR_UNIBYTE (cased);
+      FETCH_BYTE (pos) = cased;
+    }
 
-  validate_region (&b, &e);
-  start = XFASTINT (b);
-  end = XFASTINT (e);
-  modify_text (start, end);
-  record_change (start, end - start);
-  start_byte = CHAR_TO_BYTE (start);
+  *endp = last + 1;
+  return first;
+}
 
-  prepare_casing_context (&ctx, flag, true);
+/* Based on CTX, case region in a multibyte buffer from POS to *ENDP.  Return
+   first position that has changed and save last position in *ENDP.  If no
+   characters were changed, return -1 and *ENDP is unspecified. */
+static ptrdiff_t
+do_casify_multibyte_region (struct casing_context *ctx,
+                           ptrdiff_t pos, ptrdiff_t *endp)
+{
+  ptrdiff_t first = -1, last = -1;  /* Position of first and last changes. */
+  ptrdiff_t pos_byte = CHAR_TO_BYTE (pos), end = *endp;
+  ptrdiff_t opoint = PT;
+  int ch, cased, len;
 
-  while (start < end)
+  while (pos < end)
     {
-      int ch, cased, len;
-
-      if (multibyte)
-	{
-	  ch = FETCH_MULTIBYTE_CHAR (start_byte);
-	  len = CHAR_BYTES (ch);
-	}
-      else
-	{
-	  ch = FETCH_BYTE (start_byte);
-	  MAKE_CHAR_MULTIBYTE (ch);
-	  len = 1;
-	}
-      cased = case_character (&ctx, ch);
-      if (ch != cased)
+      ch = STRING_CHAR_AND_LENGTH (BYTE_POS_ADDR (pos_byte), len);
+      cased = case_character (ctx, ch);
+      if (cased != ch)
 	{
-	  last = start;
+	  last = pos;
 	  if (first < 0)
-	    first = start;
+	    first = pos;
 
-	  if (! multibyte)
-	    {
-	      MAKE_CHAR_UNIBYTE (cased);
-	      FETCH_BYTE (start_byte) = cased;
-	    }
-	  else if (ASCII_CHAR_P (cased) && ASCII_CHAR_P (ch))
-	    FETCH_BYTE (start_byte) = cased;
+	  if (ASCII_CHAR_P (cased) && ASCII_CHAR_P (ch))
+	    FETCH_BYTE (pos_byte) = cased;
 	  else
 	    {
-	      int tolen = CHAR_BYTES (cased);
-	      int j;
 	      unsigned char str[MAX_MULTIBYTE_LENGTH];
-
-	      CHAR_STRING (cased, str);
-	      if (len == tolen)
-		{
-		  /* Length is unchanged.  */
-		  for (j = 0; j < len; ++j)
-		    FETCH_BYTE (start_byte + j) = str[j];
-		}
+	      int totlen = CHAR_STRING (cased, str);
+	      if (len == totlen)
+		memcpy (BYTE_POS_ADDR (pos_byte), str, len);
 	      else
-		{
-		  /* Replace one character with the other,
-		     keeping text properties the same.  */
-		  replace_range_2 (start, start_byte,
-				   start + 1, start_byte + len,
-				   (char *) str, 1, tolen,
-				   0);
-		  len = tolen;
-		}
+		/* Replace one character with the other(s), keeping text
+		   properties the same.  */
+		replace_range_2 (pos, pos_byte, pos + 1, pos_byte + len,
+				 (char *) str, 9, totlen, 0);
+	      len = totlen;
 	    }
 	}
-      start++;
-      start_byte += len;
+      pos++;
+      pos_byte += len;
     }
 
   if (PT != opoint)
-    TEMP_SET_PT_BOTH (opoint, opoint_byte);
+    TEMP_SET_PT_BOTH (opoint, CHAR_TO_BYTE (opoint));
+
+  *endp = last;
+  return first;
+}
+
+/* flag is CASE_UP, CASE_DOWN or CASE_CAPITALIZE or CASE_CAPITALIZE_UP.
+   b and e specify range of buffer to operate on. */
+static void
+casify_region (enum case_action flag, Lisp_Object b, Lisp_Object e)
+{
+  struct casing_context ctx;
+  ptrdiff_t start, end;
+
+  if (EQ (b, e))
+    /* Not modifying because nothing marked */
+    return;
+
+  validate_region (&b, &e);
+  start = XFASTINT (b);
+  end = XFASTINT (e);
+  modify_text (start, end);
+  record_change (start, end - start);
+  prepare_casing_context (&ctx, flag, true);
+
+  if (NILP (BVAR (current_buffer, enable_multibyte_characters)))
+    start = do_casify_unibyte_region (&ctx, start, &end);
+  else
+    start = do_casify_multibyte_region (&ctx, start, &end);
 
-  if (first >= 0)
+  if (start >= 0)
     {
-      signal_after_change (first, last + 1 - first, last + 1 - first);
-      update_compositions (first, last + 1, CHECK_ALL);
+      signal_after_change (start, end + 1 - start, end + 1 - start);
+      update_compositions (start, end + 1, CHECK_ALL);
     }
 }
 
-- 
2.12.0.367.g23dc2f6d3c-goog






^ permalink raw reply related	[flat|nested] 89+ messages in thread

* bug#24603: [PATCHv6 5/6] Support casing characters which map into multiple code points (bug#24603)
  2017-03-21  1:27   ` bug#24603: [PATCHv6 0/6] Casing improvements, language-independent part Michal Nazarewicz
                       ` (3 preceding siblings ...)
  2017-03-21  1:27     ` bug#24603: [PATCHv6 4/6] Split up casify_region function (bug#24603) Michal Nazarewicz
@ 2017-03-21  1:27     ` Michal Nazarewicz
  2017-03-22 16:06       ` Eli Zaretskii
  2017-03-21  1:27     ` bug#24603: [PATCHv6 6/6] Implement special sigma casing rule (bug#24603) Michal Nazarewicz
  5 siblings, 1 reply; 89+ messages in thread
From: Michal Nazarewicz @ 2017-03-21  1:27 UTC (permalink / raw)
  To: Eli Zaretskii, Andreas Schwab, 24603

Implement unconditional special casing rules defined in Unicode standard.

Among other things, they deal with cases when a single code point is
replaced by multiple ones because single character does not exist (e.g.
‘fi’ ligature turning into ‘FL’) or is not commonly used (e.g. ß turning
into SS).

* admin/unidata/SpecialCasing.txt: New data file pulled from Unicode
standard distribution.
* admin/unidata/README: Mention SpecialCasing.txt.

* admin/unidata/unidata-get.el (unidata-gen-table-special-casing,
unidata-gen-table-special-casing--do-load): New functions generating
‘special-uppercase’, ‘special-lowercase’ and ‘special-titlecase’
character Unicode properties built from the SpecialCasing.txt Unicode
data file.

* src/casefiddle.c (struct casing_str_buf): New structure for
representing short strings used to handle one-to-many character
mappings.

(case_character_imlp): New function which can handle one-to-many
character mappings.
(case_character, case_single_character): Wrappers for the above
functions.  The former may map one character to multiple (or no)
code points while the latter does what the former used to do (i.e.
handles one-to-one mappings only).

(do_casify_natnum, do_casify_unibyte_string,
do_casify_unibyte_region): Use case_single_character.
(do_casify_multibyte_string, do_casify_multibyte_region): Support new
features of case_character.
* (do_casify_region): Updated to reflact do_casify_multibyte_string
changes.

(casify_word): Handle situation when one character-length of a word
can change affecting where end of the word is.

(upcase, capitalize, upcase-initials): Update documentation to mention
limitations when working on characters.

* test/src/casefiddle-tests.el (casefiddle-tests-char-properties):
Add test cases for the newly introduced character properties.
(casefiddle-tests-casing): Update test cases which are now passing.

* test/lisp/char-fold-tests.el (char-fold--ascii-upcase,
char-fold--ascii-downcase): New functions which behave like old ‘upcase’
and ‘downcase’.
(char-fold--test-match-exactly): Use the new functions.  This is needed
because otherwise fi and similar characters are turned into their multi-
-character representation.

* doc/lispref/strings.texi: Describe issue with casing characters versus
strings.
* doc/lispref/nonascii.texi: Describe the new character properties.
---
 admin/unidata/README            |   4 +
 admin/unidata/SpecialCasing.txt | 281 +++++++++++++++++++++++++++++++++++++++
 admin/unidata/unidata-gen.el    |  72 ++++++++++
 doc/lispref/nonascii.texi       |  22 +++
 doc/lispref/strings.texi        |  23 ++++
 etc/NEWS                        |  17 ++-
 src/casefiddle.c                | 288 ++++++++++++++++++++++++++++------------
 test/lisp/char-fold-tests.el    |  12 +-
 test/src/casefiddle-tests.el    |  73 +++++-----
 9 files changed, 664 insertions(+), 128 deletions(-)
 create mode 100644 admin/unidata/SpecialCasing.txt

diff --git a/admin/unidata/README b/admin/unidata/README
index 534670ce6db..06a66663a72 100644
--- a/admin/unidata/README
+++ b/admin/unidata/README
@@ -24,3 +24,7 @@ http://www.unicode.org/Public/8.0.0/ucd/Blocks.txt
 NormalizationTest.txt
 http://www.unicode.org/Public/UNIDATA/NormalizationTest.txt
 2016-07-16
+
+SpecialCasing.txt
+http://unicode.org/Public/UNIDATA/SpecialCasing.txt
+2016-03-03
diff --git a/admin/unidata/SpecialCasing.txt b/admin/unidata/SpecialCasing.txt
new file mode 100644
index 00000000000..b23fa7f7680
--- /dev/null
+++ b/admin/unidata/SpecialCasing.txt
@@ -0,0 +1,281 @@
+# SpecialCasing-9.0.0.txt
+# Date: 2016-03-02, 18:55:13 GMT
+# © 2016 Unicode®, Inc.
+# Unicode and the Unicode Logo are registered trademarks of Unicode, Inc. in the U.S. and other countries.
+# For terms of use, see http://www.unicode.org/terms_of_use.html
+#
+# Unicode Character Database
+#   For documentation, see http://www.unicode.org/reports/tr44/
+#
+# Special Casing
+#
+# This file is a supplement to the UnicodeData.txt file. It does not define any
+# properties, but rather provides additional information about the casing of
+# Unicode characters, for situations when casing incurs a change in string length
+# or is dependent on context or locale. For compatibility, the UnicodeData.txt
+# file only contains simple case mappings for characters where they are one-to-one
+# and independent of context and language. The data in this file, combined with
+# the simple case mappings in UnicodeData.txt, defines the full case mappings
+# Lowercase_Mapping (lc), Titlecase_Mapping (tc), and Uppercase_Mapping (uc).
+#
+# Note that the preferred mechanism for defining tailored casing operations is
+# the Unicode Common Locale Data Repository (CLDR). For more information, see the
+# discussion of case mappings and case algorithms in the Unicode Standard.
+#
+# All code points not listed in this file that do not have a simple case mappings
+# in UnicodeData.txt map to themselves.
+# ================================================================================
+# Format
+# ================================================================================
+# The entries in this file are in the following machine-readable format:
+#
+# <code>; <lower>; <title>; <upper>; (<condition_list>;)? # <comment>
+#
+# <code>, <lower>, <title>, and <upper> provide the respective full case mappings
+# of <code>, expressed as character values in hex. If there is more than one character,
+# they are separated by spaces. Other than as used to separate elements, spaces are
+# to be ignored.
+#
+# The <condition_list> is optional. Where present, it consists of one or more language IDs
+# or casing contexts, separated by spaces. In these conditions:
+# - A condition list overrides the normal behavior if all of the listed conditions are true.
+# - The casing context is always the context of the characters in the original string,
+#   NOT in the resulting string.
+# - Case distinctions in the condition list are not significant.
+# - Conditions preceded by "Not_" represent the negation of the condition.
+# The condition list is not represented in the UCD as a formal property.
+#
+# A language ID is defined by BCP 47, with '-' and '_' treated equivalently.
+#
+# A casing context for a character is defined by Section 3.13 Default Case Algorithms
+# of The Unicode Standard.
+#
+# Parsers of this file must be prepared to deal with future additions to this format:
+#  * Additional contexts
+#  * Additional fields
+# ================================================================================
+
+# ================================================================================
+# Unconditional mappings
+# ================================================================================
+
+# The German es-zed is special--the normal mapping is to SS.
+# Note: the titlecase should never occur in practice. It is equal to titlecase(uppercase(<es-zed>))
+
+00DF; 00DF; 0053 0073; 0053 0053; # LATIN SMALL LETTER SHARP S
+
+# Preserve canonical equivalence for I with dot. Turkic is handled below.
+
+0130; 0069 0307; 0130; 0130; # LATIN CAPITAL LETTER I WITH DOT ABOVE
+
+# Ligatures
+
+FB00; FB00; 0046 0066; 0046 0046; # LATIN SMALL LIGATURE FF
+FB01; FB01; 0046 0069; 0046 0049; # LATIN SMALL LIGATURE FI
+FB02; FB02; 0046 006C; 0046 004C; # LATIN SMALL LIGATURE FL
+FB03; FB03; 0046 0066 0069; 0046 0046 0049; # LATIN SMALL LIGATURE FFI
+FB04; FB04; 0046 0066 006C; 0046 0046 004C; # LATIN SMALL LIGATURE FFL
+FB05; FB05; 0053 0074; 0053 0054; # LATIN SMALL LIGATURE LONG S T
+FB06; FB06; 0053 0074; 0053 0054; # LATIN SMALL LIGATURE ST
+
+0587; 0587; 0535 0582; 0535 0552; # ARMENIAN SMALL LIGATURE ECH YIWN
+FB13; FB13; 0544 0576; 0544 0546; # ARMENIAN SMALL LIGATURE MEN NOW
+FB14; FB14; 0544 0565; 0544 0535; # ARMENIAN SMALL LIGATURE MEN ECH
+FB15; FB15; 0544 056B; 0544 053B; # ARMENIAN SMALL LIGATURE MEN INI
+FB16; FB16; 054E 0576; 054E 0546; # ARMENIAN SMALL LIGATURE VEW NOW
+FB17; FB17; 0544 056D; 0544 053D; # ARMENIAN SMALL LIGATURE MEN XEH
+
+# No corresponding uppercase precomposed character
+
+0149; 0149; 02BC 004E; 02BC 004E; # LATIN SMALL LETTER N PRECEDED BY APOSTROPHE
+0390; 0390; 0399 0308 0301; 0399 0308 0301; # GREEK SMALL LETTER IOTA WITH DIALYTIKA AND TONOS
+03B0; 03B0; 03A5 0308 0301; 03A5 0308 0301; # GREEK SMALL LETTER UPSILON WITH DIALYTIKA AND TONOS
+01F0; 01F0; 004A 030C; 004A 030C; # LATIN SMALL LETTER J WITH CARON
+1E96; 1E96; 0048 0331; 0048 0331; # LATIN SMALL LETTER H WITH LINE BELOW
+1E97; 1E97; 0054 0308; 0054 0308; # LATIN SMALL LETTER T WITH DIAERESIS
+1E98; 1E98; 0057 030A; 0057 030A; # LATIN SMALL LETTER W WITH RING ABOVE
+1E99; 1E99; 0059 030A; 0059 030A; # LATIN SMALL LETTER Y WITH RING ABOVE
+1E9A; 1E9A; 0041 02BE; 0041 02BE; # LATIN SMALL LETTER A WITH RIGHT HALF RING
+1F50; 1F50; 03A5 0313; 03A5 0313; # GREEK SMALL LETTER UPSILON WITH PSILI
+1F52; 1F52; 03A5 0313 0300; 03A5 0313 0300; # GREEK SMALL LETTER UPSILON WITH PSILI AND VARIA
+1F54; 1F54; 03A5 0313 0301; 03A5 0313 0301; # GREEK SMALL LETTER UPSILON WITH PSILI AND OXIA
+1F56; 1F56; 03A5 0313 0342; 03A5 0313 0342; # GREEK SMALL LETTER UPSILON WITH PSILI AND PERISPOMENI
+1FB6; 1FB6; 0391 0342; 0391 0342; # GREEK SMALL LETTER ALPHA WITH PERISPOMENI
+1FC6; 1FC6; 0397 0342; 0397 0342; # GREEK SMALL LETTER ETA WITH PERISPOMENI
+1FD2; 1FD2; 0399 0308 0300; 0399 0308 0300; # GREEK SMALL LETTER IOTA WITH DIALYTIKA AND VARIA
+1FD3; 1FD3; 0399 0308 0301; 0399 0308 0301; # GREEK SMALL LETTER IOTA WITH DIALYTIKA AND OXIA
+1FD6; 1FD6; 0399 0342; 0399 0342; # GREEK SMALL LETTER IOTA WITH PERISPOMENI
+1FD7; 1FD7; 0399 0308 0342; 0399 0308 0342; # GREEK SMALL LETTER IOTA WITH DIALYTIKA AND PERISPOMENI
+1FE2; 1FE2; 03A5 0308 0300; 03A5 0308 0300; # GREEK SMALL LETTER UPSILON WITH DIALYTIKA AND VARIA
+1FE3; 1FE3; 03A5 0308 0301; 03A5 0308 0301; # GREEK SMALL LETTER UPSILON WITH DIALYTIKA AND OXIA
+1FE4; 1FE4; 03A1 0313; 03A1 0313; # GREEK SMALL LETTER RHO WITH PSILI
+1FE6; 1FE6; 03A5 0342; 03A5 0342; # GREEK SMALL LETTER UPSILON WITH PERISPOMENI
+1FE7; 1FE7; 03A5 0308 0342; 03A5 0308 0342; # GREEK SMALL LETTER UPSILON WITH DIALYTIKA AND PERISPOMENI
+1FF6; 1FF6; 03A9 0342; 03A9 0342; # GREEK SMALL LETTER OMEGA WITH PERISPOMENI
+
+# IMPORTANT-when iota-subscript (0345) is uppercased or titlecased,
+#  the result will be incorrect unless the iota-subscript is moved to the end
+#  of any sequence of combining marks. Otherwise, the accents will go on the capital iota.
+#  This process can be achieved by first transforming the text to NFC before casing.
+#  E.g. <alpha><iota_subscript><acute> is uppercased to <ALPHA><acute><IOTA>
+
+# The following cases are already in the UnicodeData.txt file, so are only commented here.
+
+# 0345; 0345; 0345; 0399; # COMBINING GREEK YPOGEGRAMMENI
+
+# All letters with YPOGEGRAMMENI (iota-subscript) or PROSGEGRAMMENI (iota adscript)
+# have special uppercases.
+# Note: characters with PROSGEGRAMMENI are actually titlecase, not uppercase!
+
+1F80; 1F80; 1F88; 1F08 0399; # GREEK SMALL LETTER ALPHA WITH PSILI AND YPOGEGRAMMENI
+1F81; 1F81; 1F89; 1F09 0399; # GREEK SMALL LETTER ALPHA WITH DASIA AND YPOGEGRAMMENI
+1F82; 1F82; 1F8A; 1F0A 0399; # GREEK SMALL LETTER ALPHA WITH PSILI AND VARIA AND YPOGEGRAMMENI
+1F83; 1F83; 1F8B; 1F0B 0399; # GREEK SMALL LETTER ALPHA WITH DASIA AND VARIA AND YPOGEGRAMMENI
+1F84; 1F84; 1F8C; 1F0C 0399; # GREEK SMALL LETTER ALPHA WITH PSILI AND OXIA AND YPOGEGRAMMENI
+1F85; 1F85; 1F8D; 1F0D 0399; # GREEK SMALL LETTER ALPHA WITH DASIA AND OXIA AND YPOGEGRAMMENI
+1F86; 1F86; 1F8E; 1F0E 0399; # GREEK SMALL LETTER ALPHA WITH PSILI AND PERISPOMENI AND YPOGEGRAMMENI
+1F87; 1F87; 1F8F; 1F0F 0399; # GREEK SMALL LETTER ALPHA WITH DASIA AND PERISPOMENI AND YPOGEGRAMMENI
+1F88; 1F80; 1F88; 1F08 0399; # GREEK CAPITAL LETTER ALPHA WITH PSILI AND PROSGEGRAMMENI
+1F89; 1F81; 1F89; 1F09 0399; # GREEK CAPITAL LETTER ALPHA WITH DASIA AND PROSGEGRAMMENI
+1F8A; 1F82; 1F8A; 1F0A 0399; # GREEK CAPITAL LETTER ALPHA WITH PSILI AND VARIA AND PROSGEGRAMMENI
+1F8B; 1F83; 1F8B; 1F0B 0399; # GREEK CAPITAL LETTER ALPHA WITH DASIA AND VARIA AND PROSGEGRAMMENI
+1F8C; 1F84; 1F8C; 1F0C 0399; # GREEK CAPITAL LETTER ALPHA WITH PSILI AND OXIA AND PROSGEGRAMMENI
+1F8D; 1F85; 1F8D; 1F0D 0399; # GREEK CAPITAL LETTER ALPHA WITH DASIA AND OXIA AND PROSGEGRAMMENI
+1F8E; 1F86; 1F8E; 1F0E 0399; # GREEK CAPITAL LETTER ALPHA WITH PSILI AND PERISPOMENI AND PROSGEGRAMMENI
+1F8F; 1F87; 1F8F; 1F0F 0399; # GREEK CAPITAL LETTER ALPHA WITH DASIA AND PERISPOMENI AND PROSGEGRAMMENI
+1F90; 1F90; 1F98; 1F28 0399; # GREEK SMALL LETTER ETA WITH PSILI AND YPOGEGRAMMENI
+1F91; 1F91; 1F99; 1F29 0399; # GREEK SMALL LETTER ETA WITH DASIA AND YPOGEGRAMMENI
+1F92; 1F92; 1F9A; 1F2A 0399; # GREEK SMALL LETTER ETA WITH PSILI AND VARIA AND YPOGEGRAMMENI
+1F93; 1F93; 1F9B; 1F2B 0399; # GREEK SMALL LETTER ETA WITH DASIA AND VARIA AND YPOGEGRAMMENI
+1F94; 1F94; 1F9C; 1F2C 0399; # GREEK SMALL LETTER ETA WITH PSILI AND OXIA AND YPOGEGRAMMENI
+1F95; 1F95; 1F9D; 1F2D 0399; # GREEK SMALL LETTER ETA WITH DASIA AND OXIA AND YPOGEGRAMMENI
+1F96; 1F96; 1F9E; 1F2E 0399; # GREEK SMALL LETTER ETA WITH PSILI AND PERISPOMENI AND YPOGEGRAMMENI
+1F97; 1F97; 1F9F; 1F2F 0399; # GREEK SMALL LETTER ETA WITH DASIA AND PERISPOMENI AND YPOGEGRAMMENI
+1F98; 1F90; 1F98; 1F28 0399; # GREEK CAPITAL LETTER ETA WITH PSILI AND PROSGEGRAMMENI
+1F99; 1F91; 1F99; 1F29 0399; # GREEK CAPITAL LETTER ETA WITH DASIA AND PROSGEGRAMMENI
+1F9A; 1F92; 1F9A; 1F2A 0399; # GREEK CAPITAL LETTER ETA WITH PSILI AND VARIA AND PROSGEGRAMMENI
+1F9B; 1F93; 1F9B; 1F2B 0399; # GREEK CAPITAL LETTER ETA WITH DASIA AND VARIA AND PROSGEGRAMMENI
+1F9C; 1F94; 1F9C; 1F2C 0399; # GREEK CAPITAL LETTER ETA WITH PSILI AND OXIA AND PROSGEGRAMMENI
+1F9D; 1F95; 1F9D; 1F2D 0399; # GREEK CAPITAL LETTER ETA WITH DASIA AND OXIA AND PROSGEGRAMMENI
+1F9E; 1F96; 1F9E; 1F2E 0399; # GREEK CAPITAL LETTER ETA WITH PSILI AND PERISPOMENI AND PROSGEGRAMMENI
+1F9F; 1F97; 1F9F; 1F2F 0399; # GREEK CAPITAL LETTER ETA WITH DASIA AND PERISPOMENI AND PROSGEGRAMMENI
+1FA0; 1FA0; 1FA8; 1F68 0399; # GREEK SMALL LETTER OMEGA WITH PSILI AND YPOGEGRAMMENI
+1FA1; 1FA1; 1FA9; 1F69 0399; # GREEK SMALL LETTER OMEGA WITH DASIA AND YPOGEGRAMMENI
+1FA2; 1FA2; 1FAA; 1F6A 0399; # GREEK SMALL LETTER OMEGA WITH PSILI AND VARIA AND YPOGEGRAMMENI
+1FA3; 1FA3; 1FAB; 1F6B 0399; # GREEK SMALL LETTER OMEGA WITH DASIA AND VARIA AND YPOGEGRAMMENI
+1FA4; 1FA4; 1FAC; 1F6C 0399; # GREEK SMALL LETTER OMEGA WITH PSILI AND OXIA AND YPOGEGRAMMENI
+1FA5; 1FA5; 1FAD; 1F6D 0399; # GREEK SMALL LETTER OMEGA WITH DASIA AND OXIA AND YPOGEGRAMMENI
+1FA6; 1FA6; 1FAE; 1F6E 0399; # GREEK SMALL LETTER OMEGA WITH PSILI AND PERISPOMENI AND YPOGEGRAMMENI
+1FA7; 1FA7; 1FAF; 1F6F 0399; # GREEK SMALL LETTER OMEGA WITH DASIA AND PERISPOMENI AND YPOGEGRAMMENI
+1FA8; 1FA0; 1FA8; 1F68 0399; # GREEK CAPITAL LETTER OMEGA WITH PSILI AND PROSGEGRAMMENI
+1FA9; 1FA1; 1FA9; 1F69 0399; # GREEK CAPITAL LETTER OMEGA WITH DASIA AND PROSGEGRAMMENI
+1FAA; 1FA2; 1FAA; 1F6A 0399; # GREEK CAPITAL LETTER OMEGA WITH PSILI AND VARIA AND PROSGEGRAMMENI
+1FAB; 1FA3; 1FAB; 1F6B 0399; # GREEK CAPITAL LETTER OMEGA WITH DASIA AND VARIA AND PROSGEGRAMMENI
+1FAC; 1FA4; 1FAC; 1F6C 0399; # GREEK CAPITAL LETTER OMEGA WITH PSILI AND OXIA AND PROSGEGRAMMENI
+1FAD; 1FA5; 1FAD; 1F6D 0399; # GREEK CAPITAL LETTER OMEGA WITH DASIA AND OXIA AND PROSGEGRAMMENI
+1FAE; 1FA6; 1FAE; 1F6E 0399; # GREEK CAPITAL LETTER OMEGA WITH PSILI AND PERISPOMENI AND PROSGEGRAMMENI
+1FAF; 1FA7; 1FAF; 1F6F 0399; # GREEK CAPITAL LETTER OMEGA WITH DASIA AND PERISPOMENI AND PROSGEGRAMMENI
+1FB3; 1FB3; 1FBC; 0391 0399; # GREEK SMALL LETTER ALPHA WITH YPOGEGRAMMENI
+1FBC; 1FB3; 1FBC; 0391 0399; # GREEK CAPITAL LETTER ALPHA WITH PROSGEGRAMMENI
+1FC3; 1FC3; 1FCC; 0397 0399; # GREEK SMALL LETTER ETA WITH YPOGEGRAMMENI
+1FCC; 1FC3; 1FCC; 0397 0399; # GREEK CAPITAL LETTER ETA WITH PROSGEGRAMMENI
+1FF3; 1FF3; 1FFC; 03A9 0399; # GREEK SMALL LETTER OMEGA WITH YPOGEGRAMMENI
+1FFC; 1FF3; 1FFC; 03A9 0399; # GREEK CAPITAL LETTER OMEGA WITH PROSGEGRAMMENI
+
+# Some characters with YPOGEGRAMMENI also have no corresponding titlecases
+
+1FB2; 1FB2; 1FBA 0345; 1FBA 0399; # GREEK SMALL LETTER ALPHA WITH VARIA AND YPOGEGRAMMENI
+1FB4; 1FB4; 0386 0345; 0386 0399; # GREEK SMALL LETTER ALPHA WITH OXIA AND YPOGEGRAMMENI
+1FC2; 1FC2; 1FCA 0345; 1FCA 0399; # GREEK SMALL LETTER ETA WITH VARIA AND YPOGEGRAMMENI
+1FC4; 1FC4; 0389 0345; 0389 0399; # GREEK SMALL LETTER ETA WITH OXIA AND YPOGEGRAMMENI
+1FF2; 1FF2; 1FFA 0345; 1FFA 0399; # GREEK SMALL LETTER OMEGA WITH VARIA AND YPOGEGRAMMENI
+1FF4; 1FF4; 038F 0345; 038F 0399; # GREEK SMALL LETTER OMEGA WITH OXIA AND YPOGEGRAMMENI
+
+1FB7; 1FB7; 0391 0342 0345; 0391 0342 0399; # GREEK SMALL LETTER ALPHA WITH PERISPOMENI AND YPOGEGRAMMENI
+1FC7; 1FC7; 0397 0342 0345; 0397 0342 0399; # GREEK SMALL LETTER ETA WITH PERISPOMENI AND YPOGEGRAMMENI
+1FF7; 1FF7; 03A9 0342 0345; 03A9 0342 0399; # GREEK SMALL LETTER OMEGA WITH PERISPOMENI AND YPOGEGRAMMENI
+
+# ================================================================================
+# Conditional Mappings
+# The remainder of this file provides conditional casing data used to produce 
+# full case mappings.
+# ================================================================================
+# Language-Insensitive Mappings
+# These are characters whose full case mappings do not depend on language, but do
+# depend on context (which characters come before or after). For more information
+# see the header of this file and the Unicode Standard.
+# ================================================================================
+
+# Special case for final form of sigma
+
+03A3; 03C2; 03A3; 03A3; Final_Sigma; # GREEK CAPITAL LETTER SIGMA
+
+# Note: the following cases for non-final are already in the UnicodeData.txt file.
+
+# 03A3; 03C3; 03A3; 03A3; # GREEK CAPITAL LETTER SIGMA
+# 03C3; 03C3; 03A3; 03A3; # GREEK SMALL LETTER SIGMA
+# 03C2; 03C2; 03A3; 03A3; # GREEK SMALL LETTER FINAL SIGMA
+
+# Note: the following cases are not included, since they would case-fold in lowercasing
+
+# 03C3; 03C2; 03A3; 03A3; Final_Sigma; # GREEK SMALL LETTER SIGMA
+# 03C2; 03C3; 03A3; 03A3; Not_Final_Sigma; # GREEK SMALL LETTER FINAL SIGMA
+
+# ================================================================================
+# Language-Sensitive Mappings
+# These are characters whose full case mappings depend on language and perhaps also
+# context (which characters come before or after). For more information
+# see the header of this file and the Unicode Standard.
+# ================================================================================
+
+# Lithuanian
+
+# Lithuanian retains the dot in a lowercase i when followed by accents.
+
+# Remove DOT ABOVE after "i" with upper or titlecase
+
+0307; 0307; ; ; lt After_Soft_Dotted; # COMBINING DOT ABOVE
+
+# Introduce an explicit dot above when lowercasing capital I's and J's
+# whenever there are more accents above.
+# (of the accents used in Lithuanian: grave, acute, tilde above, and ogonek)
+
+0049; 0069 0307; 0049; 0049; lt More_Above; # LATIN CAPITAL LETTER I
+004A; 006A 0307; 004A; 004A; lt More_Above; # LATIN CAPITAL LETTER J
+012E; 012F 0307; 012E; 012E; lt More_Above; # LATIN CAPITAL LETTER I WITH OGONEK
+00CC; 0069 0307 0300; 00CC; 00CC; lt; # LATIN CAPITAL LETTER I WITH GRAVE
+00CD; 0069 0307 0301; 00CD; 00CD; lt; # LATIN CAPITAL LETTER I WITH ACUTE
+0128; 0069 0307 0303; 0128; 0128; lt; # LATIN CAPITAL LETTER I WITH TILDE
+
+# ================================================================================
+
+# Turkish and Azeri
+
+# I and i-dotless; I-dot and i are case pairs in Turkish and Azeri
+# The following rules handle those cases.
+
+0130; 0069; 0130; 0130; tr; # LATIN CAPITAL LETTER I WITH DOT ABOVE
+0130; 0069; 0130; 0130; az; # LATIN CAPITAL LETTER I WITH DOT ABOVE
+
+# When lowercasing, remove dot_above in the sequence I + dot_above, which will turn into i.
+# This matches the behavior of the canonically equivalent I-dot_above
+
+0307; ; 0307; 0307; tr After_I; # COMBINING DOT ABOVE
+0307; ; 0307; 0307; az After_I; # COMBINING DOT ABOVE
+
+# When lowercasing, unless an I is before a dot_above, it turns into a dotless i.
+
+0049; 0131; 0049; 0049; tr Not_Before_Dot; # LATIN CAPITAL LETTER I
+0049; 0131; 0049; 0049; az Not_Before_Dot; # LATIN CAPITAL LETTER I
+
+# When uppercasing, i turns into a dotted capital I
+
+0069; 0069; 0130; 0130; tr; # LATIN SMALL LETTER I
+0069; 0069; 0130; 0130; az; # LATIN SMALL LETTER I
+
+# Note: the following case is already in the UnicodeData.txt file.
+
+# 0131; 0131; 0049; 0049; tr; # LATIN SMALL LETTER DOTLESS I
+
+# EOF
+
diff --git a/admin/unidata/unidata-gen.el b/admin/unidata/unidata-gen.el
index 3c5119a8a3d..32b05eacce6 100644
--- a/admin/unidata/unidata-gen.el
+++ b/admin/unidata/unidata-gen.el
@@ -268,6 +268,33 @@ unidata-prop-alist
 The value nil means that the actual property value of a character
 is the character itself."
      string)
+    (special-uppercase
+     2 unidata-gen-table-special-casing "uni-special-uppercase.el"
+     "Unicode unconditional special casing mapping.
+
+Property value is nil, denoting no special rules, or a string, denoting
+characters maps into given sequence of characters.  The string may be empty.
+
+The mapping includes only unconditional casing rules defined by Unicode."
+     nil)
+    (special-lowercase
+     0 unidata-gen-table-special-casing "uni-special-lowercase.el"
+     "Unicode unconditional special casing mapping.
+
+Property value is nil, denoting no special rules, or a string, denoting
+characters maps into given sequence of characters.  The string may be empty.
+
+The mapping includes only unconditional casing rules defined by Unicode."
+     nil)
+    (special-titlecase
+     1 unidata-gen-table-special-casing "uni-special-titlecase.el"
+     "Unicode unconditional special casing mapping.
+
+Property value is nil, denoting no special rules, or a string, denoting
+characters maps into given sequence of characters.  The string may be empty.
+
+The mapping includes only unconditional casing rules defined by Unicode."
+     nil)
     (mirroring
      unidata-gen-mirroring-list unidata-gen-table-character "uni-mirrored.el"
      "Unicode bidi-mirroring characters.
@@ -1084,6 +1111,51 @@ unidata-gen-table-decomposition
 
 
 \f
+
+(defvar unidata-gen-table-special-casing--cache nil
+  "Cached value for `unidata-gen-table-special-casing' function.")
+
+(defun unidata-gen-table-special-casing--do-load ()
+  (let (result)
+    (with-temp-buffer
+      (insert-file-contents (expand-file-name "SpecialCasing.txt" unidata-dir))
+      (goto-char (point-min))
+      (while (not (eobp))
+        ;; Ignore empty lines and comments.
+        (unless (or (eq (char-after) ?\n) (eq (char-after) ?#))
+          (let ((line (split-string
+                       (buffer-substring (point) (progn (end-of-line) (point)))
+                       ";" "")))
+            ;; Ignore entries with conditions, i.e. those with six values.
+            (when (= (length line) 5)
+              (let ((ch (string-to-number (pop line) 16)))
+                (setcdr (cddr line) nil) ; strip comment
+                (push
+                 (cons ch
+                       (mapcar (lambda (entry)
+                                 (mapcar (lambda (n) (string-to-number n 16))
+                                         (split-string entry)))
+                               line))
+                 result)))))
+        (forward-line)))
+    result))
+
+(defun unidata-gen-table-special-casing (prop &rest ignore)
+  (let ((table (make-char-table 'char-code-property-table))
+        (prop-idx (unidata-prop-index prop)))
+    (set-char-table-extra-slot table 0 prop)
+    (mapc (lambda (entry)
+            (let ((ch (car entry)) (v (nth prop-idx (cdr entry))))
+              ;; If character maps to a single character, the mapping is already
+              ;; covered by regular casing property.  Don’t store those.
+              (when (/= (length v) 1)
+                (set-char-table-range table ch (apply 'string v)))))
+          (or unidata-gen-table-special-casing--cache
+              (setq unidata-gen-table-special-casing--cache
+                    (unidata-gen-table-special-casing--do-load))))
+    table))
+
+\f
 (defun unidata-describe-general-category (val)
   (cdr (assq val
 	     '((nil . "Uknown")
diff --git a/doc/lispref/nonascii.texi b/doc/lispref/nonascii.texi
index 05c08c6dbe5..b9c62548353 100644
--- a/doc/lispref/nonascii.texi
+++ b/doc/lispref/nonascii.texi
@@ -619,6 +619,28 @@ Character Properties
 character of a word needs to be capitalized.  The value of this
 property is a single character.  For unassigned codepoints, the value
 is @code{nil}, which means the character itself.
+
+@item special-uppercase
+Corresponds to Unicode unconditional special upper-casing rules.  The value
+of this property is a string (which may be empty).  For example
+mapping for @code{U+00DF} (@sc{latin smpall letter sharp s}) the value
+is @code{"SS"}.  For unassigned codepoints, the value is @code{nil}
+which means @code{uppercase} property needs to be consulted instead.
+
+@item special-lowercase
+Corresponds to Unicode unconditional special lower-casing rules.  The
+value of this property is a string (which may be empty).  For example
+mapping for @code{U+0130} (@sc{latin capital letter i with dot above})
+the value is @code{"i\u0307"}.  For unassigned codepoints, the value is
+@code{nil} which means @code{lowercase} property needs to be consulted
+instead.
+
+@item special-titlecase
+Corresponds to Unicode unconditional special title-casing rules.  The
+value of this property is a string (which may be empty).  For example
+mapping for @code{U+FB01} (@sc{latin small ligature fi}) the value is
+@code{"Fi"}.  For unassigned codepoints, the value is @code{nil} which
+means @code{titlecase} property needs to be consulted instead.
 @end table
 
 @defun get-char-code-property char propname
diff --git a/doc/lispref/strings.texi b/doc/lispref/strings.texi
index ae2b31c5418..58da89464aa 100644
--- a/doc/lispref/strings.texi
+++ b/doc/lispref/strings.texi
@@ -1177,6 +1177,29 @@ Case Conversion
 @end example
 @end defun
 
+  Note that case conversion is not a one-to-one mapping and the length
+of the result may differ from the length of the argument (including
+being shorter).  Furthermore, because passing a character forces
+return type to be a character, functions are unable to perform proper
+substitution and result may differ compared to treating
+a one-character string.  For example:
+
+@example
+@group
+(upcase "fi")  ; note: single character, ligature "fi"
+     @result{} "FI"
+@end group
+@group
+(upcase ?fi)
+     @result{} 64257  ; i.e. ?fi
+@end group
+@end example
+
+  To avoid this, a character must first be converted into a string,
+using @code{string} function, before being passed to one of the casing
+functions.  Of course, no assumptions on the length of the result may
+be made.
+
   @xref{Text Comparison}, for functions that compare strings; some of
 them ignore case differences, or can optionally ignore case differences.
 
diff --git a/etc/NEWS b/etc/NEWS
index ff9e5c058be..af1981b1453 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -355,12 +355,17 @@ same as in modes where the character is not whitespace.
 Instead of only checking the modification time, Emacs now also checks
 the file's actual content before prompting the user.
 
-** Title case characters are properly cased (from and into).
-'upcase', 'upcase-region' et al. convert title case characters (such
-as the single character "Dz") into their upper case form (such as "DZ").
-As a downside, 'capitalize' and 'upcase-initials' produce awkward
-words where first character is upper rather than title case, e.g.,
-"DŽungla" instead of "Džungla".
+** Various casing improvements.
+
+*** 'upcase', 'upcase-region' et al. convert title case characters
+(such as Dz) into their upper case form (such as DZ).
+
+*** 'capitalize', 'upcase-initials' et al. make use of title-case forms
+of initial characters (correctly producing for example Džungla instead
+of incorrect DŽungla).
+
+*** Characters which turn into multiple ones when cased are correctly handled.
+For example, fi ligature is converted to FI when upper cased.
 
 \f
 * Changes in Specialized Modes and Packages in Emacs 26.1
diff --git a/src/casefiddle.c b/src/casefiddle.c
index b1a5f8e236e..df94c970539 100644
--- a/src/casefiddle.c
+++ b/src/casefiddle.c
@@ -36,6 +36,9 @@ struct casing_context {
   /* A char-table with title-case character mappings or nil.  Non-nil implies
      flag is CASE_CAPITALIZE or CASE_CAPITALIZE_UP.  */
   Lisp_Object titlecase_char_table;
+  /* The unconditional special-casing Unicode property char tables for upper
+     casing, lower casing and title casing respectively. */
+  Lisp_Object specialcase_char_tables[3];
   /* User-requested action. */
   enum case_action flag;
   /* If true, function operates on a buffer as opposed to a string or character.
@@ -58,6 +61,13 @@ prepare_casing_context (struct casing_context *ctx,
   ctx->inword = flag == CASE_DOWN;
   ctx->titlecase_char_table = (int)flag < (int)CASE_CAPITALIZE ? Qnil :
     uniprop_table (intern_c_string ("titlecase"));
+  ctx->specialcase_char_tables[CASE_UP] = flag == CASE_DOWN ? Qnil :
+    uniprop_table (intern_c_string ("special-uppercase"));
+  ctx->specialcase_char_tables[CASE_DOWN] = flag == CASE_UP ? Qnil :
+    uniprop_table (intern_c_string ("special-lowercase"));
+  ctx->specialcase_char_tables[CASE_CAPITALIZE] =
+    (int)flag < (int)CASE_CAPITALIZE ? Qnil :
+    uniprop_table (intern_c_string ("special-titlecase"));
 
   /* If the case table is flagged as modified, rescan it.  */
   if (NILP (XCHAR_TABLE (BVAR (current_buffer, downcase_table))->extras[1]))
@@ -67,25 +77,98 @@ prepare_casing_context (struct casing_context *ctx,
     SETUP_BUFFER_SYNTAX_TABLE ();	/* For syntax_prefix_flag_p.  */
 }
 
-/* Based on CTX, case character CH accordingly.  Update CTX as necessary.
-   Return cased character. */
+struct casing_str_buf {
+  unsigned char data[MAX_MULTIBYTE_LENGTH > 6 ? MAX_MULTIBYTE_LENGTH : 6];
+  unsigned char len_chars;
+  unsigned char len_bytes;
+};
+
+/* Based on CTX, case character CH.  If BUF is NULL, return cased character.
+   Otherwise, if BUF is non-NULL, save result in it and return whether the
+   character has been changed.
+
+   Since meaning of return value depends on arguments, it’s more convenient to
+   use case_single_character or case_character instead. */
 static int
-case_character (struct casing_context *ctx, int ch)
+case_character_impl (struct casing_str_buf *buf,
+		     struct casing_context *ctx, int ch)
 {
+  enum case_action flag;
   Lisp_Object prop;
+  bool was_inword;
+  int cased;
+
+  /* Update inword state */
+  was_inword = ctx->inword;
+  if ((int) ctx->flag >= (int) CASE_CAPITALIZE)
+    ctx->inword = SYNTAX (ch) == Sword &&
+      (!ctx->inbuffer || was_inword || !syntax_prefix_flag_p (ch));
+
+  /* Normalise flag so its one of CASE_UP, CASE_DOWN or CASE_CAPITALIZE. */
+  if (!was_inword)
+    flag = ctx->flag == CASE_UP ? CASE_UP : CASE_CAPITALIZE;
+  else if (ctx->flag != CASE_CAPITALIZE_UP)
+    flag = CASE_DOWN;
+  else
+    {
+      cased = ch;
+      goto done;
+    }
+
+  /* Look through the special casing entries. */
+  if (buf && !NILP(ctx->specialcase_char_tables[(int)flag]))
+    {
+      prop = CHAR_TABLE_REF(ctx->specialcase_char_tables[(int)flag], ch);
+      if (STRINGP(prop))
+        {
+          struct Lisp_String *str = XSTRING(prop);
+          if (STRING_BYTES(str) <= sizeof buf->data)
+	    {
+	      buf->len_chars = str->size;
+	      buf->len_bytes = STRING_BYTES(str);
+	      memcpy(buf->data, str->data, buf->len_bytes);
+	      return 1;
+	    }
+        }
+    }
 
-  if (ctx->inword)
-    ch = ctx->flag == CASE_CAPITALIZE_UP ? ch : downcase (ch);
+  /* Handle simple, one-to-one case. */
+  if (flag == CASE_DOWN)
+    cased = downcase (ch);
   else if (!NILP (ctx->titlecase_char_table) &&
 	   CHARACTERP (prop = CHAR_TABLE_REF (ctx->titlecase_char_table, ch)))
-    ch = XFASTINT (prop);
+    cased = XFASTINT (prop);
   else
-    ch = upcase(ch);
+    cased = upcase(ch);
+
+  /* And we’re done. */
+ done:
+  if (!buf)
+    return cased;
+  buf->len_chars = 1;
+  buf->len_bytes = CHAR_STRING (cased, buf->data);
+  return cased != ch;
+}
 
-  if ((int) ctx->flag >= (int) CASE_CAPITALIZE)
-    ctx->inword = SYNTAX (ch) == Sword &&
-      (!ctx->inbuffer || ctx->inword || !syntax_prefix_flag_p (ch));
-  return ch;
+/* Based on CTX, case character CH accordingly.  Update CTX as necessary.
+   Return cased character.
+
+   Special casing rules (such as upcase(fi) = FI) are not handled.  For
+   characters whose casing results in multiple code points, the character is
+   returned unchanged. */
+static inline int
+case_single_character (struct casing_context *ctx, int ch)
+{
+  return case_character_impl (NULL, ctx, ch);
+}
+
+/* Save in BUF result of casing character CH.  Return whether casing changed the
+   character.  This is like case_single_character but also handles one-to-many
+   casing rules. */
+static inline bool
+case_character (struct casing_str_buf *buf, struct casing_context *ctx, int ch)
+{
+  return case_character_impl (buf, ctx, ch);
 }
 \f
 static Lisp_Object
@@ -112,7 +195,7 @@ do_casify_natnum (struct casing_context *ctx, Lisp_Object obj)
     || !NILP (BVAR (current_buffer, enable_multibyte_characters));
   if (! multibyte)
     MAKE_CHAR_MULTIBYTE (ch);
-  cased = case_character (ctx, ch);
+  cased = case_single_character (ctx, ch);
   if (cased == ch)
     return obj;
 
@@ -125,25 +208,34 @@ do_casify_natnum (struct casing_context *ctx, Lisp_Object obj)
 static Lisp_Object
 do_casify_multibyte_string (struct casing_context *ctx, Lisp_Object obj)
 {
-  ptrdiff_t i, i_byte, size = SCHARS (obj);
-  int len, ch, cased;
+  /* We assume data is the first member of casing_str_buf structure so that if
+     we cast a (char *) into (struct casing_str_buf *) the representation of the
+     character is at the beginning of the buffer.  This is why we don’t need
+     separate struct casing_str_buf object but rather write directly to o. */
+  typedef char static_assertion[offsetof(struct casing_str_buf, data) ? -1 : 1];
+
+  ptrdiff_t size = SCHARS (obj), n;
+  int ch;
   USE_SAFE_ALLOCA;
-  ptrdiff_t o_size;
-  if (INT_MULTIPLY_WRAPV (size, MAX_MULTIBYTE_LENGTH, &o_size))
-    o_size = PTRDIFF_MAX;
-  unsigned char *dst = SAFE_ALLOCA (o_size);
+  if (INT_MULTIPLY_WRAPV (size, MAX_MULTIBYTE_LENGTH, &n) ||
+      INT_ADD_WRAPV (n, sizeof(struct casing_str_buf), &n))
+    n = PTRDIFF_MAX;
+  unsigned char *const dst = SAFE_ALLOCA (n), *const dst_end = dst + n;
   unsigned char *o = dst;
 
-  for (i = i_byte = 0; i < size; i++, i_byte += len)
+  const unsigned char *src = SDATA (obj);
+
+  for (n = 0; size; --size)
     {
-      if (o_size - MAX_MULTIBYTE_LENGTH < o - dst)
+      if (dst_end - o < sizeof(struct casing_str_buf))
 	string_overflow ();
-      ch = STRING_CHAR_AND_LENGTH (SDATA (obj) + i_byte, len);
-      cased = case_character (ctx, ch);
-      o += CHAR_STRING (cased, o);
+      ch = STRING_CHAR_ADVANCE (src);
+      case_character ((void *)o, ctx, ch);
+      n += ((struct casing_str_buf *)o)->len_chars;
+      o += ((struct casing_str_buf *)o)->len_bytes;
     }
-  eassert (o - dst <= o_size);
-  obj = make_multibyte_string ((char *) dst, size, o - dst);
+  eassert (o <= dst_end);
+  obj = make_multibyte_string ((char *) dst, n, o - dst);
   SAFE_FREE ();
   return obj;
 }
@@ -159,7 +251,7 @@ do_casify_unibyte_string (struct casing_context *ctx, Lisp_Object obj)
     {
       ch = SREF (obj, i);
       MAKE_CHAR_MULTIBYTE (ch);
-      cased = case_character (ctx, ch);
+      cased = case_single_character (ctx, ch);
       if (ch == cased)
 	continue;
       MAKE_CHAR_UNIBYTE (cased);
@@ -191,7 +283,9 @@ casify_object (enum case_action flag, Lisp_Object obj)
 DEFUN ("upcase", Fupcase, Supcase, 1, 1, 0,
        doc: /* Convert argument to upper case and return that.
 The argument may be a character or string.  The result has the same type.
-The argument object is not altered--the value is a copy.
+The argument object is not altered--the value is a copy.  If argument
+is a character, characters which map to multiple code points when
+cased, e.g. fi, are returned unchanged.
 See also `capitalize', `downcase' and `upcase-initials'.  */)
   (Lisp_Object obj)
 {
@@ -212,7 +306,9 @@ DEFUN ("capitalize", Fcapitalize, Scapitalize, 1, 1, 0,
 This means that each word's first character is converted to either
 title case or upper case, and the rest to lower case.
 The argument may be a character or string.  The result has the same type.
-The argument object is not altered--the value is a copy.  */)
+The argument object is not altered--the value is a copy.  If argument
+is a character, characters which map to multiple code points when
+cased, e.g. fi, are returned unchanged.  */)
   (Lisp_Object obj)
 {
   return casify_object (CASE_CAPITALIZE, obj);
@@ -225,21 +321,28 @@ DEFUN ("upcase-initials", Fupcase_initials, Supcase_initials, 1, 1, 0,
 This means that each word's first character is converted to either
 title case or upper case, and the rest are left unchanged.
 The argument may be a character or string.  The result has the same type.
-The argument object is not altered--the value is a copy.  */)
+The argument object is not altered--the value is a copy.  If argument
+is a character, characters which map to multiple code points when
+cased, e.g. fi, are returned unchanged.  */)
   (Lisp_Object obj)
 {
   return casify_object (CASE_CAPITALIZE_UP, obj);
 }
 \f
-/* Based on CTX, case region in a unibyte buffer from POS to *ENDP.  Return
-   first position that has changed and save last position in *ENDP.  If no
-   characters were changed, return -1 and *ENDP is unspecified. */
+/* Based on CTX, case region in a unibyte buffer from *STARTP to *ENDP.
+
+   Save first and last positions that has changed in *STARTP and *ENDP
+   respectively.  If no characters were changed, save -1 to *STARTP and leave
+   *ENDP unspecified.
+
+   Always return 0.  This is so that interface of this function is the same as
+   do_casify_multibyte_region. */
 static ptrdiff_t
 do_casify_unibyte_region (struct casing_context *ctx,
-			  ptrdiff_t pos, ptrdiff_t *endp)
+			  ptrdiff_t *startp, ptrdiff_t *endp)
 {
   ptrdiff_t first = -1, last = -1;  /* Position of first and last changes. */
-  ptrdiff_t end = *endp;
+  ptrdiff_t pos = *startp, end = *endp;
   int ch, cased;
 
   for (; pos < end; ++pos)
@@ -247,11 +350,11 @@ do_casify_unibyte_region (struct casing_context *ctx,
       ch = FETCH_BYTE (pos);
       MAKE_CHAR_MULTIBYTE (ch);
 
-      cased = case_character (ctx, ch);
+      cased = case_single_character (ctx, ch);
       if (cased == ch)
 	continue;
 
-      last = pos;
+      last = pos + 1;
       if (first < 0)
 	first = pos;
 
@@ -259,88 +362,107 @@ do_casify_unibyte_region (struct casing_context *ctx,
       FETCH_BYTE (pos) = cased;
     }
 
-  *endp = last + 1;
-  return first;
+  *startp = first;
+  *endp = last;
+  return 0;
 }
 
-/* Based on CTX, case region in a multibyte buffer from POS to *ENDP.  Return
-   first position that has changed and save last position in *ENDP.  If no
-   characters were changed, return -1 and *ENDP is unspecified. */
+/* Based on CTX, case region in a multibyte buffer from *STARTP to *ENDP.
+
+   Return number of added characters (may be negative if more characters were
+   deleted then inserted), save first and last positions that has changed in
+   *STARTP and *ENDP respectively.  If no characters were changed, return 0,
+   save -1 to *STARTP and leave *ENDP unspecified. */
 static ptrdiff_t
 do_casify_multibyte_region (struct casing_context *ctx,
-                           ptrdiff_t pos, ptrdiff_t *endp)
+			    ptrdiff_t *startp, ptrdiff_t *endp)
 {
   ptrdiff_t first = -1, last = -1;  /* Position of first and last changes. */
-  ptrdiff_t pos_byte = CHAR_TO_BYTE (pos), end = *endp;
-  ptrdiff_t opoint = PT;
+  ptrdiff_t pos = *startp, pos_byte = CHAR_TO_BYTE (pos), size = *endp - pos;
+  ptrdiff_t opoint = PT, added = 0;
+  struct casing_str_buf buf;
   int ch, cased, len;
 
-  while (pos < end)
+  for (; size; --size)
     {
       ch = STRING_CHAR_AND_LENGTH (BYTE_POS_ADDR (pos_byte), len);
-      cased = case_character (ctx, ch);
-      if (cased != ch)
+      if (!case_character (&buf, ctx, ch))
 	{
-	  last = pos;
-	  if (first < 0)
-	    first = pos;
+	  pos_byte += len;
+	  ++pos;
+	  continue;
+	}
 
-	  if (ASCII_CHAR_P (cased) && ASCII_CHAR_P (ch))
-	    FETCH_BYTE (pos_byte) = cased;
-	  else
-	    {
-	      unsigned char str[MAX_MULTIBYTE_LENGTH];
-	      int totlen = CHAR_STRING (cased, str);
-	      if (len == totlen)
-		memcpy (BYTE_POS_ADDR (pos_byte), str, len);
-	      else
-		/* Replace one character with the other(s), keeping text
-		   properties the same.  */
-		replace_range_2 (pos, pos_byte, pos + 1, pos_byte + len,
-				 (char *) str, 9, totlen, 0);
-	      len = totlen;
-	    }
+      last = pos + buf.len_chars;
+      if (first < 0)
+	first = pos;
+
+      if (buf.len_chars == 1 && buf.len_bytes == len)
+	memcpy (BYTE_POS_ADDR (pos_byte), buf.data, len);
+      else
+	{
+	  /* Replace one character with the other(s), keeping text
+	     properties the same.  */
+	  replace_range_2 (pos, pos_byte, pos + 1, pos_byte + len,
+			   (const char *) buf.data, buf.len_chars,
+			   buf.len_bytes,
+			   0);
+	  added += (ptrdiff_t) buf.len_chars - 1;
+	  if (opoint > pos)
+	    opoint += (ptrdiff_t) buf.len_chars - 1;
 	}
-      pos++;
-      pos_byte += len;
+
+      pos_byte += buf.len_bytes;
+      pos += buf.len_chars;
     }
 
   if (PT != opoint)
     TEMP_SET_PT_BOTH (opoint, CHAR_TO_BYTE (opoint));
 
+  *startp = first;
   *endp = last;
-  return first;
+  return added;
 }
 
-/* flag is CASE_UP, CASE_DOWN or CASE_CAPITALIZE or CASE_CAPITALIZE_UP.
-   b and e specify range of buffer to operate on. */
-static void
+/* flag is CASE_UP, CASE_DOWN or CASE_CAPITALIZE or CASE_CAPITALIZE_UP.  b and
+   e specify range of buffer to operate on.  Return character position of the
+   end of the region after changes.  */
+static ptrdiff_t
 casify_region (enum case_action flag, Lisp_Object b, Lisp_Object e)
 {
+  ptrdiff_t start, end, orig_end, added;
   struct casing_context ctx;
-  ptrdiff_t start, end;
-
-  if (EQ (b, e))
-    /* Not modifying because nothing marked */
-    return;
 
   validate_region (&b, &e);
   start = XFASTINT (b);
   end = XFASTINT (e);
+  if (start == end)
+    /* Not modifying because nothing marked */
+    return end;
   modify_text (start, end);
-  record_change (start, end - start);
   prepare_casing_context (&ctx, flag, true);
 
+  orig_end = end;
+  record_delete (start, make_buffer_string (start, end, true), false);
   if (NILP (BVAR (current_buffer, enable_multibyte_characters)))
-    start = do_casify_unibyte_region (&ctx, start, &end);
+    {
+      record_insert (start, end - start);
+      added = do_casify_unibyte_region (&ctx, &start, &end);
+    }
   else
-    start = do_casify_multibyte_region (&ctx, start, &end);
+    {
+      ptrdiff_t len = end - start, ostart = start;
+      added = do_casify_multibyte_region (&ctx, &start, &end);
+      record_insert (ostart, len + added);
+    }
 
   if (start >= 0)
     {
-      signal_after_change (start, end + 1 - start, end + 1 - start);
-      update_compositions (start, end + 1, CHECK_ALL);
+      signal_after_change (start, end - start - added, end - start);
+      update_compositions (start, end, CHECK_ALL);
     }
+
+  return orig_end + added;
 }
 
 DEFUN ("upcase-region", Fupcase_region, Supcase_region, 2, 3,
@@ -432,9 +554,7 @@ casify_word (enum case_action flag, Lisp_Object arg)
   ptrdiff_t farend = scan_words (PT, XINT (arg));
   if (!farend)
     farend = XINT (arg) <= 0 ? BEGV : ZV;
-  ptrdiff_t newpoint = max (PT, farend);
-  casify_region (flag, make_number (PT), make_number (farend));
-  SET_PT (newpoint);
+  SET_PT (casify_region (flag, make_number (PT), make_number (farend)));
   return Qnil;
 }
 
diff --git a/test/lisp/char-fold-tests.el b/test/lisp/char-fold-tests.el
index d86c731b6e3..00bc3c83d05 100644
--- a/test/lisp/char-fold-tests.el
+++ b/test/lisp/char-fold-tests.el
@@ -54,6 +54,14 @@ char-fold--test-search-with-contents
        (concat w1 "\s\n\s\t\f\t\n\r\t" w2)
        (concat w1 (make-string 10 ?\s) w2)))))
 
+(defun char-fold--ascii-upcase (string)
+  "Like `upcase' but acts on ASCII characters only."
+  (replace-regexp-in-string "[a-z]+" 'upcase string))
+
+(defun char-fold--ascii-downcase (string)
+  "Like `downcase' but acts on ASCII characters only."
+  (replace-regexp-in-string "[a-z]+" 'downcase string))
+
 (defun char-fold--test-match-exactly (string &rest strings-to-match)
   (let ((re (concat "\\`" (char-fold-to-regexp string) "\\'")))
     (dolist (it strings-to-match)
@@ -61,8 +69,8 @@ char-fold--test-match-exactly
     ;; Case folding
     (let ((case-fold-search t))
       (dolist (it strings-to-match)
-        (should (string-match (upcase re) (downcase it)))
-        (should (string-match (downcase re) (upcase it)))))))
+        (should (string-match (char-fold--ascii-upcase re) (downcase it)))
+        (should (string-match (char-fold--ascii-downcase re) (upcase it)))))))
 
 (ert-deftest char-fold--test-some-defaults ()
   (dolist (it '(("ffl" . "ffl") ("ffi" . "ffi")
diff --git a/test/src/casefiddle-tests.el b/test/src/casefiddle-tests.el
index e83cb00059b..dd260633f4c 100644
--- a/test/src/casefiddle-tests.el
+++ b/test/src/casefiddle-tests.el
@@ -24,36 +24,40 @@
 
 (ert-deftest casefiddle-tests-char-properties ()
   "Sanity check of character Unicode properties."
-  (should-not
-   (let (errors)
-     ;;            character  uppercase  lowercase  titlecase
-     (dolist (test '((?A nil ?a nil)
-                     (?a ?A nil ?A)
-                     (?Ł nil ?ł nil)
-                     (?ł ?Ł nil ?Ł)
-
-                     (?DŽ nil ?dž ?Dž)
-                     (?Dž ?DŽ ?dž ?Dž)
-                     (?dž ?DŽ nil ?Dž)
-
-                     (?Σ nil ?σ nil)
-                     (?σ ?Σ nil ?Σ)
-                     (?ς ?Σ nil ?Σ)
-
-                     (?ⅷ ?Ⅷ nil ?Ⅷ)
-                     (?Ⅷ nil ?ⅷ nil)))
-       (let ((ch (car test))
-             (expected (cdr test))
-             (props '(uppercase lowercase titlecase)))
-         (while props
-           (let ((got (get-char-code-property ch (car props))))
-             (unless (equal (car expected) got)
-               (push (format "\n%c %s; expected: %s but got: %s"
-                             ch (car props) (car expected) got)
-                     errors)))
-           (setq props (cdr props) expected (cdr expected)))))
-     (when errors
-       (mapconcat (lambda (line) line) (nreverse errors) "")))))
+  (let ((props '(uppercase lowercase titlecase
+                 special-uppercase special-lowercase special-titlecase))
+        (tests '((?A nil ?a  nil  nil  nil  nil)
+                 (?a ?A  nil ?A   nil  nil  nil)
+                 (?Ł nil ?ł  nil  nil  nil  nil)
+                 (?ł ?Ł  nil ?Ł   nil  nil  nil)
+
+                 (?DŽ nil ?dž  ?Dž   nil  nil  nil)
+                 (?Dž ?DŽ  ?dž  ?Dž   nil  nil  nil)
+                 (?dž ?DŽ  nil ?Dž   nil  nil  nil)
+
+                 (?Σ nil ?σ  nil  nil  nil  nil)
+                 (?σ ?Σ  nil ?Σ   nil  nil  nil)
+                 (?ς ?Σ  nil ?Σ   nil  nil  nil)
+
+                 (?ⅷ ?Ⅷ  nil ?Ⅷ   nil  nil  nil)
+                 (?Ⅷ nil ?ⅷ  nil  nil  nil  nil)
+
+                 (?fi nil nil nil  "FI" nil "Fi")
+                 (?ß nil nil nil  "SS" nil "Ss")
+                 (?İ nil ?i  nil  nil "i\u0307" nil)))
+        errors)
+    (dolist (test tests)
+      (let ((ch (car test))
+            (expected (cdr test)))
+        (dolist (prop props)
+          (let ((got (get-char-code-property ch prop)))
+            (unless (equal (car expected) got)
+              (push (format "\n%c %s; expected: %s but got: %s"
+                            ch prop (car expected) got)
+                    errors)))
+          (setq expected (cdr expected)))))
+    (when errors
+      (ert-fail (mapconcat (lambda (line) line) (nreverse errors) "")))))
 
 
 (defconst casefiddle-tests--characters
@@ -188,16 +192,13 @@ casefiddle-tests--test-casing
         ("DŽUNGLA" "DŽUNGLA" "džungla" "Džungla" "DžUNGLA")
         ("Džungla" "DŽUNGLA" "džungla" "Džungla" "Džungla")
         ("džungla" "DŽUNGLA" "džungla" "Džungla" "Džungla")
+        ("define" "DEFINE" "define" "Define" "Define")
+        ("fish" "FISH" "fish" "Fish" "Fish")
+        ("Straße" "STRASSE" "straße" "Straße" "Straße")
         ;; FIXME(bug#24603): Everything below is broken at the moment.
         ;; Here’s what should happen:
-        ;;("define" "DEFINE" "define" "Define" "Define")
-        ;;("fish" "FIsh" "fish" "Fish" "Fish")
-        ;;("Straße" "STRASSE" "straße" "Straße" "Straße")
         ;;("ΌΣΟΣ" "ΌΣΟΣ" "όσος" "Όσος" "Όσος")
         ;; And here’s what is actually happening:
-        ("define" "DEfiNE" "define" "Define" "Define")
-        ("fish" "fiSH" "fish" "fish" "fish")
-        ("Straße" "STRAßE" "straße" "Straße" "Straße")
         ("ΌΣΟΣ" "ΌΣΟΣ" "όσοσ" "Όσοσ" "ΌΣΟΣ")
 
         ("όσος" "ΌΣΟΣ" "όσος" "Όσος" "Όσος"))))))
-- 
2.12.0.367.g23dc2f6d3c-goog






^ permalink raw reply related	[flat|nested] 89+ messages in thread

* bug#24603: [PATCHv6 6/6] Implement special sigma casing rule (bug#24603)
  2017-03-21  1:27   ` bug#24603: [PATCHv6 0/6] Casing improvements, language-independent part Michal Nazarewicz
                       ` (4 preceding siblings ...)
  2017-03-21  1:27     ` bug#24603: [PATCHv6 5/6] Support casing characters which map into multiple code points (bug#24603) Michal Nazarewicz
@ 2017-03-21  1:27     ` Michal Nazarewicz
  5 siblings, 0 replies; 89+ messages in thread
From: Michal Nazarewicz @ 2017-03-21  1:27 UTC (permalink / raw)
  To: Eli Zaretskii, Andreas Schwab, 24603

In Greek, a sigma character has two lower case forms which depend on
their position in the word.  Implement logic determining it.

* src/casefiddle.c (struct casing_context, case_character_impl): Don’t
assume inword is true when flag is CASE_UP and false when flag is
CASE_DOWN.  For final sigma detection we need this information tracked
reliably;.
(CAPITAL_SIGMA, SMALL_SIGMA, SMALL_FINAL_SIGMA): New macros defining
Unicode code point of different forms of sigma letter.
(case_character): Implement support for final sigma casing.
(do_casify_multibyte_string, do_casify_multibyte_region): Update after
changes to case_character.

* test/src/casefiddle-tests.el (casefiddle-tests-casing): Add test
cases for final sigma.
---
 etc/NEWS                     |  5 +++
 src/casefiddle.c             | 73 +++++++++++++++++++++++++++++++++-----------
 test/src/casefiddle-tests.el | 15 +++++----
 3 files changed, 69 insertions(+), 24 deletions(-)

diff --git a/etc/NEWS b/etc/NEWS
index af1981b1453..fb5cc6095bb 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -367,6 +367,11 @@ of incorrect DŽungla).
 *** Characters which turn into multiple ones when cased are correctly handled.
 For example, fi ligature is converted to FI when upper cased.
 
+*** Greek small sigma is correctly handled when at the end of the word.
+Strings such as ΌΣΟΣ are now correctly converted to Όσος when
+capitalized instead of incorrect Όσοσ (compare lowercase sigma at the
+end of the word).
+
 \f
 * Changes in Specialized Modes and Packages in Emacs 26.1
 
diff --git a/src/casefiddle.c b/src/casefiddle.c
index df94c970539..8a47641c4bb 100644
--- a/src/casefiddle.c
+++ b/src/casefiddle.c
@@ -45,9 +45,7 @@ struct casing_context {
      When run on a buffer, syntax_prefix_flag_p is taken into account when
      determined inword flag. */
   bool inbuffer;
-  /* Conceptually, this denotes whether we are inside of a word except
-     that if flag is CASE_UP it’s always false and if flag is CASE_DOWN
-     this is always true. */
+  /* Whether we are inside of a word. */
   bool inword;
 };
 
@@ -58,7 +56,7 @@ prepare_casing_context (struct casing_context *ctx,
 {
   ctx->flag = flag;
   ctx->inbuffer = inbuffer;
-  ctx->inword = flag == CASE_DOWN;
+  ctx->inword = false;
   ctx->titlecase_char_table = (int)flag < (int)CASE_CAPITALIZE ? Qnil :
     uniprop_table (intern_c_string ("titlecase"));
   ctx->specialcase_char_tables[CASE_UP] = flag == CASE_DOWN ? Qnil :
@@ -100,15 +98,16 @@ case_character_impl (struct casing_str_buf *buf,
 
   /* Update inword state */
   was_inword = ctx->inword;
-  if ((int) ctx->flag >= (int) CASE_CAPITALIZE)
-    ctx->inword = SYNTAX (ch) == Sword &&
-      (!ctx->inbuffer || was_inword || !syntax_prefix_flag_p (ch));
+  ctx->inword = SYNTAX (ch) == Sword &&
+    (!ctx->inbuffer || was_inword || !syntax_prefix_flag_p (ch));
 
   /* Normalise flag so its one of CASE_UP, CASE_DOWN or CASE_CAPITALIZE. */
-  if (!was_inword)
-    flag = ctx->flag == CASE_UP ? CASE_UP : CASE_CAPITALIZE;
+  if (ctx->flag == CASE_CAPITALIZE)
+    flag = (enum case_action)((int)ctx->flag - was_inword);
   else if (ctx->flag != CASE_CAPITALIZE_UP)
-    flag = CASE_DOWN;
+    flag = ctx->flag;
+  else if (!was_inword)
+    flag = CASE_CAPITALIZE;
   else
     {
       cased = ch;
@@ -149,7 +148,18 @@ case_character_impl (struct casing_str_buf *buf,
   buf->len_bytes = CHAR_STRING (cased, buf->data);
   return cased != ch;
 }
+\f
+/* In Greek, lower case sigma has two forms: one when used in the middle and one
+   when used at the end of a word.  Below is to help handle those cases when
+   casing.
+
+   The rule does not conflict with any other casing rules so while it is
+   a conditional one, it is independent on language. */
 
+#define CAPITAL_SIGMA     0x03A3
+#define SMALL_SIGMA       0x03C3
+#define SMALL_FINAL_SIGMA 0x03C2
+\f
 /* Based on CTX, case character CH accordingly.  Update CTX as necessary.
    Return cased character.
 
@@ -163,12 +173,34 @@ case_single_character (struct casing_context *ctx, int ch)
 }
 
 /* Save in BUF result of casing character CH.  Return whether casing changed the
-   character.  This is like case_single_character but also handles one-to-many
-   casing rules. */
-static inline bool
-case_character (struct casing_str_buf *buf, struct casing_context *ctx, int ch)
+   character.
+
+   If not-NULL, NEXT points to the next character in the cased string.  If NULL,
+   it is assumed current character is the last one being cased.  This is used to
+   apply some rules which depend on proceeding state.
+
+   This is like case_single_character but also handles one-to-many casing
+   rules. */
+static bool
+case_character (struct casing_str_buf *buf, struct casing_context *ctx,
+		int ch, const unsigned char *next)
 {
-  return case_character_impl (buf, ctx, ch);
+  bool changed, was_inword;
+
+  was_inword = ctx->inword;
+  changed = case_character_impl (buf, ctx, ch);
+
+  /* If we have just down-cased a capital sigma and the next character no longer
+     has a word syntax (i.e. current character is end of word), use final
+     sigma. */
+  if (was_inword && ch == CAPITAL_SIGMA && changed &&
+      (!next || SYNTAX (STRING_CHAR (next)) != Sword))
+    {
+      buf->len_bytes = CHAR_STRING (SMALL_FINAL_SIGMA, buf->data);
+      buf->len_chars = 1;
+    }
+
+  return changed;
 }
 \f
 static Lisp_Object
@@ -230,7 +262,7 @@ do_casify_multibyte_string (struct casing_context *ctx, Lisp_Object obj)
       if (dst_end - o < sizeof(struct casing_str_buf))
 	string_overflow ();
       ch = STRING_CHAR_ADVANCE (src);
-      case_character ((void *)o, ctx, ch);
+      case_character ((void *)o, ctx, ch, size > 1 ? src : NULL);
       n += ((struct casing_str_buf *)o)->len_chars;
       o += ((struct casing_str_buf *)o)->len_bytes;
     }
@@ -381,12 +413,17 @@ do_casify_multibyte_region (struct casing_context *ctx,
   ptrdiff_t pos = *startp, pos_byte = CHAR_TO_BYTE (pos), size = *endp - pos;
   ptrdiff_t opoint = PT, added = 0;
   struct casing_str_buf buf;
-  int ch, cased, len;
+  bool changed;
+  int ch, len;
 
   for (; size; --size)
     {
       ch = STRING_CHAR_AND_LENGTH (BYTE_POS_ADDR (pos_byte), len);
-      if (!case_character (&buf, ctx, ch))
+      changed = case_character (
+	  &buf, ctx, ch,
+	  size > 1 ? BYTE_POS_ADDR (pos_byte + len) : NULL);
+
+      if (!changed)
 	{
 	  pos_byte += len;
 	  ++pos;
diff --git a/test/src/casefiddle-tests.el b/test/src/casefiddle-tests.el
index dd260633f4c..234d233c71a 100644
--- a/test/src/casefiddle-tests.el
+++ b/test/src/casefiddle-tests.el
@@ -195,13 +195,16 @@ casefiddle-tests--test-casing
         ("define" "DEFINE" "define" "Define" "Define")
         ("fish" "FISH" "fish" "Fish" "Fish")
         ("Straße" "STRASSE" "straße" "Straße" "Straße")
-        ;; FIXME(bug#24603): Everything below is broken at the moment.
-        ;; Here’s what should happen:
-        ;;("ΌΣΟΣ" "ΌΣΟΣ" "όσος" "Όσος" "Όσος")
-        ;; And here’s what is actually happening:
-        ("ΌΣΟΣ" "ΌΣΟΣ" "όσοσ" "Όσοσ" "ΌΣΟΣ")
 
-        ("όσος" "ΌΣΟΣ" "όσος" "Όσος" "Όσος"))))))
+        ;; The word repeated twice to test behaviour at the end of a word
+        ;; inside of an input string as well as at the end of the string.
+        ("ΌΣΟΣ ΌΣΟΣ" "ΌΣΟΣ ΌΣΟΣ" "όσος όσος" "Όσος Όσος" "ΌΣΟΣ ΌΣΟΣ")
+        ;; What should be done with sole sigma?  It is ‘final’ but on the
+        ;; other hand it does not form a word.  We’re using regular sigma.
+        ("Σ Σ" "Σ Σ" "σ σ" "Σ Σ" "Σ Σ")
+        ("όσος" "ΌΣΟΣ" "όσος" "Όσος" "Όσος")
+        ;; If sigma is already lower case, we don’t want to change it.
+        ("όσοσ" "ΌΣΟΣ" "όσοσ" "Όσοσ" "Όσοσ"))))))
 
 (ert-deftest casefiddle-tests-casing-byte8 ()
   (should-not
-- 
2.12.0.367.g23dc2f6d3c-goog






^ permalink raw reply related	[flat|nested] 89+ messages in thread

* bug#24603: [PATCHv5 05/11] Support casing characters which map into multiple code points (bug#24603)
  2017-03-11  9:14     ` Eli Zaretskii
@ 2017-03-21  2:09       ` Michal Nazarewicz
  0 siblings, 0 replies; 89+ messages in thread
From: Michal Nazarewicz @ 2017-03-21  2:09 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: 24603

On Sat, Mar 11 2017, Eli Zaretskii wrote:
>> From: Michal Nazarewicz <mina86@mina86.com>
>> Date: Thu,  9 Mar 2017 22:51:44 +0100
>> 
>> Implement unconditional special casing rules defined in Unicode standard.
>> 
>> Among other things, they deal with cases when a single code point is
>> replaced by multiple ones because single character does not exist (e.g.
>> ‘fi’ ligature turning into ‘FL’) or is not commonly used (e.g. ß turning
>> into SS).
>> 
>> * admin/unidata/SpecialCasing.txt: New data file pulled from Unicode
>> standard distribution.
>> * admin/unidata/README: Mention SpecialCasing.txt.
>> 
>> * admin/unidata/unidata-get.el (unidata-gen-table-special-casing): New
>> function for generating ‘special-casing’ character Unicode property
>> built from the SpecialCasing.txt Unicode data file.
>
> This new property is attainable via get-char-code-property, right?  If
> so, it should be documented in the Elisp manual, in the "Character
> Properties" node.
>
> I think I'd also like to see a few simple tests for this property.

Done and done.  I’ve actually split this property into three separate
ones.  Previously, the property was unique in how it mapped a single
character into multiple values.

>> diff --git a/doc/lispref/strings.texi b/doc/lispref/strings.texi
>> index cf47db4a814..ba1cf2606ce 100644
>> --- a/doc/lispref/strings.texi
>> +++ b/doc/lispref/strings.texi
>> @@ -1166,6 +1166,29 @@ Case Conversion
>>  @end example
>>  @end defun
>>  
>> +  Note that case conversion is not a one-to-one mapping and the length
>> +of the result may differ from the length of the argument (including
>> +being shorter).  Furthermore, because passing a character forces
>> +return type to be a character, functions are unable to perform proper
>> +substitution and result may differ compared to treating
>> +a one-character string.  For example:
>> +
>> +@example
>> +@group
>> +(upcase "fi")  ; note: single character, ligature "fi"
>> +     @result{} "FI"
>> +@end group
>> +@group
>> +(upcase ?fi)
>> +     @result{} 64257  ; i.e. ?fi
>> +@end group
>> +@end example
>> +
>> +  To avoid this, a character must first be converted into a string,
>> +using @code{string} function, before being passed to one of the casing
>> +functions.  Of course, no assumptions on the length of the result may
>> +be made.
>
> Once the ELisp manual describes the new special-casing property, the
> above text should include a cross-reference to that description.

Ah, actually forgot about that one.  I don’t want to resend the patch,
but I’ll add:

+  Mapping for such special cases are taken from
+@code{special-uppercase}, @code{special-lowercase} and
+@code{special-titlecase} @xref{Character Properties}.
+

before submitting.

>>  DEFUN ("upcase", Fupcase, Supcase, 1, 1, 0,
>>         doc: /* Convert argument to upper case and return that.
>>  The argument may be a character or string.  The result has the same type.
>> -The argument object is not altered--the value is a copy.
>> +The argument object is not altered--the value is a copy.  If argument
>> +is a character, characters which map to multiple code points when
>> +cased, e.g. fi, are returned unchanged.
>>  See also `capitalize', `downcase' and `upcase-initials'.  */)
>
> This (and other similar doc strings) should mention the special-casing
> property as the way to know in advance which characters will remain
> unchanged due to that.

Done.

-- 
Best regards
ミハウ “𝓶𝓲𝓷𝓪86” ナザレヴイツ
«If at first you don’t succeed, give up skydiving»





^ permalink raw reply	[flat|nested] 89+ messages in thread

* bug#24603: [PATCHv6 5/6] Support casing characters which map into multiple code points (bug#24603)
  2017-03-21  1:27     ` bug#24603: [PATCHv6 5/6] Support casing characters which map into multiple code points (bug#24603) Michal Nazarewicz
@ 2017-03-22 16:06       ` Eli Zaretskii
  2017-04-03  9:01         ` Michal Nazarewicz
  0 siblings, 1 reply; 89+ messages in thread
From: Eli Zaretskii @ 2017-03-22 16:06 UTC (permalink / raw)
  To: Michal Nazarewicz; +Cc: schwab, 24603

> From: Michal Nazarewicz <mina86@mina86.com>
> Date: Tue, 21 Mar 2017 02:27:08 +0100
> 
> Implement unconditional special casing rules defined in Unicode standard.

Thanks.  A few comments below.

> diff --git a/admin/unidata/unidata-gen.el b/admin/unidata/unidata-gen.el
> index 3c5119a8a3d..32b05eacce6 100644
> --- a/admin/unidata/unidata-gen.el
> +++ b/admin/unidata/unidata-gen.el
> @@ -268,6 +268,33 @@ unidata-prop-alist
>  The value nil means that the actual property value of a character
>  is the character itself."
>       string)
> +    (special-uppercase
> +     2 unidata-gen-table-special-casing "uni-special-uppercase.el"
> +     "Unicode unconditional special casing mapping.
> +
> +Property value is nil, denoting no special rules, or a string, denoting
> +characters maps into given sequence of characters.

Something is wrong with the last sentence.  (This problem repeats in
other similar sentences in the patch.)

> +The mapping includes only unconditional casing rules defined by Unicode."

This begs for clarification: what is meant by "unconditional casing"?
I think a sentence or two of explanation are due.

> +@item special-uppercase
> +Corresponds to Unicode unconditional special upper-casing rules.  The value

Likewise here: the "unconditional" part should be explained.

> +is @code{"SS"}.  For unassigned codepoints, the value is @code{nil}
> +which means @code{uppercase} property needs to be consulted instead.

When you say "unassigned codepoints", do you mean codepoints that
don't have characters defined for them in Unicode?  Because that's the
usual meaning of this term in the context of Unicode.  If you mean
something else, please use some other term.  (I think you mean
something else, since properties of unassigned codepoints are not
really interesting for Lisp programmers.)

> +mapping for @code{U+0130} (@sc{latin capital letter i with dot above})
> +the value is @code{"i\u0307"}.  For unassigned codepoints, the value is

Instead of using "i\u0307", in the hope that the reader will
understand it's a string made of 2 characters, I would say that
explicitly.

>  DEFUN ("upcase", Fupcase, Supcase, 1, 1, 0,
>         doc: /* Convert argument to upper case and return that.
>  The argument may be a character or string.  The result has the same type.
> -The argument object is not altered--the value is a copy.
> +The argument object is not altered--the value is a copy.  If argument
> +is a character, characters which map to multiple code points when
> +cased, e.g. fi, are returned unchanged.
>  See also `capitalize', `downcase' and `upcase-initials'.  */)

Using non-ASCII characters here requires adding a 'coding' cookie to
the file's first line.  (C sources are not by default decoded as
UTF-8, unlike Lisp files.)





^ permalink raw reply	[flat|nested] 89+ messages in thread

* bug#24603: [PATCHv6 5/6] Support casing characters which map into multiple code points (bug#24603)
  2017-03-22 16:06       ` Eli Zaretskii
@ 2017-04-03  9:01         ` Michal Nazarewicz
  2017-04-03 14:52           ` Eli Zaretskii
  2019-06-25  0:09           ` Lars Ingebrigtsen
  0 siblings, 2 replies; 89+ messages in thread
From: Michal Nazarewicz @ 2017-04-03  9:01 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: schwab, 24603

On Wed, Mar 22 2017, Eli Zaretskii wrote:
>> From: Michal Nazarewicz <mina86@mina86.com>
>> Date: Tue, 21 Mar 2017 02:27:08 +0100
>> 
>> Implement unconditional special casing rules defined in Unicode standard.
>
> Thanks.  A few comments below.

Diff with fixes attached.  The rest of the patchset stays unchanged.
I figured that posting just the fixes is most readable (rather than
sending the full patch again).

Unless there are more comments I’ll push the commits in a couple of
days.

>> diff --git a/admin/unidata/unidata-gen.el b/admin/unidata/unidata-gen.el
>> index 3c5119a8a3d..32b05eacce6 100644
>> --- a/admin/unidata/unidata-gen.el
>> +++ b/admin/unidata/unidata-gen.el
>> @@ -268,6 +268,33 @@ unidata-prop-alist
>>  The value nil means that the actual property value of a character
>>  is the character itself."
>>       string)
>> +    (special-uppercase
>> +     2 unidata-gen-table-special-casing "uni-special-uppercase.el"
>> +     "Unicode unconditional special casing mapping.
>> +
>> +Property value is nil, denoting no special rules, or a string, denoting
>> +characters maps into given sequence of characters.
>
> Something is wrong with the last sentence.  (This problem repeats in
> other similar sentences in the patch.)
>
>> +The mapping includes only unconditional casing rules defined by Unicode."
>
> This begs for clarification: what is meant by "unconditional casing"?
> I think a sentence or two of explanation are due.

@@ -272,28 +272,37 @@ unidata-prop-alist
      2 unidata-gen-table-special-casing "uni-special-uppercase.el"
      "Unicode unconditional special casing mapping.
 
-Property value is nil, denoting no special rules, or a string, denoting
-characters maps into given sequence of characters.  The string may be empty.
+Property value is (possibly empty) string or nil.  The value nil denotes that
+`uppercase' property should be consulted instead.  A string denotes what
+sequence of characters given character maps into.
 
-The mapping includes only unconditional casing rules defined by Unicode."
+This mapping includes language- and context-independent special casing rules
+defined by Unicode only.  It also does not include association which would
+duplicate information from `uppercase' property."
      nil)
     (special-lowercase
      0 unidata-gen-table-special-casing "uni-special-lowercase.el"
      "Unicode unconditional special casing mapping.
 
-Property value is nil, denoting no special rules, or a string, denoting
-characters maps into given sequence of characters.  The string may be empty.
+Property value is (possibly empty) string or nil.  The value nil denotes that
+`lowercase' property should be consulted instead.  A string denotes what
+sequence of characters given character maps into.
 
-The mapping includes only unconditional casing rules defined by Unicode."
+This mapping includes language- and context-independent special casing rules
+defined by Unicode only.  It also does not include association which would
+duplicate information from `lowercase' property."
      nil)
     (special-titlecase
      1 unidata-gen-table-special-casing "uni-special-titlecase.el"
      "Unicode unconditional special casing mapping.
 
-Property value is nil, denoting no special rules, or a string, denoting
-characters maps into given sequence of characters.  The string may be empty.
+Property value is (possibly empty) string or nil.  The value nil denotes that
+`titlecase' property should be consulted instead.  A string denotes what
+sequence of characters given character maps into.
 
-The mapping includes only unconditional casing rules defined by Unicode."
+This mapping includes language- and context-independent special casing rules
+defined by Unicode only.  It also does not include association which would
+duplicate information from `titlecase' property."
      nil)
     (mirroring
      unidata-gen-mirroring-list unidata-gen-table-character "uni-mirrored.el"

>> +@item special-uppercase
>> +Corresponds to Unicode unconditional special upper-casing rules.  The value
>
> Likewise here: the "unconditional" part should be explained.
>
>> +is @code{"SS"}.  For unassigned codepoints, the value is @code{nil}
>> +which means @code{uppercase} property needs to be consulted instead.
>
> When you say "unassigned codepoints", do you mean codepoints that
> don't have characters defined for them in Unicode?  Because that's the
> usual meaning of this term in the context of Unicode.  If you mean
> something else, please use some other term.  (I think you mean
> something else, since properties of unassigned codepoints are not
> really interesting for Lisp programmers.)
>
>> +mapping for @code{U+0130} (@sc{latin capital letter i with dot above})
>> +the value is @code{"i\u0307"}.  For unassigned codepoints, the value is
>
> Instead of using "i\u0307", in the hope that the reader will
> understand it's a string made of 2 characters, I would say that
> explicitly.

@@ -621,26 +621,27 @@ Character Properties
 is @code{nil}, which means the character itself.
 
 @item special-uppercase
-Corresponds to Unicode unconditional special upper-casing rules.  The value
-of this property is a string (which may be empty).  For example
-mapping for @code{U+00DF} (@sc{latin smpall letter sharp s}) the value
-is @code{"SS"}.  For unassigned codepoints, the value is @code{nil}
+Corresponds to Unicode language- and context-independent special upper-casing
+rules.  The value of this property is a string (which may be empty).  For
+example mapping for @code{U+00DF} (@sc{latin small letter sharp s}) is
+@code{"SS"}.  For characters with no special mapping, the value is @code{nil}
 which means @code{uppercase} property needs to be consulted instead.
 
 @item special-lowercase
-Corresponds to Unicode unconditional special lower-casing rules.  The
-value of this property is a string (which may be empty).  For example
-mapping for @code{U+0130} (@sc{latin capital letter i with dot above})
-the value is @code{"i\u0307"}.  For unassigned codepoints, the value is
-@code{nil} which means @code{lowercase} property needs to be consulted
-instead.
+Corresponds to Unicode language- and context-independent special lower-casing
+rules.  The value of this property is a string (which may be empty).  For
+example mapping for @code{U+0130} (@sc{latin capital letter i with dot above})
+the value is @code{"i\u0307"} (i.e. 2-character string consisting of @sc{latin
+small letter i} followed by @sc{combining dot above}).  For characters with no
+special mapping, the value is @code{nil} which means @code{lowercase} property
+needs to be consulted instead.
 
 @item special-titlecase
-Corresponds to Unicode unconditional special title-casing rules.  The
-value of this property is a string (which may be empty).  For example
-mapping for @code{U+FB01} (@sc{latin small ligature fi}) the value is
-@code{"Fi"}.  For unassigned codepoints, the value is @code{nil} which
-means @code{titlecase} property needs to be consulted instead.
+Corresponds to Unicode unconditional special title-casing rules.  The value of
+this property is a string (which may be empty).  For example mapping for
+@code{U+FB01} (@sc{latin small ligature fi}) the value is @code{"Fi"}.  For
+characters with no special mapping, the value is @code{nil} which means
+@code{titlecase} property needs to be consulted instead.
 @end table
 
 @defun get-char-code-property char propname

>>  DEFUN ("upcase", Fupcase, Supcase, 1, 1, 0,
>>         doc: /* Convert argument to upper case and return that.
>>  The argument may be a character or string.  The result has the same type.
>> -The argument object is not altered--the value is a copy.
>> +The argument object is not altered--the value is a copy.  If argument
>> +is a character, characters which map to multiple code points when
>> +cased, e.g. fi, are returned unchanged.
>>  See also `capitalize', `downcase' and `upcase-initials'.  */)
>
> Using non-ASCII characters here requires adding a 'coding' cookie to
> the file's first line.  (C sources are not by default decoded as
> UTF-8, unlike Lisp files.)

@@ -1,3 +1,4 @@
+/* -*- coding: utf-8 -*- */
 /* GNU Emacs case conversion functions.
 
 Copyright (C) 1985, 1994, 1997-1999, 2001-2017 Free Software Foundation,

-- 
Best regards
ミハウ “𝓶𝓲𝓷𝓪86” ナザレヴイツ
«If at first you don’t succeed, give up skydiving»





^ permalink raw reply	[flat|nested] 89+ messages in thread

* bug#24603: [PATCHv6 5/6] Support casing characters which map into multiple code points (bug#24603)
  2017-04-03  9:01         ` Michal Nazarewicz
@ 2017-04-03 14:52           ` Eli Zaretskii
  2019-06-25  0:09           ` Lars Ingebrigtsen
  1 sibling, 0 replies; 89+ messages in thread
From: Eli Zaretskii @ 2017-04-03 14:52 UTC (permalink / raw)
  To: Michal Nazarewicz; +Cc: schwab, 24603

> From: Michal Nazarewicz <mina86@mina86.com>
> Cc: schwab@linux-m68k.org, 24603@debbugs.gnu.org
> Date: Mon, 03 Apr 2017 11:01:40 +0200
> 
> Diff with fixes attached.  The rest of the patchset stays unchanged.
> I figured that posting just the fixes is most readable (rather than
> sending the full patch again).
> 
> Unless there are more comments I’ll push the commits in a couple of
> days.

LGTM, thanks.





^ permalink raw reply	[flat|nested] 89+ messages in thread

* bug#24603: [PATCHv6 5/6] Support casing characters which map into multiple code points (bug#24603)
  2017-04-03  9:01         ` Michal Nazarewicz
  2017-04-03 14:52           ` Eli Zaretskii
@ 2019-06-25  0:09           ` Lars Ingebrigtsen
  2019-06-25  0:29             ` Michał Nazarewicz
  1 sibling, 1 reply; 89+ messages in thread
From: Lars Ingebrigtsen @ 2019-06-25  0:09 UTC (permalink / raw)
  To: Michal Nazarewicz; +Cc: 24603, schwab

Michal Nazarewicz <mina86@mina86.com> writes:

> Diff with fixes attached.  The rest of the patchset stays unchanged.
> I figured that posting just the fixes is most readable (rather than
> sending the full patch again).
>
> Unless there are more comments I’ll push the commits in a couple of
> days.

This was a huge bug report with large patches, but I checked a couple of
them, and they didn't seem to have been applied?  Did the work here stall?

-- 
(domestic pets only, the antidote for overdose, milk.)
   bloggy blog: http://lars.ingebrigtsen.no





^ permalink raw reply	[flat|nested] 89+ messages in thread

* bug#24603: [PATCHv6 5/6] Support casing characters which map into multiple code points (bug#24603)
  2019-06-25  0:09           ` Lars Ingebrigtsen
@ 2019-06-25  0:29             ` Michał Nazarewicz
  2020-08-11 13:46               ` Lars Ingebrigtsen
  0 siblings, 1 reply; 89+ messages in thread
From: Michał Nazarewicz @ 2019-06-25  0:29 UTC (permalink / raw)
  To: Lars Ingebrigtsen; +Cc: 24603, schwab

[-- Attachment #1: Type: text/plain, Size: 1160 bytes --]

On Tue, 25 Jun 2019 at 01:09, Lars Ingebrigtsen <larsi@gnus.org> wrote:

> This was a huge bug report with large patches, but I checked a couple of
> them, and they didn't seem to have been applied?  Did the work here stall?
>

Some of the changes have found their way into the repository:

$ git log  -i --grep=bug#24603 --oneline origin/master
c1fa07222e Implement special sigma casing rule  (bug#24603)
b3b9b258c4 Support casing characters which map into multiple code points
 (bug#24603)
2c87dabd04 Split up casify_region function  (bug#24603)
13d813b1a0 Add support for title-casing letters  (bug#24603)
6220faeb4e casing: don’t assume letters are *either* upper- or lower-case
 (bug#24603)
5ec3a58462 Generate upcase and downcase tables from Unicode data
 (bug#24603)
0d4290650d Add tests for casefiddle.c  (bug#24603)

The progress stalled on getting language-specific rules enabled.
This is because I didn't have the time to work on a good way to let
users choose language of the buffer/string.

-- 
Best regards
ミハウ “𝓶𝓲𝓷𝓪86” ナザレヴイツ
«If at first you don’t succeed, give up skydiving»

[-- Attachment #2: Type: text/html, Size: 1583 bytes --]

^ permalink raw reply	[flat|nested] 89+ messages in thread

* bug#24603: [PATCHv6 5/6] Support casing characters which map into multiple code points (bug#24603)
  2019-06-25  0:29             ` Michał Nazarewicz
@ 2020-08-11 13:46               ` Lars Ingebrigtsen
  2021-05-10 11:51                 ` bug#24603: [RFC 00/18] Improvement to casing Lars Ingebrigtsen
  0 siblings, 1 reply; 89+ messages in thread
From: Lars Ingebrigtsen @ 2020-08-11 13:46 UTC (permalink / raw)
  To: Michał Nazarewicz; +Cc: schwab, 24603

Michał Nazarewicz <mina86@mina86.com> writes:

> The progress stalled on getting language-specific rules enabled.
> This is because I didn't have the time to work on a good way to let
> users choose language of the buffer/string.

OK, should this bug report be closed, then if there's not going to be
any progress with the remaining bits?

-- 
(domestic pets only, the antidote for overdose, milk.)
   bloggy blog: http://lars.ingebrigtsen.no





^ permalink raw reply	[flat|nested] 89+ messages in thread

* bug#24603: [RFC 00/18] Improvement to casing
  2020-08-11 13:46               ` Lars Ingebrigtsen
@ 2021-05-10 11:51                 ` Lars Ingebrigtsen
  0 siblings, 0 replies; 89+ messages in thread
From: Lars Ingebrigtsen @ 2021-05-10 11:51 UTC (permalink / raw)
  To: Michał Nazarewicz; +Cc: 24603, schwab

Lars Ingebrigtsen <larsi@gnus.org> writes:

> OK, should this bug report be closed, then if there's not going to be
> any progress with the remaining bits?

There was no response was given within a few months, so I'm closing this
bug report.  If this should still be worked on, please respond to this
email and we'll reopen the bug report.  (Or perhaps opening a new one
would make more sense.)

-- 
(domestic pets only, the antidote for overdose, milk.)
   bloggy blog: http://lars.ingebrigtsen.no





^ permalink raw reply	[flat|nested] 89+ messages in thread

end of thread, other threads:[~2021-05-10 11:51 UTC | newest]

Thread overview: 89+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2016-10-04  1:05 bug#24603: [RFC 00/18] Improvement to casing Michal Nazarewicz
2016-10-04  1:10 ` bug#24603: [RFC 01/18] Add tests for casefiddle.c Michal Nazarewicz
2016-10-04  1:10   ` bug#24603: [RFC 02/18] Generate upcase and downcase tables from Unicode data Michal Nazarewicz
2016-10-04  7:27     ` Eli Zaretskii
2016-10-04 14:54       ` Michal Nazarewicz
2016-10-04 15:06         ` Eli Zaretskii
2016-10-04 16:57           ` Michal Nazarewicz
2016-10-04 17:27             ` Eli Zaretskii
2016-10-04 17:44               ` Eli Zaretskii
2016-10-06 20:29                 ` Michal Nazarewicz
2016-10-07  6:52                   ` Eli Zaretskii
2016-10-04  1:10   ` bug#24603: [RFC 03/18] Don’t assume character can be either upper- or lower-case when casing Michal Nazarewicz
2016-10-04  1:10   ` bug#24603: [RFC 04/18] Split casify_object into multiple functions Michal Nazarewicz
2016-10-04  1:10   ` bug#24603: [RFC 05/18] Introduce case_character function Michal Nazarewicz
2016-10-04  1:10   ` bug#24603: [RFC 06/18] Add support for title-casing letters Michal Nazarewicz
2016-10-04  1:10   ` bug#24603: [RFC 07/18] Split up casify_region function Michal Nazarewicz
2016-10-04  7:17     ` Eli Zaretskii
2016-10-18  2:27       ` Michal Nazarewicz
2016-10-04  1:10   ` bug#24603: [RFC 08/18] Support casing characters which map into multiple code points Michal Nazarewicz
2016-10-04  7:38     ` Eli Zaretskii
2016-10-06 21:40       ` Michal Nazarewicz
2016-10-07  7:46         ` Eli Zaretskii
2017-01-28 23:48           ` Michal Nazarewicz
2017-02-10  9:12             ` Eli Zaretskii
2016-10-04  1:10   ` bug#24603: [RFC 09/18] Implement special sigma casing rule Michal Nazarewicz
2016-10-04  7:22     ` Eli Zaretskii
2016-10-04  1:10   ` bug#24603: [RFC 10/18] Implement Turkic dotless and dotted i handling when casing strings Michal Nazarewicz
2016-10-04  7:12     ` Eli Zaretskii
2016-10-04  1:10   ` bug#24603: [RFC 11/18] Implement casing rules for Lithuanian Michal Nazarewicz
2016-10-04  1:10   ` bug#24603: [RFC 12/18] Implement rules for title-casing Dutch ij ‘letter’ Michal Nazarewicz
2016-10-04  1:10   ` bug#24603: [RFC 13/18] Add some tricky Unicode characters to regex test Michal Nazarewicz
2016-10-04  1:10   ` bug#24603: [RFC 14/18] Factor out character category lookup to separate function Michal Nazarewicz
2016-10-04  1:10   ` bug#24603: [RFC 15/18] Base lower- and upper-case tests on Unicode properties Michal Nazarewicz
2016-10-04  6:54     ` Eli Zaretskii
2016-10-04  1:10   ` bug#24603: [RFC 16/18] Refactor character class checking; optimise ASCII case Michal Nazarewicz
2016-10-04  7:48     ` Eli Zaretskii
2016-10-17 13:22       ` Michal Nazarewicz
2016-11-06 19:26       ` Michal Nazarewicz
2016-11-06 19:44         ` Eli Zaretskii
2016-12-20 14:32           ` Michal Nazarewicz
2016-12-20 16:39             ` Eli Zaretskii
2016-12-22 14:02               ` Michal Nazarewicz
2016-10-04  1:10   ` bug#24603: [RFC 17/18] Optimise character class matching in regexes Michal Nazarewicz
2016-10-04  1:10   ` bug#24603: [RFC 18/18] Fix case-fold-search character class matching Michal Nazarewicz
2016-10-17 22:03 ` bug#24603: [PATCH 0/3] Case table updates Michal Nazarewicz
2016-10-17 22:03   ` bug#24603: [PATCH 1/3] Add tests for casefiddle.c Michal Nazarewicz
2016-10-17 22:03   ` bug#24603: [PATCH 2/3] Generate upcase and downcase tables from Unicode data Michal Nazarewicz
2016-10-17 22:03   ` bug#24603: [PATCH 3/3] Don’t generate ‘X maps to X’ entries in case tables Michal Nazarewicz
2016-10-18  6:36   ` bug#24603: [PATCH 0/3] Case table updates Eli Zaretskii
2016-10-24 15:11     ` Michal Nazarewicz
2016-10-24 15:33       ` Eli Zaretskii
2017-03-09 21:51 ` bug#24603: [PATCHv5 00/11] Casing improvements Michal Nazarewicz
2017-03-09 21:51   ` bug#24603: [PATCHv5 01/11] Split casify_object into multiple functions Michal Nazarewicz
2017-03-10  9:00     ` Andreas Schwab
2017-03-09 21:51   ` bug#24603: [PATCHv5 02/11] Introduce case_character function Michal Nazarewicz
2017-03-09 21:51   ` bug#24603: [PATCHv5 03/11] Add support for title-casing letters (bug#24603) Michal Nazarewicz
2017-03-11  9:03     ` Eli Zaretskii
2017-03-09 21:51   ` bug#24603: [PATCHv5 04/11] Split up casify_region function (bug#24603) Michal Nazarewicz
2017-03-09 21:51   ` bug#24603: [PATCHv5 05/11] Support casing characters which map into multiple code points (bug#24603) Michal Nazarewicz
2017-03-11  9:14     ` Eli Zaretskii
2017-03-21  2:09       ` Michal Nazarewicz
2017-03-09 21:51   ` bug#24603: [PATCHv5 06/11] Implement special sigma casing rule (bug#24603) Michal Nazarewicz
2017-03-09 21:51   ` bug#24603: [PATCHv5 07/11] Introduce ‘buffer-language’ buffer-locar variable Michal Nazarewicz
2017-03-11  9:29     ` Eli Zaretskii
2017-03-09 21:51   ` bug#24603: [PATCHv5 08/11] Implement rules for title-casing Dutch ij ‘letter’ (bug#24603) Michal Nazarewicz
2017-03-11  9:40     ` Eli Zaretskii
2017-03-16 21:30       ` Michal Nazarewicz
2017-03-17 13:43         ` Eli Zaretskii
2017-03-09 21:51   ` bug#24603: [PATCHv5 09/11] Implement Turkic dotless and dotted i casing rules (bug#24603) Michal Nazarewicz
2017-03-09 21:51   ` bug#24603: [PATCHv5 10/11] Implement casing rules for Lithuanian (bug#24603) Michal Nazarewicz
2017-03-09 21:51   ` bug#24603: [PATCHv5 11/11] Implement Irish casing rules (bug#24603) Michal Nazarewicz
2017-03-11  9:44     ` Eli Zaretskii
2017-03-16 22:16       ` Michal Nazarewicz
2017-03-17  8:20         ` Eli Zaretskii
2017-03-11 10:00   ` bug#24603: [PATCHv5 00/11] Casing improvements Eli Zaretskii
2017-03-21  1:27   ` bug#24603: [PATCHv6 0/6] Casing improvements, language-independent part Michal Nazarewicz
2017-03-21  1:27     ` bug#24603: [PATCHv6 1/6] Split casify_object into multiple functions Michal Nazarewicz
2017-03-21  1:27     ` bug#24603: [PATCHv6 2/6] Introduce case_character function Michal Nazarewicz
2017-03-21  1:27     ` bug#24603: [PATCHv6 3/6] Add support for title-casing letters (bug#24603) Michal Nazarewicz
2017-03-21  1:27     ` bug#24603: [PATCHv6 4/6] Split up casify_region function (bug#24603) Michal Nazarewicz
2017-03-21  1:27     ` bug#24603: [PATCHv6 5/6] Support casing characters which map into multiple code points (bug#24603) Michal Nazarewicz
2017-03-22 16:06       ` Eli Zaretskii
2017-04-03  9:01         ` Michal Nazarewicz
2017-04-03 14:52           ` Eli Zaretskii
2019-06-25  0:09           ` Lars Ingebrigtsen
2019-06-25  0:29             ` Michał Nazarewicz
2020-08-11 13:46               ` Lars Ingebrigtsen
2021-05-10 11:51                 ` bug#24603: [RFC 00/18] Improvement to casing Lars Ingebrigtsen
2017-03-21  1:27     ` bug#24603: [PATCHv6 6/6] Implement special sigma casing rule (bug#24603) Michal Nazarewicz

Code repositories for project(s) associated with this external index

	https://git.savannah.gnu.org/cgit/emacs.git
	https://git.savannah.gnu.org/cgit/emacs/org-mode.git

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.