darray: cleanup
[platform/upstream/libxkbcommon.git] / src / xkbcomp / rules.c
1 /************************************************************
2  * Copyright (c) 1996 by Silicon Graphics Computer Systems, Inc.
3  *
4  * Permission to use, copy, modify, and distribute this
5  * software and its documentation for any purpose and without
6  * fee is hereby granted, provided that the above copyright
7  * notice appear in all copies and that both that copyright
8  * notice and this permission notice appear in supporting
9  * documentation, and that the name of Silicon Graphics not be
10  * used in advertising or publicity pertaining to distribution
11  * of the software without specific prior written permission.
12  * Silicon Graphics makes no representation about the suitability
13  * of this software for any purpose. It is provided "as is"
14  * without any express or implied warranty.
15  *
16  * SILICON GRAPHICS DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS
17  * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
18  * AND FITNESS FOR A PARTICULAR PURPOSE. IN NO EVENT SHALL SILICON
19  * GRAPHICS BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL
20  * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
21  * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
22  * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION  WITH
23  * THE USE OR PERFORMANCE OF THIS SOFTWARE.
24  *
25  ********************************************************/
26
27 /*
28  * Copyright © 2012 Ran Benita <ran234@gmail.com>
29  *
30  * Permission is hereby granted, free of charge, to any person obtaining a
31  * copy of this software and associated documentation files (the "Software"),
32  * to deal in the Software without restriction, including without limitation
33  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
34  * and/or sell copies of the Software, and to permit persons to whom the
35  * Software is furnished to do so, subject to the following conditions:
36  *
37  * The above copyright notice and this permission notice (including the next
38  * paragraph) shall be included in all copies or substantial portions of the
39  * Software.
40  *
41  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
42  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
43  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
44  * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
45  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
46  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
47  * DEALINGS IN THE SOFTWARE.
48  */
49
50 #include "xkbcomp-priv.h"
51 #include "rules.h"
52 #include "include.h"
53 #include "scanner-utils.h"
54
55 /*
56  * The rules file
57  * ==============
58  * The purpose of this file is to map between configuration values that
59  * are easy for a user to specify and understand, and the configuration
60  * values xkbcomp uses and understands.
61  * xkbcomp uses the xkb_component_names struct, which maps directly to
62  * include statements of the appropriate sections, called for short
63  * KcCGST (see keycodes.c, types.c, compat.c, symbols.c; geometry.c was
64  * removed). These are not really intuitive or straight-forward for
65  * the uninitiated.
66  * Instead, the user passes in a xkb_rule_names struct, which consists
67  * of the name of a rules file (in Linux this is usually "evdev"), a
68  * keyboard model (e.g. "pc105"), a set of layouts (which will end up
69  * in different groups, e.g. "us,fr"), variants (used to alter/augment
70  * the respective layout, e.g. "intl,dvorak"), and a set of options
71  * (used to tweak some general behavior of the keyboard, e.g.
72  * "ctrl:nocaps,compose:menu" to make the Caps Lock key act like Ctrl
73  * and the Menu key like Compose). We call these RMLVO.
74  *
75  * Format of the file
76  * ------------------
77  * The file consists of rule sets, each consisting of rules (one per
78  * line), which match the MLVO values on the left hand side, and, if
79  * the values match to the values the user passed in, results in the
80  * values on the right hand side being added to the resulting KcCGST.
81  * Since some values are related and repeated often, it is possible
82  * to group them together and refer to them by a group name in the
83  * rules.
84  * Along with matching values by simple string equality, and for
85  * membership in a group defined previously, rules may also contain
86  * "wildcard" values - "*" - which always match. These usually appear
87  * near the end.
88  *
89  * Grammer
90  * -------
91  * (It might be helpful to look at a file like rules/evdev along with
92  * this grammer. Comments, whitespace, etc. are not shown.)
93  *
94  * File         ::= { "!" (Group | RuleSet) }
95  *
96  * Group        ::= GroupName "=" { GroupElement } "\n"
97  * GroupName    ::= "$"<ident>
98  * GroupElement ::= <ident>
99  *
100  * RuleSet      ::= Mapping { Rule }
101  *
102  * Mapping      ::= { Mlvo } "=" { Kccgst } "\n"
103  * Mlvo         ::= "model" | "option" | ("layout" | "variant") [ Index ]
104  * Index        ::= "[" 1..XKB_NUM_GROUPS "]"
105  * Kccgst       ::= "keycodes" | "symbols" | "types" | "compat" | "geometry"
106  *
107  * Rule         ::= { MlvoValue } "=" { KccgstValue } "\n"
108  * MlvoValue    ::= "*" | GroupName | <ident>
109  * KccgstValue  ::= <ident>
110  *
111  * Notes:
112  * - The order of values in a Rule must be the same as the Mapping it
113  *   follows. The mapping line determines the meaning of the values in
114  *   the rules which follow in the RuleSet.
115  * - If a Rule is matched, %-expansion is performed on the KccgstValue,
116  *   as follows:
117  *   %m, %l, %v:
118  *      The model, layout or variant, if only one was given (e.g.
119  *      %l for "us,il" is invalid).
120  *   %l[1], %v[1]:
121  *      Layout or variant for the specified group Index, if more than
122  *      one was given (e.g. %l[1] for "us" is invalid).
123  *   %+m, %+l, %+v, %+l[1], %+v[1]
124  *      As above, but prefixed with '+'. Similarly, '|', '-', '_' may be
125  *      used instead of '+'.
126  *   %(m), %(l), %(l[1]), %(v), %(v[1]):
127  *      As above, but prefixed by '(' and suffixed by ')'.
128  *   In case the expansion is invalid, as described above, it is
129  *   skipped (the rest of the string is still processed); this includes
130  *   the prefix and suffix (that's why you shouldn't use e.g. "(%v[1])").
131  */
132
133 /* Scanner / Lexer */
134
135 /* Values returned with some tokens, like yylval. */
136 union lvalue {
137     struct sval string;
138 };
139
140 /*
141  * Holds the location in the file of the last processed token,
142  * like yylloc.
143  */
144 struct location {
145     int line, column;
146 };
147
148 enum rules_token {
149     TOK_END_OF_FILE = 0,
150     TOK_END_OF_LINE,
151     TOK_IDENTIFIER,
152     TOK_GROUP_NAME,
153     TOK_BANG,
154     TOK_EQUALS,
155     TOK_STAR,
156     TOK_ERROR
157 };
158
159 /* C99 is stupid. Just use the 1 variant when there are no args. */
160 #define scanner_error1(scanner, loc, msg) \
161     log_warn((scanner)->ctx, "rules/%s:%d:%d: %s\n", \
162              (scanner)->file_name, (loc)->line, (loc)->column, msg)
163 #define scanner_error(scanner, loc, fmt, ...) \
164     log_warn((scanner)->ctx, "rules/%s:%d:%d: " fmt "\n", \
165              (scanner)->file_name, (loc)->line, (loc)->column, __VA_ARGS__)
166
167 static inline bool
168 is_ident(char ch)
169 {
170     return is_graph(ch) && ch != '\\';
171 }
172
173 static enum rules_token
174 lex(struct scanner *s, union lvalue *val, struct location *loc)
175 {
176 skip_more_whitespace_and_comments:
177     /* Skip spaces. */
178     while (chr(s, ' ') || chr(s, '\t'));
179
180     /* Skip comments. */
181     if (lit(s, "//")) {
182         while (!eof(s) && !eol(s)) next(s);
183     }
184
185     /* New line. */
186     if (eol(s)) {
187         while (eol(s)) next(s);
188         return TOK_END_OF_LINE;
189     }
190
191     /* Escaped line continuation. */
192     if (chr(s, '\\')) {
193         if (!eol(s)) {
194             scanner_error1(s, loc,
195                            "illegal new line escape; must appear at end of line");
196             return TOK_ERROR;
197         }
198         next(s);
199         goto skip_more_whitespace_and_comments;
200     }
201
202     /* See if we're done. */
203     if (eof(s)) return TOK_END_OF_FILE;
204
205     /* New token. */
206     loc->line = s->line;
207     loc->column = s->column;
208
209     /* Operators and punctuation. */
210     if (chr(s, '!')) return TOK_BANG;
211     if (chr(s, '=')) return TOK_EQUALS;
212     if (chr(s, '*')) return TOK_STAR;
213
214     /* Group name. */
215     if (chr(s, '$')) {
216         val->string.start = s->s + s->pos;
217         val->string.len = 0;
218         while (is_ident(peek(s))) {
219             next(s);
220             val->string.len++;
221         }
222         if (val->string.len == 0) {
223             scanner_error1(s, loc,
224                            "unexpected character after \'$\'; expected name");
225             return TOK_ERROR;
226         }
227         return TOK_GROUP_NAME;
228     }
229
230     /* Identifier. */
231     if (is_ident(peek(s))) {
232         val->string.start = s->s + s->pos;
233         val->string.len = 0;
234         while (is_ident(peek(s))) {
235             next(s);
236             val->string.len++;
237         }
238         return TOK_IDENTIFIER;
239     }
240
241     scanner_error1(s, loc, "unrecognized token");
242     return TOK_ERROR;
243 }
244
245 /***====================================================================***/
246
247 enum rules_mlvo {
248     MLVO_MODEL,
249     MLVO_LAYOUT,
250     MLVO_VARIANT,
251     MLVO_OPTION,
252     _MLVO_NUM_ENTRIES
253 };
254
255 #define SVAL_LIT(literal) { literal, sizeof(literal) - 1 }
256
257 static const struct sval rules_mlvo_svals[_MLVO_NUM_ENTRIES] = {
258     [MLVO_MODEL] = SVAL_LIT("model"),
259     [MLVO_LAYOUT] = SVAL_LIT("layout"),
260     [MLVO_VARIANT] = SVAL_LIT("variant"),
261     [MLVO_OPTION] = SVAL_LIT("option"),
262 };
263
264 enum rules_kccgst {
265     KCCGST_KEYCODES,
266     KCCGST_TYPES,
267     KCCGST_COMPAT,
268     KCCGST_SYMBOLS,
269     KCCGST_GEOMETRY,
270     _KCCGST_NUM_ENTRIES
271 };
272
273 static const struct sval rules_kccgst_svals[_KCCGST_NUM_ENTRIES] = {
274     [KCCGST_KEYCODES] = SVAL_LIT("keycodes"),
275     [KCCGST_TYPES] = SVAL_LIT("types"),
276     [KCCGST_COMPAT] = SVAL_LIT("compat"),
277     [KCCGST_SYMBOLS] = SVAL_LIT("symbols"),
278     [KCCGST_GEOMETRY] = SVAL_LIT("geometry"),
279 };
280
281 /*
282  * A broken-down version of xkb_rule_names (without the rules,
283  * obviously).
284  */
285 struct rule_names {
286     struct sval model;
287     darray_sval layouts;
288     darray_sval variants;
289     darray_sval options;
290 };
291
292 struct group {
293     struct sval name;
294     darray_sval elements;
295 };
296
297 struct mapping {
298     int mlvo_at_pos[_MLVO_NUM_ENTRIES];
299     unsigned int num_mlvo;
300     unsigned int defined_mlvo_mask;
301     xkb_layout_index_t layout_idx, variant_idx;
302     int kccgst_at_pos[_KCCGST_NUM_ENTRIES];
303     unsigned int num_kccgst;
304     unsigned int defined_kccgst_mask;
305     bool skip;
306 };
307
308 enum mlvo_match_type {
309     MLVO_MATCH_NORMAL = 0,
310     MLVO_MATCH_WILDCARD,
311     MLVO_MATCH_GROUP,
312 };
313
314 struct rule {
315     struct sval mlvo_value_at_pos[_MLVO_NUM_ENTRIES];
316     enum mlvo_match_type match_type_at_pos[_MLVO_NUM_ENTRIES];
317     unsigned int num_mlvo_values;
318     struct sval kccgst_value_at_pos[_KCCGST_NUM_ENTRIES];
319     unsigned int num_kccgst_values;
320     bool skip;
321 };
322
323 /*
324  * This is the main object used to match a given RMLVO against a rules
325  * file and aggragate the results in a KcCGST. It goes through a simple
326  * matching state machine, with tokens as transitions (see
327  * matcher_match()).
328  */
329 struct matcher {
330     struct xkb_context *ctx;
331     /* Input.*/
332     struct rule_names rmlvo;
333     struct location loc;
334     union lvalue val;
335     struct scanner scanner;
336     darray(struct group) groups;
337     /* Current mapping. */
338     struct mapping mapping;
339     /* Current rule. */
340     struct rule rule;
341     /* Output. */
342     darray_char kccgst[_KCCGST_NUM_ENTRIES];
343 };
344
345 static struct sval
346 strip_spaces(struct sval v)
347 {
348     while (v.len > 0 && is_space(v.start[0])) { v.len--; v.start++; }
349     while (v.len > 0 && is_space(v.start[v.len - 1])) v.len--;
350     return v;
351 }
352
353 static darray_sval
354 split_comma_separated_string(const char *s)
355 {
356     darray_sval arr = darray_new();
357
358     /*
359      * Make sure the array returned by this function always includes at
360      * least one value, e.g. "" -> { "" } and "," -> { "", "" }.
361      */
362
363     if (!s) {
364         struct sval val = { NULL, 0 };
365         darray_append(arr, val);
366         return arr;
367     }
368
369     while (true) {
370         struct sval val = { s, 0 };
371         while (*s != '\0' && *s != ',') { s++; val.len++; }
372         darray_append(arr, strip_spaces(val));
373         if (*s == '\0') break;
374         if (*s == ',') s++;
375     }
376
377     return arr;
378 }
379
380 static struct matcher *
381 matcher_new(struct xkb_context *ctx,
382             const struct xkb_rule_names *rmlvo)
383 {
384     struct matcher *m = calloc(1, sizeof(*m));
385     if (!m)
386         return NULL;
387
388     m->ctx = ctx;
389     m->rmlvo.model.start = rmlvo->model;
390     m->rmlvo.model.len = strlen_safe(rmlvo->model);
391     m->rmlvo.layouts = split_comma_separated_string(rmlvo->layout);
392     m->rmlvo.variants = split_comma_separated_string(rmlvo->variant);
393     m->rmlvo.options = split_comma_separated_string(rmlvo->options);
394
395     return m;
396 }
397
398 static void
399 matcher_free(struct matcher *m)
400 {
401     struct group *group;
402     if (!m)
403         return;
404     darray_free(m->rmlvo.layouts);
405     darray_free(m->rmlvo.variants);
406     darray_free(m->rmlvo.options);
407     darray_foreach(group, m->groups)
408         darray_free(group->elements);
409     darray_free(m->groups);
410     free(m);
411 }
412
413 #define matcher_error1(matcher, msg) \
414     scanner_error1(&(matcher)->scanner, &(matcher)->loc, msg)
415 #define matcher_error(matcher, fmt, ...) \
416     scanner_error(&(matcher)->scanner, &(matcher)->loc, fmt, __VA_ARGS__)
417
418 static void
419 matcher_group_start_new(struct matcher *m, struct sval name)
420 {
421     struct group group = { .name = name, .elements = darray_new() };
422     darray_append(m->groups, group);
423 }
424
425 static void
426 matcher_group_add_element(struct matcher *m, struct sval element)
427 {
428     darray_append(darray_item(m->groups, darray_size(m->groups) - 1).elements,
429                   element);
430 }
431
432 static void
433 matcher_mapping_start_new(struct matcher *m)
434 {
435     for (unsigned i = 0; i < _MLVO_NUM_ENTRIES; i++)
436         m->mapping.mlvo_at_pos[i] = -1;
437     for (unsigned i = 0; i < _KCCGST_NUM_ENTRIES; i++)
438         m->mapping.kccgst_at_pos[i] = -1;
439     m->mapping.layout_idx = m->mapping.variant_idx = XKB_LAYOUT_INVALID;
440     m->mapping.num_mlvo = m->mapping.num_kccgst = 0;
441     m->mapping.defined_mlvo_mask = 0;
442     m->mapping.defined_kccgst_mask = 0;
443     m->mapping.skip = false;
444 }
445
446 static int
447 extract_layout_index(const char *s, size_t max_len, xkb_layout_index_t *out)
448 {
449     /* This function is pretty stupid, but works for now. */
450     *out = XKB_LAYOUT_INVALID;
451     if (max_len < 3)
452         return -1;
453     if (s[0] != '[' || !is_digit(s[1]) || s[2] != ']')
454         return -1;
455     if (s[1] - '0' < 1 || s[1] - '0' > XKB_MAX_GROUPS)
456         return -1;
457     /* To zero-based index. */
458     *out = s[1] - '0' - 1;
459     return 3;
460 }
461
462 static void
463 matcher_mapping_set_mlvo(struct matcher *m, struct sval ident)
464 {
465     enum rules_mlvo mlvo;
466     struct sval mlvo_sval;
467
468     for (mlvo = 0; mlvo < _MLVO_NUM_ENTRIES; mlvo++) {
469         mlvo_sval = rules_mlvo_svals[mlvo];
470
471         if (svaleq_prefix(mlvo_sval, ident))
472             break;
473     }
474
475     /* Not found. */
476     if (mlvo >= _MLVO_NUM_ENTRIES) {
477         matcher_error(m,
478                       "invalid mapping: %.*s is not a valid value here; "
479                       "ignoring rule set",
480                       ident.len, ident.start);
481         m->mapping.skip = true;
482         return;
483     }
484
485     if (m->mapping.defined_mlvo_mask & (1 << mlvo)) {
486         matcher_error(m,
487                       "invalid mapping: %.*s appears twice on the same line; "
488                       "ignoring rule set",
489                       mlvo_sval.len, mlvo_sval.start);
490         m->mapping.skip = true;
491         return;
492     }
493
494     /* If there are leftovers still, it must be an index. */
495     if (mlvo_sval.len < ident.len) {
496         xkb_layout_index_t idx;
497         int consumed = extract_layout_index(ident.start + mlvo_sval.len,
498                                             ident.len - mlvo_sval.len, &idx);
499         if ((int) (ident.len - mlvo_sval.len) != consumed) {
500             matcher_error(m,
501                           "invalid mapping:\" %.*s\" may only be followed by a valid group index; "
502                           "ignoring rule set",
503                           mlvo_sval.len, mlvo_sval.start);
504             m->mapping.skip = true;
505             return;
506         }
507
508         if (mlvo == MLVO_LAYOUT) {
509             m->mapping.layout_idx = idx;
510         }
511         else if (mlvo == MLVO_VARIANT) {
512             m->mapping.variant_idx = idx;
513         }
514         else {
515             matcher_error(m,
516                           "invalid mapping: \"%.*s\" cannot be followed by a group index; "
517                           "ignoring rule set",
518                           mlvo_sval.len, mlvo_sval.start);
519             m->mapping.skip = true;
520             return;
521         }
522     }
523
524     m->mapping.mlvo_at_pos[m->mapping.num_mlvo] = mlvo;
525     m->mapping.defined_mlvo_mask |= 1 << mlvo;
526     m->mapping.num_mlvo++;
527 }
528
529 static void
530 matcher_mapping_set_kccgst(struct matcher *m, struct sval ident)
531 {
532     enum rules_kccgst kccgst;
533     struct sval kccgst_sval;
534
535     for (kccgst = 0; kccgst < _KCCGST_NUM_ENTRIES; kccgst++) {
536         kccgst_sval = rules_kccgst_svals[kccgst];
537
538         if (svaleq(rules_kccgst_svals[kccgst], ident))
539             break;
540     }
541
542     /* Not found. */
543     if (kccgst >= _KCCGST_NUM_ENTRIES) {
544         matcher_error(m,
545                       "invalid mapping: %.*s is not a valid value here; "
546                       "ignoring rule set",
547                       ident.len, ident.start);
548         m->mapping.skip = true;
549         return;
550     }
551
552     if (m->mapping.defined_kccgst_mask & (1 << kccgst)) {
553         matcher_error(m,
554                       "invalid mapping: %.*s appears twice on the same line; "
555                       "ignoring rule set",
556                       kccgst_sval.len, kccgst_sval.start);
557         m->mapping.skip = true;
558         return;
559     }
560
561     m->mapping.kccgst_at_pos[m->mapping.num_kccgst] = kccgst;
562     m->mapping.defined_kccgst_mask |= 1 << kccgst;
563     m->mapping.num_kccgst++;
564 }
565
566 static void
567 matcher_mapping_verify(struct matcher *m)
568 {
569     if (m->mapping.num_mlvo == 0) {
570         matcher_error1(m,
571                        "invalid mapping: must have at least one value on the left hand side; "
572                        "ignoring rule set");
573         goto skip;
574     }
575
576     if (m->mapping.num_kccgst == 0) {
577         matcher_error1(m,
578                        "invalid mapping: must have at least one value on the right hand side; "
579                        "ignoring rule set");
580         goto skip;
581     }
582
583     /*
584      * This following is very stupid, but this is how it works.
585      * See the "Notes" section in the overview above.
586      */
587
588     if (m->mapping.defined_mlvo_mask & (1 << MLVO_LAYOUT)) {
589         if (m->mapping.layout_idx == XKB_LAYOUT_INVALID) {
590             if (darray_size(m->rmlvo.layouts) > 1)
591                 goto skip;
592         }
593         else {
594             if (darray_size(m->rmlvo.layouts) == 1 ||
595                 m->mapping.layout_idx >= darray_size(m->rmlvo.layouts))
596                 goto skip;
597         }
598     }
599
600     if (m->mapping.defined_mlvo_mask & (1 << MLVO_VARIANT)) {
601         if (m->mapping.variant_idx == XKB_LAYOUT_INVALID) {
602             if (darray_size(m->rmlvo.variants) > 1)
603                 goto skip;
604         }
605         else {
606             if (darray_size(m->rmlvo.variants) == 1 ||
607                 m->mapping.variant_idx >= darray_size(m->rmlvo.variants))
608                 goto skip;
609         }
610     }
611
612     return;
613
614 skip:
615     m->mapping.skip = true;
616 }
617
618 static void
619 matcher_rule_start_new(struct matcher *m)
620 {
621     memset(&m->rule, 0, sizeof(m->rule));
622     m->rule.skip = m->mapping.skip;
623 }
624
625 static void
626 matcher_rule_set_mlvo_common(struct matcher *m, struct sval ident,
627                              enum mlvo_match_type match_type)
628 {
629     if (m->rule.num_mlvo_values + 1 > m->mapping.num_mlvo) {
630         matcher_error1(m,
631                        "invalid rule: has more values than the mapping line; "
632                        "ignoring rule");
633         m->rule.skip = true;
634         return;
635     }
636     m->rule.match_type_at_pos[m->rule.num_mlvo_values] = match_type;
637     m->rule.mlvo_value_at_pos[m->rule.num_mlvo_values] = ident;
638     m->rule.num_mlvo_values++;
639 }
640
641 static void
642 matcher_rule_set_mlvo_wildcard(struct matcher *m)
643 {
644     struct sval dummy = { NULL, 0 };
645     matcher_rule_set_mlvo_common(m, dummy, MLVO_MATCH_WILDCARD);
646 }
647
648 static void
649 matcher_rule_set_mlvo_group(struct matcher *m, struct sval ident)
650 {
651     matcher_rule_set_mlvo_common(m, ident, MLVO_MATCH_GROUP);
652 }
653
654 static void
655 matcher_rule_set_mlvo(struct matcher *m, struct sval ident)
656 {
657     matcher_rule_set_mlvo_common(m, ident, MLVO_MATCH_NORMAL);
658 }
659
660 static void
661 matcher_rule_set_kccgst(struct matcher *m, struct sval ident)
662 {
663     if (m->rule.num_kccgst_values + 1 > m->mapping.num_kccgst) {
664         matcher_error1(m,
665                        "invalid rule: has more values than the mapping line; "
666                        "ignoring rule");
667         m->rule.skip = true;
668         return;
669     }
670     m->rule.kccgst_value_at_pos[m->rule.num_kccgst_values] = ident;
671     m->rule.num_kccgst_values++;
672 }
673
674 static bool
675 match_group(struct matcher *m, struct sval group_name, struct sval to)
676 {
677     struct group *group;
678     struct sval *element;
679     bool found = false;
680
681     darray_foreach(group, m->groups) {
682         if (svaleq(group->name, group_name)) {
683             found = true;
684             break;
685         }
686     }
687
688     if (!found) {
689         /*
690          * rules/evdev intentionally uses some undeclared group names
691          * in rules (e.g. commented group definitions which may be
692          * uncommented if needed). So we continue silently.
693          */
694         return false;
695     }
696
697     darray_foreach(element, group->elements)
698         if (svaleq(to, *element))
699             return true;
700
701     return false;
702 }
703
704 static bool
705 match_value(struct matcher *m, struct sval val, struct sval to,
706           enum mlvo_match_type match_type)
707 {
708     if (match_type == MLVO_MATCH_WILDCARD)
709         return true;
710     if (match_type == MLVO_MATCH_GROUP)
711         return match_group(m, val, to);
712     return svaleq(val, to);
713 }
714
715 /*
716  * This function performs %-expansion on @value (see overview above),
717  * and appends the result to @to.
718  */
719 static bool
720 append_expanded_kccgst_value(struct matcher *m, darray_char *to,
721                              struct sval value)
722 {
723     const size_t original_size = darray_size(*to);
724     const char *s = value.start;
725
726     /*
727      * Appending  bar to  foo ->  foo (not an error if this happens)
728      * Appending +bar to  foo ->  foo+bar
729      * Appending  bar to +foo ->  bar+foo
730      * Appending +bar to +foo -> +foo+bar
731      */
732     if (!darray_empty(*to) && s[0] != '+' && s[0] != '|') {
733         if (darray_item(*to, 0) == '+' || darray_item(*to, 0) == '|')
734             darray_prepends_nullterminate(*to, value.start, value.len);
735         return true;
736     }
737
738     /*
739      * Some ugly hand-lexing here, but going through the scanner is more
740      * trouble than it's worth, and the format is ugly on its own merit.
741      */
742     for (unsigned i = 0; i < value.len; ) {
743         enum rules_mlvo mlv;
744         xkb_layout_index_t idx;
745         char pfx, sfx;
746         struct sval expanded;
747
748         /* Check if that's a start of an expansion. */
749         if (s[i] != '%') {
750             /* Just a normal character. */
751             darray_appends_nullterminate(*to, &s[i++], 1);
752             continue;
753         }
754         if (++i >= value.len) goto error;
755
756         pfx = sfx = 0;
757
758         /* Check for prefix. */
759         if (s[i] == '(' || s[i] == '+' || s[i] == '|' ||
760             s[i] == '_' || s[i] == '-') {
761             pfx = s[i];
762             if (s[i] == '(') sfx = ')';
763             if (++i >= value.len) goto error;
764         }
765
766         /* Mandatory model/layout/variant specifier. */
767         switch (s[i++]) {
768         case 'm': mlv = MLVO_MODEL; break;
769         case 'l': mlv = MLVO_LAYOUT; break;
770         case 'v': mlv = MLVO_VARIANT; break;
771         default: goto error;
772         }
773
774         /* Check for index. */
775         idx = XKB_LAYOUT_INVALID;
776         if (i < value.len && s[i] == '[') {
777             int consumed;
778
779             if (mlv != MLVO_LAYOUT && mlv != MLVO_VARIANT) {
780                 matcher_error1(m,
781                                 "invalid index in %%-expansion; "
782                                 "may only index layout or variant");
783                 goto error;
784             }
785
786             consumed = extract_layout_index(s + i, value.len - i, &idx);
787             if (consumed == -1) goto error;
788             i += consumed;
789         }
790
791         /* Check for suffix, if there supposed to be one. */
792         if (sfx != 0) {
793             if (i >= value.len) goto error;
794             if (s[i++] != sfx) goto error;
795         }
796
797         /* Get the expanded value. */
798         expanded.len = 0;
799
800         if (mlv == MLVO_LAYOUT) {
801             if (idx != XKB_LAYOUT_INVALID &&
802                 idx < darray_size(m->rmlvo.layouts) &&
803                 darray_size(m->rmlvo.layouts) > 1)
804                 expanded = darray_item(m->rmlvo.layouts, idx);
805             else if (idx == XKB_LAYOUT_INVALID &&
806                      darray_size(m->rmlvo.layouts) == 1)
807                 expanded = darray_item(m->rmlvo.layouts, 0);
808         }
809         else if (mlv == MLVO_VARIANT) {
810             if (idx != XKB_LAYOUT_INVALID &&
811                 idx < darray_size(m->rmlvo.variants) &&
812                 darray_size(m->rmlvo.variants) > 1)
813                 expanded = darray_item(m->rmlvo.variants, idx);
814             else if (idx == XKB_LAYOUT_INVALID &&
815                      darray_size(m->rmlvo.variants) == 1)
816                 expanded = darray_item(m->rmlvo.variants, 0);
817         }
818         else if (mlv == MLVO_MODEL) {
819             expanded = m->rmlvo.model;
820         }
821
822         /* If we didn't get one, skip silently. */
823         if (expanded.len <= 0)
824             continue;
825
826         if (pfx != 0)
827             darray_appends_nullterminate(*to, &pfx, 1);
828         darray_appends_nullterminate(*to, expanded.start, expanded.len);
829         if (sfx != 0)
830             darray_appends_nullterminate(*to, &sfx, 1);
831     }
832
833     return true;
834
835 error:
836     matcher_error1(m, "invalid %%-expansion in value; not used");
837     darray_resize(*to, original_size);
838     return false;
839 }
840
841 static void
842 matcher_rule_verify(struct matcher *m)
843 {
844     if (m->rule.num_mlvo_values != m->mapping.num_mlvo ||
845         m->rule.num_kccgst_values != m->mapping.num_kccgst) {
846         matcher_error1(m,
847                        "invalid rule: must have same number of values as mapping line;"
848                        "ignoring rule");
849         m->rule.skip = true;
850     }
851 }
852
853 static void
854 matcher_rule_apply_if_matches(struct matcher *m)
855 {
856     for (unsigned i = 0; i < m->mapping.num_mlvo; i++) {
857         enum rules_mlvo mlvo = m->mapping.mlvo_at_pos[i];
858         struct sval value = m->rule.mlvo_value_at_pos[i];
859         enum mlvo_match_type match_type = m->rule.match_type_at_pos[i];
860         bool matched = false;
861
862         if (mlvo == MLVO_MODEL) {
863             matched = match_value(m, value, m->rmlvo.model, match_type);
864         }
865         else if (mlvo == MLVO_LAYOUT) {
866             xkb_layout_index_t idx = m->mapping.layout_idx;
867             idx = (idx == XKB_LAYOUT_INVALID ? 0 : idx);
868             matched = match_value(m, value,
869                                   darray_item(m->rmlvo.layouts, idx),
870                                   match_type);
871         }
872         else if (mlvo == MLVO_VARIANT) {
873             xkb_layout_index_t idx = m->mapping.layout_idx;
874             idx = (idx == XKB_LAYOUT_INVALID ? 0 : idx);
875             matched = match_value(m, value,
876                                   darray_item(m->rmlvo.variants, idx),
877                                   match_type);
878         }
879         else if (mlvo == MLVO_OPTION) {
880             struct sval *option;
881             darray_foreach(option, m->rmlvo.options) {
882                 matched = match_value(m, value, *option, match_type);
883                 if (matched)
884                     break;
885             }
886         }
887
888         if (!matched)
889             return;
890     }
891
892     for (unsigned i = 0; i < m->mapping.num_kccgst; i++) {
893         enum rules_kccgst kccgst = m->mapping.kccgst_at_pos[i];
894         struct sval value = m->rule.kccgst_value_at_pos[i];
895         append_expanded_kccgst_value(m, &m->kccgst[kccgst], value);
896     }
897
898     /*
899      * If a rule matches in a rule set, the rest of the set should be
900      * skipped. However, rule sets matching against options may contain
901      * several legitimate rules, so they are processed entirely.
902      */
903     if (!(m->mapping.defined_mlvo_mask & (1 << MLVO_OPTION)))
904         m->mapping.skip = true;
905 }
906
907 static enum rules_token
908 gettok(struct matcher *m)
909 {
910     return lex(&m->scanner, &m->val, &m->loc);
911 }
912
913 static bool
914 matcher_match(struct matcher *m, const char *string, size_t len,
915               const char *file_name, struct xkb_component_names *out)
916 {
917     enum rules_token tok;
918
919     if (!m)
920         return false;
921
922     scanner_init(&m->scanner, m->ctx, string, len, file_name);
923
924 initial:
925     switch (tok = gettok(m)) {
926     case TOK_BANG:
927         goto bang;
928     case TOK_END_OF_LINE:
929         goto initial;
930     case TOK_END_OF_FILE:
931         goto finish;
932     default:
933         goto unexpected;
934     }
935
936 bang:
937     switch (tok = gettok(m)) {
938     case TOK_GROUP_NAME:
939         matcher_group_start_new(m, m->val.string);
940         goto group_name;
941     case TOK_IDENTIFIER:
942         matcher_mapping_start_new(m);
943         matcher_mapping_set_mlvo(m, m->val.string);
944         goto mapping_mlvo;
945     default:
946         goto unexpected;
947     }
948
949 group_name:
950     switch (tok = gettok(m)) {
951     case TOK_EQUALS:
952         goto group_element;
953     default:
954         goto unexpected;
955     }
956
957 group_element:
958     switch (tok = gettok(m)) {
959     case TOK_IDENTIFIER:
960         matcher_group_add_element(m, m->val.string);
961         goto group_element;
962     case TOK_END_OF_LINE:
963         goto initial;
964     default:
965         goto unexpected;
966     }
967
968 mapping_mlvo:
969     switch (tok = gettok(m)) {
970     case TOK_IDENTIFIER:
971         if (!m->mapping.skip)
972             matcher_mapping_set_mlvo(m, m->val.string);
973         goto mapping_mlvo;
974     case TOK_EQUALS:
975         goto mapping_kccgst;
976     default:
977         goto unexpected;
978     }
979
980 mapping_kccgst:
981     switch (tok = gettok(m)) {
982     case TOK_IDENTIFIER:
983         if (!m->mapping.skip)
984             matcher_mapping_set_kccgst(m, m->val.string);
985         goto mapping_kccgst;
986     case TOK_END_OF_LINE:
987         if (!m->mapping.skip)
988             matcher_mapping_verify(m);
989         goto rule_mlvo_first;
990     default:
991         goto unexpected;
992     }
993
994 rule_mlvo_first:
995     switch (tok = gettok(m)) {
996     case TOK_BANG:
997         goto bang;
998     case TOK_END_OF_LINE:
999         goto rule_mlvo_first;
1000     case TOK_END_OF_FILE:
1001         goto finish;
1002     default:
1003         matcher_rule_start_new(m);
1004         goto rule_mlvo_no_tok;
1005     }
1006
1007 rule_mlvo:
1008     tok = gettok(m);
1009 rule_mlvo_no_tok:
1010     switch (tok) {
1011     case TOK_IDENTIFIER:
1012         if (!m->rule.skip)
1013             matcher_rule_set_mlvo(m, m->val.string);
1014         goto rule_mlvo;
1015     case TOK_STAR:
1016         if (!m->rule.skip)
1017             matcher_rule_set_mlvo_wildcard(m);
1018         goto rule_mlvo;
1019     case TOK_GROUP_NAME:
1020         if (!m->rule.skip)
1021             matcher_rule_set_mlvo_group(m, m->val.string);
1022         goto rule_mlvo;
1023     case TOK_EQUALS:
1024         goto rule_kccgst;
1025     default:
1026         goto unexpected;
1027     }
1028
1029 rule_kccgst:
1030     switch (tok = gettok(m)) {
1031     case TOK_IDENTIFIER:
1032         if (!m->rule.skip)
1033             matcher_rule_set_kccgst(m, m->val.string);
1034         goto rule_kccgst;
1035     case TOK_END_OF_LINE:
1036         if (!m->rule.skip)
1037             matcher_rule_verify(m);
1038         if (!m->rule.skip)
1039             matcher_rule_apply_if_matches(m);
1040         goto rule_mlvo_first;
1041     default:
1042         goto unexpected;
1043     }
1044
1045 unexpected:
1046     switch (tok) {
1047     case TOK_ERROR:
1048         goto error;
1049     default:
1050         goto state_error;
1051     }
1052
1053 finish:
1054     if (darray_empty(m->kccgst[KCCGST_KEYCODES]) ||
1055         darray_empty(m->kccgst[KCCGST_TYPES]) ||
1056         darray_empty(m->kccgst[KCCGST_COMPAT]) ||
1057         /* darray_empty(m->kccgst[KCCGST_GEOMETRY]) || */
1058         darray_empty(m->kccgst[KCCGST_SYMBOLS]))
1059         goto error;
1060
1061     out->keycodes = darray_mem(m->kccgst[KCCGST_KEYCODES], 0);
1062     out->types = darray_mem(m->kccgst[KCCGST_TYPES], 0);
1063     out->compat = darray_mem(m->kccgst[KCCGST_COMPAT], 0);
1064     /* out->geometry = darray_mem(m->kccgst[KCCGST_GEOMETRY], 0); */
1065     darray_free(m->kccgst[KCCGST_GEOMETRY]);
1066     out->symbols = darray_mem(m->kccgst[KCCGST_SYMBOLS], 0);
1067
1068     return true;
1069
1070 state_error:
1071     matcher_error1(m, "unexpected token");
1072 error:
1073     return false;
1074 }
1075
1076 bool
1077 xkb_components_from_rules(struct xkb_context *ctx,
1078                           const struct xkb_rule_names *rmlvo,
1079                           struct xkb_component_names *out)
1080 {
1081     bool ret = false;
1082     FILE *file;
1083     char *path;
1084     const char *string;
1085     size_t size;
1086     struct matcher *matcher;
1087
1088     file = FindFileInXkbPath(ctx, rmlvo->rules, FILE_TYPE_RULES, &path);
1089     if (!file)
1090         goto err_out;
1091
1092     ret = map_file(file, &string, &size);
1093     if (!ret) {
1094         log_err(ctx, "Couldn't read rules file: %s\n", strerror(errno));
1095         goto err_file;
1096     }
1097
1098     matcher = matcher_new(ctx, rmlvo);
1099     ret = matcher_match(matcher, string, size, rmlvo->rules, out);
1100     if (!ret)
1101         log_err(ctx, "No components returned from XKB rules \"%s\"\n", path);
1102     matcher_free(matcher);
1103
1104     unmap_file(string, size);
1105 err_file:
1106     free(path);
1107     fclose(file);
1108 err_out:
1109     return ret;
1110 }