Edit

kc3-lang/libxkbcommon/src/xkbcomp/rules.c

Branch :

  • Show log

    Commit

  • Author : Daniel Stone
    Date : 2012-08-03 03:22:48
    Hash : 42b2c934
    Message : Print failed include paths on failure to find rules Thus giving a hint as to which directory we're trying to find. Signed-off-by: Daniel Stone <daniel@fooishbar.org>

  • src/xkbcomp/rules.c
  • /************************************************************
     * Copyright (c) 1996 by Silicon Graphics Computer Systems, Inc.
     *
     * Permission to use, copy, modify, and distribute this
     * software and its documentation for any purpose and without
     * fee is hereby granted, provided that the above copyright
     * notice appear in all copies and that both that copyright
     * notice and this permission notice appear in supporting
     * documentation, and that the name of Silicon Graphics not be
     * used in advertising or publicity pertaining to distribution
     * of the software without specific prior written permission.
     * Silicon Graphics makes no representation about the suitability
     * of this software for any purpose. It is provided "as is"
     * without any express or implied warranty.
     *
     * SILICON GRAPHICS DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS
     * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
     * AND FITNESS FOR A PARTICULAR PURPOSE. IN NO EVENT SHALL SILICON
     * GRAPHICS BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL
     * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
     * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
     * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION  WITH
     * THE USE OR PERFORMANCE OF THIS SOFTWARE.
     *
     ********************************************************/
    
    #include <stdio.h>
    #include <ctype.h>
    
    #include "rules.h"
    #include "path.h"
    
    static bool
    input_line_get(struct xkb_context *ctx, FILE *file, darray_char *line)
    {
        int ch;
        bool end_of_file = false;
        bool space_pending;
        bool slash_pending;
        bool in_comment;
    
        while (!end_of_file && darray_empty(*line)) {
            space_pending = slash_pending = in_comment = false;
    
            while ((ch = getc(file)) != '\n' && ch != EOF) {
                if (ch == '\\') {
                    ch = getc(file);
    
                    if (ch == EOF)
                        break;
    
                    if (ch == '\n') {
                        in_comment = false;
                        ch = ' ';
                    }
                }
    
                if (in_comment)
                    continue;
    
                if (ch == '/') {
                    if (slash_pending) {
                        in_comment = true;
                        slash_pending = false;
                    }
                    else {
                        slash_pending = true;
                    }
    
                    continue;
                }
    
                if (slash_pending) {
                    if (space_pending) {
                        darray_append(*line, ' ');
                        space_pending = false;
                    }
    
                    darray_append(*line, '/');
                    slash_pending = false;
                }
    
                if (isspace(ch)) {
                    while (isspace(ch) && ch != '\n' && ch != EOF)
                        ch = getc(file);
    
                    if (ch == EOF)
                        break;
    
                    if (ch != '\n' && !darray_empty(*line))
                        space_pending = true;
    
                    ungetc(ch, file);
                }
                else {
                    if (space_pending) {
                        darray_append(*line, ' ');
                        space_pending = false;
                    }
    
                    if (ch == '!') {
                        if (!darray_empty(*line)) {
                            log_warn(ctx,
                                     "The '!' is legal only at start of line; "
                                     "Line containing '!' ignored\n");
                            darray_resize(*line, 0);
                            break;
                        }
                    }
    
                    darray_append(*line, ch);
                }
            }
    
            if (ch == EOF)
                end_of_file = true;
        }
    
        if (darray_empty(*line) && end_of_file)
            return false;
    
        darray_append(*line, '\0');
        return true;
    }
    
    /***====================================================================***/
    
    enum {
        /* "Parts" - the MLVO which rules file maps to components. */
        MODEL = 0,
        LAYOUT,
        VARIANT,
        OPTION,
    
    #define PART_MASK \
        ((1 << MODEL) | (1 << LAYOUT) | (1 << VARIANT) | (1 << OPTION))
    
        /* Components */
        KEYCODES,
        SYMBOLS,
        TYPES,
        COMPAT,
        GEOMETRY,
    
    #define COMPONENT_MASK \
        ((1 << KEYCODES) | (1 << SYMBOLS) | (1 << TYPES) | (1 << COMPAT) | \
         (1 << GEOMETRY))
    
        MAX_WORDS
    };
    
    static const char *cname[] = {
        [MODEL] = "model",
        [LAYOUT] = "layout",
        [VARIANT] = "variant",
        [OPTION] = "option",
    
        [KEYCODES] = "keycodes",
        [SYMBOLS] = "symbols",
        [TYPES] = "types",
        [COMPAT] = "compat",
        [GEOMETRY] = "geometry",
    };
    
    struct multi_defs {
        const char *model;
        const char *layout[XkbNumKbdGroups + 1];
        const char *variant[XkbNumKbdGroups + 1];
        char *options;
    };
    
    struct mapping {
        /* Sequential id for the mappings. */
        int number;
        size_t num_maps;
    
        struct {
            int word;
            int index;
        } map[MAX_WORDS];
    };
    
    struct var_desc {
        char *name;
        char *desc;
    };
    
    struct group {
        int number;
        char *name;
        char *words;
    };
    
    enum rule_flag {
        RULE_FLAG_PENDING_MATCH = (1L << 1),
        RULE_FLAG_OPTION = (1L << 2),
        RULE_FLAG_APPEND = (1L << 3),
        RULE_FLAG_NORMAL = (1L << 4),
    };
    
    struct rule {
        int number;
    
        char *model;
        char *layout;
        int layout_num;
        char *variant;
        int variant_num;
        char *option;
    
        /* yields */
    
        char *keycodes;
        char *symbols;
        char *types;
        char *compat;
        unsigned flags;
    };
    
    struct rules {
        darray(struct rule) rules;
        darray(struct group) groups;
    };
    
    /***====================================================================***/
    
    /*
     * Resolve numeric index, such as "[4]" in layout[4]. Missing index
     * means zero.
     */
    static char *
    get_index(char *str, int *ndx)
    {
        int empty = 0, consumed = 0, num;
    
        sscanf(str, "[%n%d]%n", &empty, &num, &consumed);
        if (consumed > 0) {
            *ndx = num;
            str += consumed;
        }
        else if (empty > 0) {
            *ndx = -1;
        }
        else {
            *ndx = 0;
        }
    
        return str;
    }
    
    /*
     * Match a mapping line which opens a rule, e.g:
     * ! model      layout[4]       variant[4]      =       symbols       geometry
     * Which will be followed by lines such as:
     *   *          ben             basic           =       +in(ben):4    nec(pc98)
     * So if the MLVO matches the LHS of some line, we'll get the components
     * on the RHS.
     * In this example, we will get for the second and fourth columns:
     * mapping->map[1] = {.word = LAYOUT, .index = 4}
     * mapping->map[3] = {.word = SYMBOLS, .index = 0}
     */
    static void
    match_mapping_line(struct xkb_context *ctx, darray_char *line,
                       struct mapping *mapping)
    {
        char *tok;
        char *str = darray_mem(*line, 1);
        unsigned present = 0, layout_ndx_present = 0, variant_ndx_present = 0;
        int i, tmp;
        size_t len;
        int ndx;
        char *strtok_buf;
        bool found;
    
        /*
         * Remember the last sequential mapping id (incremented if the match
         * is successful).
         */
        tmp = mapping->number;
        memset(mapping, 0, sizeof(*mapping));
        mapping->number = tmp;
    
        while ((tok = strtok_r(str, " ", &strtok_buf)) != NULL) {
            found = false;
            str = NULL;
    
            if (streq(tok, "="))
                continue;
    
            for (i = 0; i < MAX_WORDS; i++) {
                len = strlen(cname[i]);
    
                if (strncmp(cname[i], tok, len) == 0) {
                    if (strlen(tok) > len) {
                        char *end = get_index(tok + len, &ndx);
    
                        if ((i != LAYOUT && i != VARIANT) ||
                            *end != '\0' || ndx == -1) {
                            log_warn(ctx,
                                     "Illegal %s index: %d\n", cname[i], ndx);
                            log_warn(ctx, "Can only index layout and variant\n");
                            break;
                        }
    
                        if (ndx < 1 || ndx > XkbNumKbdGroups) {
                            log_warn(ctx, "Illegal %s index: %d\n",
                                     cname[i], ndx);
                            log_warn(ctx, "Index must be in range 1..%d\n",
                                     XkbNumKbdGroups);
                            break;
                        }
                    }
                    else {
                        ndx = 0;
                    }
    
                    found = true;
    
                    if (present & (1 << i)) {
                        if ((i == LAYOUT && layout_ndx_present & (1 << ndx)) ||
                            (i == VARIANT && variant_ndx_present & (1 << ndx))) {
                            log_warn(ctx,
                                     "Component \"%s\" listed twice; "
                                     "Second definition ignored\n", tok);
                            break;
                        }
                    }
    
                    present |= (1 << i);
                    if (i == LAYOUT)
                        layout_ndx_present |= 1 << ndx;
                    if (i == VARIANT)
                        variant_ndx_present |= 1 << ndx;
    
                    mapping->map[mapping->num_maps].word = i;
                    mapping->map[mapping->num_maps].index = ndx;
                    mapping->num_maps++;
                    break;
                }
            }
    
            if (!found)
                log_warn(ctx, "Unknown component \"%s\"; Ignored\n", tok);
        }
    
        if ((present & PART_MASK) == 0) {
            log_warn(ctx,
                     "Mapping needs at least one MLVO part; "
                     "Illegal mapping ignored\n");
            mapping->num_maps = 0;
            return;
        }
    
        if ((present & COMPONENT_MASK) == 0) {
            log_warn(ctx,
                     "Mapping needs at least one component; "
                     "Illegal mapping ignored\n");
            mapping->num_maps = 0;
            return;
        }
    
        mapping->number++;
    }
    
    /*
     * Match a line such as:
     * ! $pcmodels = pc101 pc102 pc104 pc105
     */
    static bool
    match_group_line(darray_char *line, struct group *group)
    {
        int i;
        char *name = strchr(darray_mem(*line, 0), '$');
        char *words = strchr(name, ' ');
    
        if (!words)
            return false;
    
        *words++ = '\0';
    
        for (; *words; words++) {
            if (*words != '=' && *words != ' ')
                break;
        }
    
        if (*words == '\0')
            return false;
    
        group->name = strdup(name);
        group->words = strdup(words);
    
        words = group->words;
        for (i = 1; *words; words++) {
            if (*words == ' ') {
                *words++ = '\0';
                i++;
            }
        }
        group->number = i;
    
        return true;
    
    }
    
    /* Match lines following a mapping (see match_mapping_line comment). */
    static bool
    match_rule_line(struct xkb_context *ctx, darray_char *line,
                    struct mapping *mapping, struct rule *rule)
    {
        char *str, *tok;
        int nread, i;
        char *strtok_buf;
        bool append = false;
        const char *names[MAX_WORDS] = { NULL };
    
        if (mapping->num_maps == 0) {
            log_warn(ctx,
                     "Must have a mapping before first line of data; "
                     "Illegal line of data ignored\n");
            return false;
        }
    
        str = darray_mem(*line, 0);
    
        for (nread = 0; (tok = strtok_r(str, " ", &strtok_buf)) != NULL;
             nread++) {
            str = NULL;
    
            if (streq(tok, "=")) {
                nread--;
                continue;
            }
    
            if (nread > mapping->num_maps) {
                log_warn(ctx,
                         "Too many words on a line; "
                         "Extra word \"%s\" ignored\n", tok);
                continue;
            }
    
            names[mapping->map[nread].word] = tok;
            if (*tok == '+' || *tok == '|')
                append = true;
        }
    
        if (nread < mapping->num_maps) {
            log_warn(ctx, "Too few words on a line: %s; Line ignored\n",
                     darray_mem(*line, 0));
            return false;
        }
    
        rule->flags = 0;
        rule->number = mapping->number;
    
        if (names[OPTION])
            rule->flags |= RULE_FLAG_OPTION;
        else if (append)
            rule->flags |= RULE_FLAG_APPEND;
        else
            rule->flags |= RULE_FLAG_NORMAL;
    
        rule->model = strdup_safe(names[MODEL]);
        rule->layout = strdup_safe(names[LAYOUT]);
        rule->variant = strdup_safe(names[VARIANT]);
        rule->option = strdup_safe(names[OPTION]);
    
        rule->keycodes = strdup_safe(names[KEYCODES]);
        rule->symbols = strdup_safe(names[SYMBOLS]);
        rule->types = strdup_safe(names[TYPES]);
        rule->compat = strdup_safe(names[COMPAT]);
    
        rule->layout_num = rule->variant_num = 0;
        for (i = 0; i < nread; i++) {
            if (mapping->map[i].index) {
                if (mapping->map[i].word == LAYOUT)
                    rule->layout_num = mapping->map[i].index;
                if (mapping->map[i].word == VARIANT)
                    rule->variant_num = mapping->map[i].index;
            }
        }
    
        return true;
    }
    
    static bool
    match_line(struct xkb_context *ctx, darray_char *line,
               struct mapping *mapping, struct rule *rule,
               struct group *group)
    {
        if (darray_item(*line, 0) != '!')
            return match_rule_line(ctx, line, mapping, rule);
    
        if (darray_item(*line, 1) == '$' ||
            (darray_item(*line, 1) == ' ' && darray_item(*line, 2) == '$'))
            return match_group_line(line, group);
    
        match_mapping_line(ctx, line, mapping);
        return false;
    }
    
    static void
    squeeze_spaces(char *p1)
    {
        char *p2;
    
        for (p2 = p1; *p2; p2++) {
            *p1 = *p2;
            if (*p1 != ' ')
                p1++;
        }
    
        *p1 = '\0';
    }
    
    /*
     * Expand the layout and variant of the rule_names and remove extraneous
     * spaces. If there's one layout/variant, it is kept in
     * .layout[0]/.variant[0], else is kept in [1], [2] and so on, and [0]
     * remains empty. For example, this rule_names:
     *      .model  = "pc105",
     *      .layout = "us,il,ru,ca"
     *      .variant = ",,,multix"
     *      .options = "grp:alts_toggle,   ctrl:nocaps,  compose:rwin"
     * Is expanded into this multi_defs:
     *      .model = "pc105"
     *      .layout = {NULL, "us", "il", "ru", "ca"},
     *      .variant = {NULL, "", "", "", "multix"},
     *      .options = "grp:alts_toggle,ctrl:nocaps,compose:rwin"
     */
    static bool
    make_multi_defs(struct multi_defs *mdefs, const struct xkb_rule_names *mlvo)
    {
        char *p;
        int i;
    
        memset(mdefs, 0, sizeof(*mdefs));
    
        if (mlvo->model) {
            mdefs->model = mlvo->model;
        }
    
        if (mlvo->options) {
            mdefs->options = strdup(mlvo->options);
            if (mdefs->options == NULL)
                return false;
    
            squeeze_spaces(mdefs->options);
        }
    
        if (mlvo->layout) {
            if (!strchr(mlvo->layout, ',')) {
                mdefs->layout[0] = mlvo->layout;
            }
            else {
                p = strdup(mlvo->layout);
                if (p == NULL)
                    return false;
    
                squeeze_spaces(p);
                mdefs->layout[1] = p;
    
                for (i = 2; i <= XkbNumKbdGroups; i++) {
                    if ((p = strchr(p, ','))) {
                        *p++ = '\0';
                        mdefs->layout[i] = p;
                    }
                    else {
                        break;
                    }
                }
    
                if (p && (p = strchr(p, ',')))
                    *p = '\0';
            }
        }
    
        if (mlvo->variant) {
            if (!strchr(mlvo->variant, ',')) {
                mdefs->variant[0] = mlvo->variant;
            }
            else {
                p = strdup(mlvo->variant);
                if (p == NULL)
                    return false;
    
                squeeze_spaces(p);
                mdefs->variant[1] = p;
    
                for (i = 2; i <= XkbNumKbdGroups; i++) {
                    if ((p = strchr(p, ','))) {
                        *p++ = '\0';
                        mdefs->variant[i] = p;
                    }
                    else {
                        break;
                    }
                }
    
                if (p && (p = strchr(p, ',')))
                    *p = '\0';
            }
        }
    
        return true;
    }
    
    static void
    free_multi_defs(struct multi_defs *defs)
    {
        free(defs->options);
        /*
         * See make_multi_defs comment for the hack; the same strdup'd
         * string is split among the indexes, but the one in [0] is const.
         */
        free(UNCONSTIFY(defs->layout[1]));
        free(UNCONSTIFY(defs->variant[1]));
    }
    
    /* See apply_rule below. */
    static void
    apply(const char *src, char **dst)
    {
        int ret;
        char *tmp;
    
        if (!src)
            return;
    
        if (*src == '+' || *src == '!') {
            tmp = *dst;
            ret = asprintf(dst, "%s%s", *dst, src);
            if (ret < 0)
                *dst = NULL;
            free(tmp);
        }
        else if (*dst == NULL) {
            *dst = strdup(src);
        }
    }
    
    /*
     * Add the info from the matching rule to the resulting
     * xkb_component_names. If we already had a match for something
     * (e.g. keycodes), and the rule is not an appending one (e.g.
     * +whatever), than we don't override but drop the new one.
     */
    static void
    apply_rule(struct rule *rule, struct xkb_component_names *kccgst)
    {
        /* Clear the flag because it's applied. */
        rule->flags &= ~RULE_FLAG_PENDING_MATCH;
    
        apply(rule->keycodes, &kccgst->keycodes);
        apply(rule->symbols, &kccgst->symbols);
        apply(rule->types, &kccgst->types);
        apply(rule->compat, &kccgst->compat);
    }
    
    /*
     * Match if name is part of the group, e.g. if the following
     * group is defined:
     *      ! $qwertz = al cz de hr hu ro si sk
     * then
     *      match_group_member(rules, "qwertz", "hr")
     * will return true.
     */
    static bool
    match_group_member(struct rules *rules, const char *group_name,
                       const char *name)
    {
        int i;
        const char *word;
        struct group *iter, *group = NULL;
    
        darray_foreach(iter, rules->groups) {
            if (streq(iter->name, group_name)) {
                group = iter;
                break;
            }
        }
    
        if (!group)
            return false;
    
        word = group->words;
        for (i = 0; i < group->number; i++, word += strlen(word) + 1)
            if (streq(word, name))
                return true;
    
        return false;
    }
    
    /* Match @needle out of @sep-seperated @haystack. */
    static bool
    match_one_of(const char *haystack, const char *needle, char sep)
    {
        const char *s = haystack;
        const size_t len = strlen(needle);
    
        do {
            if (strncmp(s, needle, len) == 0 && (s[len] == '\0' || s[len] == sep))
                return true;
            s = strchr(s, sep);
        } while (s++);
    
        return false;
    }
    
    static int
    apply_rule_if_matches(struct rules *rules, struct rule *rule,
                          struct multi_defs *mdefs,
                          struct xkb_component_names *kccgst)
    {
        bool pending = false;
    
        if (rule->model) {
            if (mdefs->model == NULL)
                return 0;
    
            if (streq(rule->model, "*")) {
                pending = true;
            }
            else if (rule->model[0] == '$') {
                if (!match_group_member(rules, rule->model, mdefs->model))
                    return 0;
            }
            else if (!streq(rule->model, mdefs->model)) {
                return 0;
            }
        }
    
        if (rule->option) {
            if (mdefs->options == NULL)
                return 0;
    
            if (!match_one_of(mdefs->options, rule->option, ','))
                return 0;
        }
    
        if (rule->layout) {
            if (mdefs->layout[rule->layout_num] == NULL)
                return 0;
    
            if (streq(rule->layout, "*")) {
                pending = true;
            }
            else if (rule->layout[0] == '$') {
                if (!match_group_member(rules, rule->layout,
                                        mdefs->layout[rule->layout_num]))
                    return 0;
            }
            else if (!streq(rule->layout, mdefs->layout[rule->layout_num])) {
                return 0;
            }
        }
    
        if (rule->variant) {
            if (mdefs->variant[rule->variant_num] == NULL)
                return 0;
    
            if (streq(rule->variant, "*")) {
                pending = true;
            }
            else if (rule->variant[0] == '$') {
                if (!match_group_member(rules, rule->variant,
                                        mdefs->variant[rule->variant_num]))
                    return 0;
            }
            else if (!streq(rule->variant, mdefs->variant[rule->variant_num])) {
                return 0;
            }
        }
    
        if (pending) {
            rule->flags |= RULE_FLAG_PENDING_MATCH;
        }
        else {
            /* Exact match, apply it now. */
            apply_rule(rule, kccgst);
        }
    
        return rule->number;
    }
    
    static void
    clear_partial_matches(struct rules *rules)
    {
        struct rule *rule;
    
        darray_foreach(rule, rules->rules)
            rule->flags &= ~RULE_FLAG_PENDING_MATCH;
    }
    
    static void
    apply_partial_matches(struct rules *rules, struct xkb_component_names *kccgst)
    {
        struct rule *rule;
    
        darray_foreach(rule, rules->rules)
            if (rule->flags & RULE_FLAG_PENDING_MATCH)
                apply_rule(rule, kccgst);
    }
    
    static void
    apply_matching_rules(struct rules *rules, struct multi_defs *mdefs,
                         struct xkb_component_names *kccgst, unsigned int flags)
    {
        int skip = -1;
        struct rule *rule;
    
        darray_foreach(rule, rules->rules) {
            if ((rule->flags & flags) != flags)
                continue;
    
            if ((flags & RULE_FLAG_OPTION) == 0 && rule->number == skip)
                continue;
    
            skip = apply_rule_if_matches(rules, rule, mdefs, kccgst);
        }
    }
    
    /***====================================================================***/
    
    static char *
    substitute_vars(char *name, struct multi_defs *mdefs)
    {
        char *str, *outstr, *var;
        char *orig = name;
        size_t len, extra_len;
        char pfx, sfx;
        int ndx;
    
        if (!name)
            return NULL;
    
        str = strchr(name, '%');
        if (str == NULL)
            return name;
    
        len = strlen(name);
    
        while (str != NULL) {
            pfx = str[1];
            extra_len = 0;
    
            if (pfx == '+' || pfx == '|' || pfx == '_' || pfx == '-') {
                extra_len = 1;
                str++;
            }
            else if (pfx == '(') {
                extra_len = 2;
                str++;
            }
    
            var = str + 1;
            str = get_index(var + 1, &ndx);
            if (ndx == -1) {
                str = strchr(str, '%');
                continue;
            }
    
            if (*var == 'l' && mdefs->layout[ndx] && *mdefs->layout[ndx])
                len += strlen(mdefs->layout[ndx]) + extra_len;
            else if (*var == 'm' && mdefs->model)
                len += strlen(mdefs->model) + extra_len;
            else if (*var == 'v' && mdefs->variant[ndx] && *mdefs->variant[ndx])
                len += strlen(mdefs->variant[ndx]) + extra_len;
    
            if (pfx == '(' && *str == ')')
                str++;
    
            str = strchr(&str[0], '%');
        }
    
        name = malloc(len + 1);
        str = orig;
        outstr = name;
    
        while (*str != '\0') {
            if (str[0] == '%') {
                str++;
                pfx = str[0];
                sfx = '\0';
    
                if (pfx == '+' || pfx == '|' || pfx == '_' || pfx == '-') {
                    str++;
                }
                else if (pfx == '(') {
                    sfx = ')';
                    str++;
                }
                else {
                    pfx = '\0';
                }
    
                var = str;
                str = get_index(var + 1, &ndx);
                if (ndx == -1)
                    continue;
    
                if (*var == 'l' && mdefs->layout[ndx] && *mdefs->layout[ndx]) {
                    if (pfx)
                        *outstr++ = pfx;
    
                    strcpy(outstr, mdefs->layout[ndx]);
                    outstr += strlen(mdefs->layout[ndx]);
    
                    if (sfx)
                        *outstr++ = sfx;
                }
                else if (*var == 'm' && mdefs->model) {
                    if (pfx)
                        *outstr++ = pfx;
    
                    strcpy(outstr, mdefs->model);
                    outstr += strlen(mdefs->model);
    
                    if (sfx)
                        *outstr++ = sfx;
                }
                else if (*var == 'v' && mdefs->variant[ndx] &&
                         *mdefs->variant[ndx]) {
                    if (pfx)
                        *outstr++ = pfx;
    
                    strcpy(outstr, mdefs->variant[ndx]);
                    outstr += strlen(mdefs->variant[ndx]);
    
                    if (sfx)
                        *outstr++ = sfx;
                }
    
                if (pfx == '(' && *str == ')')
                    str++;
            }
            else {
                *outstr++ = *str++;
            }
        }
    
        *outstr++ = '\0';
    
        if (orig != name)
            free(orig);
    
        return name;
    }
    
    /***====================================================================***/
    
    static bool
    get_components(struct rules *rules, const struct xkb_rule_names *mlvo,
                   struct xkb_component_names *kccgst)
    {
        struct multi_defs mdefs;
    
        make_multi_defs(&mdefs, mlvo);
    
        clear_partial_matches(rules);
    
        apply_matching_rules(rules, &mdefs, kccgst, RULE_FLAG_NORMAL);
        apply_partial_matches(rules, kccgst);
    
        apply_matching_rules(rules, &mdefs, kccgst, RULE_FLAG_APPEND);
        apply_partial_matches(rules, kccgst);
    
        apply_matching_rules(rules, &mdefs, kccgst, RULE_FLAG_OPTION);
        apply_partial_matches(rules, kccgst);
    
        kccgst->keycodes = substitute_vars(kccgst->keycodes, &mdefs);
        kccgst->symbols = substitute_vars(kccgst->symbols, &mdefs);
        kccgst->types = substitute_vars(kccgst->types, &mdefs);
        kccgst->compat = substitute_vars(kccgst->compat, &mdefs);
    
        free_multi_defs(&mdefs);
    
        return kccgst->keycodes && kccgst->symbols && kccgst->types &&
               kccgst->compat;
    }
    
    static struct rules *
    load_rules(struct xkb_context *ctx, FILE *file)
    {
        darray_char line;
        struct mapping mapping;
        struct rule trule;
        struct group tgroup;
        struct rules *rules;
    
        rules = calloc(1, sizeof(*rules));
        if (!rules)
            return NULL;
        darray_init(rules->rules);
        darray_growalloc(rules->rules, 16);
    
        memset(&mapping, 0, sizeof(mapping));
        memset(&tgroup, 0, sizeof(tgroup));
        darray_init(line);
        darray_growalloc(line, 128);
    
        while (input_line_get(ctx, file, &line)) {
            if (match_line(ctx, &line, &mapping, &trule, &tgroup)) {
                if (tgroup.number) {
                    darray_append(rules->groups, tgroup);
                    memset(&tgroup, 0, sizeof(tgroup));
                }
                else {
                    darray_append(rules->rules, trule);
                    memset(&trule, 0, sizeof(trule));
                }
            }
    
            darray_resize(line, 0);
        }
    
        darray_free(line);
        return rules;
    }
    
    static void
    free_rules(struct rules *rules)
    {
        struct rule *rule;
        struct group *group;
    
        if (!rules)
            return;
    
        darray_foreach(rule, rules->rules) {
            free(rule->model);
            free(rule->layout);
            free(rule->variant);
            free(rule->option);
            free(rule->keycodes);
            free(rule->symbols);
            free(rule->types);
            free(rule->compat);
        }
        darray_free(rules->rules);
    
        darray_foreach(group, rules->groups) {
            free(group->name);
            free(group->words);
        }
        darray_free(rules->groups);
    
        free(rules);
    }
    
    struct xkb_component_names *
    xkb_components_from_rules(struct xkb_context *ctx,
                              const struct xkb_rule_names *rmlvo)
    {
        FILE *file;
        char *path;
        char **include;
        struct rules *rules;
        struct xkb_component_names *kccgst = NULL;
    
        file = XkbFindFileInPath(ctx, rmlvo->rules, FILE_TYPE_RULES, &path);
        if (!file) {
            log_err(ctx, "could not find \"%s\" rules in XKB path\n",
                    rmlvo->rules);
            log_err(ctx, "%zu include paths searched:\n",
                    darray_size(ctx->includes));
            darray_foreach(include, ctx->includes)
                log_err(ctx, "\t%s\n", *include);
            log_err(ctx, "%zu include paths could not be added:\n",
                    darray_size(ctx->failed_includes));
            darray_foreach(include, ctx->failed_includes)
                log_err(ctx, "\t%s\n", *include);
            return NULL;
        }
    
        rules = load_rules(ctx, file);
        if (!rules) {
            log_err(ctx, "failed to load XKB rules \"%s\"\n", path);
            goto err;
        }
    
        kccgst = calloc(1, sizeof(*kccgst));
        if (!kccgst) {
            log_err(ctx, "failed to allocate XKB components\n");
            goto err;
        }
    
        if (!get_components(rules, rmlvo, kccgst)) {
            free(kccgst->keycodes);
            free(kccgst->types);
            free(kccgst->compat);
            free(kccgst->symbols);
            free(kccgst);
            kccgst = NULL;
            log_err(ctx, "no components returned from XKB rules \"%s\"\n", path);
            goto err;
        }
    
    err:
        free_rules(rules);
        if (file)
            fclose(file);
        free(path);
        return kccgst;
    }