Home | History | Annotate | Download | only in compose
      1 /*
      2  * Copyright  2013 Ran Benita <ran234 (at) gmail.com>
      3  *
      4  * Permission is hereby granted, free of charge, to any person obtaining a
      5  * copy of this software and associated documentation files (the "Software"),
      6  * to deal in the Software without restriction, including without limitation
      7  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
      8  * and/or sell copies of the Software, and to permit persons to whom the
      9  * Software is furnished to do so, subject to the following conditions:
     10  *
     11  * The above copyright notice and this permission notice (including the next
     12  * paragraph) shall be included in all copies or substantial portions of the
     13  * Software.
     14  *
     15  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
     16  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
     17  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
     18  * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
     19  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
     20  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
     21  * DEALINGS IN THE SOFTWARE.
     22  */
     23 
     24 /******************************************************************
     25 
     26               Copyright 1992 by Oki Technosystems Laboratory, Inc.
     27               Copyright 1992 by Fuji Xerox Co., Ltd.
     28 
     29 Permission to use, copy, modify, distribute, and sell this software
     30 and its documentation for any purpose is hereby granted without fee,
     31 provided that the above copyright notice appear in all copies and
     32 that both that copyright notice and this permission notice appear
     33 in supporting documentation, and that the name of Oki Technosystems
     34 Laboratory and Fuji Xerox not be used in advertising or publicity
     35 pertaining to distribution of the software without specific, written
     36 prior permission.
     37 Oki Technosystems Laboratory and Fuji Xerox make no representations
     38 about the suitability of this software for any purpose.  It is provided
     39 "as is" without express or implied warranty.
     40 
     41 OKI TECHNOSYSTEMS LABORATORY AND FUJI XEROX DISCLAIM ALL WARRANTIES
     42 WITH REGARD TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF
     43 MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL OKI TECHNOSYSTEMS
     44 LABORATORY AND FUJI XEROX BE LIABLE FOR ANY SPECIAL, INDIRECT OR
     45 CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
     46 OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
     47 OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE
     48 OR PERFORMANCE OF THIS SOFTWARE.
     49 
     50   Author: Yasuhiro Kawai        Oki Technosystems Laboratory
     51   Author: Kazunori Nishihara    Fuji Xerox
     52 
     53 ******************************************************************/
     54 
     55 #include <errno.h>
     56 
     57 #include "utils.h"
     58 #include "scanner-utils.h"
     59 #include "table.h"
     60 #include "paths.h"
     61 #include "utf8.h"
     62 #include "parser.h"
     63 
     64 #define MAX_LHS_LEN 10
     65 #define MAX_INCLUDE_DEPTH 5
     66 
     67 #define KEYSYM_FROM_NAME_CACHE_SIZE 8
     68 
     69 /*
     70  * xkb_keysym_from_name() is fairly slow, because for internal reasons
     71  * it must use strcasecmp().
     72  * A small cache reduces about 20% from the compilation time of
     73  * en_US.UTF-8/Compose.
     74  */
     75 struct keysym_from_name_cache {
     76     struct {
     77         char name[64];
     78         unsigned len;
     79         xkb_keysym_t keysym;
     80     } cache[KEYSYM_FROM_NAME_CACHE_SIZE];
     81     unsigned next;
     82 };
     83 
     84 static xkb_keysym_t
     85 cached_keysym_from_name(struct keysym_from_name_cache *cache,
     86                         const char *name, size_t len)
     87 {
     88     xkb_keysym_t keysym;
     89 
     90     if (len >= sizeof(cache->cache[0].name))
     91         return XKB_KEY_NoSymbol;
     92 
     93     for (unsigned i = 0; i < KEYSYM_FROM_NAME_CACHE_SIZE; i++)
     94         if (cache->cache[i].len == len &&
     95             memcmp(cache->cache[i].name, name, len) == 0)
     96             return cache->cache[i].keysym;
     97 
     98     keysym = xkb_keysym_from_name(name, XKB_KEYSYM_NO_FLAGS);
     99     strcpy(cache->cache[cache->next].name, name);
    100     cache->cache[cache->next].len = len;
    101     cache->cache[cache->next].keysym = keysym;
    102     cache->next = (cache->next + 1) % KEYSYM_FROM_NAME_CACHE_SIZE;
    103     return keysym;
    104 }
    105 
    106 /*
    107  * Grammar adapted from libX11/modules/im/ximcp/imLcPrs.c.
    108  * See also the XCompose(5) manpage.
    109  *
    110  * FILE          ::= { [PRODUCTION] [COMMENT] "\n" | INCLUDE }
    111  * INCLUDE       ::= "include" '"' INCLUDE_STRING '"'
    112  * PRODUCTION    ::= LHS ":" RHS [ COMMENT ]
    113  * COMMENT       ::= "#" {<any character except null or newline>}
    114  * LHS           ::= EVENT { EVENT }
    115  * EVENT         ::= [MODIFIER_LIST] "<" keysym ">"
    116  * MODIFIER_LIST ::= ("!" {MODIFIER} ) | "None"
    117  * MODIFIER      ::= ["~"] modifier_name
    118  * RHS           ::= ( STRING | keysym | STRING keysym )
    119  * STRING        ::= '"' { CHAR } '"'
    120  * CHAR          ::= GRAPHIC_CHAR | ESCAPED_CHAR
    121  * GRAPHIC_CHAR  ::= locale (codeset) dependent code
    122  * ESCAPED_CHAR  ::= ('\\' | '\"' | OCTAL | HEX )
    123  * OCTAL         ::= '\' OCTAL_CHAR [OCTAL_CHAR [OCTAL_CHAR]]
    124  * OCTAL_CHAR    ::= (0|1|2|3|4|5|6|7)
    125  * HEX           ::= '\' (x|X) HEX_CHAR [HEX_CHAR]]
    126  * HEX_CHAR      ::= (0|1|2|3|4|5|6|7|8|9|A|B|C|D|E|F|a|b|c|d|e|f)
    127  *
    128  * INCLUDE_STRING is a filesystem path, with the following %-expansions:
    129  *     %% - '%'.
    130  *     %H - The user's home directory (the $HOME environment variable).
    131  *     %L - The name of the locale specific Compose file (e.g.,
    132  *          "/usr/share/X11/locale/<localename>/Compose").
    133  *     %S - The name of the system directory for Compose files (e.g.,
    134  *          "/usr/share/X11/locale").
    135  */
    136 
    137 enum rules_token {
    138     TOK_END_OF_FILE = 0,
    139     TOK_END_OF_LINE,
    140     TOK_INCLUDE,
    141     TOK_INCLUDE_STRING,
    142     TOK_LHS_KEYSYM,
    143     TOK_COLON,
    144     TOK_BANG,
    145     TOK_TILDE,
    146     TOK_STRING,
    147     TOK_IDENT,
    148     TOK_ERROR
    149 };
    150 
    151 /* Values returned with some tokens, like yylval. */
    152 union lvalue {
    153     struct {
    154         /* Still \0-terminated. */
    155         const char *str;
    156         size_t len;
    157     } string;
    158 };
    159 
    160 static enum rules_token
    161 lex(struct scanner *s, union lvalue *val)
    162 {
    163 skip_more_whitespace_and_comments:
    164     /* Skip spaces. */
    165     while (is_space(peek(s)))
    166         if (next(s) == '\n')
    167             return TOK_END_OF_LINE;
    168 
    169     /* Skip comments. */
    170     if (chr(s, '#')) {
    171         skip_to_eol(s);
    172         goto skip_more_whitespace_and_comments;
    173     }
    174 
    175     /* See if we're done. */
    176     if (eof(s)) return TOK_END_OF_FILE;
    177 
    178     /* New token. */
    179     s->token_line = s->line;
    180     s->token_column = s->column;
    181     s->buf_pos = 0;
    182 
    183     /* LHS Keysym. */
    184     if (chr(s, '<')) {
    185         while (peek(s) != '>' && !eol(s))
    186             buf_append(s, next(s));
    187         if (!chr(s, '>')) {
    188             scanner_err(s, "unterminated keysym literal");
    189             return TOK_ERROR;
    190         }
    191         if (!buf_append(s, '\0')) {
    192             scanner_err(s, "keysym literal is too long");
    193             return TOK_ERROR;
    194         }
    195         val->string.str = s->buf;
    196         val->string.len = s->buf_pos;
    197         return TOK_LHS_KEYSYM;
    198     }
    199 
    200     /* Colon. */
    201     if (chr(s, ':'))
    202         return TOK_COLON;
    203     if (chr(s, '!'))
    204         return TOK_BANG;
    205     if (chr(s, '~'))
    206         return TOK_TILDE;
    207 
    208     /* String literal. */
    209     if (chr(s, '\"')) {
    210         while (!eof(s) && !eol(s) && peek(s) != '\"') {
    211             if (chr(s, '\\')) {
    212                 uint8_t o;
    213                 if (chr(s, '\\')) {
    214                     buf_append(s, '\\');
    215                 }
    216                 else if (chr(s, '"')) {
    217                     buf_append(s, '"');
    218                 }
    219                 else if (chr(s, 'x') || chr(s, 'X')) {
    220                     if (hex(s, &o))
    221                         buf_append(s, (char) o);
    222                     else
    223                         scanner_warn(s, "illegal hexadecimal escape sequence in string literal");
    224                 }
    225                 else if (oct(s, &o)) {
    226                     buf_append(s, (char) o);
    227                 }
    228                 else {
    229                     scanner_warn(s, "unknown escape sequence (%c) in string literal", peek(s));
    230                     /* Ignore. */
    231                 }
    232             } else {
    233                 buf_append(s, next(s));
    234             }
    235         }
    236         if (!chr(s, '\"')) {
    237             scanner_err(s, "unterminated string literal");
    238             return TOK_ERROR;
    239         }
    240         if (!buf_append(s, '\0')) {
    241             scanner_err(s, "string literal is too long");
    242             return TOK_ERROR;
    243         }
    244         if (!is_valid_utf8(s->buf, s->buf_pos - 1)) {
    245             scanner_err(s, "string literal is not a valid UTF-8 string");
    246             return TOK_ERROR;
    247         }
    248         val->string.str = s->buf;
    249         val->string.len = s->buf_pos;
    250         return TOK_STRING;
    251     }
    252 
    253     /* Identifier or include. */
    254     if (is_alpha(peek(s)) || peek(s) == '_') {
    255         s->buf_pos = 0;
    256         while (is_alnum(peek(s)) || peek(s) == '_')
    257             buf_append(s, next(s));
    258         if (!buf_append(s, '\0')) {
    259             scanner_err(s, "identifier is too long");
    260             return TOK_ERROR;
    261         }
    262 
    263         if (streq(s->buf, "include"))
    264             return TOK_INCLUDE;
    265 
    266         val->string.str = s->buf;
    267         val->string.len = s->buf_pos;
    268         return TOK_IDENT;
    269     }
    270 
    271     /* Discard rest of line. */
    272     skip_to_eol(s);
    273 
    274     scanner_err(s, "unrecognized token");
    275     return TOK_ERROR;
    276 }
    277 
    278 static enum rules_token
    279 lex_include_string(struct scanner *s, struct xkb_compose_table *table,
    280                    union lvalue *val_out)
    281 {
    282     while (is_space(peek(s)))
    283         if (next(s) == '\n')
    284             return TOK_END_OF_LINE;
    285 
    286     s->token_line = s->line;
    287     s->token_column = s->column;
    288     s->buf_pos = 0;
    289 
    290     if (!chr(s, '\"')) {
    291         scanner_err(s, "include statement must be followed by a path");
    292         return TOK_ERROR;
    293     }
    294 
    295     while (!eof(s) && !eol(s) && peek(s) != '\"') {
    296         if (chr(s, '%')) {
    297             if (chr(s, '%')) {
    298                 buf_append(s, '%');
    299             }
    300             else if (chr(s, 'H')) {
    301                 const char *home = secure_getenv("HOME");
    302                 if (!home) {
    303                     scanner_err(s, "%%H was used in an include statement, but the HOME environment variable is not set");
    304                     return TOK_ERROR;
    305                 }
    306                 if (!buf_appends(s, home)) {
    307                     scanner_err(s, "include path after expanding %%H is too long");
    308                     return TOK_ERROR;
    309                 }
    310             }
    311             else if (chr(s, 'L')) {
    312                 char *path = get_locale_compose_file_path(table->locale);
    313                 if (!path) {
    314                     scanner_err(s, "failed to expand %%L to the locale Compose file");
    315                     return TOK_ERROR;
    316                 }
    317                 if (!buf_appends(s, path)) {
    318                     free(path);
    319                     scanner_err(s, "include path after expanding %%L is too long");
    320                     return TOK_ERROR;
    321                 }
    322                 free(path);
    323             }
    324             else if (chr(s, 'S')) {
    325                 const char *xlocaledir = get_xlocaledir_path();
    326                 if (!buf_appends(s, xlocaledir)) {
    327                     scanner_err(s, "include path after expanding %%S is too long");
    328                     return TOK_ERROR;
    329                 }
    330             }
    331             else {
    332                 scanner_err(s, "unknown %% format (%c) in include statement", peek(s));
    333                 return TOK_ERROR;
    334             }
    335         } else {
    336             buf_append(s, next(s));
    337         }
    338     }
    339     if (!chr(s, '\"')) {
    340         scanner_err(s, "unterminated include statement");
    341         return TOK_ERROR;
    342     }
    343     if (!buf_append(s, '\0')) {
    344         scanner_err(s, "include path is too long");
    345         return TOK_ERROR;
    346     }
    347     val_out->string.str = s->buf;
    348     val_out->string.len = s->buf_pos;
    349     return TOK_INCLUDE_STRING;
    350 }
    351 
    352 struct production {
    353     xkb_keysym_t lhs[MAX_LHS_LEN];
    354     unsigned int len;
    355     xkb_keysym_t keysym;
    356     char string[256];
    357     bool has_keysym;
    358     bool has_string;
    359 
    360     xkb_mod_mask_t mods;
    361     xkb_mod_mask_t modmask;
    362 };
    363 
    364 static uint32_t
    365 add_node(struct xkb_compose_table *table, xkb_keysym_t keysym)
    366 {
    367     struct compose_node new = {
    368         .keysym = keysym,
    369         .next = 0,
    370         .is_leaf = true,
    371     };
    372     darray_append(table->nodes, new);
    373     return darray_size(table->nodes) - 1;
    374 }
    375 
    376 static void
    377 add_production(struct xkb_compose_table *table, struct scanner *s,
    378                const struct production *production)
    379 {
    380     unsigned lhs_pos;
    381     uint32_t curr;
    382     struct compose_node *node;
    383 
    384     curr = 0;
    385     node = &darray_item(table->nodes, curr);
    386 
    387     /*
    388      * Insert the sequence to the trie, creating new nodes as needed.
    389      *
    390      * TODO: This can be sped up a bit by first trying the path that the
    391      * previous production took, and only then doing the linear search
    392      * through the trie levels.  This will work because sequences in the
    393      * Compose files are often clustered by a common prefix; especially
    394      * in the 1st and 2nd keysyms, which is where the largest variation
    395      * (thus, longest search) is.
    396      */
    397     for (lhs_pos = 0; lhs_pos < production->len; lhs_pos++) {
    398         while (production->lhs[lhs_pos] != node->keysym) {
    399             if (node->next == 0) {
    400                 uint32_t next = add_node(table, production->lhs[lhs_pos]);
    401                 /* Refetch since add_node could have realloc()ed. */
    402                 node = &darray_item(table->nodes, curr);
    403                 node->next = next;
    404             }
    405 
    406             curr = node->next;
    407             node = &darray_item(table->nodes, curr);
    408         }
    409 
    410         if (lhs_pos + 1 == production->len)
    411             break;
    412 
    413         if (node->is_leaf) {
    414             if (node->u.leaf.utf8 != 0 ||
    415                 node->u.leaf.keysym != XKB_KEY_NoSymbol) {
    416                 scanner_warn(s, "a sequence already exists which is a prefix of this sequence; overriding");
    417                 node->u.leaf.utf8 = 0;
    418                 node->u.leaf.keysym = XKB_KEY_NoSymbol;
    419             }
    420 
    421             {
    422                 uint32_t successor = add_node(table, production->lhs[lhs_pos + 1]);
    423                 /* Refetch since add_node could have realloc()ed. */
    424                 node = &darray_item(table->nodes, curr);
    425                 node->is_leaf = false;
    426                 node->u.successor = successor;
    427             }
    428         }
    429 
    430         curr = node->u.successor;
    431         node = &darray_item(table->nodes, curr);
    432     }
    433 
    434     if (!node->is_leaf) {
    435         scanner_warn(s, "this compose sequence is a prefix of another; skipping line");
    436         return;
    437     }
    438 
    439     if (node->u.leaf.utf8 != 0 || node->u.leaf.keysym != XKB_KEY_NoSymbol) {
    440         if (streq(&darray_item(table->utf8, node->u.leaf.utf8),
    441                   production->string) &&
    442             node->u.leaf.keysym == production->keysym) {
    443             scanner_warn(s, "this compose sequence is a duplicate of another; skipping line");
    444             return;
    445         }
    446         scanner_warn(s, "this compose sequence already exists; overriding");
    447     }
    448 
    449     if (production->has_string) {
    450         node->u.leaf.utf8 = darray_size(table->utf8);
    451         darray_append_items(table->utf8, production->string,
    452                             strlen(production->string) + 1);
    453     }
    454     if (production->has_keysym) {
    455         node->u.leaf.keysym = production->keysym;
    456     }
    457 }
    458 
    459 static xkb_mod_index_t
    460 resolve_modifier(const char *name)
    461 {
    462     static const struct {
    463         const char *name;
    464         xkb_mod_index_t mod;
    465     } mods[] = {
    466         { "Shift", 0 },
    467         { "Ctrl", 2 },
    468         { "Alt", 3 },
    469         { "Meta", 3 },
    470         { "Lock", 1 },
    471         { "Caps", 1 },
    472     };
    473 
    474     for (unsigned i = 0; i < ARRAY_SIZE(mods); i++)
    475         if (streq(name, mods[i].name))
    476             return mods[i].mod;
    477 
    478     return XKB_MOD_INVALID;
    479 }
    480 
    481 static bool
    482 parse(struct xkb_compose_table *table, struct scanner *s,
    483       unsigned include_depth);
    484 
    485 static bool
    486 do_include(struct xkb_compose_table *table, struct scanner *s,
    487            const char *path, unsigned include_depth)
    488 {
    489     FILE *file;
    490     bool ok;
    491     const char *string;
    492     size_t size;
    493     struct scanner new_s;
    494 
    495     if (include_depth >= MAX_INCLUDE_DEPTH) {
    496         scanner_err(s, "maximum include depth (%d) exceeded; maybe there is an include loop?",
    497                     MAX_INCLUDE_DEPTH);
    498         return false;
    499     }
    500 
    501     file = fopen(path, "r");
    502     if (!file) {
    503         scanner_err(s, "failed to open included Compose file \"%s\": %s",
    504                     path, strerror(errno));
    505         return false;
    506     }
    507 
    508     ok = map_file(file, &string, &size);
    509     if (!ok) {
    510         scanner_err(s, "failed to read included Compose file \"%s\": %s",
    511                     path, strerror(errno));
    512         goto err_file;
    513     }
    514 
    515     scanner_init(&new_s, table->ctx, string, size, path, s->priv);
    516 
    517     ok = parse(table, &new_s, include_depth + 1);
    518     if (!ok)
    519         goto err_unmap;
    520 
    521 err_unmap:
    522     unmap_file(string, size);
    523 err_file:
    524     fclose(file);
    525     return ok;
    526 }
    527 
    528 static bool
    529 parse(struct xkb_compose_table *table, struct scanner *s,
    530       unsigned include_depth)
    531 {
    532     enum rules_token tok;
    533     union lvalue val;
    534     struct keysym_from_name_cache *cache = s->priv;
    535     xkb_keysym_t keysym;
    536     struct production production;
    537     enum { MAX_ERRORS = 10 };
    538     int num_errors = 0;
    539 
    540 initial:
    541     production.len = 0;
    542     production.has_keysym = false;
    543     production.has_string = false;
    544     production.mods = 0;
    545     production.modmask = 0;
    546 
    547     /* fallthrough */
    548 
    549 initial_eol:
    550     switch (tok = lex(s, &val)) {
    551     case TOK_END_OF_LINE:
    552         goto initial_eol;
    553     case TOK_END_OF_FILE:
    554         goto finished;
    555     case TOK_INCLUDE:
    556         goto include;
    557     default:
    558         goto lhs_tok;
    559     }
    560 
    561 include:
    562     switch (tok = lex_include_string(s, table, &val)) {
    563     case TOK_INCLUDE_STRING:
    564         goto include_eol;
    565     default:
    566         goto unexpected;
    567     }
    568 
    569 include_eol:
    570     switch (tok = lex(s, &val)) {
    571     case TOK_END_OF_LINE:
    572         if (!do_include(table, s, val.string.str, include_depth))
    573             goto fail;
    574         goto initial;
    575     default:
    576         goto unexpected;
    577     }
    578 
    579 lhs:
    580     tok = lex(s, &val);
    581 lhs_tok:
    582     switch (tok) {
    583     case TOK_COLON:
    584         if (production.len <= 0) {
    585             scanner_warn(s, "expected at least one keysym on left-hand side; skipping line");
    586             goto skip;
    587         }
    588         goto rhs;
    589     case TOK_IDENT:
    590         if (!streq(val.string.str, "None")) {
    591             scanner_err(s, "unrecognized identifier \"%s\"", val.string.str);
    592             goto error;
    593         }
    594         production.mods = 0;
    595         /* XXX Should only include the mods in resolve_mods(). */
    596         production.modmask = 0xff;
    597         goto lhs_keysym;
    598     case TOK_BANG:
    599         goto lhs_mod_list;
    600     default:
    601         goto lhs_keysym_tok;
    602     }
    603 
    604 lhs_keysym:
    605     tok = lex(s, &val);
    606 lhs_keysym_tok:
    607     switch (tok) {
    608     case TOK_LHS_KEYSYM:
    609         keysym = cached_keysym_from_name(cache, val.string.str, val.string.len);
    610         if (keysym == XKB_KEY_NoSymbol) {
    611             scanner_err(s, "unrecognized keysym \"%s\" on left-hand side",
    612                         val.string.str);
    613             goto error;
    614         }
    615         if (production.len + 1 > MAX_LHS_LEN) {
    616             scanner_warn(s, "too many keysyms (%d) on left-hand side; skipping line",
    617                          MAX_LHS_LEN + 1);
    618             goto skip;
    619         }
    620         production.lhs[production.len++] = keysym;
    621         production.mods = 0;
    622         production.modmask = 0;
    623         goto lhs;
    624     default:
    625         goto unexpected;
    626     }
    627 
    628 lhs_mod_list: {
    629         bool tilde = false;
    630         xkb_mod_index_t mod;
    631 
    632         tok = lex(s, &val);
    633         if (tok == TOK_TILDE) {
    634             tilde = true;
    635             tok = lex(s, &val);
    636         }
    637 
    638         if (tok != TOK_IDENT) {
    639             if (tilde || production.modmask == 0)
    640                 goto unexpected;
    641             goto lhs_keysym_tok;
    642         }
    643 
    644         mod = resolve_modifier(val.string.str);
    645         if (mod == XKB_MOD_INVALID) {
    646             scanner_err(s, "unrecognized modifier \"%s\"",
    647                         val.string.str);
    648             goto error;
    649         }
    650 
    651         production.modmask |= 1 << mod;
    652         if (tilde)
    653             production.mods &= ~(1 << mod);
    654         else
    655             production.mods |= 1 << mod;
    656 
    657         goto lhs_mod_list;
    658     }
    659 
    660 rhs:
    661     switch (tok = lex(s, &val)) {
    662     case TOK_STRING:
    663         if (production.has_string) {
    664             scanner_warn(s, "right-hand side can have at most one string; skipping line");
    665             goto skip;
    666         }
    667         if (val.string.len <= 0) {
    668             scanner_warn(s, "right-hand side string must not be empty; skipping line");
    669             goto skip;
    670         }
    671         if (val.string.len >= sizeof(production.string)) {
    672             scanner_warn(s, "right-hand side string is too long; skipping line");
    673             goto skip;
    674         }
    675         strcpy(production.string, val.string.str);
    676         production.has_string = true;
    677         goto rhs;
    678     case TOK_IDENT:
    679         keysym = cached_keysym_from_name(cache, val.string.str, val.string.len);
    680         if (keysym == XKB_KEY_NoSymbol) {
    681             scanner_err(s, "unrecognized keysym \"%s\" on right-hand side",
    682                         val.string.str);
    683             goto error;
    684         }
    685         if (production.has_keysym) {
    686             scanner_warn(s, "right-hand side can have at most one keysym; skipping line");
    687             goto skip;
    688         }
    689         production.keysym = keysym;
    690         production.has_keysym = true;
    691     case TOK_END_OF_LINE:
    692         if (!production.has_string && !production.has_keysym) {
    693             scanner_warn(s, "right-hand side must have at least one of string or keysym; skipping line");
    694             goto skip;
    695         }
    696         add_production(table, s, &production);
    697         goto initial;
    698     default:
    699         goto unexpected;
    700     }
    701 
    702 unexpected:
    703     if (tok != TOK_ERROR)
    704         scanner_err(s, "unexpected token");
    705 error:
    706     num_errors++;
    707     if (num_errors <= MAX_ERRORS)
    708         goto skip;
    709 
    710     scanner_err(s, "too many errors");
    711     goto fail;
    712 
    713 fail:
    714     scanner_err(s, "failed to parse file");
    715     return false;
    716 
    717 skip:
    718     while (tok != TOK_END_OF_LINE && tok != TOK_END_OF_FILE)
    719         tok = lex(s, &val);
    720     goto initial;
    721 
    722 finished:
    723     return true;
    724 }
    725 
    726 bool
    727 parse_string(struct xkb_compose_table *table, const char *string, size_t len,
    728              const char *file_name)
    729 {
    730     struct scanner s;
    731     struct keysym_from_name_cache cache;
    732     memset(&cache, 0, sizeof(cache));
    733     scanner_init(&s, table->ctx, string, len, file_name, &cache);
    734     if (!parse(table, &s, 0))
    735         return false;
    736     /* Maybe the allocator can use the excess space. */
    737     darray_shrink(table->nodes);
    738     darray_shrink(table->utf8);
    739     return true;
    740 }
    741 
    742 bool
    743 parse_file(struct xkb_compose_table *table, FILE *file, const char *file_name)
    744 {
    745     bool ok;
    746     const char *string;
    747     size_t size;
    748 
    749     ok = map_file(file, &string, &size);
    750     if (!ok) {
    751         log_err(table->ctx, "Couldn't read Compose file %s: %s\n",
    752                 file_name, strerror(errno));
    753         return false;
    754     }
    755 
    756     ok = parse_string(table, string, size, file_name);
    757     unmap_file(string, size);
    758     return ok;
    759 }
    760