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