symbols: disallow changing global defaults from within a key statement
[platform/upstream/libxkbcommon.git] / src / xkbcomp / symbols.c
index 5d6ad33..91feea0 100644 (file)
 /************************************************************
- Copyright (c) 1994 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.
-
Copyright (c) 1994 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 <limits.h>
+/*
+ * Copyright © 2012 Intel Corporation
+ * Copyright © 2012 Ran Benita <ran234@gmail.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ *
+ * Author: Daniel Stone <daniel@fooishbar.org>
+ *         Ran Benita <ran234@gmail.com>
+ */
 
 #include "xkbcomp-priv.h"
-#include "parseutils.h"
+#include "text.h"
+#include "expr.h"
 #include "action.h"
-#include "alias.h"
-#include "keycodes.h"
 #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 = 2,
+};
+
+enum group_field {
+    GROUP_FIELD_SYMS = (1 << 0),
+    GROUP_FIELD_ACTS = (1 << 1),
+    GROUP_FIELD_TYPE = (1 << 2),
+};
+
+enum key_field {
+    KEY_FIELD_REPEAT    = (1 << 0),
+    KEY_FIELD_TYPE_DFLT = (1 << 1),
+    KEY_FIELD_GROUPINFO = (1 << 2),
+    KEY_FIELD_VMODMAP   = (1 << 3),
+};
+
+typedef struct {
+    unsigned int num_syms;
+    unsigned int sym_index;
+    union xkb_action act;
+} LevelInfo;
 
-/* Needed to work with the typechecker. */
 typedef darray(xkb_keysym_t) darray_xkb_keysym_t;
-typedef darray(union xkb_action) darray_xkb_action;
-
-#define        RepeatYes       1
-#define        RepeatNo        0
-#define        RepeatUndefined ~((unsigned)0)
-
-#define        _Key_Syms       (1<<0)
-#define        _Key_Acts       (1<<1)
-#define        _Key_Repeat     (1<<2)
-#define        _Key_Behavior   (1<<3)
-#define        _Key_Type_Dflt  (1<<4)
-#define        _Key_Types      (1<<5)
-#define        _Key_GroupInfo  (1<<6)
-#define        _Key_VModMap    (1<<7)
-
-typedef struct _KeyInfo
-{
-    CommonInfo defs;
+
+typedef struct {
+    enum group_field defined;
+    darray_xkb_keysym_t syms;
+    darray(LevelInfo) levels;
+    xkb_atom_t type;
+} GroupInfo;
+
+typedef struct _KeyInfo {
+    enum key_field defined;
+    unsigned file_id;
+    enum merge_mode merge;
+
     unsigned long name; /* the 4 chars of the key name, as long */
-    unsigned char groupInfo;
-    unsigned char typesDefined;
-    unsigned char symsDefined;
-    unsigned char actsDefined;
-    unsigned int numLevels[XkbNumKbdGroups];
-
-    /* syms[group] -> Single array for all the keysyms in the group. */
-    darray_xkb_keysym_t syms[XkbNumKbdGroups];
-    /*
-     * symsMapIndex[group][level] -> The index from which the syms for
-     * the level begin in the syms[group] array. Remember each keycode
-     * can have multiple keysyms in each level (that is, each key press
-     * can result in multiple keysyms).
-     */
-    darray(int) symsMapIndex[XkbNumKbdGroups];
-    /*
-     * symsMapNumEntries[group][level] -> How many syms are in
-     * syms[group][symsMapIndex[group][level]].
-     */
-    darray(size_t) symsMapNumEntries[XkbNumKbdGroups];
 
-    darray_xkb_action acts[XkbNumKbdGroups];
+    GroupInfo groups[XKB_NUM_GROUPS];
 
-    xkb_atom_t types[XkbNumKbdGroups];
-    unsigned repeat;
-    struct xkb_behavior behavior;
-    unsigned short vmodmap;
+    enum key_repeat repeat;
+    xkb_mod_mask_t vmodmap;
     xkb_atom_t dfltType;
+
+    enum xkb_range_exceed_type out_of_range_group_action;
+    xkb_layout_index_t out_of_range_group_number;
 } KeyInfo;
 
-/**
- * Init the given key info to sane values.
- */
 static void
-InitKeyInfo(KeyInfo * info)
+InitGroupInfo(GroupInfo *groupi)
 {
-    int i;
-    static const char dflt[4] = "*";
-
-    info->defs.defined = 0;
-    info->defs.fileID = 0;
-    info->defs.merge = MERGE_OVERRIDE;
-    info->defs.next = NULL;
-    info->name = KeyNameToLong(dflt);
-    info->groupInfo = 0;
-    info->typesDefined = info->symsDefined = info->actsDefined = 0;
-    for (i = 0; i < XkbNumKbdGroups; i++)
-    {
-        info->numLevels[i] = 0;
-        info->types[i] = XKB_ATOM_NONE;
-        darray_init(info->syms[i]);
-        darray_init(info->symsMapIndex[i]);
-        darray_init(info->symsMapNumEntries[i]);
-        darray_init(info->acts[i]);
-    }
-    info->dfltType = XKB_ATOM_NONE;
-    info->behavior.type = XkbKB_Default;
-    info->behavior.data = 0;
-    info->vmodmap = 0;
-    info->repeat = RepeatUndefined;
+    memset(groupi, 0, sizeof(*groupi));
 }
 
-/**
- * Free memory associated with this key info and reset to sane values.
- */
 static void
-FreeKeyInfo(KeyInfo * info)
+ClearGroupInfo(GroupInfo *groupi)
 {
-    int i;
-
-    info->defs.defined = 0;
-    info->defs.fileID = 0;
-    info->defs.merge = MERGE_OVERRIDE;
-    info->defs.next = NULL;
-    info->groupInfo = 0;
-    info->typesDefined = info->symsDefined = info->actsDefined = 0;
-    for (i = 0; i < XkbNumKbdGroups; i++)
-    {
-        info->numLevels[i] = 0;
-        info->types[i] = XKB_ATOM_NONE;
-        darray_free(info->syms[i]);
-        darray_free(info->symsMapIndex[i]);
-        darray_free(info->symsMapNumEntries[i]);
-        darray_free(info->acts[i]);
-    }
-    info->dfltType = XKB_ATOM_NONE;
-    info->behavior.type = XkbKB_Default;
-    info->behavior.data = 0;
-    info->vmodmap = 0;
-    info->repeat = RepeatUndefined;
+    darray_free(groupi->syms);
+    darray_free(groupi->levels);
 }
 
-/**
- * Copy old into new, optionally reset old to 0.
- * If old is reset, new simply re-uses old's memory. Otherwise, the memory is
- * newly allocated and new points to the new memory areas.
- */
-static bool
-CopyKeyInfo(KeyInfo * old, KeyInfo * new, bool clearOld)
+static void
+InitKeyInfo(KeyInfo *keyi, unsigned file_id)
 {
-    int i;
-
-    *new = *old;
-    new->defs.next = NULL;
-
-    if (clearOld) {
-        for (i = 0; i < XkbNumKbdGroups; i++) {
-            old->numLevels[i] = 0;
-            darray_init(old->symsMapIndex[i]);
-            darray_init(old->symsMapNumEntries[i]);
-            darray_init(old->syms[i]);
-            darray_init(old->acts[i]);
-        }
-    }
-    else {
-        for (i = 0; i < XkbNumKbdGroups; i++) {
-            darray_copy(new->syms[i], old->syms[i]);
-            darray_copy(new->symsMapIndex[i], old->symsMapIndex[i]);
-            darray_copy(new->symsMapNumEntries[i], old->symsMapNumEntries[i]);
-            darray_copy(new->acts[i], old->acts[i]);
-        }
-    }
+    static const char dflt_key_name[XKB_KEY_NAME_LENGTH] = "*";
 
-    return true;
+    memset(keyi, 0, sizeof(*keyi));
+    keyi->file_id = file_id;
+    keyi->merge = MERGE_OVERRIDE;
+    keyi->name = KeyNameToLong(dflt_key_name);
+    keyi->out_of_range_group_action = RANGE_WRAP;
+}
+
+static void
+ClearKeyInfo(KeyInfo *keyi)
+{
+    xkb_layout_index_t i;
+    for (i = 0; i < XKB_NUM_GROUPS; i++)
+        ClearGroupInfo(&keyi->groups[i]);
 }
 
 /***====================================================================***/
 
-typedef struct _ModMapEntry
-{
-    CommonInfo defs;
+typedef struct _ModMapEntry {
+    enum merge_mode merge;
     bool haveSymbol;
     int modifier;
-    union
-    {
+    union {
         unsigned long keyName;
         xkb_keysym_t keySym;
     } u;
 } ModMapEntry;
 
-typedef struct _SymbolsInfo
-{
+typedef struct _SymbolsInfo {
     char *name;         /* e.g. pc+us+inet(evdev) */
     int errorCount;
-    unsigned fileID;
+    unsigned file_id;
     enum merge_mode merge;
-    unsigned explicit_group;
+    xkb_layout_index_t explicit_group;
     darray(KeyInfo) keys;
     KeyInfo dflt;
     VModInfo vmods;
-    ActionInfo *action;
-    xkb_atom_t groupNames[XkbNumKbdGroups];
+    ActionsInfo *actions;
+    xkb_atom_t groupNames[XKB_NUM_GROUPS];
+    darray(ModMapEntry) modMaps;
 
-    ModMapEntry *modMap;
-    AliasInfo *aliases;
+    struct xkb_keymap *keymap;
 } SymbolsInfo;
 
 static void
-InitSymbolsInfo(SymbolsInfo * info, struct xkb_keymap *keymap)
+InitSymbolsInfo(SymbolsInfo *info, struct xkb_keymap *keymap,
+                unsigned file_id, ActionsInfo *actions)
 {
-    int i;
-
-    info->name = NULL;
-    info->explicit_group = 0;
-    info->errorCount = 0;
-    info->fileID = 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);
-    info->modMap = NULL;
-    for (i = 0; i < XkbNumKbdGroups; i++)
-        info->groupNames[i] = XKB_ATOM_NONE;
-    InitKeyInfo(&info->dflt);
+    InitKeyInfo(&info->dflt, file_id);
     InitVModInfo(&info->vmods, keymap);
-    info->action = NULL;
-    info->aliases = NULL;
+    info->actions = actions;
 }
 
 static void
-FreeSymbolsInfo(SymbolsInfo * info)
+ClearSymbolsInfo(SymbolsInfo * info)
 {
-    KeyInfo *key;
-
+    KeyInfo *keyi;
     free(info->name);
-    darray_foreach(key, info->keys)
-        FreeKeyInfo(key);
+    darray_foreach(keyi, info->keys)
+        ClearKeyInfo(keyi);
     darray_free(info->keys);
-    if (info->modMap)
-        ClearCommonInfo(&info->modMap->defs);
-    if (info->aliases)
-        ClearAliases(&info->aliases);
-    memset(info, 0, sizeof(SymbolsInfo));
+    darray_free(info->modMaps);
 }
 
 static bool
-ResizeKeyGroup(KeyInfo * key, unsigned int group, unsigned int 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)
 {
-    int i;
+    xkb_level_index_t i, numLevels;
+    enum { INTO = (1 << 0), FROM = (1 << 1) } using;
 
-    if (darray_size(key->syms[group]) < sizeSyms)
-        darray_resize0(key->syms[group], sizeSyms);
+    /* 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);
 
-    if (darray_empty(key->symsMapIndex[group]) ||
-        key->numLevels[group] < numLevels) {
-        darray_resize(key->symsMapIndex[group], numLevels);
-        for (i = key->numLevels[group]; i < numLevels; i++)
-            darray_item(key->symsMapIndex[group], i) = -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));
+
+            into->type = use;
+        }
     }
+    into->defined |= (from->defined & GROUP_FIELD_TYPE);
 
-    if (darray_empty(key->symsMapNumEntries[group]) ||
-        key->numLevels[group] < numLevels)
-        darray_resize0(key->symsMapNumEntries[group], numLevels);
+    /* Now look at the levels. */
 
-    if ((forceActions && (key->numLevels[group] < numLevels ||
-                          darray_empty(key->acts[group]))) ||
-        (key->numLevels[group] < numLevels && !darray_empty(key->acts[group])))
-        darray_resize0(key->acts[group], numLevels);
+    if (darray_empty(from->levels)) {
+        InitGroupInfo(from);
+        return true;
+    }
 
-    if (key->numLevels[group] < numLevels)
-        key->numLevels[group] = numLevels;
+    if (darray_empty(into->levels)) {
+        from->type = into->type;
+        *into = *from;
+        InitGroupInfo(from);
+        return true;
+    }
 
-    return true;
-}
+    /* 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;
 
-enum key_group_selector {
-    NONE = 0,
-    FROM = (1 << 0),
-    TO = (1 << 1),
-};
+        if (i >= darray_size(from->levels))
+            continue;
 
-static bool
-MergeKeyGroups(SymbolsInfo * info,
-               KeyInfo * into, KeyInfo * from, unsigned group)
-{
-    darray_xkb_keysym_t resultSyms;
-    enum key_group_selector using = NONE;
-    darray_xkb_action resultActs;
-    unsigned int resultWidth;
-    unsigned int resultSize = 0;
-    int cur_idx = 0;
-    int i;
-    bool report, clobber;
-
-    clobber = (from->defs.merge != MERGE_AUGMENT);
-
-    report = (warningLevel > 9) ||
-        ((into->defs.fileID == from->defs.fileID) && (warningLevel > 0));
-
-    darray_init(resultSyms);
-
-    if (into->numLevels[group] >= from->numLevels[group])
-    {
-        resultActs = into->acts[group];
-        resultWidth = into->numLevels[group];
-    }
-    else
-    {
-        resultActs = from->acts[group];
-        resultWidth = from->numLevels[group];
-        darray_resize(into->symsMapIndex[group],
-                      from->numLevels[group]);
-        darray_resize0(into->symsMapNumEntries[group],
-                       from->numLevels[group]);
-
-        for (i = into->numLevels[group]; i < from->numLevels[group]; i++)
-            darray_item(into->symsMapIndex[group], i) = -1;
-    }
-
-    if (darray_empty(resultActs) && (!darray_empty(into->acts[group]) ||
-                                     !darray_empty(from->acts[group])))
-    {
-        darray_resize0(resultActs, resultWidth);
-        for (i = 0; i < resultWidth; i++)
-        {
-            union xkb_action *fromAct = NULL, *toAct = NULL;
-
-            if (!darray_empty(from->acts[group]))
-                fromAct = &darray_item(from->acts[group], i);
-
-            if (!darray_empty(into->acts[group]))
-                toAct = &darray_item(into->acts[group], i);
-
-            if (((fromAct == NULL) || (fromAct->type == XkbSA_NoAction))
-                && (toAct != NULL))
-            {
-                darray_item(resultActs, i) = *toAct;
-            }
-            else if (((toAct == NULL) || (toAct->type == XkbSA_NoAction))
-                     && (fromAct != NULL))
-            {
-                darray_item(resultActs, i) = *fromAct;
-            }
-            else
-            {
-                union xkb_action *use, *ignore;
-                if (clobber)
-                {
-                    use = fromAct;
-                    ignore = toAct;
-                }
-                else
-                {
-                    use = toAct;
-                    ignore = fromAct;
-                }
-                if (report)
-                {
-                    WARN
-                        ("Multiple actions for level %d/group %d on key %s\n",
-                         i + 1, group + 1, longText(into->name));
-                    ACTION("Using %s, ignoring %s\n",
-                            XkbcActionTypeText(use->type),
-                            XkbcActionTypeText(ignore->type));
-                }
-                if (use)
-                    darray_item(resultActs, 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 < resultWidth; i++)
-    {
-        unsigned int fromSize = 0;
-        unsigned toSize = 0;
+        intoAct = &darray_item(into->levels, i).act;
+        fromAct = &darray_item(from->levels, i).act;
 
-        if (!darray_empty(from->symsMapNumEntries[group]) &&
-            i < from->numLevels[group])
-            fromSize = darray_item(from->symsMapNumEntries[group], i);
+        if (fromAct->type == ACTION_TYPE_NONE) {
+        }
+        else if (intoAct->type == ACTION_TYPE_NONE) {
+            *intoAct = *fromAct;
+        }
+        else {
+            union xkb_action *use = (clobber ? fromAct : intoAct);
+            union xkb_action *ignore = (clobber ? intoAct : fromAct);
 
-        if (!darray_empty(into->symsMapNumEntries[group]) &&
-            i < into->numLevels[group])
-            toSize = darray_item(into->symsMapNumEntries[group], i);
+            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 (fromSize == 0)
-        {
-            resultSize += toSize;
-            using |= TO;
+            *intoAct = *use;
         }
-        else if (toSize == 0 || clobber)
-        {
-            resultSize += fromSize;
+    }
+    into->defined |= (from->defined & GROUP_FIELD_ACTS);
+
+    /* Then merge the keysyms. */
+
+    /*
+     * 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;
+
+        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 (intoSize == 0 && fromSize == 0)
+            using |= 0;
+        else if (intoSize == 0)
             using |= FROM;
-        }
+        else if (fromSize == 0)
+            using |= INTO;
         else
-        {
-            resultSize += toSize;
-            using |= TO;
-        }
+            using |= (clobber ? FROM : INTO);
     }
 
-    if (resultSize == 0)
-        goto out;
-
-    if (using == FROM)
-    {
-        resultSyms = from->syms[group];
-        darray_free(into->symsMapNumEntries[group]);
-        darray_free(into->symsMapIndex[group]);
-        into->symsMapNumEntries[group] = from->symsMapNumEntries[group];
-        into->symsMapIndex[group] = from->symsMapIndex[group];
-        darray_init(from->symsMapNumEntries[group]);
-        darray_init(from->symsMapIndex[group]);
-        goto out;
+    if (using == 0 || using == INTO) {
     }
-    else if (using == TO)
-    {
-        resultSyms = into->syms[group];
-        goto out;
+    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;
+        }
     }
+    else {
+        darray_xkb_keysym_t syms = darray_new();
 
-    darray_resize0(resultSyms, resultSize);
-
-    for (i = 0; i < resultWidth; i++)
-    {
-        enum key_group_selector use = NONE;
-        unsigned int fromSize = 0;
-        unsigned int toSize = 0;
+        for (i = 0; i < numLevels; i++) {
+            unsigned int intoSize, fromSize;
 
-        if (i < from->numLevels[group])
-            fromSize = darray_item(from->symsMapNumEntries[group], i);
+            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 (i < into->numLevels[group])
-            toSize = darray_item(into->symsMapNumEntries[group], i);
+            /* Empty level. */
+            if (intoSize == 0 && fromSize == 0)
+                continue;
 
-        if (fromSize == 0 && toSize == 0)
-        {
-            darray_item(into->symsMapIndex[group], i) = -1;
-            darray_item(into->symsMapNumEntries[group], i) = 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);
+        into->syms = syms;
+    }
+    into->defined |= (from->defined & GROUP_FIELD_SYMS);
 
-        if (fromSize == 0)
-            use = TO;
-        else if (toSize == 0 || clobber)
-            use = FROM;
-        else
-            use = TO;
-
-        if (toSize && fromSize && report)
-        {
-            INFO("Multiple symbols for group %d, level %d on key %s\n",
-                 group + 1, i + 1, longText(into->name));
-            ACTION("Using %s, ignoring %s\n",
-                   (use == FROM ? "from" : "to"),
-                   (use == FROM ? "to" : "from"));
-        }
+    return true;
+}
 
-        if (use == FROM)
-        {
-            memcpy(darray_mem(resultSyms, cur_idx),
-                   darray_mem(from->syms[group],
-                              darray_item(from->symsMapIndex[group], i)),
-                   darray_item(from->symsMapNumEntries[group], i) * sizeof(xkb_keysym_t));
-            darray_item(into->symsMapIndex[group], i) = cur_idx;
-            darray_item(into->symsMapNumEntries[group], i) =
-                darray_item(from->symsMapNumEntries[group], i);
-        }
-        else
-        {
-            memcpy(darray_mem(resultSyms, cur_idx),
-                   darray_mem(into->syms[group],
-                              darray_item(into->symsMapIndex[group], i)),
-                   darray_item(into->symsMapNumEntries[group], i) * sizeof(xkb_keysym_t));
-            darray_item(into->symsMapIndex[group], i) = cur_idx;
-        }
-        cur_idx += darray_item(into->symsMapNumEntries[group], i);
-    }
-
-out:
-    if (!darray_same(resultActs, into->acts[group]))
-        darray_free(into->acts[group]);
-    if (!darray_same(resultActs, from->acts[group]))
-        darray_free(from->acts[group]);
-    into->numLevels[group] = resultWidth;
-    if (!darray_same(resultSyms, into->syms[group]))
-        darray_free(into->syms[group]);
-    into->syms[group] = resultSyms;
-    if (!darray_same(resultSyms, from->syms[group]))
-        darray_free(from->syms[group]);
-    darray_init(from->syms[group]);
-    darray_free(from->symsMapIndex[group]);
-    darray_free(from->symsMapNumEntries[group]);
-    into->acts[group] = resultActs;
-    darray_init(from->acts[group]);
-    if (!darray_empty(into->syms[group]))
-        into->symsDefined |= (1 << group);
-    from->symsDefined &= ~(1 << group);
-    into->actsDefined |= (1 << group);
-    from->actsDefined &= ~(1 << group);
+static bool
+UseNewKeyField(enum key_field field, enum key_field old, enum key_field new,
+               bool clobber, bool report, enum key_field *collide)
+{
+    if (!(old & field))
+        return (new & field);
 
-    return true;
+    if (new & field) {
+        if (report)
+            *collide |= field;
+
+        if (clobber)
+            return true;
+    }
+
+    return false;
 }
 
+
 static bool
-MergeKeys(SymbolsInfo *info, struct xkb_keymap *keymap,
-          KeyInfo *into, KeyInfo *from)
+MergeKeys(SymbolsInfo *info, KeyInfo *into, KeyInfo *from)
 {
-    int i;
-    unsigned collide = 0;
-    bool report;
-
-    if (from->defs.merge == MERGE_REPLACE)
-    {
-        for (i = 0; i < XkbNumKbdGroups; i++)
-        {
-            if (into->numLevels[i] != 0)
-            {
-                darray_free(into->syms[i]);
-                darray_free(into->acts[i]);
-            }
-        }
+    xkb_layout_index_t i;
+    enum key_field collide = 0;
+    bool clobber, report;
+    int verbosity = xkb_get_log_verbosity(info->keymap->ctx);
+
+    if (from->merge == MERGE_REPLACE) {
+        ClearKeyInfo(into);
         *into = *from;
-        memset(from, 0, sizeof(KeyInfo));
+        InitKeyInfo(from, info->file_id);
         return true;
     }
-    report = ((warningLevel > 9) ||
-              ((into->defs.fileID == from->defs.fileID)
-               && (warningLevel > 0)));
-    for (i = 0; i < XkbNumKbdGroups; i++)
-    {
-        if (from->numLevels[i] > 0)
-        {
-            if (into->numLevels[i] == 0)
-            {
-                into->numLevels[i] = from->numLevels[i];
-                into->syms[i] = from->syms[i];
-                into->symsMapIndex[i] = from->symsMapIndex[i];
-                into->symsMapNumEntries[i] = from->symsMapNumEntries[i];
-                into->acts[i] = from->acts[i];
-                into->symsDefined |= (1 << i);
-                darray_init(from->syms[i]);
-                darray_init(from->symsMapIndex[i]);
-                darray_init(from->symsMapNumEntries[i]);
-                darray_init(from->acts[i]);
-                from->numLevels[i] = 0;
-                from->symsDefined &= ~(1 << i);
-                if (!darray_empty(into->syms[i]))
-                    into->defs.defined |= _Key_Syms;
-                if (!darray_empty(into->acts[i]))
-                    into->defs.defined |= _Key_Acts;
-            }
-            else
-            {
-                if (report)
-                {
-                    if (!darray_empty(into->syms[i]))
-                        collide |= _Key_Syms;
-                    if (!darray_empty(into->acts[i]))
-                        collide |= _Key_Acts;
-                }
-                MergeKeyGroups(info, into, from, (unsigned) i);
-            }
-        }
-        if (from->types[i] != XKB_ATOM_NONE)
-        {
-            if ((into->types[i] != XKB_ATOM_NONE) && report &&
-                (into->types[i] != from->types[i]))
-            {
-                xkb_atom_t use, ignore;
-                collide |= _Key_Types;
-                if (from->defs.merge != MERGE_AUGMENT)
-                {
-                    use = from->types[i];
-                    ignore = into->types[i];
-                }
-                else
-                {
-                    use = into->types[i];
-                    ignore = from->types[i];
-                }
-                WARN
-                    ("Multiple definitions for group %d type of key %s\n",
-                     i, longText(into->name));
-                ACTION("Using %s, ignoring %s\n",
-                        xkb_atom_text(keymap->ctx, use),
-                        xkb_atom_text(keymap->ctx, ignore));
-            }
-            if ((from->defs.merge != MERGE_AUGMENT)
-                || (into->types[i] == XKB_ATOM_NONE))
-            {
-                into->types[i] = from->types[i];
-            }
-        }
-    }
-    if (UseNewField(_Key_Behavior, &into->defs, &from->defs, &collide))
-    {
-        into->behavior = from->behavior;
-        into->defs.defined |= _Key_Behavior;
-    }
-    if (UseNewField(_Key_VModMap, &into->defs, &from->defs, &collide))
-    {
+
+    clobber = (from->merge != MERGE_AUGMENT);
+    report = (verbosity > 9 ||
+              (into->file_id == from->file_id && verbosity > 0));
+
+    for (i = 0; i < XKB_NUM_GROUPS; i++)
+        MergeGroups(info, &into->groups[i], &from->groups[i], clobber,
+                    report, i, into->name);
+
+    if (UseNewKeyField(KEY_FIELD_VMODMAP, into->defined, from->defined,
+                       clobber, report, &collide)) {
         into->vmodmap = from->vmodmap;
-        into->defs.defined |= _Key_VModMap;
+        into->defined |= KEY_FIELD_VMODMAP;
     }
-    if (UseNewField(_Key_Repeat, &into->defs, &from->defs, &collide))
-    {
+    if (UseNewKeyField(KEY_FIELD_REPEAT, into->defined, from->defined,
+                       clobber, report, &collide)) {
         into->repeat = from->repeat;
-        into->defs.defined |= _Key_Repeat;
+        into->defined |= KEY_FIELD_REPEAT;
     }
-    if (UseNewField(_Key_Type_Dflt, &into->defs, &from->defs, &collide))
-    {
+    if (UseNewKeyField(KEY_FIELD_TYPE_DFLT, into->defined, from->defined,
+                       clobber, report, &collide)) {
         into->dfltType = from->dfltType;
-        into->defs.defined |= _Key_Type_Dflt;
+        into->defined |= KEY_FIELD_TYPE_DFLT;
     }
-    if (UseNewField(_Key_GroupInfo, &into->defs, &from->defs, &collide))
-    {
-        into->groupInfo = from->groupInfo;
-        into->defs.defined |= _Key_GroupInfo;
+    if (UseNewKeyField(KEY_FIELD_GROUPINFO, into->defined, from->defined,
+                       clobber, report, &collide)) {
+        into->out_of_range_group_action = from->out_of_range_group_action;
+        into->out_of_range_group_number = from->out_of_range_group_number;
+        into->defined |= KEY_FIELD_GROUPINFO;
     }
+
     if (collide)
-    {
-        WARN("Symbol map for key %s redefined\n",
-              longText(into->name));
-        ACTION("Using %s definition for conflicting fields\n",
-                (from->defs.merge == MERGE_AUGMENT ? "first" : "last"));
-    }
+        log_warn(info->keymap->ctx,
+                 "Symbol map for key %s redefined; "
+                 "Using %s definition for conflicting fields\n",
+                 LongKeyNameText(into->name),
+                 (clobber ? "first" : "last"));
+
+    ClearKeyInfo(from);
+    InitKeyInfo(from, info->file_id);
     return true;
 }
 
 static bool
-AddKeySymbols(SymbolsInfo *info, KeyInfo *key, struct xkb_keymap *keymap)
+AddKeySymbols(SymbolsInfo *info, KeyInfo *keyi)
 {
     unsigned long real_name;
-    KeyInfo *iter, *new;
+    KeyInfo *iter;
 
-    darray_foreach(iter, info->keys)
-        if (iter->name == key->name)
-            return MergeKeys(info, keymap, iter, key);
+    /*
+     * 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;
 
-    if (FindKeyNameForAlias(keymap, key->name, &real_name))
-        darray_foreach(iter, info->keys)
-            if (iter->name == real_name)
-                return MergeKeys(info, keymap, iter, key);
+    darray_foreach(iter, info->keys)
+        if (iter->name == keyi->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(key, new, true);
+    darray_append(info->keys, *keyi);
+    InitKeyInfo(keyi, info->file_id);
+    return true;
 }
 
 static bool
@@ -662,72 +477,53 @@ AddModMapEntry(SymbolsInfo * info, ModMapEntry * new)
     ModMapEntry *mm;
     bool clobber;
 
-    clobber = (new->defs.merge != MERGE_AUGMENT);
-    for (mm = info->modMap; mm != NULL; mm = (ModMapEntry *) mm->defs.next)
-    {
+    clobber = (new->merge != MERGE_AUGMENT);
+    darray_foreach(mm, info->modMaps) {
         if (new->haveSymbol && mm->haveSymbol
-            && (new->u.keySym == mm->u.keySym))
-        {
+            && (new->u.keySym == mm->u.keySym)) {
             unsigned use, ignore;
-            if (mm->modifier != new->modifier)
-            {
-                if (clobber)
-                {
+            if (mm->modifier != new->modifier) {
+                if (clobber) {
                     use = new->modifier;
                     ignore = mm->modifier;
                 }
-                else
-                {
+                else {
                     use = mm->modifier;
                     ignore = new->modifier;
                 }
-                ERROR
-                    ("%s added to symbol map for multiple modifiers\n",
-                     XkbcKeysymText(new->u.keySym));
-                ACTION("Using %s, ignoring %s.\n",
-                        XkbcModIndexText(use),
-                        XkbcModIndexText(ignore));
+                log_err(info->keymap->ctx,
+                        "%s added to symbol map for multiple modifiers; "
+                        "Using %s, ignoring %s.\n",
+                        KeysymText(new->u.keySym), ModIndexText(use),
+                        ModIndexText(ignore));
                 mm->modifier = use;
             }
             return true;
         }
         if ((!new->haveSymbol) && (!mm->haveSymbol) &&
-            (new->u.keyName == mm->u.keyName))
-        {
+            (new->u.keyName == mm->u.keyName)) {
             unsigned use, ignore;
-            if (mm->modifier != new->modifier)
-            {
-                if (clobber)
-                {
+            if (mm->modifier != new->modifier) {
+                if (clobber) {
                     use = new->modifier;
                     ignore = mm->modifier;
                 }
-                else
-                {
+                else {
                     use = mm->modifier;
                     ignore = new->modifier;
                 }
-                ERROR("Key %s added to map for multiple modifiers\n",
-                       longText(new->u.keyName));
-                ACTION("Using %s, ignoring %s.\n",
-                        XkbcModIndexText(use),
-                        XkbcModIndexText(ignore));
+                log_err(info->keymap->ctx,
+                        "Key %s added to map for multiple modifiers; "
+                        "Using %s, ignoring %s.\n",
+                        LongKeyNameText(new->u.keyName), ModIndexText(use),
+                        ModIndexText(ignore));
                 mm->modifier = use;
             }
             return true;
         }
     }
-    mm = uTypedAlloc(ModMapEntry);
-    if (mm == NULL)
-    {
-        WSGO("Could not allocate modifier map entry\n");
-        ACTION("Modifier map for %s will be incomplete\n",
-                XkbcModIndexText(new->modifier));
-        return false;
-    }
-    *mm = *new;
-    mm->defs.next = &info->modMap->defs;
-    info->modMap = mm;
+
+    darray_append(info->modMaps, *new);
     return true;
 }
 
@@ -735,762 +531,690 @@ AddModMapEntry(SymbolsInfo * info, ModMapEntry * new)
 
 static void
 MergeIncludedSymbols(SymbolsInfo *into, SymbolsInfo *from,
-                     enum merge_mode merge, struct xkb_keymap *keymap)
+                     enum merge_mode merge)
 {
     unsigned int i;
-    KeyInfo *key;
+    KeyInfo *keyi;
+    ModMapEntry *mm;
 
-    if (from->errorCount > 0)
-    {
+    if (from->errorCount > 0) {
         into->errorCount += from->errorCount;
         return;
     }
-    if (into->name == NULL)
-    {
+    if (into->name == NULL) {
         into->name = from->name;
         from->name = NULL;
     }
-    for (i = 0; i < XkbNumKbdGroups; i++)
-    {
-        if (from->groupNames[i] != XKB_ATOM_NONE)
-        {
+    for (i = 0; i < XKB_NUM_GROUPS; i++) {
+        if (from->groupNames[i] != XKB_ATOM_NONE) {
             if ((merge != MERGE_AUGMENT) ||
                 (into->groupNames[i] == XKB_ATOM_NONE))
                 into->groupNames[i] = from->groupNames[i];
         }
     }
 
-    darray_foreach(key, from->keys) {
-        if (merge != MERGE_DEFAULT)
-            key->defs.merge = merge;
-
-        if (!AddKeySymbols(into, key, keymap))
+    darray_foreach(keyi, from->keys) {
+        merge = (merge == MERGE_DEFAULT ? keyi->merge : merge);
+        if (!AddKeySymbols(into, keyi))
             into->errorCount++;
     }
 
-    if (from->modMap != NULL)
-    {
-        ModMapEntry *mm, *next;
-        for (mm = from->modMap; mm != NULL; mm = next)
-        {
-            if (merge != MERGE_DEFAULT)
-                mm->defs.merge = merge;
-            if (!AddModMapEntry(into, mm))
-                into->errorCount++;
-            next = (ModMapEntry *) mm->defs.next;
-            free(mm);
-        }
-        from->modMap = NULL;
+    darray_foreach(mm, from->modMaps) {
+        mm->merge = (merge == MERGE_DEFAULT ? mm->merge : merge);
+        if (!AddModMapEntry(into, mm))
+            into->errorCount++;
     }
-    if (!MergeAliases(&into->aliases, &from->aliases, merge))
-        into->errorCount++;
 }
 
 static void
-HandleSymbolsFile(XkbFile *file, struct xkb_keymap *keymap,
-                  enum merge_mode merge, SymbolsInfo *info);
+HandleSymbolsFile(SymbolsInfo *info, XkbFile *file, enum merge_mode merge);
 
 static bool
-HandleIncludeSymbols(IncludeStmt *stmt, struct xkb_keymap *keymap,
-                     SymbolsInfo *info)
+HandleIncludeSymbols(SymbolsInfo *info, IncludeStmt *stmt)
 {
-    unsigned newMerge;
+    enum merge_mode merge = MERGE_DEFAULT;
     XkbFile *rtrn;
-    SymbolsInfo included;
-    bool haveSelf;
-
-    haveSelf = false;
-    if ((stmt->file == NULL) && (stmt->map == NULL))
-    {
-        haveSelf = true;
-        included = *info;
-        memset(info, 0, sizeof(SymbolsInfo));
-    }
-    else if (ProcessIncludeFile(keymap->ctx, stmt, FILE_TYPE_SYMBOLS, &rtrn,
-                                &newMerge))
-    {
-        InitSymbolsInfo(&included, keymap);
-        included.fileID = included.dflt.defs.fileID = rtrn->id;
-        included.merge = included.dflt.defs.merge = MERGE_OVERRIDE;
-        if (stmt->modifier)
-        {
-            included.explicit_group = atoi(stmt->modifier) - 1;
-        }
-        else
-        {
-            included.explicit_group = info->explicit_group;
-        }
-        HandleSymbolsFile(rtrn, keymap, MERGE_OVERRIDE, &included);
-        if (stmt->stmt != NULL)
-        {
-            free(included.name);
-            included.name = stmt->stmt;
-            stmt->stmt = NULL;
-        }
-        FreeXKBFile(rtrn);
-    }
-    else
-    {
-        info->errorCount += 10;
-        return false;
+    SymbolsInfo included, next_incl;
+
+    InitSymbolsInfo(&included, info->keymap, info->file_id, info->actions);
+    if (stmt->stmt) {
+        free(included.name);
+        included.name = stmt->stmt;
+        stmt->stmt = NULL;
     }
-    if ((stmt->next != NULL) && (included.errorCount < 1))
-    {
-        IncludeStmt *next;
-        unsigned op;
-        SymbolsInfo next_incl;
 
-        for (next = stmt->next; next != NULL; next = next->next)
-        {
-            if ((next->file == NULL) && (next->map == NULL))
-            {
-                haveSelf = true;
-                MergeIncludedSymbols(&included, info, next->merge, keymap);
-                FreeSymbolsInfo(info);
-            }
-            else if (ProcessIncludeFile(keymap->ctx, next, FILE_TYPE_SYMBOLS,
-                                        &rtrn, &op))
-            {
-                InitSymbolsInfo(&next_incl, keymap);
-                next_incl.fileID = next_incl.dflt.defs.fileID = rtrn->id;
-                next_incl.merge = next_incl.dflt.defs.merge = MERGE_OVERRIDE;
-                if (next->modifier)
-                {
-                    next_incl.explicit_group = atoi(next->modifier) - 1;
-                }
-                else
-                {
-                    next_incl.explicit_group = info->explicit_group;
-                }
-                HandleSymbolsFile(rtrn, keymap, MERGE_OVERRIDE, &next_incl);
-                MergeIncludedSymbols(&included, &next_incl, op, keymap);
-                FreeSymbolsInfo(&next_incl);
-                FreeXKBFile(rtrn);
-            }
-            else
-            {
-                info->errorCount += 10;
-                FreeSymbolsInfo(&included);
-                return false;
+    for (; stmt; stmt = stmt->next_incl) {
+        if (!ProcessIncludeFile(info->keymap->ctx, stmt, FILE_TYPE_SYMBOLS,
+                                &rtrn, &merge)) {
+            info->errorCount += 10;
+            ClearSymbolsInfo(&included);
+            return false;
+        }
+
+        InitSymbolsInfo(&next_incl, info->keymap, rtrn->id, info->actions);
+        next_incl.merge = next_incl.dflt.merge = MERGE_OVERRIDE;
+        if (stmt->modifier) {
+            next_incl.explicit_group = atoi(stmt->modifier) - 1;
+            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);
+
+        MergeIncludedSymbols(&included, &next_incl, merge);
+
+        ClearSymbolsInfo(&next_incl);
+        FreeXkbFile(rtrn);
     }
-    else if (stmt->next)
-    {
-        info->errorCount += included.errorCount;
-    }
-    if (haveSelf)
-        *info = included;
-    else
-    {
-        MergeIncludedSymbols(info, &included, newMerge, keymap);
-        FreeSymbolsInfo(&included);
-    }
+
+    MergeIncludedSymbols(info, &included, merge);
+    ClearSymbolsInfo(&included);
+
     return (info->errorCount == 0);
 }
 
-#define        SYMBOLS 1
-#define        ACTIONS 2
+#define SYMBOLS 1
+#define ACTIONS 2
 
 static bool
-GetGroupIndex(KeyInfo *key, struct xkb_keymap *keymap,
-              ExprDef * arrayNdx, unsigned what, unsigned *ndx_rtrn)
+GetGroupIndex(SymbolsInfo *info, KeyInfo *keyi, ExprDef *arrayNdx,
+              unsigned what, xkb_layout_index_t *ndx_rtrn)
 {
-    const char *name;
-    ExprResult tmp;
-
-    if (what == SYMBOLS)
-        name = "symbols";
-    else
-        name = "actions";
-
-    if (arrayNdx == NULL)
-    {
-        int i;
-        unsigned defined;
-        if (what == SYMBOLS)
-            defined = key->symsDefined;
-        else
-            defined = key->actsDefined;
+    const char *name = (what == SYMBOLS ? "symbols" : "actions");
 
-        for (i = 0; i < XkbNumKbdGroups; i++)
-        {
-            if ((defined & (1 << i)) == 0)
-            {
+    if (arrayNdx == NULL) {
+        xkb_layout_index_t i;
+        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)) {
                 *ndx_rtrn = i;
                 return true;
             }
         }
-        ERROR("Too many groups of %s for key %s (max %d)\n", name,
-               longText(key->name), XkbNumKbdGroups + 1);
-        ACTION("Ignoring %s defined for extra groups\n", name);
+
+        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 (!ExprResolveGroup(keymap->ctx, arrayNdx, &tmp))
-    {
-        ERROR("Illegal group index for %s of key %s\n", name,
-               longText(key->name));
-        ACTION("Definition with non-integer array index ignored\n");
+
+    if (!ExprResolveGroup(info->keymap->ctx, arrayNdx, ndx_rtrn)) {
+        log_err(info->keymap->ctx,
+                "Illegal group index for %s of key %s\n"
+                "Definition with non-integer array index ignored\n",
+                name, LongKeyNameText(keyi->name));
         return false;
     }
-    *ndx_rtrn = tmp.uval - 1;
+
+    (*ndx_rtrn)--;
     return true;
 }
 
-static bool
-AddSymbolsToKey(KeyInfo *key, struct xkb_keymap *keymap,
-                ExprDef *arrayNdx, ExprDef *value, SymbolsInfo *info)
+bool
+LookupKeysym(const char *str, xkb_keysym_t *sym_rtrn)
 {
-    unsigned ndx, nSyms, nLevels;
-    unsigned int i;
-    long j;
+    xkb_keysym_t sym;
 
-    if (!GetGroupIndex(key, keymap, arrayNdx, SYMBOLS, &ndx))
+    if (!str || istreq(str, "any") || istreq(str, "nosymbol")) {
+        *sym_rtrn = XKB_KEY_NoSymbol;
+        return 1;
+    }
+
+    if (istreq(str, "none") || istreq(str, "voidsymbol")) {
+        *sym_rtrn = XKB_KEY_VoidSymbol;
+        return 1;
+    }
+
+    sym = xkb_keysym_from_name(str);
+    if (sym != XKB_KEY_NoSymbol) {
+        *sym_rtrn = sym;
+        return 1;
+    }
+
+    return 0;
+}
+
+static bool
+AddSymbolsToKey(SymbolsInfo *info, KeyInfo *keyi, ExprDef *arrayNdx,
+                ExprDef *value)
+{
+    xkb_layout_index_t ndx;
+    GroupInfo *groupi;
+    unsigned int nSyms;
+    xkb_level_index_t nLevels;
+    xkb_level_index_t i;
+    int j;
+
+    if (!GetGroupIndex(info, keyi, arrayNdx, SYMBOLS, &ndx))
         return false;
-    if (value == NULL)
-    {
-        key->symsDefined |= (1 << ndx);
+
+    groupi = &keyi->groups[ndx];
+
+    if (value == NULL) {
+        groupi->defined |= GROUP_FIELD_SYMS;
         return true;
     }
-    if (value->op != ExprKeysymList)
-    {
-        ERROR("Expected a list of symbols, found %s\n", exprOpText(value->op));
-        ACTION("Ignoring symbols for group %d of %s\n", ndx + 1,
-                longText(key->name));
+
+    if (value->op != EXPR_KEYSYM_LIST) {
+        log_err(info->keymap->ctx,
+                "Expected a list of symbols, found %s; "
+                "Ignoring symbols for group %u of %s\n",
+                expr_op_type_to_string(value->op), ndx + 1,
+                LongKeyNameText(keyi->name));
         return false;
     }
-    if (!darray_empty(key->syms[ndx]))
-    {
-        ERROR("Symbols for key %s, group %d already defined\n",
-               longText(key->name), ndx + 1);
-        ACTION("Ignoring duplicate definition\n");
+
+    if (groupi->defined & GROUP_FIELD_SYMS) {
+        log_err(info->keymap->ctx,
+                "Symbols for key %s, group %u already defined; "
+                "Ignoring duplicate definition\n",
+                LongKeyNameText(keyi->name), ndx + 1);
         return false;
     }
+
     nSyms = darray_size(value->value.list.syms);
     nLevels = darray_size(value->value.list.symsMapIndex);
-    if ((key->numLevels[ndx] < nSyms || darray_empty(key->syms[ndx])) &&
-        (!ResizeKeyGroup(key, ndx, nLevels, nSyms, false)))
-    {
-        WSGO("Could not resize group %d of key %s to contain %d levels\n",
-             ndx + 1, longText(key->name), nSyms);
-        ACTION("Symbols lost\n");
-        return false;
-    }
-    key->symsDefined |= (1 << ndx);
+
+    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(key->symsMapIndex[ndx], i) =
-            darray_item(value->value.list.symsMapIndex, i);
-        darray_item(key->symsMapNumEntries[ndx], i) =
-            darray_item(value->value.list.symsNumEntries, i);
-
-        for (j = 0; j < darray_item(key->symsMapNumEntries[ndx], i); j++) {
-            /* FIXME: What's abort() doing here? */
-            if (darray_item(key->symsMapIndex[ndx], i) + j >= nSyms)
-                abort();
+        LevelInfo *leveli = &darray_item(groupi->levels, i);
+
+        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),
-                              &darray_item(key->syms[ndx],
-                                           darray_item(key->symsMapIndex[ndx], i) + j))) {
-                WARN("Could not resolve keysym %s for key %s, group %d (%s), level %d\n",
-                     darray_item(value->value.list.syms, i),
-                     longText(key->name),
-                     ndx + 1,
-                     xkb_atom_text(keymap->ctx, info->groupNames[ndx]), nSyms);
-                while (--j >= 0)
-                    darray_item(key->syms[ndx],
-                                darray_item(key->symsMapIndex[ndx], i) + j) = XKB_KEY_NoSymbol;
-                darray_item(key->symsMapIndex[ndx], i) = -1;
-                darray_item(key->symsMapNumEntries[ndx], i) = 0;
+                                          leveli->sym_index + j),
+                              &darray_item(groupi->syms,
+                                           leveli->sym_index + j))) {
+                log_warn(info->keymap->ctx,
+                         "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]),
+                         nSyms);
+                leveli->sym_index = 0;
+                leveli->num_syms = 0;
                 break;
             }
-            if (darray_item(key->symsMapNumEntries[ndx], i) == 1 &&
-                darray_item(key->syms[ndx],
-                            darray_item(key->symsMapIndex[ndx], i) + j) == XKB_KEY_NoSymbol) {
-                darray_item(key->symsMapIndex[ndx], i) = -1;
-                darray_item(key->symsMapNumEntries[ndx], i) = 0;
+
+            if (leveli->num_syms == 1 &&
+                darray_item(groupi->syms,
+                            leveli->sym_index + j) == XKB_KEY_NoSymbol) {
+                leveli->sym_index = 0;
+                leveli->num_syms = 0;
             }
         }
     }
-    for (j = key->numLevels[ndx] - 1;
-         j >= 0 && darray_item(key->symsMapNumEntries[ndx], j) == 0; j--)
-        key->numLevels[ndx]--;
+
+    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;
 }
 
 static bool
-AddActionsToKey(KeyInfo *key, struct xkb_keymap *keymap, ExprDef *arrayNdx,
-                ExprDef *value, SymbolsInfo *info)
+AddActionsToKey(SymbolsInfo *info, KeyInfo *keyi, ExprDef *arrayNdx,
+                ExprDef *value)
 {
     unsigned int i;
-    unsigned ndx, nActs;
+    xkb_layout_index_t ndx;
+    GroupInfo *groupi;
+    unsigned int nActs;
     ExprDef *act;
-    struct xkb_any_action *toAct;
+    union xkb_action *toAct;
 
-    if (!GetGroupIndex(key, keymap, arrayNdx, ACTIONS, &ndx))
+    if (!GetGroupIndex(info, keyi, arrayNdx, ACTIONS, &ndx))
         return false;
 
-    if (value == NULL)
-    {
-        key->actsDefined |= (1 << ndx);
+    groupi = &keyi->groups[ndx];
+
+    if (value == NULL) {
+        groupi->defined |= GROUP_FIELD_ACTS;
         return true;
     }
-    if (value->op != ExprActionList)
-    {
-        WSGO("Bad expression type (%d) for action list value\n", value->op);
-        ACTION("Ignoring actions for group %d of %s\n", ndx,
-                longText(key->name));
-        return false;
-    }
-    if (!darray_empty(key->acts[ndx]))
-    {
-        WSGO("Actions for key %s, group %d already defined\n",
-              longText(key->name), ndx);
-        return false;
-    }
-    for (nActs = 0, act = value->value.child; act != NULL; nActs++)
-    {
-        act = (ExprDef *) act->common.next;
-    }
-    if (nActs < 1)
-    {
-        WSGO("Action list but not actions in AddActionsToKey\n");
+
+    if (value->op != EXPR_ACTION_LIST) {
+        log_wsgo(info->keymap->ctx,
+                 "Bad expression type (%d) for action list value; "
+                 "Ignoring actions for group %u of %s\n",
+                 value->op, ndx, LongKeyNameText(keyi->name));
         return false;
     }
-    if ((key->numLevels[ndx] < nActs || darray_empty(key->acts[ndx])) &&
-        !ResizeKeyGroup(key, ndx, nActs, nActs, true))
-    {
-        WSGO("Could not resize group %d of key %s\n", ndx,
-              longText(key->name));
-        ACTION("Actions lost\n");
+
+    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;
     }
-    key->actsDefined |= (1 << ndx);
 
-    toAct = (struct xkb_any_action *) darray_mem(key->acts[ndx], 0);
+    nActs = 0;
+    for (act = value->value.child; act; act = (ExprDef *) act->common.next)
+        nActs++;
+
+    if (darray_size(groupi->levels) < nActs)
+        darray_resize0(groupi->levels, nActs);
+
+    groupi->defined |= GROUP_FIELD_ACTS;
+
     act = value->value.child;
-    for (i = 0; i < nActs; i++, toAct++)
-    {
-        if (!HandleActionDef(act, keymap, toAct, info->action))
-        {
-            ERROR("Illegal action definition for %s\n",
-                   longText(key->name));
-            ACTION("Action for group %d/level %d ignored\n", ndx + 1, i + 1);
-        }
+    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 %u ignored\n",
+                    LongKeyNameText(keyi->name), ndx + 1, i + 1);
+
         act = (ExprDef *) act->common.next;
     }
+
     return true;
 }
 
-static const LookupEntry lockingEntries[] = {
-    {"true", XkbKB_Lock},
-    {"yes", XkbKB_Lock},
-    {"on", XkbKB_Lock},
-    {"false", XkbKB_Default},
-    {"no", XkbKB_Default},
-    {"off", XkbKB_Default},
-    {"permanent", XkbKB_Lock | XkbKB_Permanent},
-    {NULL, 0}
-};
-
 static const LookupEntry repeatEntries[] = {
-    {"true", RepeatYes},
-    {"yes", RepeatYes},
-    {"on", RepeatYes},
-    {"false", RepeatNo},
-    {"no", RepeatNo},
-    {"off", RepeatNo},
-    {"default", RepeatUndefined},
-    {NULL, 0}
+    { "true", KEY_REPEAT_YES },
+    { "yes", KEY_REPEAT_YES },
+    { "on", KEY_REPEAT_YES },
+    { "false", KEY_REPEAT_NO },
+    { "no", KEY_REPEAT_NO },
+    { "off", KEY_REPEAT_NO },
+    { "default", KEY_REPEAT_UNDEFINED },
+    { NULL, 0 }
 };
 
 static bool
-SetSymbolsField(KeyInfo *key, struct xkb_keymap *keymap, char *field,
-                ExprDef *arrayNdx, ExprDef *value, SymbolsInfo *info)
+SetSymbolsField(SymbolsInfo *info, KeyInfo *keyi, const char *field,
+                ExprDef *arrayNdx, ExprDef *value)
 {
     bool ok = true;
-    ExprResult tmp;
-
-    if (strcasecmp(field, "type") == 0)
-    {
-        ExprResult ndx;
-        if ((!ExprResolveString(keymap->ctx, value, &tmp))
-            && (warningLevel > 0))
-        {
-            WARN("The type field of a key symbol map must be a string\n");
-            ACTION("Ignoring illegal type definition\n");
-        }
-        if (arrayNdx == NULL)
-        {
-            key->dfltType = xkb_atom_intern(keymap->ctx, tmp.str);
-            key->defs.defined |= _Key_Type_Dflt;
-        }
-        else if (!ExprResolveGroup(keymap->ctx, arrayNdx, &ndx))
-        {
-            ERROR("Illegal group index for type of key %s\n",
-                   longText(key->name));
-            ACTION("Definition with non-integer array index ignored\n");
-            free(tmp.str);
+    struct xkb_context *ctx = info->keymap->ctx;
+
+    if (istreq(field, "type")) {
+        xkb_layout_index_t ndx;
+        xkb_atom_t val;
+
+        if (!ExprResolveString(ctx, value, &val))
+            log_vrb(ctx, 1,
+                    "The type field of a key symbol map must be a string; "
+                    "Ignoring illegal type definition\n");
+
+        if (arrayNdx == NULL) {
+            keyi->dfltType = val;
+            keyi->defined |= KEY_FIELD_TYPE_DFLT;
+        }
+        else if (!ExprResolveGroup(ctx, arrayNdx, &ndx)) {
+            log_err(ctx,
+                    "Illegal group index for type of key %s; "
+                    "Definition with non-integer array index ignored\n",
+                    LongKeyNameText(keyi->name));
             return false;
         }
-        else
-        {
-            key->types[ndx.uval - 1] = xkb_atom_intern(keymap->ctx, tmp.str);
-            key->typesDefined |= (1 << (ndx.uval - 1));
-        }
-        free(tmp.str);
-    }
-    else if (strcasecmp(field, "symbols") == 0)
-        return AddSymbolsToKey(key, keymap, arrayNdx, value, info);
-    else if (strcasecmp(field, "actions") == 0)
-        return AddActionsToKey(key, keymap, arrayNdx, value, info);
-    else if ((strcasecmp(field, "vmods") == 0) ||
-             (strcasecmp(field, "virtualmods") == 0) ||
-             (strcasecmp(field, "virtualmodifiers") == 0))
-    {
-        ok = ExprResolveVModMask(value, &tmp, keymap);
-        if (ok)
-        {
-            key->vmodmap = (tmp.uval >> 8);
-            key->defs.defined |= _Key_VModMap;
-        }
-        else
-        {
-            ERROR("Expected a virtual modifier mask, found %s\n",
-                   exprOpText(value->op));
-            ACTION("Ignoring virtual modifiers definition for key %s\n",
-                    longText(key->name));
-        }
-    }
-    else if ((strcasecmp(field, "locking") == 0) ||
-             (strcasecmp(field, "lock") == 0) ||
-             (strcasecmp(field, "locks") == 0))
-    {
-        ok = ExprResolveEnum(keymap->ctx, value, &tmp, lockingEntries);
-        if (ok)
-            key->behavior.type = tmp.uval;
-        key->defs.defined |= _Key_Behavior;
-    }
-    else if ((strcasecmp(field, "radiogroup") == 0) ||
-             (strcasecmp(field, "permanentradiogroup") == 0) ||
-             (strcasecmp(field, "allownone") == 0))
-    {
-        ERROR("Radio groups not supported\n");
-        ACTION("Ignoring radio group specification for key %s\n", longText(key->name));
-        return false;
-    }
-    else if (uStrCasePrefix("overlay", field) ||
-             uStrCasePrefix("permanentoverlay", field))
-    {
-        ERROR("Overlays not supported\n");
-        ACTION("Ignoring overlay specification for key %s\n", longText(key->name));
-    }
-    else if ((strcasecmp(field, "repeating") == 0) ||
-             (strcasecmp(field, "repeats") == 0) ||
-             (strcasecmp(field, "repeat") == 0))
-    {
-        ok = ExprResolveEnum(keymap->ctx, value, &tmp, repeatEntries);
-        if (!ok)
-        {
-            ERROR("Illegal repeat setting for %s\n",
-                   longText(key->name));
-            ACTION("Non-boolean repeat setting ignored\n");
+        else {
+            ndx--;
+            keyi->groups[ndx].type = val;
+            keyi->groups[ndx].defined |= GROUP_FIELD_TYPE;
+        }
+    }
+    else if (istreq(field, "symbols"))
+        return AddSymbolsToKey(info, keyi, arrayNdx, value);
+    else if (istreq(field, "actions"))
+        return AddActionsToKey(info, keyi, arrayNdx, value);
+    else if (istreq(field, "vmods") ||
+             istreq(field, "virtualmods") ||
+             istreq(field, "virtualmodifiers")) {
+        xkb_mod_mask_t mask;
+
+        ok = ExprResolveVModMask(info->keymap, value, &mask);
+        if (ok) {
+            keyi->vmodmap = (mask >> XKB_NUM_CORE_MODS) & 0xffff;
+            keyi->defined |= KEY_FIELD_VMODMAP;
+        }
+        else {
+            log_err(info->keymap->ctx,
+                    "Expected a virtual modifier mask, found %s; "
+                    "Ignoring virtual modifiers definition for key %s\n",
+                    expr_op_type_to_string(value->op),
+                    LongKeyNameText(keyi->name));
+        }
+    }
+    else if (istreq(field, "locking") ||
+             istreq(field, "lock") ||
+             istreq(field, "locks")) {
+        log_err(info->keymap->ctx,
+                "Key behaviors not supported; "
+                "Ignoring locking specification for key %s\n",
+                LongKeyNameText(keyi->name));
+    }
+    else if (istreq(field, "radiogroup") ||
+             istreq(field, "permanentradiogroup") ||
+             istreq(field, "allownone")) {
+        log_err(info->keymap->ctx,
+                "Radio groups not supported; "
+                "Ignoring radio group specification for key %s\n",
+                LongKeyNameText(keyi->name));
+    }
+    else if (istreq_prefix("overlay", field) ||
+             istreq_prefix("permanentoverlay", field)) {
+        log_err(info->keymap->ctx,
+                "Overlays not supported; "
+                "Ignoring overlay specification for key %s\n",
+                LongKeyNameText(keyi->name));
+    }
+    else if (istreq(field, "repeating") ||
+             istreq(field, "repeats") ||
+             istreq(field, "repeat")) {
+        unsigned int val;
+
+        ok = ExprResolveEnum(ctx, value, &val, repeatEntries);
+        if (!ok) {
+            log_err(info->keymap->ctx,
+                    "Illegal repeat setting for %s; "
+                    "Non-boolean repeat setting ignored\n",
+                    LongKeyNameText(keyi->name));
             return false;
         }
-        key->repeat = tmp.uval;
-        key->defs.defined |= _Key_Repeat;
+        keyi->repeat = val;
+        keyi->defined |= KEY_FIELD_REPEAT;
     }
-    else if ((strcasecmp(field, "groupswrap") == 0) ||
-             (strcasecmp(field, "wrapgroups") == 0))
-    {
-        ok = ExprResolveBoolean(keymap->ctx, value, &tmp);
-        if (!ok)
-        {
-            ERROR("Illegal groupsWrap setting for %s\n",
-                   longText(key->name));
-            ACTION("Non-boolean value ignored\n");
+    else if (istreq(field, "groupswrap") ||
+             istreq(field, "wrapgroups")) {
+        bool set;
+
+        if (!ExprResolveBoolean(ctx, value, &set)) {
+            log_err(info->keymap->ctx,
+                    "Illegal groupsWrap setting for %s; "
+                    "Non-boolean value ignored\n",
+                    LongKeyNameText(keyi->name));
             return false;
         }
-        if (tmp.uval)
-            key->groupInfo = XkbWrapIntoRange;
+
+        if (set)
+            keyi->out_of_range_group_action = RANGE_WRAP;
         else
-            key->groupInfo = XkbClampIntoRange;
-        key->defs.defined |= _Key_GroupInfo;
+            keyi->out_of_range_group_action = RANGE_SATURATE;
+
+        keyi->defined |= KEY_FIELD_GROUPINFO;
     }
-    else if ((strcasecmp(field, "groupsclamp") == 0) ||
-             (strcasecmp(field, "clampgroups") == 0))
-    {
-        ok = ExprResolveBoolean(keymap->ctx, value, &tmp);
-        if (!ok)
-        {
-            ERROR("Illegal groupsClamp setting for %s\n",
-                   longText(key->name));
-            ACTION("Non-boolean value ignored\n");
+    else if (istreq(field, "groupsclamp") ||
+             istreq(field, "clampgroups")) {
+        bool set;
+
+        if (!ExprResolveBoolean(ctx, value, &set)) {
+            log_err(info->keymap->ctx,
+                    "Illegal groupsClamp setting for %s; "
+                    "Non-boolean value ignored\n",
+                    LongKeyNameText(keyi->name));
             return false;
         }
-        if (tmp.uval)
-            key->groupInfo = XkbClampIntoRange;
+
+        if (set)
+            keyi->out_of_range_group_action = RANGE_SATURATE;
         else
-            key->groupInfo = XkbWrapIntoRange;
-        key->defs.defined |= _Key_GroupInfo;
-    }
-    else if ((strcasecmp(field, "groupsredirect") == 0) ||
-             (strcasecmp(field, "redirectgroups") == 0))
-    {
-        if (!ExprResolveGroup(keymap->ctx, value, &tmp))
-        {
-            ERROR("Illegal group index for redirect of key %s\n",
-                   longText(key->name));
-            ACTION("Definition with non-integer group ignored\n");
+            keyi->out_of_range_group_action = RANGE_WRAP;
+
+        keyi->defined |= KEY_FIELD_GROUPINFO;
+    }
+    else if (istreq(field, "groupsredirect") ||
+             istreq(field, "redirectgroups")) {
+        xkb_layout_index_t grp;
+
+        if (!ExprResolveGroup(ctx, value, &grp)) {
+            log_err(info->keymap->ctx,
+                    "Illegal group index for redirect of key %s; "
+                    "Definition with non-integer group ignored\n",
+                    LongKeyNameText(keyi->name));
             return false;
         }
-        key->groupInfo =
-            XkbSetGroupInfo(0, XkbRedirectIntoRange, tmp.uval - 1);
-        key->defs.defined |= _Key_GroupInfo;
-    }
-    else
-    {
-        ERROR("Unknown field %s in a symbol interpretation\n", field);
-        ACTION("Definition ignored\n");
+
+        keyi->out_of_range_group_action = RANGE_REDIRECT;
+        keyi->out_of_range_group_number = grp - 1;
+        keyi->defined |= KEY_FIELD_GROUPINFO;
+    }
+    else {
+        log_err(info->keymap->ctx,
+                "Unknown field %s in a symbol interpretation; "
+                "Definition ignored\n",
+                field);
         ok = false;
     }
+
     return ok;
 }
 
 static int
-SetGroupName(SymbolsInfo *info, struct xkb_keymap *keymap, ExprDef *arrayNdx,
-             ExprDef *value)
+SetGroupName(SymbolsInfo *info, ExprDef *arrayNdx, ExprDef *value)
 {
-    ExprResult tmp, name;
+    xkb_layout_index_t grp;
+    xkb_atom_t name;
 
-    if ((arrayNdx == NULL) && (warningLevel > 0))
-    {
-        WARN("You must specify an index when specifying a group name\n");
-        ACTION("Group name definition without array subscript ignored\n");
+    if (!arrayNdx) {
+        log_vrb(info->keymap->ctx, 1,
+                "You must specify an index when specifying a group name; "
+                "Group name definition without array subscript ignored\n");
         return false;
     }
-    if (!ExprResolveGroup(keymap->ctx, arrayNdx, &tmp))
-    {
-        ERROR("Illegal index in group name definition\n");
-        ACTION("Definition with non-integer array index ignored\n");
+
+    if (!ExprResolveGroup(info->keymap->ctx, arrayNdx, &grp)) {
+        log_err(info->keymap->ctx,
+                "Illegal index in group name definition; "
+                "Definition with non-integer array index ignored\n");
         return false;
     }
-    if (!ExprResolveString(keymap->ctx, value, &name))
-    {
-        ERROR("Group name must be a string\n");
-        ACTION("Illegal name for group %d ignored\n", tmp.uval);
+
+    if (!ExprResolveString(info->keymap->ctx, value, &name)) {
+        log_err(info->keymap->ctx,
+                "Group name must be a string; "
+                "Illegal name for group %d ignored\n", grp);
         return false;
     }
-    info->groupNames[tmp.uval - 1 + info->explicit_group] =
-        xkb_atom_intern(keymap->ctx, name.str);
-    free(name.str);
 
+    info->groupNames[grp - 1 + info->explicit_group] = name;
     return true;
 }
 
 static int
-HandleSymbolsVar(VarDef *stmt, struct xkb_keymap *keymap, SymbolsInfo *info)
+HandleGlobalVar(SymbolsInfo *info, VarDef *stmt)
 {
-    ExprResult elem, field;
+    const char *elem, *field;
     ExprDef *arrayNdx;
     bool ret;
 
-    if (ExprResolveLhs(keymap, stmt->name, &elem, &field, &arrayNdx) == 0)
+    if (ExprResolveLhs(info->keymap->ctx, stmt->name, &elem, &field,
+                       &arrayNdx) == 0)
         return 0;               /* internal error, already reported */
-    if (elem.str && (strcasecmp(elem.str, "key") == 0))
-    {
-        ret = SetSymbolsField(&info->dflt, keymap, field.str, arrayNdx,
-                              stmt->value, info);
-    }
-    else if ((elem.str == NULL) && ((strcasecmp(field.str, "name") == 0) ||
-                                    (strcasecmp(field.str, "groupname") ==
-                                     0)))
-    {
-        ret = SetGroupName(info, keymap, arrayNdx, stmt->value);
-    }
-    else if ((elem.str == NULL)
-             && ((strcasecmp(field.str, "groupswrap") == 0) ||
-                 (strcasecmp(field.str, "wrapgroups") == 0)))
-    {
-        ERROR("Global \"groupswrap\" not supported\n");
-        ACTION("Ignored\n");
+    if (elem && istreq(elem, "key")) {
+        ret = SetSymbolsField(info, &info->dflt, field, arrayNdx,
+                              stmt->value);
+    }
+    else if (!elem && (istreq(field, "name") ||
+                       istreq(field, "groupname"))) {
+        ret = SetGroupName(info, arrayNdx, stmt->value);
+    }
+    else if (!elem && (istreq(field, "groupswrap") ||
+                       istreq(field, "wrapgroups"))) {
+        log_err(info->keymap->ctx,
+                "Global \"groupswrap\" not supported; Ignored\n");
         ret = true;
     }
-    else if ((elem.str == NULL)
-             && ((strcasecmp(field.str, "groupsclamp") == 0) ||
-                 (strcasecmp(field.str, "clampgroups") == 0)))
-    {
-        ERROR("Global \"groupsclamp\" not supported\n");
-        ACTION("Ignored\n");
+    else if (!elem && (istreq(field, "groupsclamp") ||
+                       istreq(field, "clampgroups"))) {
+        log_err(info->keymap->ctx,
+                "Global \"groupsclamp\" not supported; Ignored\n");
         ret = true;
     }
-    else if ((elem.str == NULL)
-             && ((strcasecmp(field.str, "groupsredirect") == 0) ||
-                 (strcasecmp(field.str, "redirectgroups") == 0)))
-    {
-        ERROR("Global \"groupsredirect\" not supported\n");
-        ACTION("Ignored\n");
+    else if (!elem && (istreq(field, "groupsredirect") ||
+                       istreq(field, "redirectgroups"))) {
+        log_err(info->keymap->ctx,
+                "Global \"groupsredirect\" not supported; Ignored\n");
         ret = true;
     }
-    else if ((elem.str == NULL) && (strcasecmp(field.str, "allownone") == 0))
-    {
-        ERROR("Radio groups not supported\n");
-        ACTION("Ignoring \"allownone\" specification\n");
+    else if (!elem && istreq(field, "allownone")) {
+        log_err(info->keymap->ctx,
+                "Radio groups not supported; "
+                "Ignoring \"allownone\" specification\n");
         ret = true;
     }
     else {
-        ret = SetActionField(keymap, elem.str, field.str, arrayNdx,
-                             stmt->value, &info->action);
+        ret = SetActionField(info->keymap, elem, field, arrayNdx, stmt->value,
+                             info->actions);
     }
 
-    free(elem.str);
-    free(field.str);
     return ret;
 }
 
 static bool
-HandleSymbolsBody(VarDef *def, struct xkb_keymap *keymap, KeyInfo *key,
-                  SymbolsInfo *info)
+HandleSymbolsBody(SymbolsInfo *info, VarDef *def, KeyInfo *keyi)
 {
     bool ok = true;
-    ExprResult tmp, field;
+    const char *elem, *field;
     ExprDef *arrayNdx;
 
-    for (; def != NULL; def = (VarDef *) def->common.next)
-    {
-        if ((def->name) && (def->name->type == ExprFieldRef))
-        {
-            ok = HandleSymbolsVar(def, keymap, info);
+    for (; def; def = (VarDef *) def->common.next) {
+        if (def->name && def->name->op == EXPR_FIELD_REF) {
+            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;
         }
-        else
-        {
-            if (def->name == NULL)
-            {
-                if ((def->value == NULL)
-                    || (def->value->op == ExprKeysymList))
-                    field.str = strdup("symbols");
-                else
-                    field.str = strdup("actions");
-                arrayNdx = NULL;
-            }
+
+        if (!def->name) {
+            if (!def->value || def->value->op == EXPR_KEYSYM_LIST)
+                field = "symbols";
             else
-            {
-                ok = ExprResolveLhs(keymap, def->name, &tmp, &field,
-                                    &arrayNdx);
-            }
-            if (ok)
-                ok = SetSymbolsField(key, keymap, field.str, arrayNdx,
-                                     def->value, info);
-            free(field.str);
+                field = "actions";
+            arrayNdx = NULL;
         }
+        else {
+            ok = ExprResolveLhs(info->keymap->ctx, def->name, &elem, &field,
+                                &arrayNdx);
+        }
+
+        if (ok)
+            ok = SetSymbolsField(info, keyi, field, arrayNdx, def->value);
     }
+
     return ok;
 }
 
 static bool
-SetExplicitGroup(SymbolsInfo *info, KeyInfo *key)
+SetExplicitGroup(SymbolsInfo *info, KeyInfo *keyi)
 {
-    unsigned group = info->explicit_group;
+    xkb_layout_index_t i;
 
-    if (group == 0)
+    if (info->explicit_group == 0)
         return true;
 
-    if ((key->typesDefined | key->symsDefined | key->actsDefined) & ~1)
-    {
-        int i;
-        WARN("For the map %s an explicit group specified\n", info->name);
-        WARN("but key %s has more than one group defined\n",
-              longText(key->name));
-        ACTION("All groups except first one will be ignored\n");
-        for (i = 1; i < XkbNumKbdGroups; i++)
-        {
-            key->numLevels[i] = 0;
-            darray_free(key->syms[i]);
-            darray_free(key->acts[i]);
-            key->types[i] = 0;
+    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;
         }
     }
-    key->typesDefined = key->symsDefined = key->actsDefined = 1 << group;
-
-    key->numLevels[group] = key->numLevels[0];
-    key->numLevels[0] = 0;
-    key->syms[group] = key->syms[0];
-    darray_init(key->syms[0]);
-    key->symsMapIndex[group] = key->symsMapIndex[0];
-    darray_init(key->symsMapIndex[0]);
-    key->symsMapNumEntries[group] = key->symsMapNumEntries[0];
-    darray_init(key->symsMapNumEntries[0]);
-    key->acts[group] = key->acts[0];
-    darray_init(key->acts[0]);
-    key->types[group] = key->types[0];
-    key->types[0] = 0;
+    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]);
     return true;
 }
 
 static int
-HandleSymbolsDef(SymbolsDef *stmt, struct xkb_keymap *keymap,
-                 SymbolsInfo *info)
+HandleSymbolsDef(SymbolsInfo *info, SymbolsDef *stmt)
 {
-    KeyInfo key;
-
-    InitKeyInfo(&key);
-    CopyKeyInfo(&info->dflt, &key, false);
-    key.defs.merge = stmt->merge;
-    key.name = KeyNameToLong(stmt->keyName);
-    if (!HandleSymbolsBody((VarDef *) stmt->symbols, keymap, &key, info))
-    {
+    KeyInfo keyi;
+    xkb_layout_index_t i;
+
+    keyi = info->dflt;
+    for (i = 0; i < XKB_NUM_GROUPS; i++) {
+        darray_copy(keyi.groups[i].syms, info->dflt.groups[i].syms);
+        darray_copy(keyi.groups[i].levels, 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;
     }
 
-    if (!SetExplicitGroup(info, &key))
-    {
+    if (!SetExplicitGroup(info, &keyi)) {
         info->errorCount++;
         return false;
     }
 
-    if (!AddKeySymbols(info, &key, keymap))
-    {
+    if (!AddKeySymbols(info, &keyi)) {
         info->errorCount++;
         return false;
     }
+
     return true;
 }
 
 static bool
-HandleModMapDef(ModMapDef *def, struct xkb_keymap *keymap, SymbolsInfo *info)
+HandleModMapDef(SymbolsInfo *info, ModMapDef *def)
 {
     ExprDef *key;
     ModMapEntry tmp;
-    ExprResult rtrn;
+    xkb_mod_index_t ndx;
     bool ok;
+    struct xkb_context *ctx = info->keymap->ctx;
 
-    if (!LookupModIndex(keymap->ctx, NULL, def->modifier, TypeInt, &rtrn))
-    {
-        ERROR("Illegal modifier map definition\n");
-        ACTION("Ignoring map for non-modifier \"%s\"\n",
-                xkb_atom_text(keymap->ctx, def->modifier));
+    if (!LookupModIndex(ctx, NULL, def->modifier, EXPR_TYPE_INT, &ndx)) {
+        log_err(info->keymap->ctx,
+                "Illegal modifier map definition; "
+                "Ignoring map for non-modifier \"%s\"\n",
+                xkb_atom_text(ctx, def->modifier));
         return false;
     }
+
     ok = true;
-    tmp.modifier = rtrn.uval;
-    for (key = def->keys; key != NULL; key = (ExprDef *) key->common.next)
-    {
-        if ((key->op == ExprValue) && (key->type == TypeKeyName))
-        {
+    tmp.modifier = ndx;
+
+    for (key = def->keys; key != NULL; key = (ExprDef *) key->common.next) {
+        xkb_keysym_t sym;
+
+        if (key->op == EXPR_VALUE && key->value_type == EXPR_TYPE_KEYNAME) {
             tmp.haveSymbol = false;
             tmp.u.keyName = KeyNameToLong(key->value.keyName);
         }
-        else if (ExprResolveKeySym(keymap->ctx, key, &rtrn))
-        {
+        else if (ExprResolveKeySym(ctx, key, &sym)) {
             tmp.haveSymbol = true;
-            tmp.u.keySym = rtrn.uval;
+            tmp.u.keySym = sym;
         }
-        else
-        {
-            ERROR("Modmap entries may contain only key names or keysyms\n");
-            ACTION("Illegal definition for %s modifier ignored\n",
-                    XkbcModIndexText(tmp.modifier));
+        else {
+            log_err(info->keymap->ctx,
+                    "Modmap entries may contain only key names or keysyms; "
+                    "Illegal definition for %s modifier ignored\n",
+                    ModIndexText(tmp.modifier));
             continue;
         }
 
@@ -1500,69 +1224,55 @@ HandleModMapDef(ModMapDef *def, struct xkb_keymap *keymap, SymbolsInfo *info)
 }
 
 static void
-HandleSymbolsFile(XkbFile *file, struct xkb_keymap *keymap,
-                  enum merge_mode merge, SymbolsInfo *info)
+HandleSymbolsFile(SymbolsInfo *info, XkbFile *file, enum merge_mode merge)
 {
+    bool ok;
     ParseCommon *stmt;
 
     free(info->name);
-    info->name = uDupString(file->name);
+    info->name = strdup_safe(file->name);
+
     stmt = file->defs;
-    while (stmt)
-    {
-        switch (stmt->stmtType)
-        {
-        case StmtInclude:
-            if (!HandleIncludeSymbols((IncludeStmt *) stmt, keymap, info))
-                info->errorCount++;
+    for (stmt = file->defs; stmt; stmt = stmt->next) {
+        switch (stmt->type) {
+        case STMT_INCLUDE:
+            ok = HandleIncludeSymbols(info, (IncludeStmt *) stmt);
             break;
-        case StmtSymbolsDef:
-            if (!HandleSymbolsDef((SymbolsDef *) stmt, keymap, info))
-                info->errorCount++;
+        case STMT_SYMBOLS:
+            ok = HandleSymbolsDef(info, (SymbolsDef *) stmt);
             break;
-        case StmtVarDef:
-            if (!HandleSymbolsVar((VarDef *) stmt, keymap, info))
-                info->errorCount++;
+        case STMT_VAR:
+            ok = HandleGlobalVar(info, (VarDef *) stmt);
             break;
-        case StmtVModDef:
-            if (!HandleVModDef((VModDef *) stmt, keymap, merge, &info->vmods))
-                info->errorCount++;
+        case STMT_VMOD:
+            ok = HandleVModDef((VModDef *) stmt, info->keymap, merge,
+                               &info->vmods);
             break;
-        case StmtInterpDef:
-            ERROR("Interpretation files may not include other types\n");
-            ACTION("Ignoring definition of symbol interpretation\n");
-            info->errorCount++;
-            break;
-        case StmtKeycodeDef:
-            ERROR("Interpretation files may not include other types\n");
-            ACTION("Ignoring definition of key name\n");
-            info->errorCount++;
-            break;
-        case StmtModMapDef:
-            if (!HandleModMapDef((ModMapDef *) stmt, keymap, info))
-                info->errorCount++;
+        case STMT_MODMAP:
+            ok = HandleModMapDef(info, (ModMapDef *) stmt);
             break;
         default:
-            WSGO("Unexpected statement type %d in HandleSymbolsFile\n",
-                  stmt->stmtType);
+            log_err(info->keymap->ctx,
+                    "Interpretation files may not include other types; "
+                    "Ignoring %s\n", stmt_type_to_string(stmt->type));
+            ok = false;
             break;
         }
-        stmt = stmt->next;
-        if (info->errorCount > 10)
-        {
-#ifdef NOISY
-            ERROR("Too many errors\n");
-#endif
-            ACTION("Abandoning symbols file \"%s\"\n", file->topName);
+
+        if (!ok)
+            info->errorCount++;
+
+        if (info->errorCount > 10) {
+            log_err(info->keymap->ctx, "Abandoning symbols file \"%s\"\n",
+                    file->topName);
             break;
         }
     }
 }
 
 /**
- * Given a keysym @sym, find the keycode which generates it
- * (returned in @kc_rtrn). This is used for example in a modifier
- * map definition, such as:
+ * Given a keysym @sym, return a key which generates it, or NULL.
+ * This is used for example in a modifier map definition, such as:
  *      modifier_map Lock           { Caps_Lock };
  * where we want to add the Lock modifier to the modmap of the key
  * which matches the keysym Caps_Lock.
@@ -1570,22 +1280,19 @@ HandleSymbolsFile(XkbFile *file, struct xkb_keymap *keymap,
  * is chosen first by lowest group in which the keysym appears, than
  * by lowest level and than by lowest key code.
  */
-static bool
-FindKeyForSymbol(struct xkb_keymap *keymap, xkb_keysym_t sym,
-                 xkb_keycode_t *kc_rtrn)
+static struct xkb_key *
+FindKeyForSymbol(struct xkb_keymap *keymap, xkb_keysym_t sym)
 {
-    xkb_keycode_t key;
-    unsigned int group, level, min_group = UINT_MAX, min_level = UINT_MAX;
+    struct xkb_key *key, *ret = NULL;
+    xkb_layout_index_t group, min_group = UINT32_MAX;
+    xkb_level_index_t level, min_level = UINT16_MAX;
 
-    for (key = keymap->min_key_code; key <= keymap->max_key_code; key++)
-    {
-        for (group = 0; group < XkbKeyNumGroups(keymap, key); group++)
-        {
+    xkb_foreach_key(key, keymap) {
+        for (group = 0; group < key->num_groups; group++) {
             for (level = 0; level < XkbKeyGroupWidth(keymap, key, group);
-                 level++)
-            {
-                if (XkbKeyNumSyms(keymap, key, group, level) != 1 ||
-                    (XkbKeySymEntry(keymap, key, group, level))[0] != sym)
+                 level++) {
+                if (XkbKeyNumSyms(key, group, level) != 1 ||
+                    (XkbKeySymEntry(key, group, level))[0] != sym)
                     continue;
 
                 /*
@@ -1595,10 +1302,11 @@ FindKeyForSymbol(struct xkb_keymap *keymap, xkb_keysym_t sym,
                  */
                 if (group < min_group ||
                     (group == min_group && level < min_level)) {
-                    *kc_rtrn = key;
+                    ret = key;
                     if (group == 0 && level == 0) {
-                        return true;
-                    } else {
+                        return ret;
+                    }
+                    else {
                         min_group = group;
                         min_level = level;
                     }
@@ -1607,33 +1315,21 @@ FindKeyForSymbol(struct xkb_keymap *keymap, xkb_keysym_t sym,
         }
     }
 
-    return min_group != UINT_MAX;
+    return ret;
 }
 
-/**
- * Find the given name in the keymap->map->types and return its index.
- *
- * @param atom The atom 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 atom, unsigned *type_rtrn)
+FindNamedType(struct xkb_keymap *keymap, xkb_atom_t name, unsigned *type_rtrn)
 {
-    unsigned n = 0;
-    const char *name = xkb_atom_text(keymap->ctx, atom);
-    struct xkb_key_type *type;
-
-    if (keymap && keymap->map) {
-        darray_foreach(type, keymap->map->types) {
-            if (strcmp(type->name, name) == 0) {
-                *type_rtrn = n;
-                return true;
-            }
-            n++;
+    unsigned int i;
+
+    for (i = 0; i < keymap->num_types; i++) {
+        if (keymap->types[i].name == name) {
+            *type_rtrn = i;
+            return true;
         }
     }
+
     return false;
 }
 
@@ -1658,522 +1354,340 @@ FindNamedType(struct xkb_keymap *keymap, xkb_atom_t atom, unsigned *type_rtrn)
  *        symbol per level.
  */
 static bool
-FindAutomaticType(struct xkb_keymap *keymap, int 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");
+    if ((width == 1) || (width == 0)) {
+        *typeNameRtrn = xkb_atom_intern(ctx, "ONE_LEVEL");
         *autoType = true;
     }
-    else if (width == 2)
-    {
+    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");
+            xkb_keysym_is_upper(syms[1])) {
+            *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");
+                          xkb_keysym_is_keypad(syms[1]))) {
+            *typeNameRtrn = xkb_atom_intern(ctx, "KEYPAD");
             *autoType = true;
         }
-        else
-        {
-            *typeNameRtrn = xkb_atom_intern(keymap->ctx, "TWO_LEVEL");
+        else {
+            *typeNameRtrn = xkb_atom_intern(ctx, "TWO_LEVEL");
             *autoType = true;
         }
     }
-    else if (width <= 4)
-    {
+    else if (width <= 4) {
         if (syms && xkb_keysym_is_lower(syms[0]) &&
             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 >= 0) && (width <= 4));
+    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 * key)
+static bool
+CopySymbolsDef(SymbolsInfo *info, KeyInfo *keyi)
 {
-    int i, j, width, defined, lastGroup;
-    bool identical;
-
-    defined = key->symsDefined | key->actsDefined | key->typesDefined;
-    /* get highest group number */
-    for (i = XkbNumKbdGroups - 1; i >= 0; i--)
-    {
-        if (defined & (1 << i))
-            break;
+    struct xkb_keymap *keymap = info->keymap;
+    struct xkb_key *key;
+    const GroupInfo *group0;
+    xkb_layout_index_t i;
+    bool haveActions;
+    unsigned int sizeSyms;
+    unsigned int symIndex;
+
+    /*
+     * 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;
     }
-    lastGroup = i;
 
-    if (lastGroup == 0)
-        return;
+    /* Find the range of groups we need. */
+    key->num_groups = 0;
+    for (i = 0; i < XKB_NUM_GROUPS; i++)
+        if (keyi->groups[i].defined)
+            key->num_groups = i + 1;
 
-    /* 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--)
-    {
-        if (defined & (1 << i))
-            continue;
-        width = key->numLevels[0];
-        if (key->typesDefined & 1)
-        {
-            for (j = 0; j < width; j++)
-            {
-                key->types[i] = key->types[0];
-            }
-            key->typesDefined |= 1 << i;
-        }
-        if ((key->actsDefined & 1) && !darray_empty(key->acts[0]))
-        {
-            darray_copy(key->acts[i], key->acts[0]);
-            key->actsDefined |= 1 << i;
-        }
-        if ((key->symsDefined & 1) && !darray_empty(key->syms[0]))
-        {
-            darray_copy(key->syms[i], key->syms[0]);
-            darray_copy(key->symsMapIndex[i], key->symsMapIndex[0]);
-            darray_copy(key->symsMapNumEntries[i], key->symsMapNumEntries[0]);
-            key->symsDefined |= 1 << i;
-        }
-        if (defined & 1)
-        {
-            key->numLevels[i] = key->numLevels[0];
-        }
-    }
-    /* If all groups are completely identical remove them all */
-    /* exept the first one. */
-    identical = true;
-    for (i = lastGroup; i > 0; i--)
-    {
-        if ((key->numLevels[i] != key->numLevels[0]) ||
-            (key->types[i] != key->types[0]))
-        {
-            identical = false;
-            break;
-        }
-        if (!darray_same(key->syms[i], key->syms[0]) &&
-            (darray_empty(key->syms[i]) || darray_empty(key->syms[0]) ||
-             darray_size(key->syms[i]) != darray_size(key->syms[0]) ||
-             memcmp(darray_mem(key->syms[i], 0),
-                    darray_mem(key->syms[0], 0),
-                   sizeof(xkb_keysym_t) * darray_size(key->syms[0]))))
-        {
-            identical = false;
-            break;
-        }
-        if (!darray_same(key->symsMapIndex[i], key->symsMapIndex[0]) &&
-            (darray_empty(key->symsMapIndex[i]) ||
-             darray_empty(key->symsMapIndex[0]) ||
-             memcmp(darray_mem(key->symsMapIndex[i], 0),
-                    darray_mem(key->symsMapIndex[0], 0),
-                    key->numLevels[0] * sizeof(int))))
-        {
-            identical = false;
-            continue;
-        }
-        if (!darray_same(key->symsMapNumEntries[i], key->symsMapNumEntries[0]) &&
-            (darray_empty(key->symsMapNumEntries[i]) ||
-             darray_empty(key->symsMapNumEntries[0]) ||
-             memcmp(darray_mem(key->symsMapNumEntries[i], 0),
-                    darray_mem(key->symsMapNumEntries[0], 0),
-                    key->numLevels[0] * sizeof(size_t))))
-        {
-            identical = false;
+    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.
+     */
+    group0 = &keyi->groups[0];
+    for (i = 1; i < key->num_groups - 1; i++) {
+        GroupInfo *groupi = &keyi->groups[i];
+
+        if (groupi->defined)
             continue;
-        }
-        if (!darray_same(key->acts[i], key->acts[0]) &&
-            (darray_empty(key->acts[i]) || darray_empty(key->acts[0]) ||
-             memcmp(darray_mem(key->acts[i], 0),
-                    darray_mem(key->acts[0], 0),
-                    key->numLevels[0] * sizeof(union xkb_action))))
-        {
-            identical = false;
-            break;
-        }
-    }
-    if (identical)
-    {
-        for (i = lastGroup; i > 0; i--)
-        {
-            key->numLevels[i] = 0;
-            darray_free(key->syms[i]);
-            darray_free(key->symsMapIndex[i]);
-            darray_free(key->symsMapNumEntries[i]);
-            darray_free(key->acts[i]);
-            key->types[i] = 0;
-        }
-        key->symsDefined &= 1;
-        key->actsDefined &= 1;
-        key->typesDefined &= 1;
-    }
-}
 
-/**
- * Copy the KeyInfo into the keyboard description.
- *
- * This function recurses.
- */
-static bool
-CopySymbolsDef(struct xkb_keymap *keymap, KeyInfo *key, int start_from)
-{
-    unsigned int i;
-    xkb_keycode_t kc;
-    unsigned int sizeSyms = 0;
-    unsigned width, tmp, nGroups;
-    struct xkb_key_type * type;
-    bool haveActions, autoType, useAlias;
-    unsigned types[XkbNumKbdGroups];
-    union xkb_action *outActs;
-    unsigned int symIndex = 0;
-    struct xkb_sym_map *sym_map;
-
-    useAlias = (start_from == 0);
-
-    /* get the keycode for the key. */
-    if (!FindNamedKey(keymap, key->name, &kc, useAlias,
-                      CreateKeyNames(keymap), start_from))
-    {
-        if ((start_from == 0) && (warningLevel >= 5))
-        {
-            WARN("Key %s not found in keycodes\n", longText(key->name));
-            ACTION("Symbols ignored\n");
-        }
-        return false;
+        groupi->type = group0->type;
+        darray_copy(groupi->syms, group0->syms);
+        darray_copy(groupi->levels, group0->levels);
+        groupi->defined = group0->defined;
     }
 
+    /* See if we need to allocate an actions array. */
     haveActions = false;
-    for (i = width = nGroups = 0; i < XkbNumKbdGroups; i++)
-    {
-        if (((i + 1) > nGroups)
-            && (((key->symsDefined | key->actsDefined) & (1 << i))
-                || (key->typesDefined) & (1 << i)))
-            nGroups = i + 1;
-        if (!darray_empty(key->acts[i]))
-            haveActions = true;
-        autoType = false;
-        /* Assign the type to the key, if it is missing. */
-        if (key->types[i] == XKB_ATOM_NONE)
-        {
-            if (key->dfltType != XKB_ATOM_NONE)
-                key->types[i] = key->dfltType;
-            else if (FindAutomaticType(keymap, key->numLevels[i],
-                                       darray_mem(key->syms[i], 0),
-                                       &key->types[i], &autoType))
-            {
-            }
-            else
-            {
-                if (warningLevel >= 5)
-                {
-                    WARN("No automatic type for %d symbols\n",
-                          (unsigned int) key->numLevels[i]);
-                    ACTION("Using %s for the %s key (keycode %d)\n",
-                            xkb_atom_text(keymap->ctx, key->types[i]),
-                            longText(key->name), kc);
-                }
-            }
-        }
-        if (FindNamedType(keymap, key->types[i], &types[i]))
-        {
-            if (!autoType || key->numLevels[i] > 2)
-                keymap->server->explicit[kc] |= (1 << i);
-        }
-        else
-        {
-            if (warningLevel >= 3)
-            {
-                WARN("Type \"%s\" is not defined\n",
-                      xkb_atom_text(keymap->ctx, key->types[i]));
-                ACTION("Using TWO_LEVEL for the %s key (keycode %d)\n",
-                        longText(key->name), kc);
-            }
-            types[i] = XkbTwoLevelIndex;
-        }
-        /* if the type specifies fewer levels than the key has, shrink the key */
-        type = &darray_item(keymap->map->types, types[i]);
-        if (type->num_levels < key->numLevels[i])
-        {
-            if (warningLevel > 0)
-            {
-                WARN("Type \"%s\" has %d levels, but %s has %d symbols\n",
-                     type->name, type->num_levels,
-                     xkb_atom_text(keymap->ctx, key->name), key->numLevels[i]);
-                ACTION("Ignoring extra symbols\n");
+    for (i = 0; i < key->num_groups; i++) {
+        LevelInfo *leveli;
+        darray_foreach(leveli, keyi->groups[i].levels) {
+            if (leveli->act.type != ACTION_TYPE_NONE) {
+                haveActions = true;
+                goto out_of_loops;
             }
-            key->numLevels[i] = type->num_levels;
         }
-        if (key->numLevels[i] > width)
-            width = key->numLevels[i];
-        if (type->num_levels > width)
-            width = type->num_levels;
-        sizeSyms += darray_size(key->syms[i]);
     }
+out_of_loops:
 
-    if (!XkbcResizeKeySyms(keymap, kc, sizeSyms))
-    {
-        WSGO("Could not enlarge symbols for %s (keycode %d)\n",
-              longText(key->name), kc);
-        return false;
-    }
-    if (haveActions)
-    {
-        outActs = XkbcResizeKeyActions(keymap, kc, width * nGroups);
-        if (outActs == NULL)
-        {
-            WSGO("Could not enlarge actions for %s (key %d)\n",
-                  longText(key->name), kc);
-            return false;
-        }
-        keymap->server->explicit[kc] |= XkbExplicitInterpretMask;
-    }
-    else
-        outActs = NULL;
-
-    sym_map = &darray_item(keymap->map->key_sym_map, kc);
-
-    if (key->defs.defined & _Key_GroupInfo)
-        i = key->groupInfo;
-    else
-        i = sym_map->group_info;
-
-    sym_map->group_info = XkbSetNumGroups(i, nGroups);
-    sym_map->width = width;
-    sym_map->sym_index = uTypedCalloc(nGroups * width, int);
-    sym_map->num_syms = uTypedCalloc(nGroups * width, unsigned int);
-
-    for (i = 0; i < nGroups; 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.
+    /*
+     * Find and assign the groups' types in the keymap. Also find the
+     * key width according to the largest type.
+     */
+    key->width = 0;
+    for (i = 0; i < key->num_groups; i++) {
+        struct xkb_key_type *type;
+        GroupInfo *groupi = &keyi->groups[i];
+        bool autoType = false;
+
+        /* 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->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 levels; "
+                        "Using %s for the %s key\n",
+                        (int) darray_size(groupi->levels),
+                        xkb_atom_text(keymap->ctx, groupi->type),
+                        LongKeyNameText(keyi->name));
+        }
+
+        /* 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\n",
+                    xkb_atom_text(keymap->ctx, groupi->type),
+                    LongKeyNameText(keyi->name));
+            /*
+             * Index 0 is guaranteed to contain something, usually
+             * ONE_LEVEL or at least some default one-level type.
+             */
+            key->kt_index[i] = 0;
+        }
+
+        /* 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 levels; "
+                    "Ignoring extra symbols\n",
+                    xkb_atom_text(keymap->ctx, type->name),
+                    type->num_levels,
+                    LongKeyNameText(keyi->name),
+                    (int) darray_size(groupi->levels));
+            darray_resize(groupi->levels, type->num_levels);
+        }
+
+        /*
+         * 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.
          */
-        if (key->numLevels[i])
-            sym_map->kt_index[i] = types[i];
-        if (!darray_empty(key->syms[i]))
-        {
-            /* fill key to "width" symbols*/
-            for (tmp = 0; tmp < width; tmp++)
-            {
-                if (tmp < key->numLevels[i] &&
-                    darray_item(key->symsMapNumEntries[i], tmp) != 0)
-                {
-                    memcpy(darray_mem(sym_map->syms, symIndex),
-                           darray_mem(key->syms[i],
-                                      darray_item(key->symsMapIndex[i], tmp)),
-                           darray_item(key->symsMapNumEntries[i], tmp) * sizeof(xkb_keysym_t));
-                    sym_map->sym_index[(i * width) + tmp] = symIndex;
-                    sym_map->num_syms[(i * width) + tmp] =
-                        darray_item(key->symsMapNumEntries[i], tmp);
-                    symIndex += sym_map->num_syms[(i * width) + tmp];
-                }
-                else
-                {
-                    sym_map->sym_index[(i * width) + tmp] = -1;
-                    sym_map->num_syms[(i * width) + tmp] = 0;
-                }
-                if (outActs != NULL && !darray_empty(key->acts[i]))
-                {
-                    if (tmp < key->numLevels[i])
-                        outActs[tmp] = darray_item(key->acts[i], tmp);
-                    else
-                        outActs[tmp].type = XkbSA_NoAction;
-                }
-            }
-        }
+        key->width = MAX(key->width, type->num_levels);
     }
-    switch (key->behavior.type & XkbKB_OpMask)
-    {
-    case XkbKB_Default:
-        break;
-    default:
-        keymap->server->behaviors[kc] = key->behavior;
-        keymap->server->explicit[kc] |= XkbExplicitBehaviorMask;
-        break;
-    }
-    if (key->defs.defined & _Key_VModMap)
-    {
-        keymap->server->vmodmap[kc] = key->vmodmap;
-        keymap->server->explicit[kc] |= XkbExplicitVModMapMask;
-    }
-    if (key->repeat != RepeatUndefined)
-    {
-        if (key->repeat == RepeatYes)
-            keymap->ctrls->per_key_repeat[kc / 8] |= (1 << (kc % 8));
-        else
-            keymap->ctrls->per_key_repeat[kc / 8] &= ~(1 << (kc % 8));
-        keymap->server->explicit[kc] |= XkbExplicitAutoRepeatMask;
+
+    /* Find the size of the syms array. */
+    sizeSyms = 0;
+    for (i = 0; i < key->num_groups; i++)
+        sizeSyms += darray_size(keyi->groups[i].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;
+    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;
+    }
+
+    if (keyi->repeat != KEY_REPEAT_UNDEFINED) {
+        key->repeats = (keyi->repeat == KEY_REPEAT_YES);
+        key->explicit |= EXPLICIT_REPEAT;
+    }
+
+    /* Copy keysyms and actions. */
+    symIndex = 0;
+    for (i = 0; i < key->num_groups; i++) {
+        GroupInfo *groupi = &keyi->groups[i];
+        xkb_level_index_t j;
+
+        /* We rely on calloc having zeroized the arrays up to key->width. */
+        for (j = 0; j < darray_size(groupi->levels); j++) {
+            LevelInfo *leveli = &darray_item(groupi->levels, j);
 
-    if (nGroups > keymap->ctrls->num_groups)
-       keymap->ctrls->num_groups = nGroups;
+            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];
+        }
+    }
 
-    /* do the same thing for the next key */
-    CopySymbolsDef(keymap, key, kc + 1);
     return true;
 }
 
 static bool
-CopyModMapDef(struct xkb_keymap *keymap, ModMapEntry *entry)
+CopyModMapDef(SymbolsInfo *info, ModMapEntry *entry)
 {
-    xkb_keycode_t kc;
-
-    if (!entry->haveSymbol &&
-        !FindNamedKey(keymap, entry->u.keyName, &kc, true,
-                      CreateKeyNames(keymap), 0))
-    {
-        if (warningLevel >= 5)
-        {
-            WARN("Key %s not found in keycodes\n",
-                  longText(entry->u.keyName));
-            ACTION("Modifier map entry for %s not updated\n",
-                    XkbcModIndexText(entry->modifier));
+    struct xkb_key *key;
+    struct xkb_keymap *keymap = info->keymap;
+
+    if (!entry->haveSymbol) {
+        key = FindNamedKey(keymap, entry->u.keyName, true);
+        if (!key) {
+            log_vrb(info->keymap->ctx, 5,
+                    "Key %s not found in keycodes; "
+                    "Modifier map entry for %s not updated\n",
+                    LongKeyNameText(entry->u.keyName),
+                    ModIndexText(entry->modifier));
+            return false;
         }
-        return false;
     }
-    else if (entry->haveSymbol &&
-             !FindKeyForSymbol(keymap, entry->u.keySym, &kc))
-    {
-        if (warningLevel > 5)
-        {
-            WARN("Key \"%s\" not found in symbol map\n",
-                  XkbcKeysymText(entry->u.keySym));
-            ACTION("Modifier map entry for %s not updated\n",
-                    XkbcModIndexText(entry->modifier));
+    else {
+        key = FindKeyForSymbol(keymap, entry->u.keySym);
+        if (!key) {
+            log_vrb(info->keymap->ctx, 5,
+                    "Key \"%s\" not found in symbol map; "
+                    "Modifier map entry for %s not updated\n",
+                    KeysymText(entry->u.keySym),
+                    ModIndexText(entry->modifier));
+            return false;
         }
-        return false;
     }
-    keymap->map->modmap[kc] |= (1 << entry->modifier);
+
+    key->modmap |= (1 << entry->modifier);
     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).
- */
-bool
-CompileSymbols(XkbFile *file, struct xkb_keymap *keymap, enum merge_mode merge)
+static bool
+CopySymbolsToKeymap(struct xkb_keymap *keymap, SymbolsInfo *info)
 {
-    unsigned int i;
-    SymbolsInfo info;
-    KeyInfo *key;
-
-    InitSymbolsInfo(&info, keymap);
-    info.dflt.defs.fileID = file->id;
-    info.dflt.defs.merge = merge;
-
-    HandleSymbolsFile(file, keymap, merge, &info);
+    KeyInfo *keyi;
+    ModMapEntry *mm;
+    xkb_layout_index_t i;
+    struct xkb_key *key;
 
-    if (darray_empty(info.keys))
-        goto err_info;
+    keymap->symbols_section_name = strdup_safe(info->name);
 
-    if (info.errorCount != 0)
-        goto err_info;
+    for (i = 0; i < XKB_NUM_GROUPS; i++)
+        if (info->groupNames[i] != XKB_ATOM_NONE)
+            keymap->group_names[i] = info->groupNames[i];
 
-    /* alloc memory in the xkb struct */
-    if (XkbcAllocNames(keymap, XkbGroupNamesMask, 0) != Success) {
-        WSGO("Can not allocate names in CompileSymbols\n");
-        ACTION("Symbols not added\n");
-        goto err_info;
-    }
+    darray_foreach(keyi, info->keys)
+        if (!CopySymbolsDef(info, keyi))
+            info->errorCount++;
 
-    if (XkbcAllocClientMap(keymap, XkbKeySymsMask | XkbModifierMapMask, 0)
-        != Success) {
-        WSGO("Could not allocate client map in CompileSymbols\n");
-        ACTION("Symbols not added\n");
-        goto err_info;
-    }
+    if (xkb_get_log_verbosity(keymap->ctx) > 3) {
+        xkb_foreach_key(key, keymap) {
+            if (key->name[0] == '\0')
+                continue;
 
-    if (XkbcAllocServerMap(keymap, XkbAllServerInfoMask, 32) != Success) {
-        WSGO("Could not allocate server map in CompileSymbols\n");
-        ACTION("Symbols not added\n");
-        goto err_info;
+            if (key->num_groups < 1)
+                log_info(keymap->ctx,
+                         "No symbols defined for %s\n",
+                         KeyNameText(key->name));
+        }
     }
 
-    if (XkbcAllocControls(keymap) != Success) {
-        WSGO("Could not allocate controls in CompileSymbols\n");
-        ACTION("Symbols not added\n");
-        goto err_info;
-    }
+    darray_foreach(mm, info->modMaps)
+        if (!CopyModMapDef(info, mm))
+            info->errorCount++;
 
-    if (info.name)
-        keymap->names->symbols = strdup(info.name);
+    /* XXX: If we don't ignore errorCount, things break. */
+    return true;
+}
 
-    /* now copy info into xkb. */
-    ApplyAliases(keymap, &info.aliases);
+bool
+CompileSymbols(XkbFile *file, struct xkb_keymap *keymap,
+               enum merge_mode merge)
+{
+    SymbolsInfo info;
+    ActionsInfo *actions;
 
-    for (i = 0; i < XkbNumKbdGroups; i++) {
-        if (info.groupNames[i] != XKB_ATOM_NONE) {
-            free(keymap->names->groups[i]);
-            keymap->names->groups[i] = xkb_atom_strdup(keymap->ctx,
-                                                       info.groupNames[i]);
-        }
-    }
+    actions = NewActionsInfo();
+    if (!actions)
+        return false;
 
-    /* sanitize keys */
-    darray_foreach(key, info.keys)
-        PrepareKeyDef(key);
+    InitSymbolsInfo(&info, keymap, file->id, actions);
+    info.dflt.merge = merge;
 
-    /* copy! */
-    darray_foreach(key, info.keys)
-        if (!CopySymbolsDef(keymap, key, 0))
-            info.errorCount++;
+    HandleSymbolsFile(&info, file, merge);
 
-    if (warningLevel > 3) {
-        for (i = keymap->min_key_code; i <= keymap->max_key_code; i++) {
-            if (darray_item(keymap->names->keys, i).name[0] == '\0')
-                continue;
+    if (darray_empty(info.keys))
+        goto err_info;
 
-            if (XkbKeyNumGroups(keymap, i) < 1) {
-                char buf[5];
-                memcpy(buf, darray_item(keymap->names->keys, i).name, 4);
-                buf[4] = '\0';
-                WARN("No symbols defined for <%s> (keycode %d)\n", buf, i);
-            }
-        }
-    }
+    if (info.errorCount != 0)
+        goto err_info;
 
-    if (info.modMap) {
-        ModMapEntry *mm, *next;
-        for (mm = info.modMap; mm != NULL; mm = next) {
-            if (!CopyModMapDef(keymap, mm))
-                info.errorCount++;
-            next = (ModMapEntry *) mm->defs.next;
-        }
-    }
+    if (!CopySymbolsToKeymap(keymap, &info))
+        goto err_info;
 
-    FreeSymbolsInfo(&info);
+    ClearSymbolsInfo(&info);
+    FreeActionsInfo(actions);
     return true;
 
 err_info:
-    FreeSymbolsInfo(&info);
+    FreeActionsInfo(actions);
+    ClearSymbolsInfo(&info);
     return false;
 }