57be1bd82a6b982de546ef1acd700dba30157a13
[platform/upstream/libxkbcommon.git] / src / compose / parser.c
1 /*
2  * Copyright © 2013 Ran Benita <ran234@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 "config.h"
56
57 #include <errno.h>
58
59 #include "utils.h"
60 #include "scanner-utils.h"
61 #include "table.h"
62 #include "paths.h"
63 #include "utf8.h"
64 #include "parser.h"
65
66 /*
67  * Grammar adapted from libX11/modules/im/ximcp/imLcPrs.c.
68  * See also the XCompose(5) manpage.
69  *
70  * FILE          ::= { [PRODUCTION] [COMMENT] "\n" | INCLUDE }
71  * INCLUDE       ::= "include" '"' INCLUDE_STRING '"'
72  * PRODUCTION    ::= LHS ":" RHS [ COMMENT ]
73  * COMMENT       ::= "#" {<any character except null or newline>}
74  * LHS           ::= EVENT { EVENT }
75  * EVENT         ::= [MODIFIER_LIST] "<" keysym ">"
76  * MODIFIER_LIST ::= (["!"] {MODIFIER} ) | "None"
77  * MODIFIER      ::= ["~"] MODIFIER_NAME
78  * MODIFIER_NAME ::= ("Ctrl"|"Lock"|"Caps"|"Shift"|"Alt"|"Meta")
79  * RHS           ::= ( STRING | keysym | STRING keysym )
80  * STRING        ::= '"' { CHAR } '"'
81  * CHAR          ::= GRAPHIC_CHAR | ESCAPED_CHAR
82  * GRAPHIC_CHAR  ::= locale (codeset) dependent code
83  * ESCAPED_CHAR  ::= ('\\' | '\"' | OCTAL | HEX )
84  * OCTAL         ::= '\' OCTAL_CHAR [OCTAL_CHAR [OCTAL_CHAR]]
85  * OCTAL_CHAR    ::= (0|1|2|3|4|5|6|7)
86  * HEX           ::= '\' (x|X) HEX_CHAR [HEX_CHAR]]
87  * HEX_CHAR      ::= (0|1|2|3|4|5|6|7|8|9|A|B|C|D|E|F|a|b|c|d|e|f)
88  *
89  * INCLUDE_STRING is a filesystem path, with the following %-expansions:
90  *     %% - '%'.
91  *     %H - The user's home directory (the $HOME environment variable).
92  *     %L - The name of the locale specific Compose file (e.g.,
93  *          "/usr/share/X11/locale/<localename>/Compose").
94  *     %S - The name of the system directory for Compose files (e.g.,
95  *          "/usr/share/X11/locale").
96  */
97
98 enum rules_token {
99     TOK_END_OF_FILE = 0,
100     TOK_END_OF_LINE,
101     TOK_INCLUDE,
102     TOK_INCLUDE_STRING,
103     TOK_LHS_KEYSYM,
104     TOK_COLON,
105     TOK_BANG,
106     TOK_TILDE,
107     TOK_STRING,
108     TOK_IDENT,
109     TOK_ERROR
110 };
111
112 /* Values returned with some tokens, like yylval. */
113 union lvalue {
114     struct {
115         /* Still \0-terminated. */
116         const char *str;
117         size_t len;
118     } string;
119 };
120
121 static enum rules_token
122 lex(struct scanner *s, union lvalue *val)
123 {
124 skip_more_whitespace_and_comments:
125     /* Skip spaces. */
126     while (is_space(scanner_peek(s)))
127         if (scanner_next(s) == '\n')
128             return TOK_END_OF_LINE;
129
130     /* Skip comments. */
131     if (scanner_chr(s, '#')) {
132         scanner_skip_to_eol(s);
133         goto skip_more_whitespace_and_comments;
134     }
135
136     /* See if we're done. */
137     if (scanner_eof(s)) return TOK_END_OF_FILE;
138
139     /* New token. */
140     s->token_line = s->line;
141     s->token_column = s->column;
142     s->buf_pos = 0;
143
144     /* LHS Keysym. */
145     if (scanner_chr(s, '<')) {
146         while (scanner_peek(s) != '>' && !scanner_eol(s) && !scanner_eof(s))
147             scanner_buf_append(s, scanner_next(s));
148         if (!scanner_chr(s, '>')) {
149             scanner_err(s, "unterminated keysym literal");
150             return TOK_ERROR;
151         }
152         if (!scanner_buf_append(s, '\0')) {
153             scanner_err(s, "keysym literal is too long");
154             return TOK_ERROR;
155         }
156         val->string.str = s->buf;
157         val->string.len = s->buf_pos;
158         return TOK_LHS_KEYSYM;
159     }
160
161     /* Colon. */
162     if (scanner_chr(s, ':'))
163         return TOK_COLON;
164     if (scanner_chr(s, '!'))
165         return TOK_BANG;
166     if (scanner_chr(s, '~'))
167         return TOK_TILDE;
168
169     /* String literal. */
170     if (scanner_chr(s, '\"')) {
171         while (!scanner_eof(s) && !scanner_eol(s) && scanner_peek(s) != '\"') {
172             if (scanner_chr(s, '\\')) {
173                 uint8_t o;
174                 if (scanner_chr(s, '\\')) {
175                     scanner_buf_append(s, '\\');
176                 }
177                 else if (scanner_chr(s, '"')) {
178                     scanner_buf_append(s, '"');
179                 }
180                 else if (scanner_chr(s, 'x') || scanner_chr(s, 'X')) {
181                     if (scanner_hex(s, &o) && is_valid_char((char) o)) {
182                         scanner_buf_append(s, (char) o);
183                     } else {
184                         // [TODO] actually show the sequence
185                         scanner_warn_with_code(s,
186                             XKB_WARNING_INVALID_ESCAPE_SEQUENCE,
187                             "illegal hexadecimal escape sequence in string literal");
188                     }
189                 }
190                 else if (scanner_oct(s, &o)) {
191                     if (is_valid_char((char) o)) {
192                         scanner_buf_append(s, (char) o);
193                     } else {
194                         // [TODO] actually show the sequence
195                         scanner_warn_with_code(s,
196                             XKB_WARNING_INVALID_ESCAPE_SEQUENCE,
197                             "illegal octal escape sequence in string literal");
198                     }
199                 }
200                 else {
201                     scanner_warn(s, "unknown escape sequence (%c) in string literal", scanner_peek(s));
202                     /* Ignore. */
203                 }
204             } else {
205                 scanner_buf_append(s, scanner_next(s));
206             }
207         }
208         if (!scanner_chr(s, '\"')) {
209             scanner_err(s, "unterminated string literal");
210             return TOK_ERROR;
211         }
212         if (!scanner_buf_append(s, '\0')) {
213             scanner_err(s, "string literal is too long");
214             return TOK_ERROR;
215         }
216         if (!is_valid_utf8(s->buf, s->buf_pos - 1)) {
217             scanner_err(s, "string literal is not a valid UTF-8 string");
218             return TOK_ERROR;
219         }
220         val->string.str = s->buf;
221         val->string.len = s->buf_pos;
222         return TOK_STRING;
223     }
224
225     /* Identifier or include. */
226     if (is_alpha(scanner_peek(s)) || scanner_peek(s) == '_') {
227         s->buf_pos = 0;
228         while (is_alnum(scanner_peek(s)) || scanner_peek(s) == '_')
229             scanner_buf_append(s, scanner_next(s));
230         if (!scanner_buf_append(s, '\0')) {
231             scanner_err(s, "identifier is too long");
232             return TOK_ERROR;
233         }
234
235         if (streq(s->buf, "include"))
236             return TOK_INCLUDE;
237
238         val->string.str = s->buf;
239         val->string.len = s->buf_pos;
240         return TOK_IDENT;
241     }
242
243     /* Discard rest of line. */
244     scanner_skip_to_eol(s);
245
246     scanner_err(s, "unrecognized token");
247     return TOK_ERROR;
248 }
249
250 static enum rules_token
251 lex_include_string(struct scanner *s, struct xkb_compose_table *table,
252                    union lvalue *val_out)
253 {
254     while (is_space(scanner_peek(s)))
255         if (scanner_next(s) == '\n')
256             return TOK_END_OF_LINE;
257
258     s->token_line = s->line;
259     s->token_column = s->column;
260     s->buf_pos = 0;
261
262     if (!scanner_chr(s, '\"')) {
263         scanner_err(s, "include statement must be followed by a path");
264         return TOK_ERROR;
265     }
266
267     while (!scanner_eof(s) && !scanner_eol(s) && scanner_peek(s) != '\"') {
268         if (scanner_chr(s, '%')) {
269             if (scanner_chr(s, '%')) {
270                 scanner_buf_append(s, '%');
271             }
272             else if (scanner_chr(s, 'H')) {
273                 const char *home = xkb_context_getenv(table->ctx, "HOME");
274                 if (!home) {
275                     scanner_err(s, "%%H was used in an include statement, but the HOME environment variable is not set");
276                     return TOK_ERROR;
277                 }
278                 if (!scanner_buf_appends(s, home)) {
279                     scanner_err(s, "include path after expanding %%H is too long");
280                     return TOK_ERROR;
281                 }
282             }
283             else if (scanner_chr(s, 'L')) {
284                 char *path = get_locale_compose_file_path(table->ctx, table->locale);
285                 if (!path) {
286                     scanner_err(s, "failed to expand %%L to the locale Compose file");
287                     return TOK_ERROR;
288                 }
289                 if (!scanner_buf_appends(s, path)) {
290                     free(path);
291                     scanner_err(s, "include path after expanding %%L is too long");
292                     return TOK_ERROR;
293                 }
294                 free(path);
295             }
296             else if (scanner_chr(s, 'S')) {
297                 const char *xlocaledir = get_xlocaledir_path(table->ctx);
298                 if (!scanner_buf_appends(s, xlocaledir)) {
299                     scanner_err(s, "include path after expanding %%S is too long");
300                     return TOK_ERROR;
301                 }
302             }
303             else {
304                 scanner_err(s, "unknown %% format (%c) in include statement", scanner_peek(s));
305                 return TOK_ERROR;
306             }
307         } else {
308             scanner_buf_append(s, scanner_next(s));
309         }
310     }
311     if (!scanner_chr(s, '\"')) {
312         scanner_err(s, "unterminated include statement");
313         return TOK_ERROR;
314     }
315     if (!scanner_buf_append(s, '\0')) {
316         scanner_err(s, "include path is too long");
317         return TOK_ERROR;
318     }
319     val_out->string.str = s->buf;
320     val_out->string.len = s->buf_pos;
321     return TOK_INCLUDE_STRING;
322 }
323
324 struct production {
325     xkb_keysym_t lhs[MAX_LHS_LEN];
326     unsigned int len;
327     xkb_keysym_t keysym;
328     char string[256];
329     /* At least one of these is true. */
330     bool has_keysym;
331     bool has_string;
332
333     /* The matching is as follows: (active_mods & modmask) == mods. */
334     xkb_mod_mask_t modmask;
335     xkb_mod_mask_t mods;
336 };
337
338 static void
339 add_production(struct xkb_compose_table *table, struct scanner *s,
340                const struct production *production)
341 {
342     unsigned lhs_pos = 0;
343     uint32_t curr = darray_size(table->nodes) == 1 ? 0 : 1;
344     uint32_t *pptr = NULL;
345     struct compose_node *node = NULL;
346
347     /* Warn before potentially going over the limit, discard silently after. */
348     if (darray_size(table->nodes) + production->len + MAX_LHS_LEN > MAX_COMPOSE_NODES)
349         scanner_warn(s, "too many sequences for one Compose file; will ignore further lines");
350     if (darray_size(table->nodes) + production->len >= MAX_COMPOSE_NODES)
351         return;
352
353     /*
354      * Insert the sequence to the ternary search tree, creating new nodes as
355      * needed.
356      *
357      * TODO: We insert in the order given, this means some inputs can create
358      * long O(n) chains, which results in total O(n^2) parsing time. We should
359      * ensure the tree is reasonably balanced somehow.
360      */
361     while (true) {
362         const xkb_keysym_t keysym = production->lhs[lhs_pos];
363         const bool last = lhs_pos + 1 == production->len;
364
365         if (curr == 0) {
366             /*
367              * Create a new node and update the parent pointer to it.
368              * Update the pointer first because the append invalidates it.
369              */
370             struct compose_node new = {
371                 .keysym = keysym,
372                 .lokid = 0,
373                 .hikid = 0,
374                 .internal = {
375                     .eqkid = 0,
376                     .is_leaf = false,
377                 },
378             };
379             curr = darray_size(table->nodes);
380             if (pptr != NULL) {
381                 *pptr = curr;
382                 pptr = NULL;
383             }
384             darray_append(table->nodes, new);
385         }
386
387         node = &darray_item(table->nodes, curr);
388
389         if (keysym < node->keysym) {
390             pptr = &node->lokid;
391             curr = node->lokid;
392         } else if (keysym > node->keysym) {
393             pptr = &node->hikid;
394             curr = node->hikid;
395         } else if (!last) {
396             if (node->is_leaf) {
397                 scanner_warn(s, "a sequence already exists which is a prefix of this sequence; overriding");
398                 node->internal.eqkid = 0;
399                 node->internal.is_leaf = false;
400             }
401             lhs_pos++;
402             pptr = &node->internal.eqkid;
403             curr = node->internal.eqkid;
404         } else {
405             if (node->is_leaf) {
406                 bool same_string =
407                     (node->leaf.utf8 == 0 && !production->has_string) ||
408                     (
409                         node->leaf.utf8 != 0 && production->has_string &&
410                         streq(&darray_item(table->utf8, node->leaf.utf8),
411                               production->string)
412                     );
413                 bool same_keysym =
414                     (node->leaf.keysym == XKB_KEY_NoSymbol && !production->has_keysym) ||
415                     (
416                         node->leaf.keysym != XKB_KEY_NoSymbol && production->has_keysym &&
417                         node->leaf.keysym == production->keysym
418                     );
419                 if (same_string && same_keysym) {
420                     scanner_warn(s, "this compose sequence is a duplicate of another; skipping line");
421                     return;
422                 } else {
423                     scanner_warn(s, "this compose sequence already exists; overriding");
424                 }
425             } else if (node->internal.eqkid != 0) {
426                 scanner_warn(s, "this compose sequence is a prefix of another; skipping line");
427                 return;
428             }
429             node->is_leaf = true;
430             if (production->has_string) {
431                 node->leaf.utf8 = darray_size(table->utf8);
432                 darray_append_items(table->utf8, production->string,
433                                     strlen(production->string) + 1);
434             }
435             if (production->has_keysym) {
436                 node->leaf.keysym = production->keysym;
437             }
438             return;
439         }
440     }
441 }
442
443 /* Should match resolve_modifier(). */
444 #define ALL_MODS_MASK ((1 << 0) | (1 << 1) | (1 << 2) | (1 << 3))
445
446 static xkb_mod_index_t
447 resolve_modifier(const char *name)
448 {
449     static const struct {
450         const char *name;
451         xkb_mod_index_t mod;
452     } mods[] = {
453         { "Shift", 0 },
454         { "Ctrl", 2 },
455         { "Alt", 3 },
456         { "Meta", 3 },
457         { "Lock", 1 },
458         { "Caps", 1 },
459     };
460
461     for (unsigned i = 0; i < ARRAY_SIZE(mods); i++)
462         if (streq(name, mods[i].name))
463             return mods[i].mod;
464
465     return XKB_MOD_INVALID;
466 }
467
468 static bool
469 parse(struct xkb_compose_table *table, struct scanner *s,
470       unsigned include_depth);
471
472 static bool
473 do_include(struct xkb_compose_table *table, struct scanner *s,
474            const char *path, unsigned include_depth)
475 {
476     FILE *file;
477     bool ok;
478     char *string;
479     size_t size;
480     struct scanner new_s;
481
482     if (include_depth >= MAX_INCLUDE_DEPTH) {
483         scanner_err(s, "maximum include depth (%d) exceeded; maybe there is an include loop?",
484                     MAX_INCLUDE_DEPTH);
485         return false;
486     }
487
488     file = fopen(path, "rb");
489     if (!file) {
490         scanner_err(s, "failed to open included Compose file \"%s\": %s",
491                     path, strerror(errno));
492         return false;
493     }
494
495     ok = map_file(file, &string, &size);
496     if (!ok) {
497         scanner_err(s, "failed to read included Compose file \"%s\": %s",
498                     path, strerror(errno));
499         goto err_file;
500     }
501
502     scanner_init(&new_s, table->ctx, string, size, path, s->priv);
503
504     ok = parse(table, &new_s, include_depth + 1);
505     if (!ok)
506         goto err_unmap;
507
508 err_unmap:
509     unmap_file(string, size);
510 err_file:
511     fclose(file);
512     return ok;
513 }
514
515 static bool
516 parse(struct xkb_compose_table *table, struct scanner *s,
517       unsigned include_depth)
518 {
519     enum rules_token tok;
520     union lvalue val;
521     xkb_keysym_t keysym;
522     struct production production;
523     enum { MAX_ERRORS = 10 };
524     int num_errors = 0;
525
526 initial:
527     production.len = 0;
528     production.has_keysym = false;
529     production.has_string = false;
530     production.mods = 0;
531     production.modmask = 0;
532
533     /* fallthrough */
534
535 initial_eol:
536     switch (tok = lex(s, &val)) {
537     case TOK_END_OF_LINE:
538         goto initial_eol;
539     case TOK_END_OF_FILE:
540         goto finished;
541     case TOK_INCLUDE:
542         goto include;
543     default:
544         goto lhs_tok;
545     }
546
547 include:
548     switch (tok = lex_include_string(s, table, &val)) {
549     case TOK_INCLUDE_STRING:
550         goto include_eol;
551     default:
552         goto unexpected;
553     }
554
555 include_eol:
556     switch (tok = lex(s, &val)) {
557     case TOK_END_OF_LINE:
558         if (!do_include(table, s, val.string.str, include_depth))
559             goto fail;
560         goto initial;
561     default:
562         goto unexpected;
563     }
564
565 lhs:
566     tok = lex(s, &val);
567 lhs_tok:
568     switch (tok) {
569     case TOK_COLON:
570         if (production.len <= 0) {
571             scanner_warn(s, "expected at least one keysym on left-hand side; skipping line");
572             goto skip;
573         }
574         goto rhs;
575     case TOK_IDENT:
576         if (streq(val.string.str, "None")) {
577             production.mods = 0;
578             production.modmask = ALL_MODS_MASK;
579             goto lhs_keysym;
580         }
581         goto lhs_mod_list_tok;
582     case TOK_TILDE:
583         goto lhs_mod_list_tok;
584     case TOK_BANG:
585         production.modmask = ALL_MODS_MASK;
586         goto lhs_mod_list;
587     default:
588         goto lhs_keysym_tok;
589     }
590
591 lhs_keysym:
592     tok = lex(s, &val);
593 lhs_keysym_tok:
594     switch (tok) {
595     case TOK_LHS_KEYSYM:
596         keysym = xkb_keysym_from_name(val.string.str, XKB_KEYSYM_NO_FLAGS);
597         if (keysym == XKB_KEY_NoSymbol) {
598             scanner_err(s, "unrecognized keysym \"%s\" on left-hand side",
599                         val.string.str);
600             goto error;
601         }
602         if (production.len + 1 > MAX_LHS_LEN) {
603             scanner_warn(s, "too many keysyms (%d) on left-hand side; skipping line",
604                          MAX_LHS_LEN + 1);
605             goto skip;
606         }
607         production.lhs[production.len++] = keysym;
608         production.mods = 0;
609         production.modmask = 0;
610         goto lhs;
611     default:
612         goto unexpected;
613     }
614
615 lhs_mod_list:
616     tok = lex(s, &val);
617 lhs_mod_list_tok: {
618         bool tilde = false;
619         xkb_mod_index_t mod;
620
621         if (tok != TOK_TILDE && tok != TOK_IDENT)
622             goto lhs_keysym_tok;
623
624         if (tok == TOK_TILDE) {
625             tilde = true;
626             tok = lex(s, &val);
627         }
628
629         if (tok != TOK_IDENT)
630             goto unexpected;
631
632         mod = resolve_modifier(val.string.str);
633         if (mod == XKB_MOD_INVALID) {
634             scanner_err(s, "unrecognized modifier \"%s\"",
635                         val.string.str);
636             goto error;
637         }
638
639         production.modmask |= 1 << mod;
640         if (tilde)
641             production.mods &= ~(1 << mod);
642         else
643             production.mods |= 1 << mod;
644
645         goto lhs_mod_list;
646     }
647
648 rhs:
649     switch (tok = lex(s, &val)) {
650     case TOK_STRING:
651         if (production.has_string) {
652             scanner_warn(s, "right-hand side can have at most one string; skipping line");
653             goto skip;
654         }
655         if (val.string.len <= 0) {
656             scanner_warn(s, "right-hand side string must not be empty; skipping line");
657             goto skip;
658         }
659         if (val.string.len >= sizeof(production.string)) {
660             scanner_warn(s, "right-hand side string is too long; skipping line");
661             goto skip;
662         }
663         strcpy(production.string, val.string.str);
664         production.has_string = true;
665         goto rhs;
666     case TOK_IDENT:
667         keysym = xkb_keysym_from_name(val.string.str, XKB_KEYSYM_NO_FLAGS);
668         if (keysym == XKB_KEY_NoSymbol) {
669             scanner_err(s, "unrecognized keysym \"%s\" on right-hand side",
670                         val.string.str);
671             goto error;
672         }
673         if (production.has_keysym) {
674             scanner_warn(s, "right-hand side can have at most one keysym; skipping line");
675             goto skip;
676         }
677         production.keysym = keysym;
678         production.has_keysym = true;
679         /* fallthrough */
680     case TOK_END_OF_LINE:
681         if (!production.has_string && !production.has_keysym) {
682             scanner_warn(s, "right-hand side must have at least one of string or keysym; skipping line");
683             goto skip;
684         }
685         add_production(table, s, &production);
686         goto initial;
687     default:
688         goto unexpected;
689     }
690
691 unexpected:
692     if (tok != TOK_ERROR)
693         scanner_err(s, "unexpected token");
694 error:
695     num_errors++;
696     if (num_errors <= MAX_ERRORS)
697         goto skip;
698
699     scanner_err(s, "too many errors");
700     goto fail;
701
702 fail:
703     scanner_err(s, "failed to parse file");
704     return false;
705
706 skip:
707     while (tok != TOK_END_OF_LINE && tok != TOK_END_OF_FILE)
708         tok = lex(s, &val);
709     goto initial;
710
711 finished:
712     return true;
713 }
714
715 bool
716 parse_string(struct xkb_compose_table *table, const char *string, size_t len,
717              const char *file_name)
718 {
719     struct scanner s;
720     scanner_init(&s, table->ctx, string, len, file_name, NULL);
721     if (!parse(table, &s, 0))
722         return false;
723     /* Maybe the allocator can use the excess space. */
724     darray_shrink(table->nodes);
725     darray_shrink(table->utf8);
726     return true;
727 }
728
729 bool
730 parse_file(struct xkb_compose_table *table, FILE *file, const char *file_name)
731 {
732     bool ok;
733     char *string;
734     size_t size;
735
736     ok = map_file(file, &string, &size);
737     if (!ok) {
738         log_err(table->ctx,
739                 XKB_LOG_MESSAGE_NO_ID,
740                 "Couldn't read Compose file %s: %s\n",
741                 file_name, strerror(errno));
742         return false;
743     }
744
745     ok = parse_string(table, string, size, file_name);
746     unmap_file(string, size);
747     return ok;
748 }