blob: 58884c0947ffdc332904cc057ba7ae3f4096245d [file]
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <inttypes.h>
#include <string.h>
#include <uchar.h>
#include "uninorm.h"
#include "unictype.h"
/* Used as maximum number of codepoints in a sequence. */
#define MAX_SEQUENCE_LENGTH 32
/* Used as maximum number of collation elements in a sequence (observed max is
about 18). */
#define MAX_COLLATION_ELEMENTS 20
/* Collation element - represents [.XXXX.XXXX.XXXX] or [*XXXX.XXXX.XXXX]. */
struct collation_element
{
uint16_t primary;
uint16_t secondary;
uint8_t tertiary;
};
struct collation_data
{
uint8_t num_elements;
struct collation_element elements[MAX_COLLATION_ELEMENTS];
};
struct single_codepoint_data
{
uint32_t codepoint;
struct collation_data data;
};
struct collation_entry_list
{
struct single_codepoint_data *entries;
size_t count;
size_t alloc;
};
struct trie_node
{
char32_t codepoint;
struct collation_data data;
struct trie_node **children;
int num_children;
size_t trie_index; /* used for serialization */
};
struct implicit_block
{
char32_t low;
char32_t high;
uint32_t primary;
char32_t low_base;
};
#define MAX_IMPLICIT_BLOCKS 16
/* Data read from allkeys.txt */
struct allkeys_info
{
struct collation_entry_list singles;
struct trie_node *trie_root;
uint16_t max_variable_weight;
size_t num_records;
int max_collation_elements;
int max_sequence_length;
struct implicit_block implicit_blocks[MAX_IMPLICIT_BLOCKS];
int num_implicit_blocks;
};
static struct allkeys_info info;
/***********************/
/* Error reporting */
/***********************/
static const char *program_name = "gen-collation-table";
void
fatal (char *message)
{
fprintf (stderr, "%s: %s\n", program_name, message);
exit (1);
}
/***********************/
/* Reading allkeys.txt */
/***********************/
static int
parse_collation_element (const char **str,
struct collation_element *elem,
int *variable_weight)
{
char variable_weight_indicator;
int result;
int read;
const char *s = *str;
result = sscanf (s, " [ %c"
"%" SCNx16
".%" SCNx16
".%" SCNx8 " ] %n",
&variable_weight_indicator,
&elem->primary,
&elem->secondary,
&elem->tertiary,
&read);
if (result != 4)
return 0;
*variable_weight = (variable_weight_indicator == '*') ? 1 : 0;
*str = s + read;
return 1;
}
#if 0
static long
parse_version (const char *line)
{
unsigned int major = 0, minor = 0, patch = 0;
if (sscanf (line, "@version %u.%u.%u", &major, &minor, &patch) == 3)
{
/* result not used */
return 1;
}
return 0;
}
#endif
static void
parse_implicitweight (const char *line)
{
char32_t low, high;
uint32_t primary;
if (sscanf (line, "@implicitweights %x..%x; %x", &low, &high, &primary) == 3)
{
if (info.num_implicit_blocks == MAX_IMPLICIT_BLOCKS)
fatal ("too many implicit blocks");
info.implicit_blocks[info.num_implicit_blocks++]
= (struct implicit_block) {low, high, primary};
}
}
/* Used to skip decomposable sequences. */
int
check_codepoint_nondecomposable (char32_t codepoint)
{
size_t length;
char32_t *normalized;
normalized = u32_normalize (UNINORM_NFD, &codepoint, 1, NULL, &length);
free (normalized);
if (length > 1)
return 0;
return 1;
}
/* Read allkeys.txt. Return 0 on failure. */
static int
build_allkeys_info (const char *filename)
{
FILE *fp = fopen (filename, "r");
if (!fp)
fatal ("failed to open input file");
info.singles.alloc = 1000;
info.singles.count = 0;
info.singles.entries
= malloc (info.singles.alloc * sizeof (struct single_codepoint_data));
info.trie_root = calloc (1, sizeof (struct trie_node));
char line[4096];
size_t line_num = 0;
while (fgets (line, sizeof (line), fp))
{
line_num++;
const char *p = line;
p += strspn (p, " \t");
if (*p == '#' || *p == '\0')
continue;
if (*p == '@')
{
if (strncmp (line, "@version", strlen ("@version")) == 0)
{
/* not used */
/* parse_version (line); */
}
else if (strncmp (line, "@implicitweights",
strlen ("@implicitweights")) == 0)
{
parse_implicitweight (line);
}
continue;
}
/* Parse codepoints */
char32_t codepoints[MAX_SEQUENCE_LENGTH];
size_t num_codepoints = 0;
while (1)
{
int read = 0;
if (sscanf (p, " %" SCNx32 "%n",
&codepoints[num_codepoints],
&read) != 1 || read == 0)
break;
p += read;
if (codepoints[num_codepoints] > 0x10FFFF)
fatal ("codepoint too high");
num_codepoints++;
if (num_codepoints >= MAX_SEQUENCE_LENGTH)
break;
}
if (num_codepoints == 0)
continue;
if (num_codepoints == 1)
{
/* We could omit codepoints with canonical decompositions
from the lookup table, which would save space. However, this
would mean that normalization of the input string would be
required in more cases to get correct results. We want to
avoid normalization where possible as it takes significant
time. */
if (!check_codepoint_nondecomposable (codepoints[0]))
continue;
if (uc_is_property_unified_ideograph (codepoints[0]))
continue;
}
if (num_codepoints > info.max_sequence_length)
info.max_sequence_length = num_codepoints;
while (*p && *p != ';')
p++;
if (*p != ';')
continue;
p++;
/* Parse collation elements */
struct collation_data data = { 0 };
while (*p && *p != '#')
{
if (*p == '[')
{
struct collation_element elem;
int variable_weight;
if (parse_collation_element (&p, &elem, &variable_weight))
{
if (data.num_elements < MAX_COLLATION_ELEMENTS)
data.elements[data.num_elements++] = elem;
else
fatal
("maximum collation element sequence length exceeded");
if (variable_weight)
{
if (elem.primary > info.max_variable_weight)
info.max_variable_weight = elem.primary;
}
}
else
fatal ("parse_collation_element error");
}
else
{
p++;
}
}
if (info.max_collation_elements < data.num_elements)
info.max_collation_elements = data.num_elements;
if (data.num_elements == 0)
continue;
if (num_codepoints == 1)
{
if (info.singles.count == info.singles.alloc)
{
/* Increase allocation by roughly 50%. */
info.singles.alloc += (info.singles.alloc >> 1) + 1,
info.singles.entries =
realloc (info.singles.entries,
info.singles.alloc
* sizeof (struct single_codepoint_data));
}
info.singles.entries[info.singles.count].codepoint = codepoints[0];
info.singles.entries[info.singles.count].data = data;
info.singles.count++;
info.num_records++;
}
else
{
/* Insert into trie. */
struct trie_node *node = info.trie_root;
for (size_t i = 0; i < num_codepoints; i++)
{
struct trie_node *child = NULL;
for (uint16_t j = 0; j < node->num_children; j++)
{
if (node->children[j]->codepoint == codepoints[i])
{
child = node->children[j];
break;
}
}
if (!child)
{
child = calloc (1, sizeof (struct trie_node));
child->codepoint = codepoints[i];
node->children =
realloc (node->children,
(node->num_children + 1)
* sizeof (struct trie_node *));
node->children[node->num_children++] = child;
}
node = child;
}
node->data = data;
info.num_records++;
}
}
fclose (fp);
return 1;
}
/***********************/
/* Output */
/***********************/
/*---------------------*/
/* Collation units */
/*---------------------*/
/* Convert collation units into to the form they will be written as. */
static void
expand_collation_sequence (struct collation_data *data)
{
if (!data)
return;
struct collation_data new;
new.num_elements = 0;
uint16_t primary_extension = 0;
uint8_t secondary_extension = 0;
for (int i = 0; i < data->num_elements; i++)
{
uint16_t primary = data->elements[i].primary;
uint16_t primary_write;
if (primary < 0xFE00)
{
primary_write = primary;
}
else
{
/* For very high primary weights, output an additional
collation element. Same transformation in
collation_data_lookup.c:get_implicit_weight. */
primary_write = 0xFE00;
primary_extension = primary - 0xFE00 + 1;
/* If these numbers are too small, they look like variable weights. */
primary_extension += 0x8000;
}
uint16_t secondary = data->elements[i].secondary;
uint8_t secondary_write;
/* Fit secondary weight in a single byte for 254 possible
secondary weights (0x0000 and 0x0020 - 0x011C) */
if (secondary == 0x00)
secondary_write = secondary;
else if (secondary <= 0x011C)
{
/* same in collation_data_lookup.c:get_implicit_weight */
secondary_write = secondary - 0x1f;
}
else if (secondary <= 0x0127)
{
/* For larger weights, output an extra collation element. */
secondary_write = 0xFE;
secondary_extension = secondary - 0x0100;
}
else
{
fprintf (stderr, "secondary weight too big: %4x\n", secondary);
exit (1);
}
uint8_t tertiary_write = data->elements[i].tertiary;
new.elements[new.num_elements++]
= (struct collation_element) { primary_write, secondary_write, tertiary_write };
if (new.num_elements == MAX_COLLATION_ELEMENTS)
fatal ("too many collation units in record");
if (primary_extension || secondary_extension)
{
new.elements[new.num_elements++]
= (struct collation_element) { primary_extension, secondary_extension, 0 };
if (new.num_elements == MAX_COLLATION_ELEMENTS)
fatal ("too many collation units in record");
primary_extension = 0;
secondary_extension = 0;
}
}
*data = new;
}
typedef struct collation_data * PendingCollationData;
/* The data to write later. */
static PendingCollationData *collation_records;
static uint32_t collation_records_count;
/* Running count of number of collation units to be output, used
for index into collation unit data array. */
static long collation_units_written;
/*---------------------*/
/* Trie */
/*---------------------*/
/* Function for qsort. Return negative if the first argument is "less"
than the second, zero if they are "equal", and positive if the first
argument is "greater". */
static int
compare_trie_node_children (const void *a, const void *b)
{
struct trie_node *node1 = *(struct trie_node **) a;
struct trie_node *node2 = *(struct trie_node **) b;
return (node1->codepoint > node2->codepoint)
- (node1->codepoint < node2->codepoint);
}
static size_t trie_node_index;
static void
sort_trie_node_children (struct trie_node *node)
{
qsort (node->children, node->num_children, sizeof(node->children[0]),
compare_trie_node_children);
}
/* Used to check if there are sequences beginning with a given codepoint. */
int
codepoint_initial_in_trie (struct trie_node *root, char32_t codepoint)
{
struct trie_node dummy, *pdummy;
memset (&dummy, 0, sizeof (dummy));
dummy.codepoint = codepoint;
pdummy = &dummy;
struct trie_node *found = bsearch (&pdummy,
root->children, root->num_children,
sizeof(root->children[0]),
compare_trie_node_children);
return found ? 1 : 0;
}
/* Traverse tree rooted at NODE, assigning 'index' on each node. Assign
contiguous indices to children of each node. This is neither depth-
or breadth-first traversal:
A
/ \
B C
/\ /\
D E F G
/\ /\ /\ /\
H I J K L M N O
Our order: A B C D E H I J K F G L M N O
Breadth-first: A B C D E F G H I J K L M N O
Depth-first: A B D H I E J K C F L M G N O
This allows storing trie as array of fixed-size structures. We store
the index of the first child and number of children for each node.
*/
static void
assign_trie_node_indices (struct trie_node *node)
{
sort_trie_node_children (node);
for (uint16_t i = 0; i < node->num_children; i++)
node->children[i]->trie_index = trie_node_index++;
for (uint16_t i = 0; i < node->num_children; i++)
assign_trie_node_indices (node->children[i]);
}
/* Write contents of a trie node, but not any of its children */
static void
write_trie_node_only (FILE *fp, struct trie_node *node)
{
uint32_t data_index = 0;
if (node->data.num_elements > 0)
{
data_index = collation_units_written;
expand_collation_sequence (&node->data);
collation_units_written += node->data.num_elements;
collation_records[collation_records_count] = &node->data;
collation_records_count++;
}
fprintf (fp, "{ 0x%X, %d, 0x%08X, %d, %zu },\n",
node->codepoint,
(int) node->data.num_elements,
data_index,
(int) node->num_children,
node->num_children > 0
? node->children[0]->trie_index
: 0);
}
/* Write trie recursively. */
static void
write_trie_node_children (FILE *fp, struct trie_node *node)
{
for (uint16_t i = 0; i < node->num_children; i++)
write_trie_node_only (fp, node->children[i]);
for (uint16_t i = 0; i < node->num_children; i++)
write_trie_node_children (fp, node->children[i]);
}
static int
count_trie_node_collation_units (struct trie_node *node)
{
int count = 0;
if (node->data.num_elements > 0)
count += node->data.num_elements;
count += node->num_children;
for (int i = 0; i < node->num_children; i++)
count += count_trie_node_collation_units (node->children[i]);
return count;
}
/*------------------------*/
/* Single codepoint table */
/*------------------------*/
/* Compare function for sorting page entries by offset */
static int
compare_codepoint_entries (const void *a, const void *b)
{
const struct single_codepoint_data *ea, *eb;
ea = (const struct single_codepoint_data *) a;
eb = (const struct single_codepoint_data *) b;
return (int64_t) ea->codepoint - (int64_t) eb->codepoint;
}
#define NUM_PLANES 17
/* level1 stores is indexed by Unicode plane (each plane containing 2**16
possible codepoint positions). */
static int level1[NUM_PLANES];
static int n_used_planes = 0;
static int n_used_level2 = 0;
/* Each entry in level2 array represents 256 codepoints (U+XXXX??).
Maximum Unicode codepoint is U+10FFFF. */
#define MAX_LEVEL2_SIZE 0x1100
/* Index in level3 of data about a block of 256 codepoints. */
static int level2[MAX_LEVEL2_SIZE];
/* Preprocess collation unit data for writing and
build the LEVEL1 and LEVEL2 arrays. */
static void
process_singles (void)
{
/* Sort single codepoint data. Probably unnecessary but do it
just in case. */
qsort (info.singles.entries, info.singles.count,
sizeof (struct single_codepoint_data), compare_codepoint_entries);
size_t i;
for (i = 0; i < info.singles.count; i++)
{
expand_collation_sequence (&info.singles.entries[i].data);
collation_records[collation_records_count++]
= &info.singles.entries[i].data;
}
for (i = 0; i < NUM_PLANES; i++)
level1[i] = -1;
for (i = 0; i < MAX_LEVEL2_SIZE; i++)
level2[i] = -1;
for (i = 0; i < info.singles.count; i++)
{
uint32_t codepoint = info.singles.entries[i].codepoint;
int index1 = info.singles.entries[i].codepoint >> 16;
if (level1[index1] == -1)
level1[index1] = n_used_planes++;
uint32_t index2 = (level1[index1] << 8)
+ ((codepoint >> 8) & 0xff);
if (level2[index2] == -1)
level2[index2] = n_used_level2++;
}
}
/*---------------------*/
/* Implicit blocks */
/*---------------------*/
/* Set 'low_base' to least value of 'low' for all blocks that
share the same value of 'primary'. */
static void
process_implicit_blocks (void)
{
int i;
for (i = 0; i < info.num_implicit_blocks; i++)
{
info.implicit_blocks[i].low_base = info.implicit_blocks[i].low;
int j;
for (j = 0; j < info.num_implicit_blocks; j++)
{
if (info.implicit_blocks[j].primary
== info.implicit_blocks[i].primary)
{
if (info.implicit_blocks[j].low
< info.implicit_blocks[i].low_base)
{
info.implicit_blocks[i].low_base
= info.implicit_blocks[j].low;
}
}
}
}
}
/***********************/
/* C code generation */
/***********************/
/* Write collation data as part of C initialiser. */
static void
write_collation_data (FILE *fp, struct collation_data *data)
{
for (int i = 0; i < data->num_elements; i++)
{
struct collation_element unit = data->elements[i];
fprintf (fp, "{ 0x%04x, 0x%02x, 0x%02x },\n",
unit.primary, unit.secondary, unit.tertiary);
}
}
/* Write as C source file */
static void
write_c_source (const char *output_file)
{
FILE *fp = fopen (output_file, "w");
if (!fp)
fatal ("failed to open output file");
/* prepare trie for use of codepoint_initial_in_trie */
sort_trie_node_children (info.trie_root);
long i;
long n_collation_units = 0;
for (i = 0; i < collation_records_count; i++)
{
n_collation_units += collation_records[i]->num_elements;
}
/* Trie data is not in collation_records yet. */
n_collation_units += count_trie_node_collation_units (info.trie_root);
fprintf (fp, "/* DO NOT EDIT:\n");
fprintf (fp, " * Automatically generated from allkeys.txt\n");
fprintf (fp, " */\n\n");
fprintf (fp, "#include <stdint.h>\n\n");
fprintf (fp, "struct block256_data {\n");
fprintf (fp, " uint8_t num_elements[256];\n");
fprintf (fp, " uint32_t data_index[256];\n");
fprintf (fp, "};\n");
fprintf (fp, "struct trie_node {\n");
fprintf (fp, " char32_t codepoint;\n");
fprintf (fp, " uint8_t num_elements;\n");
fprintf (fp, " uint32_t data_index;\n");
fprintf (fp, " int num_children;\n");
fprintf (fp, " uint32_t first_child;\n");
fprintf (fp, "};\n");
fprintf (fp, "struct implicit_block {\n");
fprintf (fp, " char32_t low;\n");
fprintf (fp, " char32_t high;\n");
fprintf (fp, " uint32_t primary;\n");
fprintf (fp, " char32_t low_base;\n");
fprintf (fp, "};\n");
fprintf (fp, "#define NUM_PLANES %d\n", NUM_PLANES);
fprintf (fp, "#define NUM_TRIE_NODES %d\n", (int) trie_node_index);
fprintf (fp, "#define NUM_COLLATION_UNITS %ld\n\n", n_collation_units);
fprintf (fp, "#define NUM_IMPLICIT_BLOCKS %d\n\n", info.num_implicit_blocks);
fprintf (fp, "#define MAX_SEQUENCE_LENGTH %d\n", info.max_sequence_length);
fprintf (fp, "#define MAX_COLLATION_ELEMENTS %d\n\n", info.max_collation_elements);
fprintf (fp, "static const\nstruct\n {\n");
fprintf (fp, " uint16_t max_variable_weight;\n");
fprintf (fp, " int level1[NUM_PLANES];\n");
fprintf (fp, " int level2[%d << 8];\n", n_used_planes);
fprintf (fp, " struct block256_data level3[%d];\n", n_used_level2);
fprintf (fp, " struct trie_node trie_array[NUM_TRIE_NODES];\n");
fprintf (fp, " struct collation_unit collation_data[NUM_COLLATION_UNITS];\n");
fprintf (fp, " struct implicit_block implicit_blocks[NUM_IMPLICIT_BLOCKS];\n");
fprintf (fp, " }\n");
fprintf (fp, "collation_data = {\n");
fprintf (fp, " 0x%04X,\n", info.max_variable_weight);
fprintf (fp, " { /* .level1 */\n");
for (i = 0; i < NUM_PLANES; i++)
fprintf (fp, " %d,\n", level1[i]);
fprintf (fp, " },\n");
fprintf (fp, " { /* .level2 */\n");
/* collation_data.level2 */
for (int j = 0; j < (n_used_planes << 8); j++)
{
if (level2[j] >= 0)
fprintf (fp, " 0x%04X,%s", level2[j],
(j % 4) == 3 ? "\n" : "");
else
fprintf (fp, " -1,%s", (j % 4) == 3 ? "\n" : "");
}
fprintf (fp, " },\n");
fprintf (fp, " { /* .level3 */\n");
/* collation_data.level3 */
int first_point = 0;
for (int i = 0; i < NUM_PLANES; i++)
{
if (level1[i] == -1)
continue;
for (int j = 0; j < 256; j++)
{
uint32_t index2 = (level1[i] << 8) + j;
if (level2[index2] == -1)
continue;
/* Data exists for a codepoint somewhere in this block of 256.
Output struct block256_data. */
int cur_page = (i << 8) + j;
fprintf (fp, " {\n");
fprintf (fp, " {\n");
/* Output num_elements[256] */
uint32_t point_count = first_point;
uint32_t next_data = info.singles.entries[point_count].codepoint;
for (int j = 0; j < 256; j++)
{
if (j == (next_data & 0xff) && next_data != -1)
{
#define CHECK_SEQUENCE_BIT 0x80
/* We encode whether there is a sequence beginning with this
codepoint by setting a high bit in length data. That
way we only need to try to lookup a sequence if this bit
is set. */
int sequence_exists
= codepoint_initial_in_trie (info.trie_root, next_data);
int num_elements_write
= info.singles.entries[point_count].data.num_elements;
if (sequence_exists)
num_elements_write |= CHECK_SEQUENCE_BIT;
fprintf (fp, " %3d,%s",
num_elements_write,
(j % 8) == 7 ? "\n" : "");
if (++point_count == info.singles.count)
next_data = -1;
else
{
next_data = info.singles.entries[point_count].codepoint;
if ((next_data >> 8) > cur_page)
next_data = -1;
}
}
else
{
fprintf (fp, " 0,%s", (j % 8) == 7 ? "\n" : "");
}
}
fprintf (fp, " },\n");
fprintf (fp, " {\n");
/* Output data_index[256] */
point_count = first_point;
next_data = info.singles.entries[point_count].codepoint;
for (int j = 0; j < 256; j++)
{
if (j == (next_data & 0xff) && next_data != -1)
{
fprintf (fp, " 0x%08lx,%s", collation_units_written,
(j % 4) == 3 ? "\n" : "");
collation_units_written
+= info.singles.entries[point_count].data.num_elements;
if (++point_count == info.singles.count)
next_data = -1;
else
{
next_data = info.singles.entries[point_count].codepoint;
if ((next_data >> 8) > cur_page)
next_data = -1;
}
}
else
{
fprintf (fp, " 0x00000000,%s",
(j % 4) == 3 ? "\n" : "");
}
}
fprintf (fp, " }\n");
fprintf (fp, " },\n");
first_point = point_count;
if (point_count == info.singles.count)
break;
}
}
fprintf (fp, " },\n");
fprintf (fp, " { /* .trie_array */\n");
write_trie_node_only (fp, info.trie_root);
write_trie_node_children (fp, info.trie_root);
fprintf (fp, "\n },\n");
fprintf (fp, "\n { /* .collation_data */\n");
for (uint32_t i = 0; i < collation_records_count; i++)
write_collation_data (fp, collation_records[i]);
free (collation_records);
fprintf (fp, " },\n");
fprintf (fp, "\n { /* .implicitweights */\n");
for (int i = 0; i < info.num_implicit_blocks; i++)
{
struct implicit_block *block = &info.implicit_blocks[i];
fprintf (fp, "{ 0x%04x, 0x%04x, 0x%04x, 0x%04x },\n",
block->low, block->high, block->primary,
block->low_base);
}
fprintf (fp, " }\n");
fprintf (fp, "};\n");
fclose (fp);
}
/***********************/
/* Main entry point */
/***********************/
int
main (int argc, char *argv[])
{
if (argc != 3)
{
fprintf (stderr, "Usage: %s <allkeys.txt> <output.c>\n",
argv[0]);
return 1;
}
const char *input_file = argv[1];
const char *c_file = argv[2];
if (!build_allkeys_info (input_file))
return 1;
/* keep running count of collation units which will be written to
the collation units array so we can output indices into that array. */
collation_units_written = 0;
collation_records = malloc ((info.num_records + 1)
* sizeof (PendingCollationData));
collation_records_count = 0;
/* Output dummy entry at index 0. */
collation_records[0] = malloc (sizeof (struct collation_data));
collation_records[0]->num_elements = 1;
collation_records[0]->elements[0] = (struct collation_element) {0, 0, 0};
collation_records_count++;
collation_units_written++;
process_singles ();
trie_node_index = 0;
info.trie_root->trie_index = trie_node_index++;
assign_trie_node_indices (info.trie_root);
process_implicit_blocks ();
write_c_source (c_file);
return 0;
}