Removed build dependency on xproto.
[platform/upstream/libxkbcommon.git] / src / xkbcomp / rules.c
index 61799e7..f5d9c49 100644 (file)
  * DEALINGS IN THE SOFTWARE.
  */
 
+#include "config.h"
+
 #include "xkbcomp-priv.h"
 #include "rules.h"
 #include "include.h"
 #include "scanner-utils.h"
 
+#define MAX_INCLUDE_DEPTH 5
+
 /* Scanner / Lexer */
 
 /* Values returned with some tokens, like yylval. */
@@ -67,6 +71,7 @@ enum rules_token {
     TOK_BANG,
     TOK_EQUALS,
     TOK_STAR,
+    TOK_INCLUDE,
     TOK_ERROR
 };
 
@@ -81,47 +86,49 @@ lex(struct scanner *s, union lvalue *val)
 {
 skip_more_whitespace_and_comments:
     /* Skip spaces. */
-    while (chr(s, ' ') || chr(s, '\t'));
+    while (scanner_chr(s, ' ') || scanner_chr(s, '\t') || scanner_chr(s, '\r'));
 
     /* Skip comments. */
-    if (lit(s, "//")) {
-        while (!eof(s) && !eol(s)) next(s);
+    if (scanner_lit(s, "//")) {
+        scanner_skip_to_eol(s);
     }
 
     /* New line. */
-    if (eol(s)) {
-        while (eol(s)) next(s);
+    if (scanner_eol(s)) {
+        while (scanner_eol(s)) scanner_next(s);
         return TOK_END_OF_LINE;
     }
 
     /* Escaped line continuation. */
-    if (chr(s, '\\')) {
-        if (!eol(s)) {
+    if (scanner_chr(s, '\\')) {
+        /* Optional \r. */
+        scanner_chr(s, '\r');
+        if (!scanner_eol(s)) {
             scanner_err(s, "illegal new line escape; must appear at end of line");
             return TOK_ERROR;
         }
-        next(s);
+        scanner_next(s);
         goto skip_more_whitespace_and_comments;
     }
 
     /* See if we're done. */
-    if (eof(s)) return TOK_END_OF_FILE;
+    if (scanner_eof(s)) return TOK_END_OF_FILE;
 
     /* New token. */
     s->token_line = s->line;
     s->token_column = s->column;
 
     /* Operators and punctuation. */
-    if (chr(s, '!')) return TOK_BANG;
-    if (chr(s, '=')) return TOK_EQUALS;
-    if (chr(s, '*')) return TOK_STAR;
+    if (scanner_chr(s, '!')) return TOK_BANG;
+    if (scanner_chr(s, '=')) return TOK_EQUALS;
+    if (scanner_chr(s, '*')) return TOK_STAR;
 
     /* Group name. */
-    if (chr(s, '$')) {
+    if (scanner_chr(s, '$')) {
         val->string.start = s->s + s->pos;
         val->string.len = 0;
-        while (is_ident(peek(s))) {
-            next(s);
+        while (is_ident(scanner_peek(s))) {
+            scanner_next(s);
             val->string.len++;
         }
         if (val->string.len == 0) {
@@ -131,12 +138,16 @@ skip_more_whitespace_and_comments:
         return TOK_GROUP_NAME;
     }
 
+    /* Include statement. */
+    if (scanner_lit(s, "include"))
+        return TOK_INCLUDE;
+
     /* Identifier. */
-    if (is_ident(peek(s))) {
+    if (is_ident(scanner_peek(s))) {
         val->string.start = s->s + s->pos;
         val->string.len = 0;
-        while (is_ident(peek(s))) {
-            next(s);
+        while (is_ident(scanner_peek(s))) {
+            scanner_next(s);
             val->string.len++;
         }
         return TOK_IDENTIFIER;
@@ -182,15 +193,23 @@ static const struct sval rules_kccgst_svals[_KCCGST_NUM_ENTRIES] = {
     [KCCGST_GEOMETRY] = SVAL_LIT("geometry"),
 };
 
+/* We use this to keep score whether an mlvo was matched or not; if not,
+ * we warn the user that his preference was ignored. */
+struct matched_sval {
+    struct sval sval;
+    bool matched;
+};
+typedef darray(struct matched_sval) darray_matched_sval;
+
 /*
  * A broken-down version of xkb_rule_names (without the rules,
  * obviously).
  */
 struct rule_names {
-    struct sval model;
-    darray_sval layouts;
-    darray_sval variants;
-    darray_sval options;
+    struct matched_sval model;
+    darray_matched_sval layouts;
+    darray_matched_sval variants;
+    darray_matched_sval options;
 };
 
 struct group {
@@ -235,7 +254,6 @@ struct matcher {
     /* Input.*/
     struct rule_names rmlvo;
     union lvalue val;
-    struct scanner scanner;
     darray(struct group) groups;
     /* Current mapping. */
     struct mapping mapping;
@@ -253,10 +271,10 @@ strip_spaces(struct sval v)
     return v;
 }
 
-static darray_sval
-split_comma_separated_string(const char *s)
+static darray_matched_sval
+split_comma_separated_mlvo(const char *s)
 {
-    darray_sval arr = darray_new();
+    darray_matched_sval arr = darray_new();
 
     /*
      * Make sure the array returned by this function always includes at
@@ -264,15 +282,16 @@ split_comma_separated_string(const char *s)
      */
 
     if (!s) {
-        struct sval val = { NULL, 0 };
+        struct matched_sval val = { .sval = { NULL, 0 } };
         darray_append(arr, val);
         return arr;
     }
 
     while (true) {
-        struct sval val = { s, 0 };
-        while (*s != '\0' && *s != ',') { s++; val.len++; }
-        darray_append(arr, strip_spaces(val));
+        struct matched_sval val = { .sval = { s, 0 } };
+        while (*s != '\0' && *s != ',') { s++; val.sval.len++; }
+        val.sval = strip_spaces(val.sval);
+        darray_append(arr, val);
         if (*s == '\0') break;
         if (*s == ',') s++;
     }
@@ -289,11 +308,11 @@ matcher_new(struct xkb_context *ctx,
         return NULL;
 
     m->ctx = ctx;
-    m->rmlvo.model.start = rmlvo->model;
-    m->rmlvo.model.len = strlen_safe(rmlvo->model);
-    m->rmlvo.layouts = split_comma_separated_string(rmlvo->layout);
-    m->rmlvo.variants = split_comma_separated_string(rmlvo->variant);
-    m->rmlvo.options = split_comma_separated_string(rmlvo->options);
+    m->rmlvo.model.sval.start = rmlvo->model;
+    m->rmlvo.model.sval.len = strlen_safe(rmlvo->model);
+    m->rmlvo.layouts = split_comma_separated_mlvo(rmlvo->layout);
+    m->rmlvo.variants = split_comma_separated_mlvo(rmlvo->variant);
+    m->rmlvo.options = split_comma_separated_mlvo(rmlvo->options);
 
     return m;
 }
@@ -309,13 +328,12 @@ matcher_free(struct matcher *m)
     darray_free(m->rmlvo.options);
     darray_foreach(group, m->groups)
         darray_free(group->elements);
+    for (int i = 0; i < _KCCGST_NUM_ENTRIES; i++)
+        darray_free(m->kccgst[i]);
     darray_free(m->groups);
     free(m);
 }
 
-#define matcher_err(matcher, fmt, ...) \
-    scanner_err(&(matcher)->scanner, fmt, ## __VA_ARGS__)
-
 static void
 matcher_group_start_new(struct matcher *m, struct sval name)
 {
@@ -324,12 +342,99 @@ matcher_group_start_new(struct matcher *m, struct sval name)
 }
 
 static void
-matcher_group_add_element(struct matcher *m, struct sval element)
+matcher_group_add_element(struct matcher *m, struct scanner *s,
+                          struct sval element)
 {
     darray_append(darray_item(m->groups, darray_size(m->groups) - 1).elements,
                   element);
 }
 
+static bool
+read_rules_file(struct xkb_context *ctx,
+                struct matcher *matcher,
+                unsigned include_depth,
+                FILE *file,
+                const char *path);
+
+static void
+matcher_include(struct matcher *m, struct scanner *parent_scanner,
+                unsigned include_depth,
+                struct sval inc)
+{
+    struct scanner s; /* parses the !include value */
+    FILE *file;
+
+    scanner_init(&s, m->ctx, inc.start, inc.len,
+                 parent_scanner->file_name, NULL);
+    s.token_line = parent_scanner->token_line;
+    s.token_column = parent_scanner->token_column;
+    s.buf_pos = 0;
+
+    if (include_depth >= MAX_INCLUDE_DEPTH) {
+        scanner_err(&s, "maximum include depth (%d) exceeded; maybe there is an include loop?",
+                    MAX_INCLUDE_DEPTH);
+        return;
+    }
+
+    while (!scanner_eof(&s) && !scanner_eol(&s)) {
+        if (scanner_chr(&s, '%')) {
+            if (scanner_chr(&s, '%')) {
+                scanner_buf_append(&s, '%');
+            }
+            else if (scanner_chr(&s, 'H')) {
+                const char *home = xkb_context_getenv(m->ctx, "HOME");
+                if (!home) {
+                    scanner_err(&s, "%%H was used in an include statement, but the HOME environment variable is not set");
+                    return;
+                }
+                if (!scanner_buf_appends(&s, home)) {
+                    scanner_err(&s, "include path after expanding %%H is too long");
+                    return;
+                }
+            }
+            else if (scanner_chr(&s, 'S')) {
+                const char *default_root = xkb_context_include_path_get_system_path(m->ctx);
+                if (!scanner_buf_appends(&s, default_root) || !scanner_buf_appends(&s, "/rules")) {
+                    scanner_err(&s, "include path after expanding %%S is too long");
+                    return;
+                }
+            }
+            else if (scanner_chr(&s, 'E')) {
+                const char *default_root = xkb_context_include_path_get_extra_path(m->ctx);
+                if (!scanner_buf_appends(&s, default_root) || !scanner_buf_appends(&s, "/rules")) {
+                    scanner_err(&s, "include path after expanding %%E is too long");
+                    return;
+                }
+            }
+            else {
+                scanner_err(&s, "unknown %% format (%c) in include statement", scanner_peek(&s));
+                return;
+            }
+        }
+        else {
+            scanner_buf_append(&s, scanner_next(&s));
+        }
+    }
+    if (!scanner_buf_append(&s, '\0')) {
+        scanner_err(&s, "include path is too long");
+        return;
+    }
+
+    file = fopen(s.buf, "rb");
+    if (file) {
+        bool ret = read_rules_file(m->ctx, m, include_depth + 1, file, s.buf);
+        if (!ret)
+            log_err(m->ctx, XKB_LOG_MESSAGE_NO_ID,
+                    "No components returned from included XKB rules \"%s\"\n",
+                    s.buf);
+        fclose(file);
+    } else {
+        log_err(m->ctx, XKB_LOG_MESSAGE_NO_ID,
+                "Failed to open included XKB rules \"%s\"\n",
+                s.buf);
+    }
+}
+
 static void
 matcher_mapping_start_new(struct matcher *m)
 {
@@ -361,7 +466,8 @@ extract_layout_index(const char *s, size_t max_len, xkb_layout_index_t *out)
 }
 
 static void
-matcher_mapping_set_mlvo(struct matcher *m, struct sval ident)
+matcher_mapping_set_mlvo(struct matcher *m, struct scanner *s,
+                         struct sval ident)
 {
     enum rules_mlvo mlvo;
     struct sval mlvo_sval;
@@ -375,14 +481,14 @@ matcher_mapping_set_mlvo(struct matcher *m, struct sval ident)
 
     /* Not found. */
     if (mlvo >= _MLVO_NUM_ENTRIES) {
-        matcher_err(m, "invalid mapping: %.*s is not a valid value here; ignoring rule set",
+        scanner_err(s, "invalid mapping: %.*s is not a valid value here; ignoring rule set",
                     ident.len, ident.start);
         m->mapping.skip = true;
         return;
     }
 
     if (m->mapping.defined_mlvo_mask & (1u << mlvo)) {
-        matcher_err(m, "invalid mapping: %.*s appears twice on the same line; ignoring rule set",
+        scanner_err(s, "invalid mapping: %.*s appears twice on the same line; ignoring rule set",
                     mlvo_sval.len, mlvo_sval.start);
         m->mapping.skip = true;
         return;
@@ -394,7 +500,7 @@ matcher_mapping_set_mlvo(struct matcher *m, struct sval ident)
         int consumed = extract_layout_index(ident.start + mlvo_sval.len,
                                             ident.len - mlvo_sval.len, &idx);
         if ((int) (ident.len - mlvo_sval.len) != consumed) {
-            matcher_err(m, "invalid mapping: \"%.*s\" may only be followed by a valid group index; ignoring rule set",
+            scanner_err(s, "invalid mapping: \"%.*s\" may only be followed by a valid group index; ignoring rule set",
                         mlvo_sval.len, mlvo_sval.start);
             m->mapping.skip = true;
             return;
@@ -407,7 +513,7 @@ matcher_mapping_set_mlvo(struct matcher *m, struct sval ident)
             m->mapping.variant_idx = idx;
         }
         else {
-            matcher_err(m, "invalid mapping: \"%.*s\" cannot be followed by a group index; ignoring rule set",
+            scanner_err(s, "invalid mapping: \"%.*s\" cannot be followed by a group index; ignoring rule set",
                         mlvo_sval.len, mlvo_sval.start);
             m->mapping.skip = true;
             return;
@@ -420,7 +526,7 @@ matcher_mapping_set_mlvo(struct matcher *m, struct sval ident)
 }
 
 static void
-matcher_mapping_set_kccgst(struct matcher *m, struct sval ident)
+matcher_mapping_set_kccgst(struct matcher *m, struct scanner *s, struct sval ident)
 {
     enum rules_kccgst kccgst;
     struct sval kccgst_sval;
@@ -434,14 +540,14 @@ matcher_mapping_set_kccgst(struct matcher *m, struct sval ident)
 
     /* Not found. */
     if (kccgst >= _KCCGST_NUM_ENTRIES) {
-        matcher_err(m, "invalid mapping: %.*s is not a valid value here; ignoring rule set",
+        scanner_err(s, "invalid mapping: %.*s is not a valid value here; ignoring rule set",
                     ident.len, ident.start);
         m->mapping.skip = true;
         return;
     }
 
     if (m->mapping.defined_kccgst_mask & (1u << kccgst)) {
-        matcher_err(m, "invalid mapping: %.*s appears twice on the same line; ignoring rule set",
+        scanner_err(s, "invalid mapping: %.*s appears twice on the same line; ignoring rule set",
                     kccgst_sval.len, kccgst_sval.start);
         m->mapping.skip = true;
         return;
@@ -453,15 +559,15 @@ matcher_mapping_set_kccgst(struct matcher *m, struct sval ident)
 }
 
 static void
-matcher_mapping_verify(struct matcher *m)
+matcher_mapping_verify(struct matcher *m, struct scanner *s)
 {
     if (m->mapping.num_mlvo == 0) {
-        matcher_err(m, "invalid mapping: must have at least one value on the left hand side; ignoring rule set");
+        scanner_err(s, "invalid mapping: must have at least one value on the left hand side; ignoring rule set");
         goto skip;
     }
 
     if (m->mapping.num_kccgst == 0) {
-        matcher_err(m, "invalid mapping: must have at least one value on the right hand side; ignoring rule set");
+        scanner_err(s, "invalid mapping: must have at least one value on the right hand side; ignoring rule set");
         goto skip;
     }
 
@@ -508,11 +614,12 @@ matcher_rule_start_new(struct matcher *m)
 }
 
 static void
-matcher_rule_set_mlvo_common(struct matcher *m, struct sval ident,
+matcher_rule_set_mlvo_common(struct matcher *m, struct scanner *s,
+                             struct sval ident,
                              enum mlvo_match_type match_type)
 {
     if (m->rule.num_mlvo_values + 1 > m->mapping.num_mlvo) {
-        matcher_err(m, "invalid rule: has more values than the mapping line; ignoring rule");
+        scanner_err(s, "invalid rule: has more values than the mapping line; ignoring rule");
         m->rule.skip = true;
         return;
     }
@@ -522,29 +629,32 @@ matcher_rule_set_mlvo_common(struct matcher *m, struct sval ident,
 }
 
 static void
-matcher_rule_set_mlvo_wildcard(struct matcher *m)
+matcher_rule_set_mlvo_wildcard(struct matcher *m, struct scanner *s)
 {
     struct sval dummy = { NULL, 0 };
-    matcher_rule_set_mlvo_common(m, dummy, MLVO_MATCH_WILDCARD);
+    matcher_rule_set_mlvo_common(m, s, dummy, MLVO_MATCH_WILDCARD);
 }
 
 static void
-matcher_rule_set_mlvo_group(struct matcher *m, struct sval ident)
+matcher_rule_set_mlvo_group(struct matcher *m, struct scanner *s,
+                            struct sval ident)
 {
-    matcher_rule_set_mlvo_common(m, ident, MLVO_MATCH_GROUP);
+    matcher_rule_set_mlvo_common(m, s, ident, MLVO_MATCH_GROUP);
 }
 
 static void
-matcher_rule_set_mlvo(struct matcher *m, struct sval ident)
+matcher_rule_set_mlvo(struct matcher *m, struct scanner *s,
+                      struct sval ident)
 {
-    matcher_rule_set_mlvo_common(m, ident, MLVO_MATCH_NORMAL);
+    matcher_rule_set_mlvo_common(m, s, ident, MLVO_MATCH_NORMAL);
 }
 
 static void
-matcher_rule_set_kccgst(struct matcher *m, struct sval ident)
+matcher_rule_set_kccgst(struct matcher *m, struct scanner *s,
+                        struct sval ident)
 {
     if (m->rule.num_kccgst_values + 1 > m->mapping.num_kccgst) {
-        matcher_err(m, "invalid rule: has more values than the mapping line; ignoring rule");
+        scanner_err(s, "invalid rule: has more values than the mapping line; ignoring rule");
         m->rule.skip = true;
         return;
     }
@@ -584,7 +694,7 @@ match_group(struct matcher *m, struct sval group_name, struct sval to)
 
 static bool
 match_value(struct matcher *m, struct sval val, struct sval to,
-          enum mlvo_match_type match_type)
+            enum mlvo_match_type match_type)
 {
     if (match_type == MLVO_MATCH_WILDCARD)
         return true;
@@ -593,15 +703,25 @@ match_value(struct matcher *m, struct sval val, struct sval to,
     return svaleq(val, to);
 }
 
+static bool
+match_value_and_mark(struct matcher *m, struct sval val,
+                     struct matched_sval *to, enum mlvo_match_type match_type)
+{
+    bool matched = match_value(m, val, to->sval, match_type);
+    if (matched)
+        to->matched = true;
+    return matched;
+}
+
 /*
  * This function performs %-expansion on @value (see overview above),
  * and appends the result to @to.
  */
 static bool
-append_expanded_kccgst_value(struct matcher *m, darray_char *to,
-                             struct sval value)
+append_expanded_kccgst_value(struct matcher *m, struct scanner *s,
+                             darray_char *to, struct sval value)
 {
-    const char *s = value.start;
+    const char *str = value.start;
     darray_char expanded = darray_new();
     char ch;
     bool expanded_plus, to_plus;
@@ -614,12 +734,12 @@ append_expanded_kccgst_value(struct matcher *m, darray_char *to,
         enum rules_mlvo mlv;
         xkb_layout_index_t idx;
         char pfx, sfx;
-        struct sval expanded_value;
+        struct matched_sval *expanded_value;
 
         /* Check if that's a start of an expansion. */
-        if (s[i] != '%') {
+        if (str[i] != '%') {
             /* Just a normal character. */
-            darray_appends_nullterminate(expanded, &s[i++], 1);
+            darray_appends_nullterminate(expanded, &str[i++], 1);
             continue;
         }
         if (++i >= value.len) goto error;
@@ -627,15 +747,15 @@ append_expanded_kccgst_value(struct matcher *m, darray_char *to,
         pfx = sfx = 0;
 
         /* Check for prefix. */
-        if (s[i] == '(' || s[i] == '+' || s[i] == '|' ||
-            s[i] == '_' || s[i] == '-') {
-            pfx = s[i];
-            if (s[i] == '(') sfx = ')';
+        if (str[i] == '(' || str[i] == '+' || str[i] == '|' ||
+            str[i] == '_' || str[i] == '-') {
+            pfx = str[i];
+            if (str[i] == '(') sfx = ')';
             if (++i >= value.len) goto error;
         }
 
         /* Mandatory model/layout/variant specifier. */
-        switch (s[i++]) {
+        switch (str[i++]) {
         case 'm': mlv = MLVO_MODEL; break;
         case 'l': mlv = MLVO_LAYOUT; break;
         case 'v': mlv = MLVO_VARIANT; break;
@@ -644,15 +764,15 @@ append_expanded_kccgst_value(struct matcher *m, darray_char *to,
 
         /* Check for index. */
         idx = XKB_LAYOUT_INVALID;
-        if (i < value.len && s[i] == '[') {
+        if (i < value.len && str[i] == '[') {
             int consumed;
 
             if (mlv != MLVO_LAYOUT && mlv != MLVO_VARIANT) {
-                matcher_err(m, "invalid index in %%-expansion; may only index layout or variant");
+                scanner_err(s, "invalid index in %%-expansion; may only index layout or variant");
                 goto error;
             }
 
-            consumed = extract_layout_index(s + i, value.len - i, &idx);
+            consumed = extract_layout_index(str + i, value.len - i, &idx);
             if (consumed == -1) goto error;
             i += consumed;
         }
@@ -660,44 +780,46 @@ append_expanded_kccgst_value(struct matcher *m, darray_char *to,
         /* Check for suffix, if there supposed to be one. */
         if (sfx != 0) {
             if (i >= value.len) goto error;
-            if (s[i++] != sfx) goto error;
+            if (str[i++] != sfx) goto error;
         }
 
         /* Get the expanded value. */
-        expanded_value.len = 0;
+        expanded_value = NULL;
 
         if (mlv == MLVO_LAYOUT) {
             if (idx != XKB_LAYOUT_INVALID &&
                 idx < darray_size(m->rmlvo.layouts) &&
                 darray_size(m->rmlvo.layouts) > 1)
-                expanded_value = darray_item(m->rmlvo.layouts, idx);
+                expanded_value = &darray_item(m->rmlvo.layouts, idx);
             else if (idx == XKB_LAYOUT_INVALID &&
                      darray_size(m->rmlvo.layouts) == 1)
-                expanded_value = darray_item(m->rmlvo.layouts, 0);
+                expanded_value = &darray_item(m->rmlvo.layouts, 0);
         }
         else if (mlv == MLVO_VARIANT) {
             if (idx != XKB_LAYOUT_INVALID &&
                 idx < darray_size(m->rmlvo.variants) &&
                 darray_size(m->rmlvo.variants) > 1)
-                expanded_value = darray_item(m->rmlvo.variants, idx);
+                expanded_value = &darray_item(m->rmlvo.variants, idx);
             else if (idx == XKB_LAYOUT_INVALID &&
                      darray_size(m->rmlvo.variants) == 1)
-                expanded_value = darray_item(m->rmlvo.variants, 0);
+                expanded_value = &darray_item(m->rmlvo.variants, 0);
         }
         else if (mlv == MLVO_MODEL) {
-            expanded_value = m->rmlvo.model;
+            expanded_value = &m->rmlvo.model;
         }
 
         /* If we didn't get one, skip silently. */
-        if (expanded_value.len <= 0)
+        if (!expanded_value || expanded_value->sval.len == 0)
             continue;
 
         if (pfx != 0)
             darray_appends_nullterminate(expanded, &pfx, 1);
         darray_appends_nullterminate(expanded,
-                                     expanded_value.start, expanded_value.len);
+                                     expanded_value->sval.start,
+                                     expanded_value->sval.len);
         if (sfx != 0)
             darray_appends_nullterminate(expanded, &sfx, 1);
+        expanded_value->matched = true;
     }
 
     /*
@@ -722,50 +844,49 @@ append_expanded_kccgst_value(struct matcher *m, darray_char *to,
 
 error:
     darray_free(expanded);
-    matcher_err(m, "invalid %%-expansion in value; not used");
+    scanner_err(s, "invalid %%-expansion in value; not used");
     return false;
 }
 
 static void
-matcher_rule_verify(struct matcher *m)
+matcher_rule_verify(struct matcher *m, struct scanner *s)
 {
     if (m->rule.num_mlvo_values != m->mapping.num_mlvo ||
         m->rule.num_kccgst_values != m->mapping.num_kccgst) {
-        matcher_err(m, "invalid rule: must have same number of values as mapping line; ignoring rule");
+        scanner_err(s, "invalid rule: must have same number of values as mapping line; ignoring rule");
         m->rule.skip = true;
     }
 }
 
 static void
-matcher_rule_apply_if_matches(struct matcher *m)
+matcher_rule_apply_if_matches(struct matcher *m, struct scanner *s)
 {
     for (unsigned i = 0; i < m->mapping.num_mlvo; i++) {
         enum rules_mlvo mlvo = m->mapping.mlvo_at_pos[i];
         struct sval value = m->rule.mlvo_value_at_pos[i];
         enum mlvo_match_type match_type = m->rule.match_type_at_pos[i];
+        struct matched_sval *to;
         bool matched = false;
 
         if (mlvo == MLVO_MODEL) {
-            matched = match_value(m, value, m->rmlvo.model, match_type);
+            to = &m->rmlvo.model;
+            matched = match_value_and_mark(m, value, to, match_type);
         }
         else if (mlvo == MLVO_LAYOUT) {
             xkb_layout_index_t idx = m->mapping.layout_idx;
             idx = (idx == XKB_LAYOUT_INVALID ? 0 : idx);
-            matched = match_value(m, value,
-                                  darray_item(m->rmlvo.layouts, idx),
-                                  match_type);
+            to = &darray_item(m->rmlvo.layouts, idx);
+            matched = match_value_and_mark(m, value, to, match_type);
         }
         else if (mlvo == MLVO_VARIANT) {
             xkb_layout_index_t idx = m->mapping.layout_idx;
             idx = (idx == XKB_LAYOUT_INVALID ? 0 : idx);
-            matched = match_value(m, value,
-                                  darray_item(m->rmlvo.variants, idx),
-                                  match_type);
+            to = &darray_item(m->rmlvo.variants, idx);
+            matched = match_value_and_mark(m, value, to, match_type);
         }
         else if (mlvo == MLVO_OPTION) {
-            struct sval *option;
-            darray_foreach(option, m->rmlvo.options) {
-                matched = match_value(m, value, *option, match_type);
+            darray_foreach(to, m->rmlvo.options) {
+                matched = match_value_and_mark(m, value, to, match_type);
                 if (matched)
                     break;
             }
@@ -778,7 +899,7 @@ matcher_rule_apply_if_matches(struct matcher *m)
     for (unsigned i = 0; i < m->mapping.num_kccgst; i++) {
         enum rules_kccgst kccgst = m->mapping.kccgst_at_pos[i];
         struct sval value = m->rule.kccgst_value_at_pos[i];
-        append_expanded_kccgst_value(m, &m->kccgst[kccgst], value);
+        append_expanded_kccgst_value(m, s, &m->kccgst[kccgst], value);
     }
 
     /*
@@ -791,24 +912,24 @@ matcher_rule_apply_if_matches(struct matcher *m)
 }
 
 static enum rules_token
-gettok(struct matcher *m)
+gettok(struct matcher *m, struct scanner *s)
 {
-    return lex(&m->scanner, &m->val);
+    return lex(s, &m->val);
 }
 
 static bool
-matcher_match(struct matcher *m, const char *string, size_t len,
-              const char *file_name, struct xkb_component_names *out)
+matcher_match(struct matcher *m, struct scanner *s,
+              unsigned include_depth,
+              const char *string, size_t len,
+              const char *file_name)
 {
     enum rules_token tok;
 
     if (!m)
         return false;
 
-    scanner_init(&m->scanner, m->ctx, string, len, file_name);
-
 initial:
-    switch (tok = gettok(m)) {
+    switch (tok = gettok(m, s)) {
     case TOK_BANG:
         goto bang;
     case TOK_END_OF_LINE:
@@ -820,20 +941,22 @@ initial:
     }
 
 bang:
-    switch (tok = gettok(m)) {
+    switch (tok = gettok(m, s)) {
     case TOK_GROUP_NAME:
         matcher_group_start_new(m, m->val.string);
         goto group_name;
+    case TOK_INCLUDE:
+        goto include_statement;
     case TOK_IDENTIFIER:
         matcher_mapping_start_new(m);
-        matcher_mapping_set_mlvo(m, m->val.string);
+        matcher_mapping_set_mlvo(m, s, m->val.string);
         goto mapping_mlvo;
     default:
         goto unexpected;
     }
 
 group_name:
-    switch (tok = gettok(m)) {
+    switch (tok = gettok(m, s)) {
     case TOK_EQUALS:
         goto group_element;
     default:
@@ -841,9 +964,9 @@ group_name:
     }
 
 group_element:
-    switch (tok = gettok(m)) {
+    switch (tok = gettok(m, s)) {
     case TOK_IDENTIFIER:
-        matcher_group_add_element(m, m->val.string);
+        matcher_group_add_element(m, s, m->val.string);
         goto group_element;
     case TOK_END_OF_LINE:
         goto initial;
@@ -851,11 +974,20 @@ group_element:
         goto unexpected;
     }
 
+include_statement:
+    switch (tok = gettok(m, s)) {
+    case TOK_IDENTIFIER:
+        matcher_include(m, s, include_depth, m->val.string);
+        goto initial;
+    default:
+        goto unexpected;
+    }
+
 mapping_mlvo:
-    switch (tok = gettok(m)) {
+    switch (tok = gettok(m, s)) {
     case TOK_IDENTIFIER:
         if (!m->mapping.skip)
-            matcher_mapping_set_mlvo(m, m->val.string);
+            matcher_mapping_set_mlvo(m, s, m->val.string);
         goto mapping_mlvo;
     case TOK_EQUALS:
         goto mapping_kccgst;
@@ -864,21 +996,21 @@ mapping_mlvo:
     }
 
 mapping_kccgst:
-    switch (tok = gettok(m)) {
+    switch (tok = gettok(m, s)) {
     case TOK_IDENTIFIER:
         if (!m->mapping.skip)
-            matcher_mapping_set_kccgst(m, m->val.string);
+            matcher_mapping_set_kccgst(m, s, m->val.string);
         goto mapping_kccgst;
     case TOK_END_OF_LINE:
         if (!m->mapping.skip)
-            matcher_mapping_verify(m);
+            matcher_mapping_verify(m, s);
         goto rule_mlvo_first;
     default:
         goto unexpected;
     }
 
 rule_mlvo_first:
-    switch (tok = gettok(m)) {
+    switch (tok = gettok(m, s)) {
     case TOK_BANG:
         goto bang;
     case TOK_END_OF_LINE:
@@ -891,20 +1023,20 @@ rule_mlvo_first:
     }
 
 rule_mlvo:
-    tok = gettok(m);
+    tok = gettok(m, s);
 rule_mlvo_no_tok:
     switch (tok) {
     case TOK_IDENTIFIER:
         if (!m->rule.skip)
-            matcher_rule_set_mlvo(m, m->val.string);
+            matcher_rule_set_mlvo(m, s, m->val.string);
         goto rule_mlvo;
     case TOK_STAR:
         if (!m->rule.skip)
-            matcher_rule_set_mlvo_wildcard(m);
+            matcher_rule_set_mlvo_wildcard(m, s);
         goto rule_mlvo;
     case TOK_GROUP_NAME:
         if (!m->rule.skip)
-            matcher_rule_set_mlvo_group(m, m->val.string);
+            matcher_rule_set_mlvo_group(m, s, m->val.string);
         goto rule_mlvo;
     case TOK_EQUALS:
         goto rule_kccgst;
@@ -913,16 +1045,16 @@ rule_mlvo_no_tok:
     }
 
 rule_kccgst:
-    switch (tok = gettok(m)) {
+    switch (tok = gettok(m, s)) {
     case TOK_IDENTIFIER:
         if (!m->rule.skip)
-            matcher_rule_set_kccgst(m, m->val.string);
+            matcher_rule_set_kccgst(m, s, m->val.string);
         goto rule_kccgst;
     case TOK_END_OF_LINE:
         if (!m->rule.skip)
-            matcher_rule_verify(m);
+            matcher_rule_verify(m, s);
         if (!m->rule.skip)
-            matcher_rule_apply_if_matches(m);
+            matcher_rule_apply_if_matches(m, s);
         goto rule_mlvo_first;
     default:
         goto unexpected;
@@ -937,28 +1069,43 @@ unexpected:
     }
 
 finish:
-    if (darray_empty(m->kccgst[KCCGST_KEYCODES]) ||
-        darray_empty(m->kccgst[KCCGST_TYPES]) ||
-        darray_empty(m->kccgst[KCCGST_COMPAT]) ||
-        /* darray_empty(m->kccgst[KCCGST_GEOMETRY]) || */
-        darray_empty(m->kccgst[KCCGST_SYMBOLS]))
-        goto error;
-
-    out->keycodes = darray_mem(m->kccgst[KCCGST_KEYCODES], 0);
-    out->types = darray_mem(m->kccgst[KCCGST_TYPES], 0);
-    out->compat = darray_mem(m->kccgst[KCCGST_COMPAT], 0);
-    /* out->geometry = darray_mem(m->kccgst[KCCGST_GEOMETRY], 0); */
-    darray_free(m->kccgst[KCCGST_GEOMETRY]);
-    out->symbols = darray_mem(m->kccgst[KCCGST_SYMBOLS], 0);
-
     return true;
 
 state_error:
-    matcher_err(m, "unexpected token");
+    scanner_err(s, "unexpected token");
 error:
     return false;
 }
 
+static bool
+read_rules_file(struct xkb_context *ctx,
+                struct matcher *matcher,
+                unsigned include_depth,
+                FILE *file,
+                const char *path)
+{
+    bool ret = false;
+    char *string;
+    size_t size;
+    struct scanner scanner;
+
+    ret = map_file(file, &string, &size);
+    if (!ret) {
+        log_err(ctx, XKB_LOG_MESSAGE_NO_ID,
+                "Couldn't read rules file \"%s\": %s\n",
+                path, strerror(errno));
+        goto out;
+    }
+
+    scanner_init(&scanner, matcher->ctx, string, size, path, NULL);
+
+    ret = matcher_match(matcher, &scanner, include_depth, string, size, path);
+
+    unmap_file(string, size);
+out:
+    return ret;
+}
+
 bool
 xkb_components_from_rules(struct xkb_context *ctx,
                           const struct xkb_rule_names *rmlvo,
@@ -966,32 +1113,61 @@ xkb_components_from_rules(struct xkb_context *ctx,
 {
     bool ret = false;
     FILE *file;
-    char *path;
-    const char *string;
-    size_t size;
-    struct matcher *matcher;
+    char *path = NULL;
+    struct matcher *matcher = NULL;
+    struct matched_sval *mval;
+    unsigned int offset = 0;
 
-    file = FindFileInXkbPath(ctx, rmlvo->rules, FILE_TYPE_RULES, &path);
+    file = FindFileInXkbPath(ctx, rmlvo->rules, FILE_TYPE_RULES, &path, &offset);
     if (!file)
         goto err_out;
 
-    ret = map_file(file, &string, &size);
-    if (!ret) {
-        log_err(ctx, "Couldn't read rules file \"%s\": %s\n",
-                path, strerror(errno));
-        goto err_file;
+    matcher = matcher_new(ctx, rmlvo);
+
+    ret = read_rules_file(ctx, matcher, 0, file, path);
+    if (!ret ||
+        darray_empty(matcher->kccgst[KCCGST_KEYCODES]) ||
+        darray_empty(matcher->kccgst[KCCGST_TYPES]) ||
+        darray_empty(matcher->kccgst[KCCGST_COMPAT]) ||
+        /* darray_empty(matcher->kccgst[KCCGST_GEOMETRY]) || */
+        darray_empty(matcher->kccgst[KCCGST_SYMBOLS])) {
+        log_err(ctx, XKB_LOG_MESSAGE_NO_ID,
+                "No components returned from XKB rules \"%s\"\n", path);
+        ret = false;
+        goto err_out;
     }
 
-    matcher = matcher_new(ctx, rmlvo);
-    ret = matcher_match(matcher, string, size, path, out);
-    if (!ret)
-        log_err(ctx, "No components returned from XKB rules \"%s\"\n", path);
-    matcher_free(matcher);
+    darray_steal(matcher->kccgst[KCCGST_KEYCODES], &out->keycodes, NULL);
+    darray_steal(matcher->kccgst[KCCGST_TYPES], &out->types, NULL);
+    darray_steal(matcher->kccgst[KCCGST_COMPAT], &out->compat, NULL);
+    darray_steal(matcher->kccgst[KCCGST_SYMBOLS], &out->symbols, NULL);
+    darray_free(matcher->kccgst[KCCGST_GEOMETRY]);
+
+    mval = &matcher->rmlvo.model;
+    if (!mval->matched && mval->sval.len > 0)
+        log_err(matcher->ctx, XKB_LOG_MESSAGE_NO_ID,
+                "Unrecognized RMLVO model \"%.*s\" was ignored\n",
+                mval->sval.len, mval->sval.start);
+    darray_foreach(mval, matcher->rmlvo.layouts)
+        if (!mval->matched && mval->sval.len > 0)
+            log_err(matcher->ctx, XKB_LOG_MESSAGE_NO_ID,
+                    "Unrecognized RMLVO layout \"%.*s\" was ignored\n",
+                    mval->sval.len, mval->sval.start);
+    darray_foreach(mval, matcher->rmlvo.variants)
+        if (!mval->matched && mval->sval.len > 0)
+            log_err(matcher->ctx, XKB_LOG_MESSAGE_NO_ID,
+                    "Unrecognized RMLVO variant \"%.*s\" was ignored\n",
+                    mval->sval.len, mval->sval.start);
+    darray_foreach(mval, matcher->rmlvo.options)
+        if (!mval->matched && mval->sval.len > 0)
+            log_err(matcher->ctx, XKB_LOG_MESSAGE_NO_ID,
+                    "Unrecognized RMLVO option \"%.*s\" was ignored\n",
+                    mval->sval.len, mval->sval.start);
 
-    unmap_file(string, size);
-err_file:
-    free(path);
-    fclose(file);
 err_out:
+    if (file)
+        fclose(file);
+    matcher_free(matcher);
+    free(path);
     return ret;
 }