symbols: rename SymbolsInfo groupNames to group_names
[platform/upstream/libxkbcommon.git] / src / xkbcomp / symbols.c
index e07260d..a412495 100644 (file)
 #include "vmod.h"
 #include "keycodes.h"
 #include "include.h"
+#include "keysym.h"
 
 enum key_repeat {
+    KEY_REPEAT_UNDEFINED = 0,
     KEY_REPEAT_YES = 1,
-    KEY_REPEAT_NO = 0,
-    KEY_REPEAT_UNDEFINED = -1
+    KEY_REPEAT_NO = 2,
 };
 
 enum group_field {
@@ -79,23 +80,17 @@ enum key_field {
 };
 
 typedef struct {
-    enum group_field defined;
+    unsigned int num_syms;
+    unsigned int sym_index;
+    union xkb_action act;
+} LevelInfo;
 
-    xkb_level_index_t numLevels;
-    darray(xkb_keysym_t) syms;
-    /*
-     * symsMapIndex[level] -> The index from which the syms for the
-     * level begin in the syms array. Remember each keycode can have
-     * multiple keysyms in each level (that is, each key press can
-     * result in multiple keysyms).
-     */
-    darray(int) symsMapIndex;
-    /*
-     * symsMapNumEntries[level] -> How many syms are in
-     * syms[symsMapIndex[level]].
-     */
-    darray(size_t) symsMapNumEntries;
-    darray(union xkb_action) acts;
+typedef darray(xkb_keysym_t) darray_xkb_keysym_t;
+
+typedef struct {
+    enum group_field defined;
+    darray_xkb_keysym_t syms;
+    darray(LevelInfo) levels;
     xkb_atom_t type;
 } GroupInfo;
 
@@ -106,14 +101,14 @@ typedef struct _KeyInfo {
 
     unsigned long name; /* the 4 chars of the key name, as long */
 
-    GroupInfo groups[XKB_NUM_GROUPS];
+    darray(GroupInfo) groups;
 
     enum key_repeat repeat;
     xkb_mod_mask_t vmodmap;
     xkb_atom_t dfltType;
 
     enum xkb_range_exceed_type out_of_range_group_action;
-    xkb_group_index_t out_of_range_group_number;
+    xkb_layout_index_t out_of_range_group_number;
 } KeyInfo;
 
 static void
@@ -126,63 +121,28 @@ static void
 ClearGroupInfo(GroupInfo *groupi)
 {
     darray_free(groupi->syms);
-    darray_free(groupi->symsMapIndex);
-    darray_free(groupi->symsMapNumEntries);
-    darray_free(groupi->acts);
-    InitGroupInfo(groupi);
+    darray_free(groupi->levels);
 }
 
 static void
 InitKeyInfo(KeyInfo *keyi, unsigned file_id)
 {
-    xkb_group_index_t i;
-    static const char dflt[4] = "*";
+    static const char dflt_key_name[XKB_KEY_NAME_LENGTH] = "*";
 
-    keyi->defined = 0;
+    memset(keyi, 0, sizeof(*keyi));
     keyi->file_id = file_id;
     keyi->merge = MERGE_OVERRIDE;
-    keyi->name = KeyNameToLong(dflt);
-    for (i = 0; i < XKB_NUM_GROUPS; i++)
-        InitGroupInfo(&keyi->groups[i]);
-    keyi->dfltType = XKB_ATOM_NONE;
-    keyi->vmodmap = 0;
-    keyi->repeat = KEY_REPEAT_UNDEFINED;
+    keyi->name = KeyNameToLong(dflt_key_name);
     keyi->out_of_range_group_action = RANGE_WRAP;
-    keyi->out_of_range_group_number = 0;
 }
 
 static void
 ClearKeyInfo(KeyInfo *keyi)
 {
-    xkb_group_index_t i;
-
-    for (i = 0; i < XKB_NUM_GROUPS; i++)
-        ClearGroupInfo(&keyi->groups[i]);
-}
-
-static bool
-CopyKeyInfo(KeyInfo * old, KeyInfo * new, bool clearOld)
-{
-    xkb_group_index_t i;
-
-    *new = *old;
-
-    if (clearOld) {
-        for (i = 0; i < XKB_NUM_GROUPS; i++) {
-            InitGroupInfo(&old->groups[i]);
-        }
-    }
-    else {
-        for (i = 0; i < XKB_NUM_GROUPS; i++) {
-            GroupInfo *n = &new->groups[i], *o = &old->groups[i];
-            darray_copy(n->syms, o->syms);
-            darray_copy(n->symsMapIndex, o->symsMapIndex);
-            darray_copy(n->symsMapNumEntries, o->symsMapNumEntries);
-            darray_copy(n->acts, o->acts);
-        }
-    }
-
-    return true;
+    GroupInfo *groupi;
+    darray_foreach(groupi, keyi->groups)
+        ClearGroupInfo(groupi);
+    darray_free(keyi->groups);
 }
 
 /***====================================================================***/
@@ -202,12 +162,12 @@ typedef struct _SymbolsInfo {
     int errorCount;
     unsigned file_id;
     enum merge_mode merge;
-    xkb_group_index_t explicit_group;
+    xkb_layout_index_t explicit_group;
     darray(KeyInfo) keys;
     KeyInfo dflt;
     VModInfo vmods;
     ActionsInfo *actions;
-    xkb_atom_t groupNames[XKB_NUM_GROUPS];
+    xkb_atom_t group_names[XKB_NUM_GROUPS];
     darray(ModMapEntry) modMaps;
 
     struct xkb_keymap *keymap;
@@ -217,267 +177,198 @@ static void
 InitSymbolsInfo(SymbolsInfo *info, struct xkb_keymap *keymap,
                 unsigned file_id, ActionsInfo *actions)
 {
-    xkb_group_index_t i;
-
-    info->name = NULL;
-    info->explicit_group = 0;
-    info->errorCount = 0;
+    memset(info, 0, sizeof(*info));
+    info->keymap = keymap;
     info->file_id = file_id;
     info->merge = MERGE_OVERRIDE;
-    darray_init(info->keys);
-    darray_growalloc(info->keys, 110);
-    darray_init(info->modMaps);
-    for (i = 0; i < XKB_NUM_GROUPS; i++)
-        info->groupNames[i] = XKB_ATOM_NONE;
     InitKeyInfo(&info->dflt, file_id);
     InitVModInfo(&info->vmods, keymap);
     info->actions = actions;
-    info->keymap = keymap;
 }
 
 static void
 ClearSymbolsInfo(SymbolsInfo * info)
 {
     KeyInfo *keyi;
-
     free(info->name);
     darray_foreach(keyi, info->keys)
         ClearKeyInfo(keyi);
     darray_free(info->keys);
     darray_free(info->modMaps);
-    memset(info, 0, sizeof(SymbolsInfo));
+    ClearKeyInfo(&info->dflt);
 }
 
 static bool
-ResizeGroupInfo(GroupInfo *groupi, xkb_level_index_t numLevels,
-                unsigned sizeSyms, bool forceActions)
+MergeGroups(SymbolsInfo *info, GroupInfo *into, GroupInfo *from, bool clobber,
+            bool report, xkb_layout_index_t group, unsigned long key_name)
 {
-    xkb_level_index_t i;
-
-    if (darray_size(groupi->syms) < sizeSyms)
-        darray_resize0(groupi->syms, sizeSyms);
-
-    if (darray_empty(groupi->symsMapIndex) ||
-        groupi->numLevels < numLevels) {
-        darray_resize(groupi->symsMapIndex, numLevels);
-        for (i = groupi->numLevels; i < numLevels; i++)
-            darray_item(groupi->symsMapIndex, i) = -1;
-    }
+    xkb_level_index_t i, numLevels;
+    enum { INTO = (1 << 0), FROM = (1 << 1) } using;
 
-    if (darray_empty(groupi->symsMapNumEntries) ||
-        groupi->numLevels < numLevels)
-        darray_resize0(groupi->symsMapNumEntries, numLevels);
-
-    if ((forceActions && (groupi->numLevels < numLevels ||
-                          darray_empty(groupi->acts))) ||
-        (groupi->numLevels < numLevels && !darray_empty(groupi->acts)))
-        darray_resize0(groupi->acts, numLevels);
-
-    groupi->numLevels = MAX(groupi->numLevels, numLevels);
-
-    return true;
-}
+    /* First find the type of the merged group. */
+    if (into->type != from->type) {
+        if (from->type == XKB_ATOM_NONE) {
+        }
+        else if (into->type == XKB_ATOM_NONE) {
+            into->type = from->type;
+        }
+        else {
+            xkb_atom_t use = (clobber ? from->type : into->type);
+            xkb_atom_t ignore = (clobber ? into->type : from->type);
 
-enum key_group_selector {
-    NONE = 0,
-    FROM = (1 << 0),
-    TO = (1 << 1),
-};
+            if (report)
+                log_warn(info->keymap->ctx,
+                         "Multiple definitions for group %d type of key %s; "
+                         "Using %s, ignoring %s\n",
+                         group + 1, LongKeyNameText(key_name),
+                         xkb_atom_text(info->keymap->ctx, use),
+                         xkb_atom_text(info->keymap->ctx, ignore));
 
-/*
- * Merge @from into @into, where both are groups with the same index
- * for the same key, and have at least on level to merge.
- * @group and @key_name are just for reporting.
- */
-static bool
-MergeGroups(SymbolsInfo *info, GroupInfo *into, GroupInfo *from, bool clobber,
-            bool report, xkb_group_index_t group, unsigned long key_name)
-{
-    GroupInfo result;
-    unsigned int resultSize = 0;
-    enum key_group_selector using = NONE;
-    size_t cur_idx = 0;
-    xkb_level_index_t i;
+            into->type = use;
+        }
+    }
+    into->defined |= (from->defined & GROUP_FIELD_TYPE);
 
-    InitGroupInfo(&result);
+    /* Now look at the levels. */
 
-    if (into->numLevels >= from->numLevels) {
-        result.acts = into->acts;
-        result.numLevels = into->numLevels;
-    }
-    else {
-        result.acts = from->acts;
-        result.numLevels = from->numLevels;
-        darray_resize(into->symsMapIndex, from->numLevels);
-        darray_resize0(into->symsMapNumEntries, from->numLevels);
-        for (i = into->numLevels; i < from->numLevels; i++)
-            darray_item(into->symsMapIndex, i) = -1;
+    if (darray_empty(from->levels)) {
+        InitGroupInfo(from);
+        return true;
     }
 
-    if (darray_empty(result.acts) && (!darray_empty(into->acts) ||
-                                      !darray_empty(from->acts))) {
-        darray_resize0(result.acts, result.numLevels);
-        for (i = 0; i < result.numLevels; i++) {
-            union xkb_action *fromAct = NULL, *toAct = NULL;
+    if (darray_empty(into->levels)) {
+        from->type = into->type;
+        *into = *from;
+        InitGroupInfo(from);
+        return true;
+    }
 
-            if (!darray_empty(from->acts))
-                fromAct = &darray_item(from->acts, i);
+    /* First we merge the actions and ensure @into has all the levels. */
+    numLevels = MAX(darray_size(into->levels), darray_size(from->levels));
+    for (i = 0; i < numLevels; i++) {
+        union xkb_action *intoAct, *fromAct;
 
-            if (!darray_empty(into->acts))
-                toAct = &darray_item(into->acts, i);
+        if (i >= darray_size(from->levels))
+            continue;
 
-            if (((fromAct == NULL) || (fromAct->type == ACTION_TYPE_NONE))
-                && (toAct != NULL)) {
-                darray_item(result.acts, i) = *toAct;
-            }
-            else if (((toAct == NULL) || (toAct->type == ACTION_TYPE_NONE))
-                     && (fromAct != NULL)) {
-                darray_item(result.acts, i) = *fromAct;
-            }
-            else {
-                union xkb_action *use, *ignore;
-                if (clobber) {
-                    use = fromAct;
-                    ignore = toAct;
-                }
-                else {
-                    use = toAct;
-                    ignore = fromAct;
-                }
-                if (report)
-                    log_warn(info->keymap->ctx,
-                             "Multiple actions for level %d/group %u on key %s; "
-                             "Using %s, ignoring %s\n",
-                             i + 1, group + 1, LongKeyNameText(key_name),
-                             ActionTypeText(use->type),
-                             ActionTypeText(ignore->type));
-                if (use)
-                    darray_item(result.acts, i) = *use;
-            }
+        if (i >= darray_size(into->levels)) {
+            darray_append(into->levels, darray_item(from->levels, i));
+            darray_item(into->levels, i).num_syms = 0;
+            darray_item(into->levels, i).sym_index = 0;
+            continue;
         }
-    }
 
-    for (i = 0; i < result.numLevels; i++) {
-        unsigned int fromSize = 0;
-        unsigned int toSize = 0;
+        intoAct = &darray_item(into->levels, i).act;
+        fromAct = &darray_item(from->levels, i).act;
 
-        if (!darray_empty(from->symsMapNumEntries) && i < from->numLevels)
-            fromSize = darray_item(from->symsMapNumEntries, i);
-
-        if (!darray_empty(into->symsMapNumEntries) && i < into->numLevels)
-            toSize = darray_item(into->symsMapNumEntries, i);
-
-        if (fromSize == 0) {
-            resultSize += toSize;
-            using |= TO;
+        if (fromAct->type == ACTION_TYPE_NONE) {
         }
-        else if (toSize == 0 || clobber) {
-            resultSize += fromSize;
-            using |= FROM;
+        else if (intoAct->type == ACTION_TYPE_NONE) {
+            *intoAct = *fromAct;
         }
         else {
-            resultSize += toSize;
-            using |= TO;
-        }
-    }
+            union xkb_action *use = (clobber ? fromAct : intoAct);
+            union xkb_action *ignore = (clobber ? intoAct : fromAct);
 
-    if (resultSize == 0)
-        goto out;
+            if (report)
+                log_warn(info->keymap->ctx,
+                         "Multiple actions for level %d/group %u on key %s; "
+                         "Using %s, ignoring %s\n",
+                         i + 1, group + 1, LongKeyNameText(key_name),
+                         ActionTypeText(use->type),
+                         ActionTypeText(ignore->type));
 
-    if (using == FROM) {
-        result.syms = from->syms;
-        darray_free(into->symsMapNumEntries);
-        darray_free(into->symsMapIndex);
-        into->symsMapNumEntries = from->symsMapNumEntries;
-        into->symsMapIndex = from->symsMapIndex;
-        darray_init(from->symsMapNumEntries);
-        darray_init(from->symsMapIndex);
-        goto out;
-    }
-    else if (using == TO) {
-        result.syms = into->syms;
-        goto out;
+            *intoAct = *use;
+        }
     }
+    into->defined |= (from->defined & GROUP_FIELD_ACTS);
 
-    darray_resize0(result.syms, resultSize);
+    /* Then merge the keysyms. */
 
-    for (i = 0; i < result.numLevels; i++) {
-        enum key_group_selector use = NONE;
-        unsigned int fromSize = 0;
-        unsigned int toSize = 0;
-
-        if (i < from->numLevels)
-            fromSize = darray_item(from->symsMapNumEntries, i);
-
-        if (i < into->numLevels)
-            toSize = darray_item(into->symsMapNumEntries, i);
+    /*
+     * We want to avoid copying and allocating if not necessary. So
+     * here we do a pre-scan of the levels to check if we'll only use
+     * @into's or @from's keysyms, and if so we'll just assign them.
+     * However if one level uses @into's and another uses @from's, we
+     * will need to construct a new syms array.
+     */
+    using = 0;
+    for (i = 0; i < numLevels; i++) {
+        unsigned int intoSize, fromSize;
 
-        if (fromSize == 0 && toSize == 0) {
-            darray_item(into->symsMapIndex, i) = -1;
-            darray_item(into->symsMapNumEntries, i) = 0;
-            continue;
-        }
+        intoSize = darray_item(into->levels, i).num_syms;
+        if (i < darray_size(from->levels))
+            fromSize = darray_item(from->levels, i).num_syms;
+        else
+            fromSize = 0;
 
-        if (fromSize == 0)
-            use = TO;
-        else if (toSize == 0 || clobber)
-            use = FROM;
+        if (intoSize == 0 && fromSize == 0)
+            using |= 0;
+        else if (intoSize == 0)
+            using |= FROM;
+        else if (fromSize == 0)
+            using |= INTO;
         else
-            use = TO;
-
-        if (toSize && fromSize && report) {
-            log_info(info->keymap->ctx,
-                     "Multiple symbols for group %u, level %d on key %s; "
-                     "Using %s, ignoring %s\n",
-                     group + 1, i + 1, LongKeyNameText(key_name),
-                     (use == FROM ? "from" : "to"),
-                     (use == FROM ? "to" : "from"));
-        }
+            using |= (clobber ? FROM : INTO);
+    }
 
-        if (use == FROM) {
-            memcpy(darray_mem(result.syms, cur_idx),
-                   darray_mem(from->syms, darray_item(from->symsMapIndex, i)),
-                   darray_item(from->symsMapNumEntries, i) * sizeof(xkb_keysym_t));
-            darray_item(into->symsMapIndex, i) = cur_idx;
-            darray_item(into->symsMapNumEntries, i) =
-                darray_item(from->symsMapNumEntries, i);
-        }
-        else {
-            memcpy(darray_mem(result.syms, cur_idx),
-                   darray_mem(into->syms, darray_item(into->symsMapIndex, i)),
-                   darray_item(into->symsMapNumEntries, i) * sizeof(xkb_keysym_t));
-            darray_item(into->symsMapIndex, i) = cur_idx;
+    if (using == 0 || using == INTO) {
+    }
+    else if (using == FROM) {
+        darray_free(into->syms);
+        into->syms = from->syms;
+        darray_init(from->syms);
+        for (i = 0; i < darray_size(from->levels); i++) {
+            darray_item(into->levels, i).num_syms =
+                darray_item(from->levels, i).num_syms;
+            darray_item(into->levels, i).sym_index =
+                darray_item(from->levels, i).sym_index;
         }
-
-        cur_idx += darray_item(into->symsMapNumEntries, i);
     }
+    else {
+        darray_xkb_keysym_t syms = darray_new();
 
-out:
-    into->numLevels = result.numLevels;
+        for (i = 0; i < numLevels; i++) {
+            unsigned int intoSize, fromSize;
 
-    if (!darray_same(result.acts, into->acts))
-        darray_free(into->acts);
-    if (!darray_same(result.acts, from->acts))
-        darray_free(from->acts);
-    into->acts = result.acts;
-    into->defined |= GROUP_FIELD_ACTS;
-    darray_init(from->acts);
-    from->defined &= ~GROUP_FIELD_ACTS;
+            intoSize = darray_item(into->levels, i).num_syms;
+            if (i < darray_size(from->levels))
+                fromSize = darray_item(from->levels, i).num_syms;
+            else
+                fromSize = 0;
 
-    if (!darray_same(result.syms, into->syms))
+            /* Empty level. */
+            if (intoSize == 0 && fromSize == 0)
+                continue;
+
+            if (intoSize != 0 && fromSize != 0 && report)
+                log_info(info->keymap->ctx,
+                         "Multiple symbols for group %u, level %d on key %s; "
+                         "Using %s, ignoring %s\n",
+                         group + 1, i + 1, LongKeyNameText(key_name),
+                         (clobber ? "from" : "to"),
+                         (clobber ? "to" : "from"));
+
+            if (intoSize == 0 || (fromSize != 0 && clobber)) {
+                unsigned sym_index = darray_item(from->levels, i).sym_index;
+                darray_item(into->levels, i).sym_index = darray_size(syms);
+                darray_item(into->levels, i).num_syms = fromSize;
+                darray_append_items(syms, &darray_item(from->syms, sym_index),
+                                    fromSize);
+            }
+            else
+            {
+                unsigned sym_index = darray_item(into->levels, i).sym_index;
+                darray_item(into->levels, i).sym_index = darray_size(syms);
+                darray_item(into->levels, i).num_syms = intoSize;
+                darray_append_items(syms, &darray_item(into->syms, sym_index),
+                                    intoSize);
+            }
+        }
         darray_free(into->syms);
-    if (!darray_same(result.syms, from->syms))
-        darray_free(from->syms);
-    into->syms = result.syms;
-    if (darray_empty(into->syms))
-        into->defined &= ~GROUP_FIELD_SYMS;
-    else
-        into->defined |= GROUP_FIELD_SYMS;
-    darray_init(from->syms);
-    from->defined &= ~GROUP_FIELD_SYMS;
-
-    darray_free(from->symsMapIndex);
-    darray_free(from->symsMapNumEntries);
+        into->syms = syms;
+    }
+    into->defined |= (from->defined & GROUP_FIELD_SYMS);
 
     return true;
 }
@@ -501,20 +392,17 @@ UseNewKeyField(enum key_field field, enum key_field old, enum key_field new,
 }
 
 
-/* Merge @from into @into, where both contain info for the same key. */
 static bool
 MergeKeys(SymbolsInfo *info, KeyInfo *into, KeyInfo *from)
 {
-    xkb_group_index_t i;
+    xkb_layout_index_t i;
+    xkb_layout_index_t groups_in_both;
     enum key_field collide = 0;
     bool clobber, report;
-    int verbosity = xkb_get_log_verbosity(info->keymap->ctx);
+    int verbosity = xkb_context_get_log_verbosity(info->keymap->ctx);
 
-    /* Forget about @into; just copy @from as-is. */
     if (from->merge == MERGE_REPLACE) {
-        for (i = 0; i < XKB_NUM_GROUPS; i++)
-            if (into->groups[i].numLevels != 0)
-                ClearGroupInfo(&into->groups[i]);
+        ClearKeyInfo(into);
         *into = *from;
         InitKeyInfo(from, info->file_id);
         return true;
@@ -524,57 +412,17 @@ MergeKeys(SymbolsInfo *info, KeyInfo *into, KeyInfo *from)
     report = (verbosity > 9 ||
               (into->file_id == from->file_id && verbosity > 0));
 
-    /* Start looking into the key groups. */
-    for (i = 0; i < XKB_NUM_GROUPS; i++) {
-        GroupInfo *into_group = &into->groups[i];
-        GroupInfo *from_group = &from->groups[i];
-
-        /* Has any interesting symbols/actions? */
-        if (from_group->numLevels > 0) {
-            if (into_group->numLevels == 0) {
-                /* Easy case: the group in @into is empty. Just copy. */
-
-                into_group->numLevels = from_group->numLevels;
-                into_group->syms = from_group->syms;
-                into_group->symsMapIndex = from_group->symsMapIndex;
-                into_group->symsMapNumEntries = from_group->symsMapNumEntries;
-                into_group->acts = from_group->acts;
-                into_group->defined |= (from_group->defined &
-                                        (GROUP_FIELD_SYMS | GROUP_FIELD_ACTS));
-
-                from_group->numLevels = 0;
-                darray_init(from_group->syms);
-                darray_init(from_group->symsMapIndex);
-                darray_init(from_group->symsMapNumEntries);
-                darray_init(from_group->acts);
-                from_group->defined &= ~(GROUP_FIELD_SYMS | GROUP_FIELD_ACTS);
-            }
-            else {
-                /* Hard case; both groups are non-empty. Merge. */
-                MergeGroups(info, into_group, from_group, clobber,
-                            report, i, into->name);
-            }
-        }
-
-        if (from_group->type != XKB_ATOM_NONE) {
-            if (into_group->type != XKB_ATOM_NONE && report &&
-                into_group->type != from_group->type) {
-                xkb_atom_t use, ignore;
-
-                use = (clobber ? from_group->type : into_group->type);
-                ignore = (clobber ? into_group->type : from_group->type);
-
-                log_warn(info->keymap->ctx,
-                         "Multiple definitions for group %d type of key %s; "
-                         "Using %s, ignoring %s\n",
-                         i + 1, LongKeyNameText(into->name),
-                         xkb_atom_text(info->keymap->ctx, use),
-                         xkb_atom_text(info->keymap->ctx, ignore));
-            }
-
-            if (clobber || into_group->type == XKB_ATOM_NONE)
-                into_group->type = from_group->type;
-        }
+    groups_in_both = MIN(darray_size(into->groups),
+                         darray_size(from->groups));
+    for (i = 0; i < groups_in_both; i++)
+        MergeGroups(info,
+                    &darray_item(into->groups, i),
+                    &darray_item(from->groups, i),
+                    clobber, report, i, into->name);
+    /* If @from has extra groups, just move them to @into. */
+    for (i = groups_in_both; i < darray_size(from->groups); i++) {
+        darray_append(into->groups, darray_item(from->groups, i));
+        InitGroupInfo(&darray_item(from->groups, i));
     }
 
     if (UseNewKeyField(KEY_FIELD_VMODMAP, into->defined, from->defined,
@@ -606,6 +454,8 @@ MergeKeys(SymbolsInfo *info, KeyInfo *into, KeyInfo *from)
                  LongKeyNameText(into->name),
                  (clobber ? "first" : "last"));
 
+    ClearKeyInfo(from);
+    InitKeyInfo(from, info->file_id);
     return true;
 }
 
@@ -613,20 +463,24 @@ static bool
 AddKeySymbols(SymbolsInfo *info, KeyInfo *keyi)
 {
     unsigned long real_name;
-    KeyInfo *iter, *new;
+    KeyInfo *iter;
+
+    /*
+     * Don't keep aliases in the keys array; this guarantees that
+     * searching for keys to merge with by straight comparison (see the
+     * following loop) is enough, and we won't get multiple KeyInfo's
+     * for the same key because of aliases.
+     */
+    if (FindKeyNameForAlias(info->keymap, keyi->name, &real_name))
+        keyi->name = real_name;
 
     darray_foreach(iter, info->keys)
         if (iter->name == keyi->name)
             return MergeKeys(info, iter, keyi);
 
-    if (FindKeyNameForAlias(info->keymap, keyi->name, &real_name))
-        darray_foreach(iter, info->keys)
-            if (iter->name == real_name)
-                return MergeKeys(info, iter, keyi);
-
-    darray_resize0(info->keys, darray_size(info->keys) + 1);
-    new = &darray_item(info->keys, darray_size(info->keys) - 1);
-    return CopyKeyInfo(keyi, new, true);
+    darray_append(info->keys, *keyi);
+    InitKeyInfo(keyi, info->file_id);
+    return true;
 }
 
 static bool
@@ -704,10 +558,10 @@ MergeIncludedSymbols(SymbolsInfo *into, SymbolsInfo *from,
         from->name = NULL;
     }
     for (i = 0; i < XKB_NUM_GROUPS; i++) {
-        if (from->groupNames[i] != XKB_ATOM_NONE) {
+        if (from->group_names[i] != XKB_ATOM_NONE) {
             if ((merge != MERGE_AUGMENT) ||
-                (into->groupNames[i] == XKB_ATOM_NONE))
-                into->groupNames[i] = from->groupNames[i];
+                (into->group_names[i] == XKB_ATOM_NONE))
+                into->group_names[i] = from->group_names[i];
         }
     }
 
@@ -751,10 +605,19 @@ HandleIncludeSymbols(SymbolsInfo *info, IncludeStmt *stmt)
 
         InitSymbolsInfo(&next_incl, info->keymap, rtrn->id, info->actions);
         next_incl.merge = next_incl.dflt.merge = MERGE_OVERRIDE;
-        if (stmt->modifier)
+        if (stmt->modifier) {
             next_incl.explicit_group = atoi(stmt->modifier) - 1;
-        else
+            if (next_incl.explicit_group >= XKB_NUM_GROUPS) {
+                log_err(info->keymap->ctx,
+                        "Cannot set explicit group to %d - must be between 1..%d; "
+                        "Ignoring group number\n",
+                        next_incl.explicit_group + 1, XKB_NUM_GROUPS);
+                next_incl.explicit_group = info->explicit_group;
+            }
+        }
+        else {
             next_incl.explicit_group = info->explicit_group;
+        }
 
         HandleSymbolsFile(&next_incl, rtrn, MERGE_OVERRIDE);
 
@@ -775,27 +638,34 @@ HandleIncludeSymbols(SymbolsInfo *info, IncludeStmt *stmt)
 
 static bool
 GetGroupIndex(SymbolsInfo *info, KeyInfo *keyi, ExprDef *arrayNdx,
-              unsigned what, xkb_group_index_t *ndx_rtrn)
+              unsigned what, xkb_layout_index_t *ndx_rtrn)
 {
     const char *name = (what == SYMBOLS ? "symbols" : "actions");
 
     if (arrayNdx == NULL) {
-        xkb_group_index_t i;
+        xkb_layout_index_t i;
+        GroupInfo *groupi;
         enum group_field field = (what == SYMBOLS ?
                                   GROUP_FIELD_SYMS : GROUP_FIELD_ACTS);
 
-        for (i = 0; i < XKB_NUM_GROUPS; i++) {
-            if (!(keyi->groups[i].defined & field)) {
+        darray_enumerate(i, groupi, keyi->groups) {
+            if (!(groupi->defined & field)) {
                 *ndx_rtrn = i;
                 return true;
             }
         }
 
-        log_err(info->keymap->ctx,
-                "Too many groups of %s for key %s (max %u); "
-                "Ignoring %s defined for extra groups\n",
-                name, LongKeyNameText(keyi->name), XKB_NUM_GROUPS + 1, name);
-        return false;
+        if (i >= XKB_NUM_GROUPS) {
+            log_err(info->keymap->ctx,
+                    "Too many groups of %s for key %s (max %u); "
+                    "Ignoring %s defined for extra groups\n",
+                    name, LongKeyNameText(keyi->name), XKB_NUM_GROUPS + 1, name);
+            return false;
+        }
+
+        darray_resize0(keyi->groups, darray_size(keyi->groups) + 1);
+        *ndx_rtrn = darray_size(keyi->groups) - 1;
+        return true;
     }
 
     if (!ExprResolveGroup(info->keymap->ctx, arrayNdx, ndx_rtrn)) {
@@ -807,6 +677,9 @@ GetGroupIndex(SymbolsInfo *info, KeyInfo *keyi, ExprDef *arrayNdx,
     }
 
     (*ndx_rtrn)--;
+    if (*ndx_rtrn >= darray_size(keyi->groups))
+        darray_resize0(keyi->groups, *ndx_rtrn + 1);
+
     return true;
 }
 
@@ -838,9 +711,9 @@ static bool
 AddSymbolsToKey(SymbolsInfo *info, KeyInfo *keyi, ExprDef *arrayNdx,
                 ExprDef *value)
 {
-    xkb_group_index_t ndx;
+    xkb_layout_index_t ndx;
     GroupInfo *groupi;
-    size_t nSyms;
+    unsigned int nSyms;
     xkb_level_index_t nLevels;
     xkb_level_index_t i;
     int j;
@@ -848,7 +721,7 @@ AddSymbolsToKey(SymbolsInfo *info, KeyInfo *keyi, ExprDef *arrayNdx,
     if (!GetGroupIndex(info, keyi, arrayNdx, SYMBOLS, &ndx))
         return false;
 
-    groupi = &keyi->groups[ndx];
+    groupi = &darray_item(keyi->groups, ndx);
 
     if (value == NULL) {
         groupi->defined |= GROUP_FIELD_SYMS;
@@ -864,7 +737,7 @@ AddSymbolsToKey(SymbolsInfo *info, KeyInfo *keyi, ExprDef *arrayNdx,
         return false;
     }
 
-    if (!darray_empty(groupi->syms)) {
+    if (groupi->defined & GROUP_FIELD_SYMS) {
         log_err(info->keymap->ctx,
                 "Symbols for key %s, group %u already defined; "
                 "Ignoring duplicate definition\n",
@@ -875,63 +748,50 @@ AddSymbolsToKey(SymbolsInfo *info, KeyInfo *keyi, ExprDef *arrayNdx,
     nSyms = darray_size(value->value.list.syms);
     nLevels = darray_size(value->value.list.symsMapIndex);
 
-    if ((groupi->numLevels < nSyms || darray_empty(groupi->syms))) {
-        if (!ResizeGroupInfo(groupi, nLevels, nSyms, false)) {
-            log_wsgo(info->keymap->ctx,
-                     "Could not resize group %u of key %s to contain %zu levels; "
-                     "Symbols lost\n",
-                     ndx + 1, LongKeyNameText(keyi->name), nSyms);
-            return false;
-        }
-    }
+    if (darray_size(groupi->syms) < nSyms)
+        darray_resize0(groupi->syms, nSyms);
+
+    if (darray_size(groupi->levels) < nLevels)
+        darray_resize0(groupi->levels, nLevels);
 
     groupi->defined |= GROUP_FIELD_SYMS;
 
     for (i = 0; i < nLevels; i++) {
-        darray_item(groupi->symsMapIndex, i) =
-            darray_item(value->value.list.symsMapIndex, i);
-        darray_item(groupi->symsMapNumEntries, i) =
-            darray_item(value->value.list.symsNumEntries, i);
+        LevelInfo *leveli = &darray_item(groupi->levels, i);
 
-        for (j = 0; j < darray_item(groupi->symsMapNumEntries, i); j++) {
-            /* FIXME: What's abort() doing here? */
-            if (darray_item(groupi->symsMapIndex, i) + j >= nSyms)
-                abort();
+        leveli->sym_index = darray_item(value->value.list.symsMapIndex, i);
+        leveli->num_syms = darray_item(value->value.list.symsNumEntries, i);
 
+        for (j = 0; j < leveli->num_syms; j++) {
             if (!LookupKeysym(darray_item(value->value.list.syms,
-                                          darray_item(value->value.list.symsMapIndex, i) + j),
+                                          leveli->sym_index + j),
                               &darray_item(groupi->syms,
-                                           darray_item(groupi->symsMapIndex, i) + j))) {
+                                           leveli->sym_index + j))) {
                 log_warn(info->keymap->ctx,
-                         "Could not resolve keysym %s for key %s, group %u (%s), level %zu\n",
+                         "Could not resolve keysym %s for key %s, group %u (%s), level %u\n",
                          darray_item(value->value.list.syms, i),
                          LongKeyNameText(keyi->name),
                          ndx + 1,
                          xkb_atom_text(info->keymap->ctx,
-                                       info->groupNames[ndx]),
+                                       info->group_names[ndx]),
                          nSyms);
-
-                while (--j >= 0)
-                    darray_item(groupi->syms,
-                                darray_item(groupi->symsMapIndex, i) + j) = XKB_KEY_NoSymbol;
-
-                darray_item(groupi->symsMapIndex, i) = -1;
-                darray_item(groupi->symsMapNumEntries, i) = 0;
+                leveli->sym_index = 0;
+                leveli->num_syms = 0;
                 break;
             }
 
-            if (darray_item(groupi->symsMapNumEntries, i) == 1 &&
+            if (leveli->num_syms == 1 &&
                 darray_item(groupi->syms,
-                            darray_item(groupi->symsMapIndex, i) + j) == XKB_KEY_NoSymbol) {
-                darray_item(groupi->symsMapIndex, i) = -1;
-                darray_item(groupi->symsMapNumEntries, i) = 0;
+                            leveli->sym_index + j) == XKB_KEY_NoSymbol) {
+                leveli->sym_index = 0;
+                leveli->num_syms = 0;
             }
         }
     }
 
-    for (j = groupi->numLevels - 1;
-         j >= 0 && darray_item(groupi->symsMapNumEntries, j) == 0; j--)
-        groupi->numLevels--;
+    for (j = darray_size(groupi->levels) - 1;
+         j >= 0 && darray_item(groupi->levels, j).num_syms == 0; j--)
+        (void) darray_pop(groupi->levels);
 
     return true;
 }
@@ -940,17 +800,17 @@ static bool
 AddActionsToKey(SymbolsInfo *info, KeyInfo *keyi, ExprDef *arrayNdx,
                 ExprDef *value)
 {
-    size_t i;
-    xkb_group_index_t ndx;
+    unsigned int i;
+    xkb_layout_index_t ndx;
     GroupInfo *groupi;
-    size_t nActs;
+    unsigned int nActs;
     ExprDef *act;
     union xkb_action *toAct;
 
     if (!GetGroupIndex(info, keyi, arrayNdx, ACTIONS, &ndx))
         return false;
 
-    groupi = &keyi->groups[ndx];
+    groupi = &darray_item(keyi->groups, ndx);
 
     if (value == NULL) {
         groupi->defined |= GROUP_FIELD_ACTS;
@@ -965,44 +825,32 @@ AddActionsToKey(SymbolsInfo *info, KeyInfo *keyi, ExprDef *arrayNdx,
         return false;
     }
 
-    if (!darray_empty(groupi->acts)) {
+    if (groupi->defined & GROUP_FIELD_ACTS) {
         log_wsgo(info->keymap->ctx,
                  "Actions for key %s, group %u already defined\n",
                  LongKeyNameText(keyi->name), ndx);
         return false;
     }
 
-    for (nActs = 0, act = value->value.child; act != NULL; nActs++) {
-        act = (ExprDef *) act->common.next;
-    }
+    nActs = 0;
+    for (act = value->value.child; act; act = (ExprDef *) act->common.next)
+        nActs++;
 
-    if (nActs < 1) {
-        log_wsgo(info->keymap->ctx,
-                 "Action list but not actions in AddActionsToKey\n");
-        return false;
-    }
-
-    if ((groupi->numLevels < nActs || darray_empty(groupi->acts))) {
-        if (!ResizeGroupInfo(&keyi->groups[ndx], nActs, nActs, true)) {
-            log_wsgo(info->keymap->ctx,
-                     "Could not resize group %u of key %s; "
-                     "Actions lost\n",
-                     ndx, LongKeyNameText(keyi->name));
-            return false;
-        }
-    }
+    if (darray_size(groupi->levels) < nActs)
+        darray_resize0(groupi->levels, nActs);
 
     groupi->defined |= GROUP_FIELD_ACTS;
 
-    toAct = darray_mem(groupi->acts, 0);
     act = value->value.child;
-    for (i = 0; i < nActs; i++, toAct++) {
-        if (!HandleActionDef(act, info->keymap, toAct, info->actions)) {
+    for (i = 0; i < nActs; i++) {
+        toAct = &darray_item(groupi->levels, i).act;
+
+        if (!HandleActionDef(act, info->keymap, toAct, info->actions))
             log_err(info->keymap->ctx,
                     "Illegal action definition for %s; "
-                    "Action for group %u/level %zu ignored\n",
+                    "Action for group %u/level %u ignored\n",
                     LongKeyNameText(keyi->name), ndx + 1, i + 1);
-        }
+
         act = (ExprDef *) act->common.next;
     }
 
@@ -1028,7 +876,7 @@ SetSymbolsField(SymbolsInfo *info, KeyInfo *keyi, const char *field,
     struct xkb_context *ctx = info->keymap->ctx;
 
     if (istreq(field, "type")) {
-        xkb_group_index_t ndx;
+        xkb_layout_index_t ndx;
         xkb_atom_t val;
 
         if (!ExprResolveString(ctx, value, &val))
@@ -1049,8 +897,10 @@ SetSymbolsField(SymbolsInfo *info, KeyInfo *keyi, const char *field,
         }
         else {
             ndx--;
-            keyi->groups[ndx].type = val;
-            keyi->groups[ndx].defined |= GROUP_FIELD_TYPE;
+            if (ndx >= darray_size(keyi->groups))
+                darray_resize0(keyi->groups, ndx + 1);
+            darray_item(keyi->groups, ndx).type = val;
+            darray_item(keyi->groups, ndx).defined |= GROUP_FIELD_TYPE;
         }
     }
     else if (istreq(field, "symbols"))
@@ -1154,7 +1004,7 @@ SetSymbolsField(SymbolsInfo *info, KeyInfo *keyi, const char *field,
     }
     else if (istreq(field, "groupsredirect") ||
              istreq(field, "redirectgroups")) {
-        xkb_group_index_t grp;
+        xkb_layout_index_t grp;
 
         if (!ExprResolveGroup(ctx, value, &grp)) {
             log_err(info->keymap->ctx,
@@ -1182,7 +1032,7 @@ SetSymbolsField(SymbolsInfo *info, KeyInfo *keyi, const char *field,
 static int
 SetGroupName(SymbolsInfo *info, ExprDef *arrayNdx, ExprDef *value)
 {
-    xkb_group_index_t grp;
+    xkb_layout_index_t grp;
     xkb_atom_t name;
 
     if (!arrayNdx) {
@@ -1206,12 +1056,12 @@ SetGroupName(SymbolsInfo *info, ExprDef *arrayNdx, ExprDef *value)
         return false;
     }
 
-    info->groupNames[grp - 1 + info->explicit_group] = name;
+    info->group_names[grp - 1 + info->explicit_group] = name;
     return true;
 }
 
 static int
-HandleSymbolsVar(SymbolsInfo *info, VarDef *stmt)
+HandleGlobalVar(SymbolsInfo *info, VarDef *stmt)
 {
     const char *elem, *field;
     ExprDef *arrayNdx;
@@ -1269,7 +1119,9 @@ HandleSymbolsBody(SymbolsInfo *info, VarDef *def, KeyInfo *keyi)
 
     for (; def; def = (VarDef *) def->common.next) {
         if (def->name && def->name->op == EXPR_FIELD_REF) {
-            ok = HandleSymbolsVar(info, def);
+            log_err(info->keymap->ctx,
+                    "Cannot set a global default value from within a key statement; "
+                    "Move statements to the global file scope\n");
             continue;
         }
 
@@ -1295,27 +1147,31 @@ HandleSymbolsBody(SymbolsInfo *info, VarDef *def, KeyInfo *keyi)
 static bool
 SetExplicitGroup(SymbolsInfo *info, KeyInfo *keyi)
 {
-    xkb_group_index_t i;
+    xkb_layout_index_t i;
+    GroupInfo *groupi;
+    bool warn = false;
 
     if (info->explicit_group == 0)
         return true;
 
-    for (i = 1; i < XKB_NUM_GROUPS; i++) {
-        if (keyi->groups[i].defined) {
-            log_warn(info->keymap->ctx,
-                     "For the map %s an explicit group specified, "
-                     "but key %s has more than one group defined; "
-                     "All groups except first one will be ignored\n",
-                     info->name, LongKeyNameText(keyi->name));
-            break;
+    darray_enumerate_from(i, groupi, keyi->groups, 1) {
+        if (groupi->defined) {
+            warn = true;
+            ClearGroupInfo(groupi);
         }
     }
-    if (i < XKB_NUM_GROUPS)
-        for (i = 1; i < XKB_NUM_GROUPS; i++)
-            ClearGroupInfo(&keyi->groups[i]);
 
-    keyi->groups[info->explicit_group] = keyi->groups[0];
-    InitGroupInfo(&keyi->groups[0]);
+    if (warn)
+        log_warn(info->keymap->ctx,
+                 "For the map %s an explicit group specified, "
+                 "but key %s has more than one group defined; "
+                 "All groups except first one will be ignored\n",
+                 info->name, LongKeyNameText(keyi->name));
+
+    darray_resize0(keyi->groups, info->explicit_group + 1);
+    darray_item(keyi->groups, info->explicit_group) =
+        darray_item(keyi->groups, 0);
+    InitGroupInfo(&darray_item(keyi->groups, 0));
     return true;
 }
 
@@ -1323,11 +1179,22 @@ static int
 HandleSymbolsDef(SymbolsInfo *info, SymbolsDef *stmt)
 {
     KeyInfo keyi;
-
-    InitKeyInfo(&keyi, info->file_id);
-    CopyKeyInfo(&info->dflt, &keyi, false);
+    xkb_layout_index_t i;
+
+    keyi = info->dflt;
+    darray_init(keyi.groups);
+    darray_copy(keyi.groups, info->dflt.groups);
+    for (i = 0; i < darray_size(keyi.groups); i++) {
+        darray_init(darray_item(keyi.groups, i).syms);
+        darray_copy(darray_item(keyi.groups, i).syms,
+                    darray_item(info->dflt.groups, i).syms);
+        darray_init(darray_item(keyi.groups, i).levels);
+        darray_copy(darray_item(keyi.groups, i).levels,
+                    darray_item(info->dflt.groups, i).levels);
+    }
     keyi.merge = stmt->merge;
     keyi.name = KeyNameToLong(stmt->keyName);
+
     if (!HandleSymbolsBody(info, (VarDef *) stmt->symbols, &keyi)) {
         info->errorCount++;
         return false;
@@ -1342,6 +1209,7 @@ HandleSymbolsDef(SymbolsInfo *info, SymbolsDef *stmt)
         info->errorCount++;
         return false;
     }
+
     return true;
 }
 
@@ -1408,7 +1276,7 @@ HandleSymbolsFile(SymbolsInfo *info, XkbFile *file, enum merge_mode merge)
             ok = HandleSymbolsDef(info, (SymbolsDef *) stmt);
             break;
         case STMT_VAR:
-            ok = HandleSymbolsVar(info, (VarDef *) stmt);
+            ok = HandleGlobalVar(info, (VarDef *) stmt);
             break;
         case STMT_VMOD:
             ok = HandleVModDef((VModDef *) stmt, info->keymap, merge,
@@ -1450,7 +1318,7 @@ static struct xkb_key *
 FindKeyForSymbol(struct xkb_keymap *keymap, xkb_keysym_t sym)
 {
     struct xkb_key *key, *ret = NULL;
-    xkb_group_index_t group, min_group = UINT32_MAX;
+    xkb_layout_index_t group, min_group = UINT32_MAX;
     xkb_level_index_t level, min_level = UINT16_MAX;
 
     xkb_foreach_key(key, keymap) {
@@ -1484,14 +1352,6 @@ FindKeyForSymbol(struct xkb_keymap *keymap, xkb_keysym_t sym)
     return ret;
 }
 
-/**
- * Find the given name in the keymap->map->types and return its index.
- *
- * @param name The name to search for.
- * @param type_rtrn Set to the index of the name if found.
- *
- * @return true if found, false otherwise.
- */
 static bool
 FindNamedType(struct xkb_keymap *keymap, xkb_atom_t name, unsigned *type_rtrn)
 {
@@ -1528,27 +1388,27 @@ FindNamedType(struct xkb_keymap *keymap, xkb_atom_t name, unsigned *type_rtrn)
  *        symbol per level.
  */
 static bool
-FindAutomaticType(struct xkb_keymap *keymap, xkb_level_index_t width,
+FindAutomaticType(struct xkb_context *ctx, xkb_level_index_t width,
                   const xkb_keysym_t *syms, xkb_atom_t *typeNameRtrn,
                   bool *autoType)
 {
     *autoType = false;
     if ((width == 1) || (width == 0)) {
-        *typeNameRtrn = xkb_atom_intern(keymap->ctx, "ONE_LEVEL");
+        *typeNameRtrn = xkb_atom_intern(ctx, "ONE_LEVEL");
         *autoType = true;
     }
     else if (width == 2) {
         if (syms && xkb_keysym_is_lower(syms[0]) &&
             xkb_keysym_is_upper(syms[1])) {
-            *typeNameRtrn = xkb_atom_intern(keymap->ctx, "ALPHABETIC");
+            *typeNameRtrn = xkb_atom_intern(ctx, "ALPHABETIC");
         }
         else if (syms && (xkb_keysym_is_keypad(syms[0]) ||
                           xkb_keysym_is_keypad(syms[1]))) {
-            *typeNameRtrn = xkb_atom_intern(keymap->ctx, "KEYPAD");
+            *typeNameRtrn = xkb_atom_intern(ctx, "KEYPAD");
             *autoType = true;
         }
         else {
-            *typeNameRtrn = xkb_atom_intern(keymap->ctx, "TWO_LEVEL");
+            *typeNameRtrn = xkb_atom_intern(ctx, "TWO_LEVEL");
             *autoType = true;
         }
     }
@@ -1557,281 +1417,172 @@ FindAutomaticType(struct xkb_keymap *keymap, xkb_level_index_t width,
             xkb_keysym_is_upper(syms[1]))
             if (xkb_keysym_is_lower(syms[2]) && xkb_keysym_is_upper(syms[3]))
                 *typeNameRtrn =
-                    xkb_atom_intern(keymap->ctx, "FOUR_LEVEL_ALPHABETIC");
+                    xkb_atom_intern(ctx, "FOUR_LEVEL_ALPHABETIC");
             else
-                *typeNameRtrn = xkb_atom_intern(keymap->ctx,
+                *typeNameRtrn = xkb_atom_intern(ctx,
                                                 "FOUR_LEVEL_SEMIALPHABETIC");
 
         else if (syms && (xkb_keysym_is_keypad(syms[0]) ||
                           xkb_keysym_is_keypad(syms[1])))
-            *typeNameRtrn = xkb_atom_intern(keymap->ctx, "FOUR_LEVEL_KEYPAD");
+            *typeNameRtrn = xkb_atom_intern(ctx, "FOUR_LEVEL_KEYPAD");
         else
-            *typeNameRtrn = xkb_atom_intern(keymap->ctx, "FOUR_LEVEL");
+            *typeNameRtrn = xkb_atom_intern(ctx, "FOUR_LEVEL");
         /* XXX: why not set autoType here? */
     }
     return width <= 4;
 }
 
-/**
- * Ensure the given KeyInfo is in a coherent state, i.e. no gaps between the
- * groups, and reduce to one group if all groups are identical anyway.
- */
-static void
-PrepareKeyDef(KeyInfo *keyi)
+static bool
+CopySymbolsDef(SymbolsInfo *info, KeyInfo *keyi)
 {
-    xkb_group_index_t i, lastGroup;
+    struct xkb_keymap *keymap = info->keymap;
+    struct xkb_key *key;
+    GroupInfo *groupi;
     const GroupInfo *group0;
-    bool identical;
+    xkb_layout_index_t i;
+    bool haveActions;
+    unsigned int sizeSyms;
+    unsigned int symIndex;
 
-    /* get highest group number */
-    for (i = XKB_NUM_GROUPS - 1; i > 0; i--)
-        if (keyi->groups[i].defined)
-            break;
-    lastGroup = i;
+    /*
+     * The name is guaranteed to be real and not an alias (see
+     * AddKeySymbols), so 'false' is safe here.
+     */
+    key = FindNamedKey(keymap, keyi->name, false);
+    if (!key) {
+        log_vrb(info->keymap->ctx, 5,
+                "Key %s not found in keycodes; Symbols ignored\n",
+                LongKeyNameText(keyi->name));
+        return false;
+    }
 
-    if (lastGroup == 0)
-        return;
+    /* Find the range of groups we need. */
+    key->num_groups = 0;
+    darray_enumerate(i, groupi, keyi->groups)
+        if (groupi->defined)
+            key->num_groups = i + 1;
 
-    group0 = &keyi->groups[0];
+    if (key->num_groups <= 0)
+        return false; /* WSGO */
 
-    /* If there are empty groups between non-empty ones fill them with data */
-    /* from the first group. */
-    /* We can make a wrong assumption here. But leaving gaps is worse. */
-    for (i = lastGroup; i > 0; i--) {
-        GroupInfo *groupi = &keyi->groups[i];
+    darray_resize(keyi->groups, key->num_groups);
 
+    /*
+     * If there are empty groups between non-empty ones, fill them with data
+     * from the first group.
+     * We can make a wrong assumption here. But leaving gaps is worse.
+     */
+    group0 = &darray_item(keyi->groups, 0);
+    darray_foreach_from(groupi, keyi->groups, 1) {
         if (groupi->defined)
             continue;
 
-        if (group0->defined & GROUP_FIELD_TYPE) {
-            groupi->type = group0->type;
-            groupi->defined |= GROUP_FIELD_TYPE;
-        }
-        if ((group0->defined & GROUP_FIELD_ACTS) &&
-            !darray_empty(group0->acts)) {
-            darray_copy(groupi->acts, group0->acts);
-            groupi->defined |= GROUP_FIELD_ACTS;
-        }
-        if ((group0->defined & GROUP_FIELD_SYMS) &&
-            !darray_empty(group0->syms)) {
-            darray_copy(groupi->syms, group0->syms);
-            darray_copy(groupi->symsMapIndex, group0->symsMapIndex);
-            darray_copy(groupi->symsMapNumEntries, group0->symsMapNumEntries);
-            groupi->defined |= GROUP_FIELD_SYMS;
-        }
-        if (group0->defined)
-            groupi->numLevels = group0->numLevels;
+        groupi->type = group0->type;
+        darray_copy(groupi->syms, group0->syms);
+        darray_copy(groupi->levels, group0->levels);
+        groupi->defined = group0->defined;
     }
 
-    /* If all groups are completely identical remove them all */
-    /* exept the first one. */
-    identical = true;
-    for (i = lastGroup; i > 0; i--) {
-        GroupInfo *groupi = &keyi->groups[i];
-
-        if (groupi->numLevels != group0->numLevels ||
-            groupi->type != group0->type) {
-            identical = false;
-            break;
-        }
-        if (!darray_same(groupi->syms, group0->syms) &&
-            (darray_empty(groupi->syms) || darray_empty(group0->syms) ||
-             darray_size(groupi->syms) != darray_size(group0->syms) ||
-             memcmp(darray_mem(groupi->syms, 0),
-                    darray_mem(group0->syms, 0),
-                    sizeof(xkb_keysym_t) * darray_size(group0->syms)))) {
-            identical = false;
-            break;
-        }
-        if (!darray_same(groupi->symsMapIndex, group0->symsMapIndex) &&
-            (darray_empty(groupi->symsMapIndex) ||
-             darray_empty(group0->symsMapIndex) ||
-             memcmp(darray_mem(groupi->symsMapIndex, 0),
-                    darray_mem(group0->symsMapIndex, 0),
-                    group0->numLevels * sizeof(int)))) {
-            identical = false;
-            continue;
-        }
-        if (!darray_same(groupi->symsMapNumEntries,
-                         group0->symsMapNumEntries) &&
-            (darray_empty(groupi->symsMapNumEntries) ||
-             darray_empty(group0->symsMapNumEntries) ||
-             memcmp(darray_mem(groupi->symsMapNumEntries, 0),
-                    darray_mem(group0->symsMapNumEntries, 0),
-                    group0->numLevels * sizeof(size_t)))) {
-            identical = false;
-            continue;
-        }
-        if (!darray_same(groupi->acts, group0->acts) &&
-            (darray_empty(groupi->acts) || darray_empty(group0->acts) ||
-             memcmp(darray_mem(groupi->acts, 0),
-                    darray_mem(group0->acts, 0),
-                    group0->numLevels * sizeof(union xkb_action)))) {
-            identical = false;
-            break;
+    /* See if we need to allocate an actions array. */
+    haveActions = false;
+    darray_foreach(groupi, keyi->groups) {
+        LevelInfo *leveli;
+        darray_foreach(leveli, groupi->levels) {
+            if (leveli->act.type != ACTION_TYPE_NONE) {
+                haveActions = true;
+                goto out_of_loops;
+            }
         }
     }
+out_of_loops:
 
-    if (identical)
-        for (i = lastGroup; i > 0; i--)
-            ClearGroupInfo(&keyi->groups[i]);
-}
-
-/**
- * Copy the KeyInfo into the keyboard description.
- *
- * This function recurses.
- */
-static bool
-CopySymbolsDef(SymbolsInfo *info, KeyInfo *keyi,
-               xkb_keycode_t start_from)
-{
-    struct xkb_keymap *keymap = info->keymap;
-    xkb_keycode_t kc;
-    struct xkb_key *key;
-    size_t sizeSyms = 0;
-    xkb_group_index_t i, nGroups;
-    xkb_level_index_t width, tmp;
-    struct xkb_key_type * type;
-    bool haveActions, autoType, useAlias;
-    unsigned types[XKB_NUM_GROUPS];
-    unsigned int symIndex = 0;
-
-    useAlias = (start_from == 0);
-
-    key = FindNamedKey(keymap, keyi->name, useAlias, start_from);
-    if (!key) {
-        if (start_from == 0)
-            log_vrb(info->keymap->ctx, 5,
-                    "Key %s not found in keycodes; Symbols ignored\n",
-                    LongKeyNameText(keyi->name));
-        return false;
-    }
-    kc = XkbKeyGetKeycode(keymap, key);
-
-    haveActions = false;
-    width = 0;
-    for (i = nGroups = 0; i < XKB_NUM_GROUPS; i++) {
-        GroupInfo *groupi = &keyi->groups[i];
-
-        if (i + 1 > nGroups && groupi->defined)
-            nGroups = i + 1;
-
-        if (!darray_empty(groupi->acts))
-            haveActions = true;
-
-        autoType = false;
+    /*
+     * Find and assign the groups' types in the keymap. Also find the
+     * key width according to the largest type.
+     */
+    key->kt_index = calloc(key->num_groups, sizeof(*key->kt_index));
+    key->width = 0;
+    darray_enumerate(i, groupi, keyi->groups) {
+        struct xkb_key_type *type;
+        bool autoType = false;
 
-        /* Assign the type to the key, if it is missing. */
+        /* Find the type of the group, if it is missing. */
         if (groupi->type == XKB_ATOM_NONE) {
             if (keyi->dfltType != XKB_ATOM_NONE)
                 groupi->type = keyi->dfltType;
-            else if (FindAutomaticType(keymap, groupi->numLevels,
+            else if (FindAutomaticType(keymap->ctx,
+                                       darray_size(groupi->levels),
                                        darray_mem(groupi->syms, 0),
                                        &groupi->type, &autoType)) { }
             else
                 log_vrb(info->keymap->ctx, 5,
-                        "No automatic type for %d symbols; "
-                        "Using %s for the %s key (keycode %d)\n",
-                        groupi->numLevels,
+                        "No automatic type for %d levels; "
+                        "Using %s for the %s key\n",
+                        (int) darray_size(groupi->levels),
                         xkb_atom_text(keymap->ctx, groupi->type),
-                        LongKeyNameText(keyi->name), kc);
+                        LongKeyNameText(keyi->name));
         }
 
-        if (FindNamedType(keymap, groupi->type, &types[i])) {
-            if (!autoType || groupi->numLevels > 2)
+        /* Find the type in the keymap, if it was defined in xkb_types. */
+        if (FindNamedType(keymap, groupi->type, &key->kt_index[i])) {
+            if (!autoType || darray_size(groupi->levels) > 2)
                 key->explicit_groups |= (1 << i);
         }
         else {
             log_vrb(info->keymap->ctx, 3,
                     "Type \"%s\" is not defined; "
-                    "Using default type for the %s key (keycode %d)\n",
+                    "Using default type for the %s key\n",
                     xkb_atom_text(keymap->ctx, groupi->type),
-                    LongKeyNameText(keyi->name), kc);
+                    LongKeyNameText(keyi->name));
             /*
              * Index 0 is guaranteed to contain something, usually
              * ONE_LEVEL or at least some default one-level type.
              */
-            types[i] = 0;
+            key->kt_index[i] = 0;
         }
 
-        /* if the type specifies fewer levels than the key has, shrink the key */
-        type = &keymap->types[types[i]];
-        if (type->num_levels < groupi->numLevels) {
+        /* If the type specifies fewer levels than the key has, shrink the key. */
+        type = &keymap->types[key->kt_index[i]];
+        if (type->num_levels < darray_size(groupi->levels)) {
             log_vrb(info->keymap->ctx, 1,
-                    "Type \"%s\" has %d levels, but %s has %d symbols; "
+                    "Type \"%s\" has %d levels, but %s has %d levels; "
                     "Ignoring extra symbols\n",
                     xkb_atom_text(keymap->ctx, type->name),
                     type->num_levels,
                     LongKeyNameText(keyi->name),
-                    groupi->numLevels);
-            groupi->numLevels = type->num_levels;
+                    (int) darray_size(groupi->levels));
+            darray_resize(groupi->levels, type->num_levels);
         }
 
-        width = MAX3(width, groupi->numLevels, type->num_levels);
-        sizeSyms += darray_size(groupi->syms);
+        /*
+         * Why type->num_levels and not darray_size(groupi->levels)?
+         * Because the type may have more levels, and each group must
+         * have at least as many levels as its type. Because the
+         * key->syms array is indexed by (group * width + level), we
+         * must take the largest one.
+         * Maybe we can change it to save some space.
+         */
+        key->width = MAX(key->width, type->num_levels);
     }
 
-    darray_resize0(key->syms, sizeSyms);
-
-    key->num_groups = nGroups;
-
-    key->width = width;
-
-    key->sym_index = calloc(nGroups * width, sizeof(*key->sym_index));
-
-    key->num_syms = calloc(nGroups * width, sizeof(*key->num_syms));
-
-    if (haveActions) {
-        key->actions = calloc(nGroups * width, sizeof(*key->actions));
-        key->explicit |= EXPLICIT_INTERP;
-    }
+    /* Find the size of the syms array. */
+    sizeSyms = 0;
+    darray_foreach(groupi, keyi->groups)
+        sizeSyms += darray_size(groupi->syms);
 
+    /* Initialize the xkb_key, now that we know the sizes. */
+    key->syms = calloc(sizeSyms, sizeof(*key->syms));
+    key->sym_index = calloc(key->num_groups * key->width,
+                            sizeof(*key->sym_index));
+    key->num_syms = calloc(key->num_groups * key->width,
+                           sizeof(*key->num_syms));
     key->out_of_range_group_number = keyi->out_of_range_group_number;
     key->out_of_range_group_action = keyi->out_of_range_group_action;
-
-    for (i = 0; i < nGroups; i++) {
-        GroupInfo *groupi = &keyi->groups[i];
-
-        /* assign kt_index[i] to the index of the type in map->types.
-         * kt_index[i] may have been set by a previous run (if we have two
-         * layouts specified). Let's not overwrite it with the ONE_LEVEL
-         * default group if we dont even have keys for this group anyway.
-         *
-         * FIXME: There should be a better fix for this.
-         */
-        if (groupi->numLevels)
-            key->kt_index[i] = types[i];
-
-        if (!darray_empty(groupi->syms)) {
-            /* fill key to "width" symbols*/
-            for (tmp = 0; tmp < width; tmp++) {
-                if (tmp < groupi->numLevels &&
-                    darray_item(groupi->symsMapNumEntries, tmp) != 0) {
-                    memcpy(darray_mem(key->syms, symIndex),
-                           darray_mem(groupi->syms,
-                                      darray_item(groupi->symsMapIndex, tmp)),
-                           darray_item(groupi->symsMapNumEntries, tmp) * sizeof(xkb_keysym_t));
-                    key->sym_index[(i * width) + tmp] = symIndex;
-                    key->num_syms[(i * width) + tmp] =
-                        darray_item(groupi->symsMapNumEntries, tmp);
-                    symIndex += key->num_syms[(i * width) + tmp];
-                }
-                else {
-                    key->sym_index[(i * width) + tmp] = -1;
-                    key->num_syms[(i * width) + tmp] = 0;
-                }
-
-                if (key->actions && !darray_empty(groupi->acts)) {
-                    if (tmp < groupi->numLevels)
-                        key->actions[tmp] = darray_item(groupi->acts, tmp);
-                    else
-                        key->actions[tmp].type = ACTION_TYPE_NONE;
-                }
-            }
-        }
+    if (haveActions) {
+        key->actions = calloc(key->num_groups * key->width,
+                              sizeof(*key->actions));
+        key->explicit |= EXPLICIT_INTERP;
     }
-
     if (keyi->defined & KEY_FIELD_VMODMAP) {
         key->vmodmap = keyi->vmodmap;
         key->explicit |= EXPLICIT_VMODMAP;
@@ -1842,8 +1593,29 @@ CopySymbolsDef(SymbolsInfo *info, KeyInfo *keyi,
         key->explicit |= EXPLICIT_REPEAT;
     }
 
-    /* do the same thing for the next key */
-    CopySymbolsDef(info, keyi, kc + 1);
+    /* Copy keysyms and actions. */
+    symIndex = 0;
+    darray_enumerate(i, groupi, keyi->groups) {
+        xkb_level_index_t j;
+        LevelInfo *leveli;
+
+        /* We rely on calloc having zeroized the arrays up to key->width. */
+        darray_enumerate(j, leveli, groupi->levels) {
+            if (leveli->act.type != ACTION_TYPE_NONE)
+                key->actions[i * key->width + j] = leveli->act;
+
+            if (leveli->num_syms <= 0)
+                continue;
+
+            memcpy(&key->syms[symIndex],
+                   &darray_item(groupi->syms, leveli->sym_index),
+                   leveli->num_syms * sizeof(*key->syms));
+            key->sym_index[i * key->width + j] = symIndex;
+            key->num_syms[i * key->width + j] = leveli->num_syms;
+            symIndex += key->num_syms[i * key->width + j];
+        }
+    }
+
     return true;
 }
 
@@ -1854,7 +1626,7 @@ CopyModMapDef(SymbolsInfo *info, ModMapEntry *entry)
     struct xkb_keymap *keymap = info->keymap;
 
     if (!entry->haveSymbol) {
-        key = FindNamedKey(keymap, entry->u.keyName, true, 0);
+        key = FindNamedKey(keymap, entry->u.keyName, true);
         if (!key) {
             log_vrb(info->keymap->ctx, 5,
                     "Key %s not found in keycodes; "
@@ -1880,23 +1652,50 @@ CopyModMapDef(SymbolsInfo *info, ModMapEntry *entry)
     return true;
 }
 
-/**
- * Handle the xkb_symbols section of an xkb file.
- *
- * @param file The parsed xkb_symbols section of the xkb file.
- * @param keymap Handle to the keyboard description to store the symbols in.
- * @param merge Merge strategy (e.g. MERGE_OVERRIDE).
- */
+static bool
+CopySymbolsToKeymap(struct xkb_keymap *keymap, SymbolsInfo *info)
+{
+    KeyInfo *keyi;
+    ModMapEntry *mm;
+    xkb_layout_index_t i;
+    struct xkb_key *key;
+
+    keymap->symbols_section_name = strdup_safe(info->name);
+
+    for (i = 0; i < XKB_NUM_GROUPS; i++)
+        if (info->group_names[i] != XKB_ATOM_NONE)
+            keymap->group_names[i] = info->group_names[i];
+
+    darray_foreach(keyi, info->keys)
+        if (!CopySymbolsDef(info, keyi))
+            info->errorCount++;
+
+    if (xkb_context_get_log_verbosity(keymap->ctx) > 3) {
+        xkb_foreach_key(key, keymap) {
+            if (key->name[0] == '\0')
+                continue;
+
+            if (key->num_groups < 1)
+                log_info(keymap->ctx,
+                         "No symbols defined for %s\n",
+                         KeyNameText(key->name));
+        }
+    }
+
+    darray_foreach(mm, info->modMaps)
+        if (!CopyModMapDef(info, mm))
+            info->errorCount++;
+
+    /* XXX: If we don't ignore errorCount, things break. */
+    return true;
+}
+
 bool
 CompileSymbols(XkbFile *file, struct xkb_keymap *keymap,
                enum merge_mode merge)
 {
-    xkb_group_index_t i;
-    struct xkb_key *key;
     SymbolsInfo info;
     ActionsInfo *actions;
-    KeyInfo *keyi;
-    ModMapEntry *mm;
 
     actions = NewActionsInfo();
     if (!actions)
@@ -1913,38 +1712,8 @@ CompileSymbols(XkbFile *file, struct xkb_keymap *keymap,
     if (info.errorCount != 0)
         goto err_info;
 
-    if (info.name)
-        keymap->symbols_section_name = strdup(info.name);
-
-    for (i = 0; i < XKB_NUM_GROUPS; i++)
-        if (info.groupNames[i] != XKB_ATOM_NONE)
-            keymap->group_names[i] = info.groupNames[i];
-
-    /* sanitize keys */
-    darray_foreach(keyi, info.keys)
-        PrepareKeyDef(keyi);
-
-    /* copy! */
-    darray_foreach(keyi, info.keys)
-        if (!CopySymbolsDef(&info, keyi, 0))
-            info.errorCount++;
-
-    if (xkb_get_log_verbosity(keymap->ctx) > 3) {
-        xkb_foreach_key(key, keymap) {
-            if (key->name[0] == '\0')
-                continue;
-
-            if (key->num_groups < 1)
-                log_info(info.keymap->ctx,
-                         "No symbols defined for %s (keycode %d)\n",
-                         KeyNameText(key->name),
-                         XkbKeyGetKeycode(keymap, key));
-        }
-    }
-
-    darray_foreach(mm, info.modMaps)
-        if (!CopyModMapDef(&info, mm))
-            info.errorCount++;
+    if (!CopySymbolsToKeymap(keymap, &info))
+        goto err_info;
 
     ClearSymbolsInfo(&info);
     FreeActionsInfo(actions);