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 /* Scanner / Lexer */
56
57 /* Values returned with some tokens, like yylval. */
58 union lvalue {
59 struct sval string;
60 };
61
62 enum rules_token {
63 TOK_END_OF_FILE = 0,
64 TOK_END_OF_LINE,
65 TOK_IDENTIFIER,
66 TOK_GROUP_NAME,
67 TOK_BANG,
68 TOK_EQUALS,
69 TOK_STAR,
70 TOK_ERROR
71 };
72
73 static inline bool
is_ident(char ch)74 is_ident(char ch)
75 {
76 return is_graph(ch) && ch != '\\';
77 }
78
79 static enum rules_token
lex(struct scanner * s,union lvalue * val)80 lex(struct scanner *s, union lvalue *val)
81 {
82 skip_more_whitespace_and_comments:
83 /* Skip spaces. */
84 while (chr(s, ' ') || chr(s, '\t'));
85
86 /* Skip comments. */
87 if (lit(s, "//")) {
88 skip_to_eol(s);
89 }
90
91 /* New line. */
92 if (eol(s)) {
93 while (eol(s)) next(s);
94 return TOK_END_OF_LINE;
95 }
96
97 /* Escaped line continuation. */
98 if (chr(s, '\\')) {
99 if (!eol(s)) {
100 scanner_err(s, "illegal new line escape; must appear at end of line");
101 return TOK_ERROR;
102 }
103 next(s);
104 goto skip_more_whitespace_and_comments;
105 }
106
107 /* See if we're done. */
108 if (eof(s)) return TOK_END_OF_FILE;
109
110 /* New token. */
111 s->token_line = s->line;
112 s->token_column = s->column;
113
114 /* Operators and punctuation. */
115 if (chr(s, '!')) return TOK_BANG;
116 if (chr(s, '=')) return TOK_EQUALS;
117 if (chr(s, '*')) return TOK_STAR;
118
119 /* Group name. */
120 if (chr(s, '$')) {
121 val->string.start = s->s + s->pos;
122 val->string.len = 0;
123 while (is_ident(peek(s))) {
124 next(s);
125 val->string.len++;
126 }
127 if (val->string.len == 0) {
128 scanner_err(s, "unexpected character after \'$\'; expected name");
129 return TOK_ERROR;
130 }
131 return TOK_GROUP_NAME;
132 }
133
134 /* Identifier. */
135 if (is_ident(peek(s))) {
136 val->string.start = s->s + s->pos;
137 val->string.len = 0;
138 while (is_ident(peek(s))) {
139 next(s);
140 val->string.len++;
141 }
142 return TOK_IDENTIFIER;
143 }
144
145 scanner_err(s, "unrecognized token");
146 return TOK_ERROR;
147 }
148
149 /***====================================================================***/
150
151 enum rules_mlvo {
152 MLVO_MODEL,
153 MLVO_LAYOUT,
154 MLVO_VARIANT,
155 MLVO_OPTION,
156 _MLVO_NUM_ENTRIES
157 };
158
159 #define SVAL_LIT(literal) { literal, sizeof(literal) - 1 }
160
161 static const struct sval rules_mlvo_svals[_MLVO_NUM_ENTRIES] = {
162 [MLVO_MODEL] = SVAL_LIT("model"),
163 [MLVO_LAYOUT] = SVAL_LIT("layout"),
164 [MLVO_VARIANT] = SVAL_LIT("variant"),
165 [MLVO_OPTION] = SVAL_LIT("option"),
166 };
167
168 enum rules_kccgst {
169 KCCGST_KEYCODES,
170 KCCGST_TYPES,
171 KCCGST_COMPAT,
172 KCCGST_SYMBOLS,
173 KCCGST_GEOMETRY,
174 _KCCGST_NUM_ENTRIES
175 };
176
177 static const struct sval rules_kccgst_svals[_KCCGST_NUM_ENTRIES] = {
178 [KCCGST_KEYCODES] = SVAL_LIT("keycodes"),
179 [KCCGST_TYPES] = SVAL_LIT("types"),
180 [KCCGST_COMPAT] = SVAL_LIT("compat"),
181 [KCCGST_SYMBOLS] = SVAL_LIT("symbols"),
182 [KCCGST_GEOMETRY] = SVAL_LIT("geometry"),
183 };
184
185 /* We use this to keep score whether an mlvo was matched or not; if not,
186 * we warn the user that his preference was ignored. */
187 struct matched_sval {
188 struct sval sval;
189 bool matched;
190 };
191 typedef darray(struct matched_sval) darray_matched_sval;
192
193 /*
194 * A broken-down version of xkb_rule_names (without the rules,
195 * obviously).
196 */
197 struct rule_names {
198 struct matched_sval model;
199 darray_matched_sval layouts;
200 darray_matched_sval variants;
201 darray_matched_sval options;
202 };
203
204 struct group {
205 struct sval name;
206 darray_sval elements;
207 };
208
209 struct mapping {
210 int mlvo_at_pos[_MLVO_NUM_ENTRIES];
211 unsigned int num_mlvo;
212 unsigned int defined_mlvo_mask;
213 xkb_layout_index_t layout_idx, variant_idx;
214 int kccgst_at_pos[_KCCGST_NUM_ENTRIES];
215 unsigned int num_kccgst;
216 unsigned int defined_kccgst_mask;
217 bool skip;
218 };
219
220 enum mlvo_match_type {
221 MLVO_MATCH_NORMAL = 0,
222 MLVO_MATCH_WILDCARD,
223 MLVO_MATCH_GROUP,
224 };
225
226 struct rule {
227 struct sval mlvo_value_at_pos[_MLVO_NUM_ENTRIES];
228 enum mlvo_match_type match_type_at_pos[_MLVO_NUM_ENTRIES];
229 unsigned int num_mlvo_values;
230 struct sval kccgst_value_at_pos[_KCCGST_NUM_ENTRIES];
231 unsigned int num_kccgst_values;
232 bool skip;
233 };
234
235 /*
236 * This is the main object used to match a given RMLVO against a rules
237 * file and aggragate the results in a KcCGST. It goes through a simple
238 * matching state machine, with tokens as transitions (see
239 * matcher_match()).
240 */
241 struct matcher {
242 struct xkb_context *ctx;
243 /* Input.*/
244 struct rule_names rmlvo;
245 union lvalue val;
246 struct scanner scanner;
247 darray(struct group) groups;
248 /* Current mapping. */
249 struct mapping mapping;
250 /* Current rule. */
251 struct rule rule;
252 /* Output. */
253 darray_char kccgst[_KCCGST_NUM_ENTRIES];
254 };
255
256 static struct sval
strip_spaces(struct sval v)257 strip_spaces(struct sval v)
258 {
259 while (v.len > 0 && is_space(v.start[0])) { v.len--; v.start++; }
260 while (v.len > 0 && is_space(v.start[v.len - 1])) v.len--;
261 return v;
262 }
263
264 static darray_matched_sval
split_comma_separated_mlvo(const char * s)265 split_comma_separated_mlvo(const char *s)
266 {
267 darray_matched_sval arr = darray_new();
268
269 /*
270 * Make sure the array returned by this function always includes at
271 * least one value, e.g. "" -> { "" } and "," -> { "", "" }.
272 */
273
274 if (!s) {
275 struct matched_sval val = { .sval = { NULL, 0 } };
276 darray_append(arr, val);
277 return arr;
278 }
279
280 while (true) {
281 struct matched_sval val = { .sval = { s, 0 } };
282 while (*s != '\0' && *s != ',') { s++; val.sval.len++; }
283 val.sval = strip_spaces(val.sval);
284 darray_append(arr, val);
285 if (*s == '\0') break;
286 if (*s == ',') s++;
287 }
288
289 return arr;
290 }
291
292 static struct matcher *
matcher_new(struct xkb_context * ctx,const struct xkb_rule_names * rmlvo)293 matcher_new(struct xkb_context *ctx,
294 const struct xkb_rule_names *rmlvo)
295 {
296 struct matcher *m = calloc(1, sizeof(*m));
297 if (!m)
298 return NULL;
299
300 m->ctx = ctx;
301 m->rmlvo.model.sval.start = rmlvo->model;
302 m->rmlvo.model.sval.len = strlen_safe(rmlvo->model);
303 m->rmlvo.layouts = split_comma_separated_mlvo(rmlvo->layout);
304 m->rmlvo.variants = split_comma_separated_mlvo(rmlvo->variant);
305 m->rmlvo.options = split_comma_separated_mlvo(rmlvo->options);
306
307 return m;
308 }
309
310 static void
matcher_free(struct matcher * m)311 matcher_free(struct matcher *m)
312 {
313 struct group *group;
314 if (!m)
315 return;
316 darray_free(m->rmlvo.layouts);
317 darray_free(m->rmlvo.variants);
318 darray_free(m->rmlvo.options);
319 darray_foreach(group, m->groups)
320 darray_free(group->elements);
321 for (int i = 0; i < _KCCGST_NUM_ENTRIES; i++)
322 darray_free(m->kccgst[i]);
323 darray_free(m->groups);
324 free(m);
325 }
326
327 #define matcher_err(matcher, fmt, ...) \
328 scanner_err(&(matcher)->scanner, fmt, ## __VA_ARGS__)
329
330 static void
matcher_group_start_new(struct matcher * m,struct sval name)331 matcher_group_start_new(struct matcher *m, struct sval name)
332 {
333 struct group group = { .name = name, .elements = darray_new() };
334 darray_append(m->groups, group);
335 }
336
337 static void
matcher_group_add_element(struct matcher * m,struct sval element)338 matcher_group_add_element(struct matcher *m, struct sval element)
339 {
340 darray_append(darray_item(m->groups, darray_size(m->groups) - 1).elements,
341 element);
342 }
343
344 static void
matcher_mapping_start_new(struct matcher * m)345 matcher_mapping_start_new(struct matcher *m)
346 {
347 for (unsigned i = 0; i < _MLVO_NUM_ENTRIES; i++)
348 m->mapping.mlvo_at_pos[i] = -1;
349 for (unsigned i = 0; i < _KCCGST_NUM_ENTRIES; i++)
350 m->mapping.kccgst_at_pos[i] = -1;
351 m->mapping.layout_idx = m->mapping.variant_idx = XKB_LAYOUT_INVALID;
352 m->mapping.num_mlvo = m->mapping.num_kccgst = 0;
353 m->mapping.defined_mlvo_mask = 0;
354 m->mapping.defined_kccgst_mask = 0;
355 m->mapping.skip = false;
356 }
357
358 static int
extract_layout_index(const char * s,size_t max_len,xkb_layout_index_t * out)359 extract_layout_index(const char *s, size_t max_len, xkb_layout_index_t *out)
360 {
361 /* This function is pretty stupid, but works for now. */
362 *out = XKB_LAYOUT_INVALID;
363 if (max_len < 3)
364 return -1;
365 if (s[0] != '[' || !is_digit(s[1]) || s[2] != ']')
366 return -1;
367 if (s[1] - '0' < 1 || s[1] - '0' > XKB_MAX_GROUPS)
368 return -1;
369 /* To zero-based index. */
370 *out = s[1] - '0' - 1;
371 return 3;
372 }
373
374 static void
matcher_mapping_set_mlvo(struct matcher * m,struct sval ident)375 matcher_mapping_set_mlvo(struct matcher *m, struct sval ident)
376 {
377 enum rules_mlvo mlvo;
378 struct sval mlvo_sval;
379
380 for (mlvo = 0; mlvo < _MLVO_NUM_ENTRIES; mlvo++) {
381 mlvo_sval = rules_mlvo_svals[mlvo];
382
383 if (svaleq_prefix(mlvo_sval, ident))
384 break;
385 }
386
387 /* Not found. */
388 if (mlvo >= _MLVO_NUM_ENTRIES) {
389 matcher_err(m, "invalid mapping: %.*s is not a valid value here; ignoring rule set",
390 ident.len, ident.start);
391 m->mapping.skip = true;
392 return;
393 }
394
395 if (m->mapping.defined_mlvo_mask & (1u << mlvo)) {
396 matcher_err(m, "invalid mapping: %.*s appears twice on the same line; ignoring rule set",
397 mlvo_sval.len, mlvo_sval.start);
398 m->mapping.skip = true;
399 return;
400 }
401
402 /* If there are leftovers still, it must be an index. */
403 if (mlvo_sval.len < ident.len) {
404 xkb_layout_index_t idx;
405 int consumed = extract_layout_index(ident.start + mlvo_sval.len,
406 ident.len - mlvo_sval.len, &idx);
407 if ((int) (ident.len - mlvo_sval.len) != consumed) {
408 matcher_err(m, "invalid mapping: \"%.*s\" may only be followed by a valid group index; ignoring rule set",
409 mlvo_sval.len, mlvo_sval.start);
410 m->mapping.skip = true;
411 return;
412 }
413
414 if (mlvo == MLVO_LAYOUT) {
415 m->mapping.layout_idx = idx;
416 }
417 else if (mlvo == MLVO_VARIANT) {
418 m->mapping.variant_idx = idx;
419 }
420 else {
421 matcher_err(m, "invalid mapping: \"%.*s\" cannot be followed by a group index; ignoring rule set",
422 mlvo_sval.len, mlvo_sval.start);
423 m->mapping.skip = true;
424 return;
425 }
426 }
427
428 m->mapping.mlvo_at_pos[m->mapping.num_mlvo] = mlvo;
429 m->mapping.defined_mlvo_mask |= 1u << mlvo;
430 m->mapping.num_mlvo++;
431 }
432
433 static void
matcher_mapping_set_kccgst(struct matcher * m,struct sval ident)434 matcher_mapping_set_kccgst(struct matcher *m, struct sval ident)
435 {
436 enum rules_kccgst kccgst;
437 struct sval kccgst_sval;
438
439 for (kccgst = 0; kccgst < _KCCGST_NUM_ENTRIES; kccgst++) {
440 kccgst_sval = rules_kccgst_svals[kccgst];
441
442 if (svaleq(rules_kccgst_svals[kccgst], ident))
443 break;
444 }
445
446 /* Not found. */
447 if (kccgst >= _KCCGST_NUM_ENTRIES) {
448 matcher_err(m, "invalid mapping: %.*s is not a valid value here; ignoring rule set",
449 ident.len, ident.start);
450 m->mapping.skip = true;
451 return;
452 }
453
454 if (m->mapping.defined_kccgst_mask & (1u << kccgst)) {
455 matcher_err(m, "invalid mapping: %.*s appears twice on the same line; ignoring rule set",
456 kccgst_sval.len, kccgst_sval.start);
457 m->mapping.skip = true;
458 return;
459 }
460
461 m->mapping.kccgst_at_pos[m->mapping.num_kccgst] = kccgst;
462 m->mapping.defined_kccgst_mask |= 1u << kccgst;
463 m->mapping.num_kccgst++;
464 }
465
466 static void
matcher_mapping_verify(struct matcher * m)467 matcher_mapping_verify(struct matcher *m)
468 {
469 if (m->mapping.num_mlvo == 0) {
470 matcher_err(m, "invalid mapping: must have at least one value on the left hand side; ignoring rule set");
471 goto skip;
472 }
473
474 if (m->mapping.num_kccgst == 0) {
475 matcher_err(m, "invalid mapping: must have at least one value on the right hand side; ignoring rule set");
476 goto skip;
477 }
478
479 /*
480 * This following is very stupid, but this is how it works.
481 * See the "Notes" section in the overview above.
482 */
483
484 if (m->mapping.defined_mlvo_mask & (1u << MLVO_LAYOUT)) {
485 if (m->mapping.layout_idx == XKB_LAYOUT_INVALID) {
486 if (darray_size(m->rmlvo.layouts) > 1)
487 goto skip;
488 }
489 else {
490 if (darray_size(m->rmlvo.layouts) == 1 ||
491 m->mapping.layout_idx >= darray_size(m->rmlvo.layouts))
492 goto skip;
493 }
494 }
495
496 if (m->mapping.defined_mlvo_mask & (1u << MLVO_VARIANT)) {
497 if (m->mapping.variant_idx == XKB_LAYOUT_INVALID) {
498 if (darray_size(m->rmlvo.variants) > 1)
499 goto skip;
500 }
501 else {
502 if (darray_size(m->rmlvo.variants) == 1 ||
503 m->mapping.variant_idx >= darray_size(m->rmlvo.variants))
504 goto skip;
505 }
506 }
507
508 return;
509
510 skip:
511 m->mapping.skip = true;
512 }
513
514 static void
matcher_rule_start_new(struct matcher * m)515 matcher_rule_start_new(struct matcher *m)
516 {
517 memset(&m->rule, 0, sizeof(m->rule));
518 m->rule.skip = m->mapping.skip;
519 }
520
521 static void
matcher_rule_set_mlvo_common(struct matcher * m,struct sval ident,enum mlvo_match_type match_type)522 matcher_rule_set_mlvo_common(struct matcher *m, struct sval ident,
523 enum mlvo_match_type match_type)
524 {
525 if (m->rule.num_mlvo_values + 1 > m->mapping.num_mlvo) {
526 matcher_err(m, "invalid rule: has more values than the mapping line; ignoring rule");
527 m->rule.skip = true;
528 return;
529 }
530 m->rule.match_type_at_pos[m->rule.num_mlvo_values] = match_type;
531 m->rule.mlvo_value_at_pos[m->rule.num_mlvo_values] = ident;
532 m->rule.num_mlvo_values++;
533 }
534
535 static void
matcher_rule_set_mlvo_wildcard(struct matcher * m)536 matcher_rule_set_mlvo_wildcard(struct matcher *m)
537 {
538 struct sval dummy = { NULL, 0 };
539 matcher_rule_set_mlvo_common(m, dummy, MLVO_MATCH_WILDCARD);
540 }
541
542 static void
matcher_rule_set_mlvo_group(struct matcher * m,struct sval ident)543 matcher_rule_set_mlvo_group(struct matcher *m, struct sval ident)
544 {
545 matcher_rule_set_mlvo_common(m, ident, MLVO_MATCH_GROUP);
546 }
547
548 static void
matcher_rule_set_mlvo(struct matcher * m,struct sval ident)549 matcher_rule_set_mlvo(struct matcher *m, struct sval ident)
550 {
551 matcher_rule_set_mlvo_common(m, ident, MLVO_MATCH_NORMAL);
552 }
553
554 static void
matcher_rule_set_kccgst(struct matcher * m,struct sval ident)555 matcher_rule_set_kccgst(struct matcher *m, struct sval ident)
556 {
557 if (m->rule.num_kccgst_values + 1 > m->mapping.num_kccgst) {
558 matcher_err(m, "invalid rule: has more values than the mapping line; ignoring rule");
559 m->rule.skip = true;
560 return;
561 }
562 m->rule.kccgst_value_at_pos[m->rule.num_kccgst_values] = ident;
563 m->rule.num_kccgst_values++;
564 }
565
566 static bool
match_group(struct matcher * m,struct sval group_name,struct sval to)567 match_group(struct matcher *m, struct sval group_name, struct sval to)
568 {
569 struct group *group;
570 struct sval *element;
571 bool found = false;
572
573 darray_foreach(group, m->groups) {
574 if (svaleq(group->name, group_name)) {
575 found = true;
576 break;
577 }
578 }
579
580 if (!found) {
581 /*
582 * rules/evdev intentionally uses some undeclared group names
583 * in rules (e.g. commented group definitions which may be
584 * uncommented if needed). So we continue silently.
585 */
586 return false;
587 }
588
589 darray_foreach(element, group->elements)
590 if (svaleq(to, *element))
591 return true;
592
593 return false;
594 }
595
596 static bool
match_value(struct matcher * m,struct sval val,struct sval to,enum mlvo_match_type match_type)597 match_value(struct matcher *m, struct sval val, struct sval to,
598 enum mlvo_match_type match_type)
599 {
600 if (match_type == MLVO_MATCH_WILDCARD)
601 return true;
602 if (match_type == MLVO_MATCH_GROUP)
603 return match_group(m, val, to);
604 return svaleq(val, to);
605 }
606
607 static bool
match_value_and_mark(struct matcher * m,struct sval val,struct matched_sval * to,enum mlvo_match_type match_type)608 match_value_and_mark(struct matcher *m, struct sval val,
609 struct matched_sval *to, enum mlvo_match_type match_type)
610 {
611 bool matched = match_value(m, val, to->sval, match_type);
612 if (matched)
613 to->matched = true;
614 return matched;
615 }
616
617 /*
618 * This function performs %-expansion on @value (see overview above),
619 * and appends the result to @to.
620 */
621 static bool
append_expanded_kccgst_value(struct matcher * m,darray_char * to,struct sval value)622 append_expanded_kccgst_value(struct matcher *m, darray_char *to,
623 struct sval value)
624 {
625 const char *s = value.start;
626 darray_char expanded = darray_new();
627 char ch;
628 bool expanded_plus, to_plus;
629
630 /*
631 * Some ugly hand-lexing here, but going through the scanner is more
632 * trouble than it's worth, and the format is ugly on its own merit.
633 */
634 for (unsigned i = 0; i < value.len; ) {
635 enum rules_mlvo mlv;
636 xkb_layout_index_t idx;
637 char pfx, sfx;
638 struct matched_sval *expanded_value;
639
640 /* Check if that's a start of an expansion. */
641 if (s[i] != '%') {
642 /* Just a normal character. */
643 darray_appends_nullterminate(expanded, &s[i++], 1);
644 continue;
645 }
646 if (++i >= value.len) goto error;
647
648 pfx = sfx = 0;
649
650 /* Check for prefix. */
651 if (s[i] == '(' || s[i] == '+' || s[i] == '|' ||
652 s[i] == '_' || s[i] == '-') {
653 pfx = s[i];
654 if (s[i] == '(') sfx = ')';
655 if (++i >= value.len) goto error;
656 }
657
658 /* Mandatory model/layout/variant specifier. */
659 switch (s[i++]) {
660 case 'm': mlv = MLVO_MODEL; break;
661 case 'l': mlv = MLVO_LAYOUT; break;
662 case 'v': mlv = MLVO_VARIANT; break;
663 default: goto error;
664 }
665
666 /* Check for index. */
667 idx = XKB_LAYOUT_INVALID;
668 if (i < value.len && s[i] == '[') {
669 int consumed;
670
671 if (mlv != MLVO_LAYOUT && mlv != MLVO_VARIANT) {
672 matcher_err(m, "invalid index in %%-expansion; may only index layout or variant");
673 goto error;
674 }
675
676 consumed = extract_layout_index(s + i, value.len - i, &idx);
677 if (consumed == -1) goto error;
678 i += consumed;
679 }
680
681 /* Check for suffix, if there supposed to be one. */
682 if (sfx != 0) {
683 if (i >= value.len) goto error;
684 if (s[i++] != sfx) goto error;
685 }
686
687 /* Get the expanded value. */
688 expanded_value = NULL;
689
690 if (mlv == MLVO_LAYOUT) {
691 if (idx != XKB_LAYOUT_INVALID &&
692 idx < darray_size(m->rmlvo.layouts) &&
693 darray_size(m->rmlvo.layouts) > 1)
694 expanded_value = &darray_item(m->rmlvo.layouts, idx);
695 else if (idx == XKB_LAYOUT_INVALID &&
696 darray_size(m->rmlvo.layouts) == 1)
697 expanded_value = &darray_item(m->rmlvo.layouts, 0);
698 }
699 else if (mlv == MLVO_VARIANT) {
700 if (idx != XKB_LAYOUT_INVALID &&
701 idx < darray_size(m->rmlvo.variants) &&
702 darray_size(m->rmlvo.variants) > 1)
703 expanded_value = &darray_item(m->rmlvo.variants, idx);
704 else if (idx == XKB_LAYOUT_INVALID &&
705 darray_size(m->rmlvo.variants) == 1)
706 expanded_value = &darray_item(m->rmlvo.variants, 0);
707 }
708 else if (mlv == MLVO_MODEL) {
709 expanded_value = &m->rmlvo.model;
710 }
711
712 /* If we didn't get one, skip silently. */
713 if (!expanded_value || expanded_value->sval.len == 0)
714 continue;
715
716 if (pfx != 0)
717 darray_appends_nullterminate(expanded, &pfx, 1);
718 darray_appends_nullterminate(expanded,
719 expanded_value->sval.start,
720 expanded_value->sval.len);
721 if (sfx != 0)
722 darray_appends_nullterminate(expanded, &sfx, 1);
723 expanded_value->matched = true;
724 }
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
733 ch = (darray_empty(expanded) ? '\0' : darray_item(expanded, 0));
734 expanded_plus = (ch == '+' || ch == '|');
735 ch = (darray_empty(*to) ? '\0' : darray_item(*to, 0));
736 to_plus = (ch == '+' || ch == '|');
737
738 if (expanded_plus || darray_empty(*to))
739 darray_appends_nullterminate(*to, expanded.item, expanded.size);
740 else if (to_plus)
741 darray_prepends_nullterminate(*to, expanded.item, expanded.size);
742
743 darray_free(expanded);
744 return true;
745
746 error:
747 darray_free(expanded);
748 matcher_err(m, "invalid %%-expansion in value; not used");
749 return false;
750 }
751
752 static void
matcher_rule_verify(struct matcher * m)753 matcher_rule_verify(struct matcher *m)
754 {
755 if (m->rule.num_mlvo_values != m->mapping.num_mlvo ||
756 m->rule.num_kccgst_values != m->mapping.num_kccgst) {
757 matcher_err(m, "invalid rule: must have same number of values as mapping line; ignoring rule");
758 m->rule.skip = true;
759 }
760 }
761
762 static void
matcher_rule_apply_if_matches(struct matcher * m)763 matcher_rule_apply_if_matches(struct matcher *m)
764 {
765 for (unsigned i = 0; i < m->mapping.num_mlvo; i++) {
766 enum rules_mlvo mlvo = m->mapping.mlvo_at_pos[i];
767 struct sval value = m->rule.mlvo_value_at_pos[i];
768 enum mlvo_match_type match_type = m->rule.match_type_at_pos[i];
769 struct matched_sval *to;
770 bool matched = false;
771
772 if (mlvo == MLVO_MODEL) {
773 to = &m->rmlvo.model;
774 matched = match_value_and_mark(m, value, to, match_type);
775 }
776 else if (mlvo == MLVO_LAYOUT) {
777 xkb_layout_index_t idx = m->mapping.layout_idx;
778 idx = (idx == XKB_LAYOUT_INVALID ? 0 : idx);
779 to = &darray_item(m->rmlvo.layouts, idx);
780 matched = match_value_and_mark(m, value, to, match_type);
781 }
782 else if (mlvo == MLVO_VARIANT) {
783 xkb_layout_index_t idx = m->mapping.layout_idx;
784 idx = (idx == XKB_LAYOUT_INVALID ? 0 : idx);
785 to = &darray_item(m->rmlvo.variants, idx);
786 matched = match_value_and_mark(m, value, to, match_type);
787 }
788 else if (mlvo == MLVO_OPTION) {
789 darray_foreach(to, m->rmlvo.options) {
790 matched = match_value_and_mark(m, value, to, match_type);
791 if (matched)
792 break;
793 }
794 }
795
796 if (!matched)
797 return;
798 }
799
800 for (unsigned i = 0; i < m->mapping.num_kccgst; i++) {
801 enum rules_kccgst kccgst = m->mapping.kccgst_at_pos[i];
802 struct sval value = m->rule.kccgst_value_at_pos[i];
803 append_expanded_kccgst_value(m, &m->kccgst[kccgst], value);
804 }
805
806 /*
807 * If a rule matches in a rule set, the rest of the set should be
808 * skipped. However, rule sets matching against options may contain
809 * several legitimate rules, so they are processed entirely.
810 */
811 if (!(m->mapping.defined_mlvo_mask & (1 << MLVO_OPTION)))
812 m->mapping.skip = true;
813 }
814
815 static enum rules_token
gettok(struct matcher * m)816 gettok(struct matcher *m)
817 {
818 return lex(&m->scanner, &m->val);
819 }
820
821 static bool
matcher_match(struct matcher * m,const char * string,size_t len,const char * file_name,struct xkb_component_names * out)822 matcher_match(struct matcher *m, const char *string, size_t len,
823 const char *file_name, struct xkb_component_names *out)
824 {
825 enum rules_token tok;
826 struct matched_sval *mval;
827
828 if (!m)
829 return false;
830
831 scanner_init(&m->scanner, m->ctx, string, len, file_name, NULL);
832
833 initial:
834 switch (tok = gettok(m)) {
835 case TOK_BANG:
836 goto bang;
837 case TOK_END_OF_LINE:
838 goto initial;
839 case TOK_END_OF_FILE:
840 goto finish;
841 default:
842 goto unexpected;
843 }
844
845 bang:
846 switch (tok = gettok(m)) {
847 case TOK_GROUP_NAME:
848 matcher_group_start_new(m, m->val.string);
849 goto group_name;
850 case TOK_IDENTIFIER:
851 matcher_mapping_start_new(m);
852 matcher_mapping_set_mlvo(m, m->val.string);
853 goto mapping_mlvo;
854 default:
855 goto unexpected;
856 }
857
858 group_name:
859 switch (tok = gettok(m)) {
860 case TOK_EQUALS:
861 goto group_element;
862 default:
863 goto unexpected;
864 }
865
866 group_element:
867 switch (tok = gettok(m)) {
868 case TOK_IDENTIFIER:
869 matcher_group_add_element(m, m->val.string);
870 goto group_element;
871 case TOK_END_OF_LINE:
872 goto initial;
873 default:
874 goto unexpected;
875 }
876
877 mapping_mlvo:
878 switch (tok = gettok(m)) {
879 case TOK_IDENTIFIER:
880 if (!m->mapping.skip)
881 matcher_mapping_set_mlvo(m, m->val.string);
882 goto mapping_mlvo;
883 case TOK_EQUALS:
884 goto mapping_kccgst;
885 default:
886 goto unexpected;
887 }
888
889 mapping_kccgst:
890 switch (tok = gettok(m)) {
891 case TOK_IDENTIFIER:
892 if (!m->mapping.skip)
893 matcher_mapping_set_kccgst(m, m->val.string);
894 goto mapping_kccgst;
895 case TOK_END_OF_LINE:
896 if (!m->mapping.skip)
897 matcher_mapping_verify(m);
898 goto rule_mlvo_first;
899 default:
900 goto unexpected;
901 }
902
903 rule_mlvo_first:
904 switch (tok = gettok(m)) {
905 case TOK_BANG:
906 goto bang;
907 case TOK_END_OF_LINE:
908 goto rule_mlvo_first;
909 case TOK_END_OF_FILE:
910 goto finish;
911 default:
912 matcher_rule_start_new(m);
913 goto rule_mlvo_no_tok;
914 }
915
916 rule_mlvo:
917 tok = gettok(m);
918 rule_mlvo_no_tok:
919 switch (tok) {
920 case TOK_IDENTIFIER:
921 if (!m->rule.skip)
922 matcher_rule_set_mlvo(m, m->val.string);
923 goto rule_mlvo;
924 case TOK_STAR:
925 if (!m->rule.skip)
926 matcher_rule_set_mlvo_wildcard(m);
927 goto rule_mlvo;
928 case TOK_GROUP_NAME:
929 if (!m->rule.skip)
930 matcher_rule_set_mlvo_group(m, m->val.string);
931 goto rule_mlvo;
932 case TOK_EQUALS:
933 goto rule_kccgst;
934 default:
935 goto unexpected;
936 }
937
938 rule_kccgst:
939 switch (tok = gettok(m)) {
940 case TOK_IDENTIFIER:
941 if (!m->rule.skip)
942 matcher_rule_set_kccgst(m, m->val.string);
943 goto rule_kccgst;
944 case TOK_END_OF_LINE:
945 if (!m->rule.skip)
946 matcher_rule_verify(m);
947 if (!m->rule.skip)
948 matcher_rule_apply_if_matches(m);
949 goto rule_mlvo_first;
950 default:
951 goto unexpected;
952 }
953
954 unexpected:
955 switch (tok) {
956 case TOK_ERROR:
957 goto error;
958 default:
959 goto state_error;
960 }
961
962 finish:
963 if (darray_empty(m->kccgst[KCCGST_KEYCODES]) ||
964 darray_empty(m->kccgst[KCCGST_TYPES]) ||
965 darray_empty(m->kccgst[KCCGST_COMPAT]) ||
966 /* darray_empty(m->kccgst[KCCGST_GEOMETRY]) || */
967 darray_empty(m->kccgst[KCCGST_SYMBOLS]))
968 goto error;
969
970 darray_steal(m->kccgst[KCCGST_KEYCODES], &out->keycodes, NULL);
971 darray_steal(m->kccgst[KCCGST_TYPES], &out->types, NULL);
972 darray_steal(m->kccgst[KCCGST_COMPAT], &out->compat, NULL);
973 darray_steal(m->kccgst[KCCGST_SYMBOLS], &out->symbols, NULL);
974 darray_free(m->kccgst[KCCGST_GEOMETRY]);
975
976
977 mval = &m->rmlvo.model;
978 if (!mval->matched && mval->sval.len > 0)
979 log_err(m->ctx, "Unrecognized RMLVO model \"%.*s\" was ignored\n",
980 mval->sval.len, mval->sval.start);
981 darray_foreach(mval, m->rmlvo.layouts)
982 if (!mval->matched && mval->sval.len > 0)
983 log_err(m->ctx, "Unrecognized RMLVO layout \"%.*s\" was ignored\n",
984 mval->sval.len, mval->sval.start);
985 darray_foreach(mval, m->rmlvo.variants)
986 if (!mval->matched && mval->sval.len > 0)
987 log_err(m->ctx, "Unrecognized RMLVO variant \"%.*s\" was ignored\n",
988 mval->sval.len, mval->sval.start);
989 darray_foreach(mval, m->rmlvo.options)
990 if (!mval->matched && mval->sval.len > 0)
991 log_err(m->ctx, "Unrecognized RMLVO option \"%.*s\" was ignored\n",
992 mval->sval.len, mval->sval.start);
993
994 return true;
995
996 state_error:
997 matcher_err(m, "unexpected token");
998 error:
999 return false;
1000 }
1001
1002 bool
xkb_components_from_rules(struct xkb_context * ctx,const struct xkb_rule_names * rmlvo,struct xkb_component_names * out)1003 xkb_components_from_rules(struct xkb_context *ctx,
1004 const struct xkb_rule_names *rmlvo,
1005 struct xkb_component_names *out)
1006 {
1007 bool ret = false;
1008 FILE *file;
1009 char *path;
1010 const char *string;
1011 size_t size;
1012 struct matcher *matcher;
1013
1014 file = FindFileInXkbPath(ctx, rmlvo->rules, FILE_TYPE_RULES, &path);
1015 if (!file)
1016 goto err_out;
1017
1018 ret = map_file(file, &string, &size);
1019 if (!ret) {
1020 log_err(ctx, "Couldn't read rules file \"%s\": %s\n",
1021 path, strerror(errno));
1022 goto err_file;
1023 }
1024
1025 matcher = matcher_new(ctx, rmlvo);
1026 ret = matcher_match(matcher, string, size, path, out);
1027 if (!ret)
1028 log_err(ctx, "No components returned from XKB rules \"%s\"\n", path);
1029 matcher_free(matcher);
1030
1031 unmap_file(string, size);
1032 err_file:
1033 free(path);
1034 fclose(file);
1035 err_out:
1036 return ret;
1037 }
1038