chiark / gitweb /
more space efficient unicode tables
[disorder] / scripts / make-unidata
index 46f972430a33cfebc4372eb0a8690569a7039f41..9fa0dec65cb622b265a61cdf8809b37095f7a021 100755 (executable)
@@ -31,7 +31,6 @@
 #  - SpecialCasing.txt data for case mapping
 #  - Title case offsets
 #  - Some kind of hinting for composition
-#  - Word boundary support
 #  - ...
 #
 # NB the generated files DO NOT offer a stable ABI and so are not immediately
@@ -60,7 +59,24 @@ sub key {
 # Size of a subtable
 #
 # This can be varied to trade off the number of subtables against their size.
-our $modulus = 128;
+# 16 gave the smallest results last time I checked (on a Mac with a 32-bit
+# build).
+our $modulus = 16;
+
+if(@ARGV) {
+    $modulus = shift;
+}
+
+# Where to break the table.  There is a huge empty section of the Unicode
+# code space and we deal with this by simply leaving it out of the table.
+# This complicates the lookup function a little but should not affect
+# performance in the cases we care about.
+our $break_start = 0x30000;
+our $break_end = 0xE0000;
+
+# Similarly we simply omit the very top of the table and sort it out in the
+# lookup function.
+our $break_top = 0xE0200;
 
 my %cats = ();                 # known general categories
 my %data = ();                 # mapping of codepoints to information
@@ -82,20 +98,31 @@ sub input {
        chmod(0444, $lpath) or die "$lpath: $!\n";
     }
     open(STDIN, "<$lpath") or die "$lpath: $!\n";
+    print STDERR "Reading $lpath...\n";
 }
 
 
 # Read the main data file
 input("UnicodeData.txt");
+my ($start, $end);
 while(<>) {
     my @f = split(/;/, $_);
     my $c = hex($f[0]);                # codepoint
-    next if $c >= 0xE0000;     # ignore various high-numbered stuff
-    # TODO justify this exclusion!
     my $name = $f[1];
+    die "$f[0] $name is in the break\n" 
+       if $c >= $break_start && $c < $break_end;
     my $gc = $f[2];            # General_Category
+    # Variuos GCs we don't expect to see in UnicodeData.txt
     $cats{$gc} = 1;            # always record all GCs
-    next if $name =~ /(first|last)>/i; # ignore placeholders
+    if($name =~ /first>/i) {
+       $start = $c;
+       next;
+    } elsif($name =~ /last>/i) {
+       $end = $c;
+    } else {
+       $start = $end = $c;
+    }
+    die "unexpected Cn" if $gc eq 'Cn';
     my $ccc = $f[3];           # Canonical_Combining_Class
     my $dm = $f[5];            # Decomposition_Type + Decomposition_Mapping
     my $sum = hex($f[12]) || $c; # Simple_Uppercase_Mapping
@@ -109,25 +136,31 @@ while(<>) {
     $maxud = $ud if $ud > $maxud;
     $minld = $ld if $ld < $minld;
     $maxld = $ld if $ld > $maxld;
-    $data{$c} = {
-       "gc" => $gc,
-       "ccc" => $ccc,
-       "ud" => $ud,
-       "ld" => $ld,
+    if($start != $end) {
+       printf STDERR "> range %04X-%04X is %s\n", $start, $end, $gc;
+    }
+    for($c = $start; $c <= $end; ++$c) {
+       my $d = {
+           "gc" => $gc,
+           "ccc" => $ccc,
+           "ud" => $ud,
+           "ld" => $ld,
        };
-    if($dm ne '') {
-       if($dm !~ /</) {
-           # This is a canonical decomposition
-           $data{$c}->{canon} = $dm;
-           $data{$c}->{compat} = $dm;
-       } else {
-           # This is only a compatibility decomposition
-           $dm =~ s/^<.*>\s*//;
-           $data{$c}->{compat} = $dm;
+       if($dm ne '') {
+           if($dm !~ /</) {
+               # This is a canonical decomposition
+               $d->{canon} = $dm;
+               $d->{compat} = $dm;
+           } else {
+               # This is only a compatibility decomposition
+               $dm =~ s/^<.*>\s*//;
+               $d->{compat} = $dm;
+           }
        }
+       $data{$c} = $d;
     }
     $cats{$gc} = 1;
-    $max = $c if $c > $max;
+    $max = $end if $end > $max;
 }
 
 sub read_prop_with_ranges {
@@ -141,22 +174,17 @@ sub read_prop_with_ranges {
        my ($range, $propval) = split(/\s*;\s*/, $_);
        if($range =~ /(.*)\.\.(.*)/) {
            for my $c (hex($1) .. hex($2)) {
-               if(exists $data{$c}) {
-                   $data{$c}->{$propkey} = $propval;
-               }
+               die "($range)\n" if($c == 0xAC00 and $propkey eq 'gbreak');
+               $data{$c}->{$propkey} = $propval;
            }
        } else {
            my $c = hex($range);
-           if(exists $data{$c}) {
-               $data{$c}->{$propkey} = $propval;
-           }
+           $data{$c}->{$propkey} = $propval;
        }
     }
 }
 
 # Grapheme_Break etc
-# NB we do this BEFORE filling in blanks so that the Hangul characters
-# don't get filled in; we can compute their properties mechanically.
 read_prop_with_ranges("auxiliary/GraphemeBreakProperty.txt", "gbreak");
 read_prop_with_ranges("auxiliary/WordBreakProperty.txt", "wbreak");
 read_prop_with_ranges("auxiliary/SentenceBreakProperty.txt", "sbreak");
@@ -193,18 +221,43 @@ for my $c (keys %data) {
 # Round up the maximum value to a whole number of subtables
 $max += ($modulus - 1) - ($max % $modulus);
 
-# Make sure there are no gaps
+# Private use characters
+# We only fill in values below $max, utf32__unidata() 
+my $Co = {
+    "gc" => "Co",
+    "ccc" => 0,
+    "ud" => 0,
+    "ld" => 0
+};
+for(my $c = 0xE000; $c <= 0xF8FF && $c <= $max; ++$c) {
+    $data{$c} = $Co;
+}
+for(my $c = 0xF0000; $c <= 0xFFFFD && $c <= $max; ++$c) {
+    $data{$c} = $Co;
+}
+for(my $c = 0x100000; $c <= 0x10FFFD && $c <= $max; ++$c) {
+    $data{$c} = $Co;
+}
+
+# Anything left is not assigned
+my $Cn = {
+    "gc" => "Cn",              # not assigned
+    "ccc" => 0,
+    "ud" => 0,
+    "ld" => 0
+};
 for(my $c = 0; $c <= $max; ++$c) {
     if(!exists $data{$c}) {
-       $data{$c} = {
-           "gc" => "Cn",       # not assigned
-           "ccc" => 0,
-           "ud" => 0,
-           "ld" => 0,
-           "wbreak" => 'Other',
-           "gbreak" => 'Other',
-           "sbreak" => 'Other',
-           };
+       $data{$c} = $Cn;
+    }
+    if(!exists $data{$c}->{wbreak}) {
+       $data{$c}->{wbreak} = 'Other';
+    }
+    if(!exists $data{$c}->{gbreak}) {
+       $data{$c}->{gbreak} = 'Other';
+    }
+    if(!exists $data{$c}->{sbreak}) {
+       $data{$c}->{sbreak} = 'Other';
     }
 }
 $cats{'Cn'} = 1;
@@ -247,6 +300,7 @@ while(<>) {
 }
 
 # Generate the header file
+print STDERR "Generating unidata.h...\n";
 open(STDOUT, ">unidata.h") or die "unidata.h: $!\n";
 
 out("/* Automatically generated file, see scripts/make-unidata */\n",
@@ -301,8 +355,8 @@ out("struct unidata {\n",
     "  const uint32_t *compat;\n",
     "  const uint32_t *canon;\n",
     "  const uint32_t *casefold;\n",
-    "  ".choosetype($minud, $maxud)." upper_offset;\n",
-    "  ".choosetype($minld, $maxld)." lower_offset;\n",
+#    "  ".choosetype($minud, $maxud)." upper_offset;\n",
+#    "  ".choosetype($minld, $maxld)." lower_offset;\n",
     "  ".choosetype(0, $maxccc)." ccc;\n",
     "  char general_category;\n",
     "  uint8_t flags;\n",
@@ -319,11 +373,15 @@ out("extern const struct unidata *const unidata[];\n");
 
 out("#define UNICODE_NCHARS ", ($max + 1), "\n");
 out("#define UNICODE_MODULUS $modulus\n");
+out("#define UNICODE_BREAK_START $break_start\n");
+out("#define UNICODE_BREAK_END $break_end\n");
+out("#define UNICODE_BREAK_TOP $break_top\n");
 
 out("#endif\n");
 
 close STDOUT or die "unidata.h: $!\n";
 
+print STDERR "Generating unidata.c...\n";
 open(STDOUT, ">unidata.c") or die "unidata.c: $!\n";
 
 out("/* Automatically generated file, see scripts/make-unidata */\n",
@@ -367,7 +425,7 @@ for(my $c = 0; $c <= $max; ++$c) {
     # If canon is set then compat will be too and will be identical.
     # If compat is set the canon might be clear.  So we use the
     # compat version and fix up the symbols after.
-    if(exists $data{$c}->{compat}) {
+    if(exists $data{$c} && exists $data{$c}->{compat}) {
        my $s = join(",",
                     (map(hex($_), split(/\s+/, $data{$c}->{compat})), 0));
        if(!exists $decompnums{$s}) {
@@ -393,7 +451,7 @@ my %cfnums = ();
 my $cfsaved = 0;
 out("static const uint32_t ");
 for(my $c = 0; $c <= $max; ++$c) {
-    if(exists $data{$c}->{casefold}) {
+    if(exists $data{$c} && exists $data{$c}->{casefold}) {
        my $s = join(",",
                     (map(hex($_), split(/\s+/, $data{$c}->{casefold})), 0));
        if(!exists $cfnums{$s}) {
@@ -419,6 +477,8 @@ my %subtableno = ();                # subtable number -> content
 my $subtablecounter = 0;       # counter for subtable numbers
 my $subtablessaved = 0;                # number of tables saved
 for(my $base = 0; $base <= $max; $base += $modulus) {
+    next if $base >= $break_start && $base < $break_end;
+    next if $base >= $break_top;
     my @t;
     for(my $c = $base; $c < $base + $modulus; ++$c) {
        my $d = $data{$c};
@@ -435,8 +495,8 @@ for(my $base = 0; $base <= $max; $base += $modulus) {
                  $compatsym,
                  $canonsym,
                  $cfsym,
-                 $d->{ud},
-                 $d->{ld},
+#                $d->{ud},
+#                $d->{ld},
                  $d->{ccc},
                  $d->{gc},
                  $flags,
@@ -447,6 +507,7 @@ for(my $base = 0; $base <= $max; $base += $modulus) {
     }
     my $t = join(",\n", @t);
     if(!exists $subtable{$t}) {
+       out(sprintf("/* %04X-%04X */\n", $base, $base + $modulus - 1));
        out("static const struct unidata st$subtablecounter\[] = {\n",
            "$t\n",
            "};\n");
@@ -457,8 +518,10 @@ for(my $base = 0; $base <= $max; $base += $modulus) {
     $subtableno{$base} = $subtable{$t};
 }
 
-out("const struct unidata*const unidata[]={\n");
+out("const struct unidata *const unidata[]={\n");
 for(my $base = 0; $base <= $max; $base += $modulus) {
+    next if $base >= $break_start && $base < $break_end;
+    next if $base >= $break_top;
     #out("st$subtableno{$base} /* ".sprintf("%04x", $base)." */,\n");
     out("st$subtableno{$base},\n");
 }
@@ -466,5 +529,7 @@ out("};\n");
 
 close STDOUT or die "unidata.c: $!\n";
 
-print STDERR "max=$max, subtables=$subtablecounter, subtablessaved=$subtablessaved\n";
+printf STDERR "modulus=%d\n", $modulus;
+printf STDERR "max=%04X\n", $max;
+print STDERR "subtables=$subtablecounter, subtablessaved=$subtablessaved\n";
 print STDERR "decompsaved=$decompsaved cfsaved=$cfsaved\n";