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