text: explicitly take mod_type in mod functions
[platform/upstream/libxkbcommon.git] / src / xkbcomp / compat.c
index 7cd88b5..522389b 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.
+ *
  ********************************************************/
 
+/*
+ * 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.
+ */
+
 #include "xkbcomp-priv.h"
-#include "parseutils.h"
+#include "text.h"
+#include "expr.h"
 #include "action.h"
-#include "indicators.h"
 #include "vmod.h"
+#include "include.h"
+
+/*
+ * The xkb_compat section
+ * =====================
+ * This section is the third to be processesed, after xkb_keycodes and
+ * xkb_types.
+ *
+ * Interpret statements
+ * --------------------
+ * Statements of the form:
+ *      interpret Num_Lock+Any { ... }
+ *
+ * The body of the statment may include statements of the following
+ * forms:
+ *
+ * - action statement:
+ *      action = LockMods(modifiers=NumLock);
+ *
+ * - virtual modifier statement:
+ *      virtualModifier = NumLock;
+ *
+ * - repeat statement:
+ *      repeat = True;
+ *
+ * - useModMapMods statement:
+ *      useModMapMods = level1;
+ *
+ * Indicator map statements
+ * ------------------------
+ * Statements of the form:
+ *      indicator "Shift Lock" { ... }
+ *
+ *   This statement specifies the behavior and binding of the indicator
+ *   with the given name ("Shift Lock" above). The name should have been
+ *   declared previously in the xkb_keycodes section (see Indicator name
+ *   statement), and given an index there. If it wasn't, it is created
+ *   with the next free index.
+ *   The body of the statement describes the conditions of the keyboard
+ *   state which will cause the indicator to be lit. It may include the
+ *   following statements:
+ *
+ * - modifiers statment:
+ *      modifiers = ScrollLock;
+ *
+ *   If the given modifiers are in the required state (see below), the
+ *   led is lit.
+ *
+ * - whichModifierState statment:
+ *      whichModState = Latched + Locked;
+ *
+ *   Can be any combination of:
+ *      base, latched, locked, effective
+ *      any (i.e. all of the above)
+ *      none (i.e. none of the above)
+ *      compat (this is legal, but unused)
+ *   This will cause the respective portion of the modifer state (see
+ *   struct xkb_state) to be matched against the modifiers given in the
+ *   "modifiers" statement.
+ *
+ *   Here's a simple example:
+ *      indicator "Num Lock" {
+ *          modifiers = NumLock;
+ *          whichModState = Locked;
+ *      };
+ *   Whenever the NumLock modifier is locked, the Num Lock indicator
+ *   will light up.
+ *
+ * - groups statment:
+ *      groups = All - group1;
+ *
+ *   If the given groups are in the required state (see below), the led
+ *   is lit.
+ *
+ * - whichGroupState statment:
+ *      whichGroupState = Effective;
+ *
+ *   Can be any combination of:
+ *      base, latched, locked, effective
+ *      any (i.e. all of the above)
+ *      none (i.e. none of the above)
+ *   This will cause the respective portion of the group state (see
+ *   struct xkb_state) to be matched against the groups given in the
+ *   "groups" statement.
+ *
+ *   Note: the above conditions are disjunctive, i.e. if any of them are
+ *   satisfied the led is lit.
+ *
+ * Virtual modifier statements
+ * ---------------------------
+ * Statements of the form:
+ *     virtual_modifiers LControl;
+ *
+ * Can appear in the xkb_types, xkb_compat, xkb_symbols sections.
+ * TODO
+ *
+ * Effect on keymap
+ * ----------------
+ * After all of the xkb_compat sections have been compiled, the following
+ * members of struct xkb_keymap are finalized:
+ *      darray(struct xkb_sym_interpret) sym_interpret;
+ *      struct xkb_indicator_map indicators[XKB_NUM_INDICATORS];
+ *      char *compat_section_name;
+ * TODO: virtual modifiers.
+ */
+
+enum si_field {
+    SI_FIELD_VIRTUAL_MOD    = (1 << 0),
+    SI_FIELD_ACTION         = (1 << 1),
+    SI_FIELD_AUTO_REPEAT    = (1 << 2),
+    SI_FIELD_LEVEL_ONE_ONLY = (1 << 3),
+};
+
+typedef struct {
+    enum si_field defined;
+    unsigned file_id;
+    enum merge_mode merge;
 
-typedef struct _SymInterpInfo
-{
-    CommonInfo defs;
     struct xkb_sym_interpret interp;
 } SymInterpInfo;
 
-#define        _SI_VirtualMod          (1<<0)
-#define        _SI_Action              (1<<1)
-#define        _SI_AutoRepeat          (1<<2)
-#define        _SI_LockingKey          (1<<3)
-#define        _SI_LevelOneOnly        (1<<4)
+enum led_field {
+    LED_FIELD_MODS       = (1 << 0),
+    LED_FIELD_GROUPS     = (1 << 1),
+    LED_FIELD_CTRLS      = (1 << 2),
+};
 
-typedef struct _GroupCompatInfo
-{
-    unsigned char fileID;
-    unsigned char merge;
-    bool defined;
-    unsigned char real_mods;
-    xkb_atom_t vmods;
-} GroupCompatInfo;
-
-typedef struct _CompatInfo
-{
+typedef struct {
+    enum led_field defined;
+    unsigned file_id;
+    enum merge_mode merge;
+
+    struct xkb_indicator_map im;
+} LEDInfo;
+
+typedef struct {
     char *name;
-    unsigned fileID;
+    unsigned file_id;
     int errorCount;
-    int nInterps;
-    SymInterpInfo *interps;
     SymInterpInfo dflt;
+    darray(SymInterpInfo) interps;
     LEDInfo ledDflt;
-    GroupCompatInfo groupCompat[XkbNumKbdGroups];
-    LEDInfo *leds;
-    VModInfo vmods;
-    ActionInfo *act;
+    darray(LEDInfo) leds;
+    ActionsInfo *actions;
     struct xkb_keymap *keymap;
 } CompatInfo;
 
-/***====================================================================***/
-
-#define        ReportSINotArray(si,f,i) \
-       ReportNotArray("symbol interpretation",(f),siText((si),(i)))
-#define        ReportSIBadType(si,f,w,i) \
-       ReportBadType("symbol interpretation",(f),siText((si),(i)),(w))
-
-/***====================================================================***/
-
 static const char *
-siText(SymInterpInfo * si, CompatInfo * info)
+siText(SymInterpInfo *si, CompatInfo *info)
 {
     static char buf[128];
 
     if (si == &info->dflt)
-    {
-        snprintf(buf, sizeof(buf), "default");
-    }
-    else
-    {
-        snprintf(buf, sizeof(buf), "%s+%s(%s)",
-                XkbcKeysymText(si->interp.sym),
-                XkbcSIMatchText(si->interp.match),
-                XkbcModMaskText(si->interp.mods, false));
-    }
+        return "default";
+
+    snprintf(buf, sizeof(buf), "%s+%s(%s)",
+             KeysymText(si->interp.sym),
+             SIMatchText(si->interp.match),
+             ModMaskText(info->keymap, si->interp.mods, MOD_REAL));
     return buf;
 }
 
-static void
-InitCompatInfo(CompatInfo *info, struct xkb_keymap *keymap)
+static inline bool
+ReportSINotArray(CompatInfo *info, SymInterpInfo *si, const char *field)
 {
-    unsigned int i;
+    return ReportNotArray(info->keymap, "symbol interpretation", field,
+                          siText(si, info));
+}
 
-    info->keymap = keymap;
-    info->name = NULL;
-    info->fileID = 0;
-    info->errorCount = 0;
-    info->nInterps = 0;
-    info->interps = NULL;
-    info->act = NULL;
-    info->dflt.defs.fileID = info->fileID;
-    info->dflt.defs.defined = 0;
-    info->dflt.defs.merge = MERGE_OVERRIDE;
-    info->dflt.interp.flags = 0;
-    info->dflt.interp.virtual_mod = XkbNoModifier;
-    info->dflt.interp.act.type = XkbSA_NoAction;
-    for (i = 0; i < sizeof(info->dflt.interp.act.any.data); i++)
-        info->dflt.interp.act.any.data[i] = 0;
-    ClearIndicatorMapInfo(keymap->ctx, &info->ledDflt);
-    info->ledDflt.defs.fileID = info->fileID;
-    info->ledDflt.defs.defined = 0;
-    info->ledDflt.defs.merge = MERGE_OVERRIDE;
-    memset(&info->groupCompat[0], 0,
-           XkbNumKbdGroups * sizeof(GroupCompatInfo));
-    info->leds = NULL;
-    InitVModInfo(&info->vmods, keymap);
+static inline bool
+ReportSIBadType(CompatInfo *info, SymInterpInfo *si, const char *field,
+                const char *wanted)
+{
+    return ReportBadType(info->keymap->ctx, "symbol interpretation", field,
+                         siText(si, info), wanted);
 }
 
-static void
-ClearCompatInfo(CompatInfo *info, struct xkb_keymap *keymap)
+static inline bool
+ReportIndicatorBadType(CompatInfo *info, LEDInfo *led,
+                       const char *field, const char *wanted)
 {
-    unsigned int i;
-    ActionInfo *next;
+    return ReportBadType(info->keymap->ctx, "indicator map", field,
+                         xkb_atom_text(info->keymap->ctx, led->im.name),
+                         wanted);
+}
 
-    free(info->name);
-    info->name = NULL;
-    info->dflt.defs.defined = 0;
-    info->dflt.defs.merge = MERGE_AUGMENT;
-    info->dflt.interp.flags = 0;
-    info->dflt.interp.virtual_mod = XkbNoModifier;
-    info->dflt.interp.act.type = XkbSA_NoAction;
-    for (i = 0; i < sizeof(info->dflt.interp.act.any.data); i++)
-        info->dflt.interp.act.any.data[i] = 0;
-    ClearIndicatorMapInfo(keymap->ctx, &info->ledDflt);
-    info->nInterps = 0;
-    info->interps = ClearCommonInfo(&info->interps->defs);
-    memset(&info->groupCompat[0], 0,
-           XkbNumKbdGroups * sizeof(GroupCompatInfo));
-    info->leds = ClearCommonInfo(&info->leds->defs);
-    while (info->act) {
-            next = info->act->next;
-            free(info->act);
-            info->act = next;
-    }
-    ClearVModInfo(&info->vmods, keymap);
+static inline bool
+ReportIndicatorNotArray(CompatInfo *info, LEDInfo *led,
+                        const char *field)
+{
+    return ReportNotArray(info->keymap, "indicator map", field,
+                          xkb_atom_text(info->keymap->ctx, led->im.name));
 }
 
-static SymInterpInfo *
-NextInterp(CompatInfo * info)
+static void
+InitCompatInfo(CompatInfo *info, struct xkb_keymap *keymap, unsigned file_id,
+               ActionsInfo *actions)
 {
-    SymInterpInfo *si;
+    memset(info, 0, sizeof(*info));
+    info->keymap = keymap;
+    info->file_id = file_id;
+    info->actions = actions;
+    info->dflt.file_id = file_id;
+    info->dflt.merge = MERGE_OVERRIDE;
+    info->dflt.interp.virtual_mod = XKB_MOD_INVALID;
+    info->ledDflt.file_id = file_id;
+    info->ledDflt.merge = MERGE_OVERRIDE;
+}
 
-    si = uTypedAlloc(SymInterpInfo);
-    if (si)
-    {
-        memset(si, 0, sizeof(SymInterpInfo));
-        info->interps = AddCommonInfo(&info->interps->defs, &si->defs);
-        info->nInterps++;
-    }
-    return si;
+static void
+ClearCompatInfo(CompatInfo *info)
+{
+    free(info->name);
+    darray_free(info->interps);
+    darray_free(info->leds);
 }
 
 static SymInterpInfo *
-FindMatchingInterp(CompatInfo * info, SymInterpInfo * new)
+FindMatchingInterp(CompatInfo *info, SymInterpInfo *new)
 {
     SymInterpInfo *old;
 
-    for (old = info->interps; old != NULL;
-         old = (SymInterpInfo *) old->defs.next)
-    {
-        if ((old->interp.sym == new->interp.sym) &&
-            (old->interp.mods == new->interp.mods) &&
-            (old->interp.match == new->interp.match))
-        {
+    darray_foreach(old, info->interps)
+        if (old->interp.sym == new->interp.sym &&
+            old->interp.mods == new->interp.mods &&
+            old->interp.match == new->interp.match)
             return old;
-        }
-    }
+
     return NULL;
 }
 
 static bool
-AddInterp(CompatInfo * info, SymInterpInfo * new)
+UseNewInterpField(enum si_field field, SymInterpInfo *old, SymInterpInfo *new,
+                  bool report, enum si_field *collide)
 {
-    unsigned collide;
+    if (!(old->defined & field))
+        return true;
+
+    if (new->defined & field) {
+        if (report)
+            *collide |= field;
+
+        if (new->merge != MERGE_AUGMENT)
+            return true;
+    }
+
+    return false;
+}
+
+static bool
+AddInterp(CompatInfo *info, SymInterpInfo *new)
+{
+    enum si_field collide = 0;
     SymInterpInfo *old;
 
-    collide = 0;
     old = FindMatchingInterp(info, new);
-    if (old != NULL)
-    {
-        if (new->defs.merge == MERGE_REPLACE)
-        {
-            SymInterpInfo *next = (SymInterpInfo *) old->defs.next;
-            if (((old->defs.fileID == new->defs.fileID)
-                 && (warningLevel > 0)) || (warningLevel > 9))
-            {
-                WARN("Multiple definitions for \"%s\"\n", siText(new, info));
-                ACTION("Earlier interpretation ignored\n");
-            }
+    if (old) {
+        int verbosity = xkb_context_get_log_verbosity(info->keymap->ctx);
+        bool report = ((old->file_id == new->file_id && verbosity > 0) ||
+                       verbosity > 9);
+
+        if (new->merge == MERGE_REPLACE) {
+            if (report)
+                log_warn(info->keymap->ctx,
+                         "Multiple definitions for \"%s\"; "
+                         "Earlier interpretation ignored\n",
+                         siText(new, info));
             *old = *new;
-            old->defs.next = &next->defs;
             return true;
         }
-        if (UseNewField(_SI_VirtualMod, &old->defs, &new->defs, &collide))
-        {
+
+        if (UseNewInterpField(SI_FIELD_VIRTUAL_MOD, old, new, report,
+                              &collide)) {
             old->interp.virtual_mod = new->interp.virtual_mod;
-            old->defs.defined |= _SI_VirtualMod;
+            old->defined |= SI_FIELD_VIRTUAL_MOD;
         }
-        if (UseNewField(_SI_Action, &old->defs, &new->defs, &collide))
-        {
+        if (UseNewInterpField(SI_FIELD_ACTION, old, new, report,
+                              &collide)) {
             old->interp.act = new->interp.act;
-            old->defs.defined |= _SI_Action;
-        }
-        if (UseNewField(_SI_AutoRepeat, &old->defs, &new->defs, &collide))
-        {
-            old->interp.flags &= ~XkbSI_AutoRepeat;
-            old->interp.flags |= (new->interp.flags & XkbSI_AutoRepeat);
-            old->defs.defined |= _SI_AutoRepeat;
-        }
-        if (UseNewField(_SI_LockingKey, &old->defs, &new->defs, &collide))
-        {
-            old->interp.flags &= ~XkbSI_LockingKey;
-            old->interp.flags |= (new->interp.flags & XkbSI_LockingKey);
-            old->defs.defined |= _SI_LockingKey;
+            old->defined |= SI_FIELD_ACTION;
         }
-        if (UseNewField(_SI_LevelOneOnly, &old->defs, &new->defs, &collide))
-        {
-            old->interp.match &= ~XkbSI_LevelOneOnly;
-            old->interp.match |= (new->interp.match & XkbSI_LevelOneOnly);
-            old->defs.defined |= _SI_LevelOneOnly;
+        if (UseNewInterpField(SI_FIELD_AUTO_REPEAT, old, new, report,
+                              &collide)) {
+            old->interp.repeat = new->interp.repeat;
+            old->defined |= SI_FIELD_AUTO_REPEAT;
         }
-        if (collide)
-        {
-            WARN("Multiple interpretations of \"%s\"\n", siText(new, info));
-            ACTION("Using %s definition for duplicate fields\n",
-                    (new->defs.merge != MERGE_AUGMENT ? "last" : "first"));
+        if (UseNewInterpField(SI_FIELD_LEVEL_ONE_ONLY, old, new, report,
+                              &collide)) {
+            old->interp.match &= ~MATCH_LEVEL_ONE_ONLY;
+            old->interp.match |= (new->interp.match & MATCH_LEVEL_ONE_ONLY);
+            old->defined |= SI_FIELD_LEVEL_ONE_ONLY;
         }
-        return true;
-    }
-    old = new;
-    if ((new = NextInterp(info)) == NULL)
-        return false;
-    *new = *old;
-    new->defs.next = NULL;
-    return true;
-}
 
-static bool
-AddGroupCompat(CompatInfo * info, unsigned group, GroupCompatInfo * newGC)
-{
-    GroupCompatInfo *gc;
-    enum merge_mode merge;
+        if (collide) {
+            log_warn(info->keymap->ctx,
+                     "Multiple interpretations of \"%s\"; "
+                     "Using %s definition for duplicate fields\n",
+                     siText(new, info),
+                     (new->merge != MERGE_AUGMENT ? "last" : "first"));
+        }
 
-    merge = newGC->merge;
-    gc = &info->groupCompat[group];
-    if (((gc->real_mods == newGC->real_mods) && (gc->vmods == newGC->vmods)))
-    {
         return true;
     }
-    if (((gc->fileID == newGC->fileID) && (warningLevel > 0))
-        || (warningLevel > 9))
-    {
-        WARN("Compat map for group %d redefined\n", group + 1);
-        ACTION("Using %s definition\n",
-                (merge == MERGE_AUGMENT ? "old" : "new"));
-    }
-    if (newGC->defined && (merge != MERGE_AUGMENT || !gc->defined))
-        *gc = *newGC;
+
+    darray_append(info->interps, *new);
     return true;
 }
 
+
 /***====================================================================***/
 
 static bool
-ResolveStateAndPredicate(ExprDef * expr,
-                         unsigned *pred_rtrn,
-                         unsigned *mods_rtrn, CompatInfo * info)
+ResolveStateAndPredicate(ExprDef *expr, enum xkb_match_operation *pred_rtrn,
+                         xkb_mod_mask_t *mods_rtrn, CompatInfo *info)
 {
-    ExprResult result;
-
-    if (expr == NULL)
-    {
-        *pred_rtrn = XkbSI_AnyOfOrNone;
+    if (expr == NULL) {
+        *pred_rtrn = MATCH_ANY_OR_NONE;
         *mods_rtrn = ~0;
         return true;
     }
 
-    *pred_rtrn = XkbSI_Exactly;
-    if (expr->op == ExprActionDecl)
-    {
+    *pred_rtrn = MATCH_EXACTLY;
+    if (expr->op == EXPR_ACTION_DECL) {
         const char *pred_txt = xkb_atom_text(info->keymap->ctx,
                                              expr->value.action.name);
-        if (strcasecmp(pred_txt, "noneof") == 0)
-            *pred_rtrn = XkbSI_NoneOf;
-        else if (strcasecmp(pred_txt, "anyofornone") == 0)
-            *pred_rtrn = XkbSI_AnyOfOrNone;
-        else if (strcasecmp(pred_txt, "anyof") == 0)
-            *pred_rtrn = XkbSI_AnyOf;
-        else if (strcasecmp(pred_txt, "allof") == 0)
-            *pred_rtrn = XkbSI_AllOf;
-        else if (strcasecmp(pred_txt, "exactly") == 0)
-            *pred_rtrn = XkbSI_Exactly;
-        else
-        {
-            ERROR("Illegal modifier predicate \"%s\"\n", pred_txt);
-            ACTION("Ignored\n");
+        if (!LookupString(symInterpretMatchMaskNames, pred_txt, pred_rtrn)) {
+            log_err(info->keymap->ctx,
+                    "Illegal modifier predicate \"%s\"; Ignored\n", pred_txt);
             return false;
         }
         expr = expr->value.action.args;
     }
-    else if (expr->op == ExprIdent)
-    {
+    else if (expr->op == EXPR_IDENT) {
         const char *pred_txt = xkb_atom_text(info->keymap->ctx,
                                              expr->value.str);
-        if ((pred_txt) && (strcasecmp(pred_txt, "any") == 0))
-        {
-            *pred_rtrn = XkbSI_AnyOf;
+        if (pred_txt && istreq(pred_txt, "any")) {
+            *pred_rtrn = MATCH_ANY;
             *mods_rtrn = 0xff;
             return true;
         }
     }
 
-    if (ExprResolveModMask(info->keymap->ctx, expr, &result))
-    {
-        *mods_rtrn = result.uval;
+    return ExprResolveModMask(info->keymap, expr, mods_rtrn);
+}
+
+/***====================================================================***/
+
+static bool
+UseNewLEDField(enum led_field field, LEDInfo *old, LEDInfo *new,
+               bool report, enum led_field *collide)
+{
+    if (!(old->defined & field))
         return true;
+
+    if (new->defined & field) {
+        if (report)
+            *collide |= field;
+
+        if (new->merge != MERGE_AUGMENT)
+            return true;
     }
+
     return false;
 }
 
-/***====================================================================***/
+static bool
+AddIndicatorMap(CompatInfo *info, LEDInfo *new)
+{
+    LEDInfo *old;
+    enum led_field collide;
+    struct xkb_context *ctx = info->keymap->ctx;
+    int verbosity = xkb_context_get_log_verbosity(ctx);
+
+    darray_foreach(old, info->leds) {
+        bool report;
+
+        if (old->im.name != new->im.name)
+            continue;
+
+        if (old->im.mods.mods == new->im.mods.mods &&
+            old->im.groups == new->im.groups &&
+            old->im.ctrls == new->im.ctrls &&
+            old->im.which_mods == new->im.which_mods &&
+            old->im.which_groups == new->im.which_groups) {
+            old->defined |= new->defined;
+            return true;
+        }
+
+        report = ((old->file_id == new->file_id && verbosity > 0) ||
+                  verbosity > 9);
+
+        if (new->merge == MERGE_REPLACE) {
+            if (report)
+                log_warn(info->keymap->ctx,
+                         "Map for indicator %s redefined; "
+                         "Earlier definition ignored\n",
+                         xkb_atom_text(ctx, old->im.name));
+            *old = *new;
+            return true;
+        }
+
+        collide = 0;
+        if (UseNewLEDField(LED_FIELD_MODS, old, new, report, &collide)) {
+            old->im.which_mods = new->im.which_mods;
+            old->im.mods = new->im.mods;
+            old->defined |= LED_FIELD_MODS;
+        }
+        if (UseNewLEDField(LED_FIELD_GROUPS, old, new, report, &collide)) {
+            old->im.which_groups = new->im.which_groups;
+            old->im.groups = new->im.groups;
+            old->defined |= LED_FIELD_GROUPS;
+        }
+        if (UseNewLEDField(LED_FIELD_CTRLS, old, new, report, &collide)) {
+            old->im.ctrls = new->im.ctrls;
+            old->defined |= LED_FIELD_CTRLS;
+        }
+
+        if (collide) {
+            log_warn(info->keymap->ctx,
+                     "Map for indicator %s redefined; "
+                     "Using %s definition for duplicate fields\n",
+                     xkb_atom_text(ctx, old->im.name),
+                     (new->merge == MERGE_AUGMENT ? "first" : "last"));
+        }
+
+        return true;
+    }
+
+    darray_append(info->leds, *new);
+    return true;
+}
 
 static void
-MergeIncludedCompatMaps(CompatInfo * into, CompatInfo * from, enum merge_mode merge)
+MergeIncludedCompatMaps(CompatInfo *into, CompatInfo *from,
+                        enum merge_mode merge)
 {
     SymInterpInfo *si;
-    LEDInfo *led, *rtrn, *next;
-    GroupCompatInfo *gcm;
-    int i;
+    LEDInfo *led;
 
-    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 (si = from->interps; si; si = (SymInterpInfo *) si->defs.next)
-    {
-        if (merge != MERGE_DEFAULT)
-            si->defs.merge = merge;
+
+    darray_foreach(si, from->interps) {
+        si->merge = (merge == MERGE_DEFAULT ? si->merge : merge);
         if (!AddInterp(into, si))
             into->errorCount++;
     }
-    for (i = 0, gcm = &from->groupCompat[0]; i < XkbNumKbdGroups; i++, gcm++)
-    {
-        if (merge != MERGE_DEFAULT)
-            gcm->merge = merge;
-        if (!AddGroupCompat(into, i, gcm))
-            into->errorCount++;
-    }
-    for (led = from->leds; led != NULL; led = next)
-    {
-        next = (LEDInfo *) led->defs.next;
-        if (merge != MERGE_DEFAULT)
-            led->defs.merge = merge;
-        rtrn = AddIndicatorMap(from->keymap, into->leds, led);
-        if (rtrn != NULL)
-            into->leds = rtrn;
-        else
+
+    darray_foreach(led, from->leds) {
+        led->merge = (merge == MERGE_DEFAULT ? led->merge : merge);
+        if (!AddIndicatorMap(into, led))
             into->errorCount++;
     }
 }
 
 static void
-HandleCompatMapFile(XkbFile *file, struct xkb_keymap *keymap, enum merge_mode merge,
-                    CompatInfo *info);
+HandleCompatMapFile(CompatInfo *info, XkbFile *file, enum merge_mode merge);
 
 static bool
-HandleIncludeCompatMap(IncludeStmt *stmt, struct xkb_keymap *keymap,
-                       CompatInfo *info)
+HandleIncludeCompatMap(CompatInfo *info, IncludeStmt *stmt)
 {
-    unsigned newMerge;
+    enum merge_mode merge = MERGE_DEFAULT;
     XkbFile *rtrn;
-    CompatInfo included;
-    bool haveSelf;
-
-    haveSelf = false;
-    if ((stmt->file == NULL) && (stmt->map == NULL))
-    {
-        haveSelf = true;
-        included = *info;
-        memset(info, 0, sizeof(CompatInfo));
+    CompatInfo included, next_incl;
+
+    InitCompatInfo(&included, info->keymap, info->file_id, info->actions);
+    if (stmt->stmt) {
+        free(included.name);
+        included.name = stmt->stmt;
+        stmt->stmt = NULL;
     }
-    else if (ProcessIncludeFile(keymap->ctx, stmt, FILE_TYPE_COMPAT, &rtrn,
-                                &newMerge))
-    {
-        InitCompatInfo(&included, keymap);
-        included.fileID = rtrn->id;
-        included.dflt = info->dflt;
-        included.dflt.defs.fileID = rtrn->id;
-        included.dflt.defs.merge = newMerge;
-        included.ledDflt.defs.fileID = rtrn->id;
-        included.ledDflt.defs.merge = newMerge;
-        included.act = info->act;
-        HandleCompatMapFile(rtrn, keymap, MERGE_OVERRIDE, &included);
-        if (stmt->stmt != NULL)
-        {
-            free(included.name);
-            included.name = stmt->stmt;
-            stmt->stmt = NULL;
+
+    for (; stmt; stmt = stmt->next_incl) {
+        if (!ProcessIncludeFile(info->keymap->ctx, stmt, FILE_TYPE_COMPAT,
+                                &rtrn, &merge)) {
+            info->errorCount += 10;
+            ClearCompatInfo(&included);
+            return false;
         }
-        if (info->act != NULL)
-                included.act = NULL;
-        FreeXKBFile(rtrn);
+
+        InitCompatInfo(&next_incl, info->keymap, rtrn->id, info->actions);
+        next_incl.file_id = rtrn->id;
+        next_incl.dflt = info->dflt;
+        next_incl.dflt.file_id = rtrn->id;
+        next_incl.dflt.merge = merge;
+        next_incl.ledDflt.file_id = rtrn->id;
+        next_incl.ledDflt.merge = merge;
+
+        HandleCompatMapFile(&next_incl, rtrn, MERGE_OVERRIDE);
+
+        MergeIncludedCompatMaps(&included, &next_incl, merge);
+
+        ClearCompatInfo(&next_incl);
+        FreeXkbFile(rtrn);
     }
-    else
-    {
-        info->errorCount += 10;
-        return false;
+
+    MergeIncludedCompatMaps(info, &included, merge);
+    ClearCompatInfo(&included);
+
+    return (info->errorCount == 0);
+}
+
+static bool
+SetInterpField(CompatInfo *info, SymInterpInfo *si, const char *field,
+               ExprDef *arrayNdx, ExprDef *value)
+{
+    struct xkb_keymap *keymap = info->keymap;
+    xkb_mod_index_t ndx;
+
+    if (istreq(field, "action")) {
+        if (arrayNdx)
+            return ReportSINotArray(info, si, field);
+
+        if (!HandleActionDef(value, keymap, &si->interp.act, info->actions))
+            return false;
+
+        si->defined |= SI_FIELD_ACTION;
     }
-    if ((stmt->next != NULL) && (included.errorCount < 1))
-    {
-        IncludeStmt *next;
-        unsigned op;
-        CompatInfo next_incl;
-
-        for (next = stmt->next; next != NULL; next = next->next)
-        {
-            if ((next->file == NULL) && (next->map == NULL))
-            {
-                haveSelf = true;
-                MergeIncludedCompatMaps(&included, info, next->merge);
-                ClearCompatInfo(info, keymap);
-            }
-            else if (ProcessIncludeFile(keymap->ctx, next, FILE_TYPE_COMPAT,
-                                        &rtrn, &op))
-            {
-                InitCompatInfo(&next_incl, keymap);
-                next_incl.fileID = rtrn->id;
-                next_incl.dflt = info->dflt;
-                next_incl.dflt.defs.fileID = rtrn->id;
-                next_incl.dflt.defs.merge = op;
-                next_incl.ledDflt.defs.fileID = rtrn->id;
-                next_incl.ledDflt.defs.merge = op;
-                next_incl.act = info->act;
-                HandleCompatMapFile(rtrn, keymap, MERGE_OVERRIDE, &next_incl);
-                MergeIncludedCompatMaps(&included, &next_incl, op);
-                if (info->act != NULL)
-                        next_incl.act = NULL;
-                ClearCompatInfo(&next_incl, keymap);
-                FreeXKBFile(rtrn);
-            }
-            else
-            {
-                info->errorCount += 10;
-                return false;
-            }
-        }
+    else if (istreq(field, "virtualmodifier") ||
+             istreq(field, "virtualmod")) {
+        if (arrayNdx)
+            return ReportSINotArray(info, si, field);
+
+        if (!ExprResolveVMod(keymap, value, &ndx))
+            return ReportSIBadType(info, si, field, "virtual modifier");
+
+        si->interp.virtual_mod = ndx;
+        si->defined |= SI_FIELD_VIRTUAL_MOD;
     }
-    if (haveSelf)
-        *info = included;
-    else
-    {
-        MergeIncludedCompatMaps(info, &included, newMerge);
-        ClearCompatInfo(&included, keymap);
+    else if (istreq(field, "repeat")) {
+        bool set;
+
+        if (arrayNdx)
+            return ReportSINotArray(info, si, field);
+
+        if (!ExprResolveBoolean(keymap->ctx, value, &set))
+            return ReportSIBadType(info, si, field, "boolean");
+
+        si->interp.repeat = set;
+
+        si->defined |= SI_FIELD_AUTO_REPEAT;
     }
-    return (info->errorCount == 0);
-}
+    else if (istreq(field, "locking")) {
+        log_dbg(info->keymap->ctx,
+                "The \"locking\" field in symbol interpretation is unsupported; "
+                "Ignored\n");
+    }
+    else if (istreq(field, "usemodmap") ||
+             istreq(field, "usemodmapmods")) {
+        unsigned int val;
 
-static const LookupEntry useModMapValues[] = {
-    {"levelone", 1},
-    {"level1", 1},
-    {"anylevel", 0},
-    {"any", 0},
-    {NULL, 0}
-};
+        if (arrayNdx)
+            return ReportSINotArray(info, si, field);
+
+        if (!ExprResolveEnum(keymap->ctx, value, &val, useModMapValueNames))
+            return ReportSIBadType(info, si, field, "level specification");
+
+        if (val)
+            si->interp.match |= MATCH_LEVEL_ONE_ONLY;
+        else
+            si->interp.match &= ~MATCH_LEVEL_ONE_ONLY;
+
+        si->defined |= SI_FIELD_LEVEL_ONE_ONLY;
+    }
+    else {
+        return ReportBadField(keymap, "symbol interpretation", field,
+                              siText(si, info));
+    }
+
+    return true;
+}
 
-static int
-SetInterpField(SymInterpInfo *si, struct xkb_keymap *keymap, char *field,
-               ExprDef *arrayNdx, ExprDef *value, CompatInfo *info)
+static bool
+SetIndicatorMapField(CompatInfo *info, LEDInfo *led,
+                     const char *field, ExprDef *arrayNdx, ExprDef *value)
 {
-    int ok = 1;
-    ExprResult tmp;
-
-    if (strcasecmp(field, "action") == 0)
-    {
-        if (arrayNdx != NULL)
-            return ReportSINotArray(si, field, info);
-        ok = HandleActionDef(value, keymap, &si->interp.act.any, info->act);
-        if (ok)
-            si->defs.defined |= _SI_Action;
+    bool ok = true;
+    struct xkb_keymap *keymap = info->keymap;
+
+    if (istreq(field, "modifiers") || istreq(field, "mods")) {
+        if (arrayNdx)
+            return ReportIndicatorNotArray(info, led, field);
+
+        if (!ExprResolveVModMask(keymap, value, &led->im.mods.mods))
+            return ReportIndicatorBadType(info, led, field, "modifier mask");
+
+        led->defined |= LED_FIELD_MODS;
     }
-    else if ((strcasecmp(field, "virtualmodifier") == 0) ||
-             (strcasecmp(field, "virtualmod") == 0))
-    {
-        if (arrayNdx != NULL)
-            return ReportSINotArray(si, field, info);
-        ok = ResolveVirtualModifier(value, keymap, &tmp, &info->vmods);
-        if (ok)
-        {
-            si->interp.virtual_mod = tmp.uval;
-            si->defs.defined |= _SI_VirtualMod;
-        }
-        else
-            return ReportSIBadType(si, field, "virtual modifier", info);
+    else if (istreq(field, "groups")) {
+        unsigned int mask;
+
+        if (arrayNdx)
+            return ReportIndicatorNotArray(info, led, field);
+
+        if (!ExprResolveMask(keymap->ctx, value, &mask, groupMaskNames))
+            return ReportIndicatorBadType(info, led, field, "group mask");
+
+        led->im.groups = mask;
+        led->defined |= LED_FIELD_GROUPS;
     }
-    else if (strcasecmp(field, "repeat") == 0)
-    {
-        if (arrayNdx != NULL)
-            return ReportSINotArray(si, field, info);
-        ok = ExprResolveBoolean(keymap->ctx, value, &tmp);
-        if (ok)
-        {
-            if (tmp.uval)
-                si->interp.flags |= XkbSI_AutoRepeat;
-            else
-                si->interp.flags &= ~XkbSI_AutoRepeat;
-            si->defs.defined |= _SI_AutoRepeat;
-        }
-        else
-            return ReportSIBadType(si, field, "boolean", info);
+    else if (istreq(field, "controls") || istreq(field, "ctrls")) {
+        unsigned int mask;
+
+        if (arrayNdx)
+            return ReportIndicatorNotArray(info, led, field);
+
+        if (!ExprResolveMask(keymap->ctx, value, &mask, ctrlMaskNames))
+            return ReportIndicatorBadType(info, led, field,
+                                          "controls mask");
+
+        led->im.ctrls = mask;
+        led->defined |= LED_FIELD_CTRLS;
     }
-    else if (strcasecmp(field, "locking") == 0)
-    {
-        if (arrayNdx != NULL)
-            return ReportSINotArray(si, field, info);
-        ok = ExprResolveBoolean(keymap->ctx, value, &tmp);
-        if (ok)
-        {
-            if (tmp.uval)
-                si->interp.flags |= XkbSI_LockingKey;
-            else
-                si->interp.flags &= ~XkbSI_LockingKey;
-            si->defs.defined |= _SI_LockingKey;
-        }
-        else
-            return ReportSIBadType(si, field, "boolean", info);
+    else if (istreq(field, "allowexplicit")) {
+        log_dbg(info->keymap->ctx,
+                "The \"allowExplicit\" field in indicator statements is unsupported; "
+                "Ignored\n");
     }
-    else if ((strcasecmp(field, "usemodmap") == 0) ||
-             (strcasecmp(field, "usemodmapmods") == 0))
-    {
-        if (arrayNdx != NULL)
-            return ReportSINotArray(si, field, info);
-        ok = ExprResolveEnum(keymap->ctx, value, &tmp, useModMapValues);
-        if (ok)
-        {
-            if (tmp.uval)
-                si->interp.match |= XkbSI_LevelOneOnly;
-            else
-                si->interp.match &= ~XkbSI_LevelOneOnly;
-            si->defs.defined |= _SI_LevelOneOnly;
-        }
-        else
-            return ReportSIBadType(si, field, "level specification", info);
+    else if (istreq(field, "whichmodstate") ||
+             istreq(field, "whichmodifierstate")) {
+        unsigned int mask;
+
+        if (arrayNdx)
+            return ReportIndicatorNotArray(info, led, field);
+
+        if (!ExprResolveMask(keymap->ctx, value, &mask,
+                             modComponentMaskNames))
+            return ReportIndicatorBadType(info, led, field,
+                                          "mask of modifier state components");
+
+        led->im.which_mods = mask;
     }
-    else
-    {
-        ok = ReportBadField("symbol interpretation", field, siText(si, info));
+    else if (istreq(field, "whichgroupstate")) {
+        unsigned mask;
+
+        if (arrayNdx)
+            return ReportIndicatorNotArray(info, led, field);
+
+        if (!ExprResolveMask(keymap->ctx, value, &mask,
+                             groupComponentMaskNames))
+            return ReportIndicatorBadType(info, led, field,
+                                          "mask of group state components");
+
+        led->im.which_groups = mask;
+    }
+    else if (istreq(field, "driveskbd") ||
+             istreq(field, "driveskeyboard") ||
+             istreq(field, "leddriveskbd") ||
+             istreq(field, "leddriveskeyboard") ||
+             istreq(field, "indicatordriveskbd") ||
+             istreq(field, "indicatordriveskeyboard")) {
+        log_dbg(info->keymap->ctx,
+                "The \"%s\" field in indicator statements is unsupported; "
+                "Ignored\n", field);
+    }
+    else if (istreq(field, "index")) {
+        /* Users should see this, it might cause unexpected behavior. */
+        log_err(info->keymap->ctx,
+                "The \"index\" field in indicator statements is unsupported; "
+                "Ignored\n");
     }
+    else {
+        log_err(info->keymap->ctx,
+                "Unknown field %s in map for %s indicator; "
+                "Definition ignored\n",
+                field, xkb_atom_text(keymap->ctx, led->im.name));
+        ok = false;
+    }
+
     return ok;
 }
 
-static int
-HandleInterpVar(VarDef * stmt, struct xkb_keymap *keymap, CompatInfo * info)
+static bool
+HandleGlobalVar(CompatInfo *info, VarDef *stmt)
 {
-    ExprResult elem, field;
+    const char *elem, *field;
     ExprDef *ndx;
-    int ret;
-
-    if (ExprResolveLhs(keymap, stmt->name, &elem, &field, &ndx) == 0)
-        ret = 0;               /* internal error, already reported */
-    else if (elem.str && (strcasecmp(elem.str, "interpret") == 0))
-        ret = SetInterpField(&info->dflt, keymap, field.str, ndx, stmt->value,
-                              info);
-    else if (elem.str && (strcasecmp(elem.str, "indicator") == 0))
-        ret = SetIndicatorMapField(&info->ledDflt, keymap, field.str, ndx,
-                                  stmt->value);
+    bool ret;
+
+    if (!ExprResolveLhs(info->keymap->ctx, stmt->name, &elem, &field, &ndx))
+        ret = false;
+    else if (elem && istreq(elem, "interpret"))
+        ret = SetInterpField(info, &info->dflt, field, ndx, stmt->value);
+    else if (elem && istreq(elem, "indicator"))
+        ret = SetIndicatorMapField(info, &info->ledDflt, field, ndx,
+                                   stmt->value);
     else
-        ret = SetActionField(keymap, elem.str, field.str, ndx, stmt->value,
-                            &info->act);
-    free(elem.str);
-    free(field.str);
+        ret = SetActionField(info->keymap, elem, field, ndx, stmt->value,
+                             info->actions);
     return ret;
 }
 
-static int
-HandleInterpBody(VarDef *def, struct xkb_keymap *keymap, SymInterpInfo *si,
-                 CompatInfo *info)
+static bool
+HandleInterpBody(CompatInfo *info, VarDef *def, SymInterpInfo *si)
 {
-    int ok = 1;
-    ExprResult tmp, field;
+    bool ok = true;
+    const char *elem, *field;
     ExprDef *arrayNdx;
 
-    for (; def != NULL; def = (VarDef *) def->common.next)
-    {
-        if ((def->name) && (def->name->type == ExprFieldRef))
-        {
-            ok = HandleInterpVar(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 an interpret statement; "
+                    "Move statements to the global file scope\n");
+            ok = false;
             continue;
         }
-        ok = ExprResolveLhs(keymap, def->name, &tmp, &field, &arrayNdx);
-        if (ok) {
-            ok = SetInterpField(si, keymap, field.str, arrayNdx, def->value,
-                                info);
-            free(field.str);
-        }
+
+        ok = ExprResolveLhs(info->keymap->ctx, def->name, &elem, &field,
+                            &arrayNdx);
+        if (!ok)
+            continue;
+
+        ok = SetInterpField(info, si, field, arrayNdx, def->value);
     }
+
     return ok;
 }
 
-static int
-HandleInterpDef(InterpDef *def, struct xkb_keymap *keymap, enum merge_mode merge,
-                CompatInfo *info)
+static bool
+HandleInterpDef(CompatInfo *info, InterpDef *def, enum merge_mode merge)
 {
-    unsigned pred, mods;
+    enum xkb_match_operation pred;
+    xkb_mod_mask_t mods;
     SymInterpInfo si;
 
-    if (!ResolveStateAndPredicate(def->match, &pred, &mods, info))
-    {
-        ERROR("Couldn't determine matching modifiers\n");
-        ACTION("Symbol interpretation ignored\n");
+    if (!ResolveStateAndPredicate(def->match, &pred, &mods, info)) {
+        log_err(info->keymap->ctx,
+                "Couldn't determine matching modifiers; "
+                "Symbol interpretation ignored\n");
         return false;
     }
-    if (def->merge != MERGE_DEFAULT)
-        merge = def->merge;
 
     si = info->dflt;
-    si.defs.merge = merge;
-    if (!LookupKeysym(def->sym, &si.interp.sym))
-    {
-        ERROR("Could not resolve keysym %s\n", def->sym);
-        ACTION("Symbol interpretation ignored\n");
+
+    si.merge = merge = (def->merge == MERGE_DEFAULT ? merge : def->merge);
+
+    if (!LookupKeysym(def->sym, &si.interp.sym)) {
+        log_err(info->keymap->ctx,
+                "Could not resolve keysym %s; "
+                "Symbol interpretation ignored\n",
+                def->sym);
         return false;
     }
-    si.interp.match = pred & XkbSI_OpMask;
+
+    si.interp.match = pred & MATCH_OP_MASK;
+
     si.interp.mods = mods;
-    if (!HandleInterpBody(def->def, keymap, &si, info))
-    {
+
+    if (!HandleInterpBody(info, def->def, &si)) {
         info->errorCount++;
         return false;
     }
 
-    if (!AddInterp(info, &si))
-    {
+    if (!AddInterp(info, &si)) {
         info->errorCount++;
         return false;
     }
+
     return true;
 }
 
-static int
-HandleGroupCompatDef(GroupCompatDef *def, struct xkb_keymap *keymap,
-                     enum merge_mode merge, CompatInfo *info)
+static bool
+HandleIndicatorMapDef(CompatInfo *info, IndicatorMapDef *def,
+                      enum merge_mode merge)
 {
-    ExprResult val;
-    GroupCompatInfo tmp;
+    LEDInfo led;
+    VarDef *var;
+    bool ok;
 
     if (def->merge != MERGE_DEFAULT)
         merge = def->merge;
-    if (!XkbIsLegalGroup(def->group - 1))
-    {
-        ERROR("Keyboard group must be in the range 1..%d\n",
-               XkbNumKbdGroups + 1);
-        ACTION("Compatibility map for illegal group %d ignored\n",
-                def->group);
-        return false;
-    }
-    tmp.fileID = info->fileID;
-    tmp.merge = merge;
-    if (!ExprResolveVModMask(def->def, &val, keymap))
-    {
-        ERROR("Expected a modifier mask in group compatibility definition\n");
-        ACTION("Ignoring illegal compatibility map for group %d\n",
-                def->group);
-        return false;
+
+    led = info->ledDflt;
+    led.merge = merge;
+    led.im.name = def->name;
+
+    ok = true;
+    for (var = def->body; var != NULL; var = (VarDef *) var->common.next) {
+        const char *elem, *field;
+        ExprDef *arrayNdx;
+        if (!ExprResolveLhs(info->keymap->ctx, var->name, &elem, &field,
+                            &arrayNdx)) {
+            ok = false;
+            continue;
+        }
+
+        if (elem) {
+            log_err(info->keymap->ctx,
+                    "Cannot set defaults for \"%s\" element in indicator map; "
+                    "Assignment to %s.%s ignored\n", elem, elem, field);
+            ok = false;
+        }
+        else {
+            ok = SetIndicatorMapField(info, &led, field, arrayNdx,
+                                      var->value) && ok;
+        }
     }
-    tmp.real_mods = val.uval & 0xff;
-    tmp.vmods = (val.uval >> 8) & 0xffff;
-    tmp.defined = true;
-    return AddGroupCompat(info, def->group - 1, &tmp);
+
+    if (ok)
+        return AddIndicatorMap(info, &led);
+
+    return false;
 }
 
 static void
-HandleCompatMapFile(XkbFile *file, struct xkb_keymap *keymap, enum merge_mode merge,
-                    CompatInfo *info)
+HandleCompatMapFile(CompatInfo *info, XkbFile *file, enum merge_mode merge)
 {
+    bool ok;
     ParseCommon *stmt;
 
-    if (merge == MERGE_DEFAULT)
-        merge = MERGE_AUGMENT;
+    merge = (merge == MERGE_DEFAULT ? MERGE_AUGMENT : merge);
+
     free(info->name);
-    info->name = uDupString(file->name);
-    stmt = file->defs;
-    while (stmt)
-    {
-        switch (stmt->stmtType)
-        {
-        case StmtInclude:
-            if (!HandleIncludeCompatMap((IncludeStmt *) stmt, keymap, info))
-                info->errorCount++;
+    info->name = strdup_safe(file->name);
+
+    for (stmt = file->defs; stmt; stmt = stmt->next) {
+        switch (stmt->type) {
+        case STMT_INCLUDE:
+            ok = HandleIncludeCompatMap(info, (IncludeStmt *) stmt);
             break;
-        case StmtInterpDef:
-            if (!HandleInterpDef((InterpDef *) stmt, keymap, merge, info))
-                info->errorCount++;
+        case STMT_INTERP:
+            ok = HandleInterpDef(info, (InterpDef *) stmt, merge);
             break;
-        case StmtGroupCompatDef:
-            if (!HandleGroupCompatDef
-                ((GroupCompatDef *) stmt, keymap, merge, info))
-                info->errorCount++;
+        case STMT_GROUP_COMPAT:
+            log_dbg(info->keymap->ctx,
+                    "The \"group\" statement in compat is unsupported; "
+                    "Ignored\n");
+            ok = true;
             break;
-        case StmtIndicatorMapDef:
-        {
-            LEDInfo *rtrn;
-            rtrn = HandleIndicatorMapDef((IndicatorMapDef *) stmt, keymap,
-                                         &info->ledDflt, info->leds, merge);
-            if (rtrn != NULL)
-                info->leds = rtrn;
-            else
-                info->errorCount++;
-        }
+        case STMT_INDICATOR_MAP:
+            ok = HandleIndicatorMapDef(info, (IndicatorMapDef *) stmt, merge);
             break;
-        case StmtVarDef:
-            if (!HandleInterpVar((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++;
-            break;
-        case StmtKeycodeDef:
-            ERROR("Interpretation files may not include other types\n");
-            ACTION("Ignoring definition of key name\n");
-            info->errorCount++;
+        case STMT_VMOD:
+            ok = HandleVModDef(info->keymap, (VModDef *) stmt);
             break;
         default:
-            WSGO("Unexpected statement type %d in HandleCompatMapFile\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 compatibility map \"%s\"\n", file->topName);
+
+        if (!ok)
+            info->errorCount++;
+
+        if (info->errorCount > 10) {
+            log_err(info->keymap->ctx,
+                    "Abandoning compatibility map \"%s\"\n", file->topName);
             break;
         }
     }
 }
 
 static void
-CopyInterps(CompatInfo * info,
-            struct xkb_compat_map * compat, bool needSymbol, unsigned pred)
+CopyInterps(CompatInfo *info, bool needSymbol, enum xkb_match_operation pred)
 {
     SymInterpInfo *si;
 
-    for (si = info->interps; si; si = (SymInterpInfo *) si->defs.next)
-    {
-        if (((si->interp.match & XkbSI_OpMask) != pred) ||
-            (needSymbol && (si->interp.sym == XKB_KEY_NoSymbol)) ||
-            ((!needSymbol) && (si->interp.sym != XKB_KEY_NoSymbol)))
+    darray_foreach(si, info->interps) {
+        if (((si->interp.match & MATCH_OP_MASK) != pred) ||
+            (needSymbol && si->interp.sym == XKB_KEY_NoSymbol) ||
+            (!needSymbol && si->interp.sym != XKB_KEY_NoSymbol))
             continue;
-        darray_append(compat->sym_interpret, si->interp);
-    }
-}
-
-bool
-CompileCompatMap(XkbFile *file, struct xkb_keymap *keymap, enum merge_mode merge)
-{
-    int i;
-    CompatInfo info;
-    GroupCompatInfo *gcm;
-    LEDInfo *unbound = NULL, *next;
-
-    InitCompatInfo(&info, keymap);
-    info.dflt.defs.merge = merge;
-    info.ledDflt.defs.merge = merge;
-
-    HandleCompatMapFile(file, keymap, merge, &info);
-
-    if (info.errorCount != 0)
-        goto err_info;
-
-    if (XkbcAllocCompatMap(keymap, info.nInterps) != Success) {
-        WSGO("Couldn't allocate compatibility map\n");
-        goto err_info;
-    }
-
-    if (info.nInterps > 0) {
-        CopyInterps(&info, keymap->compat, true, XkbSI_Exactly);
-        CopyInterps(&info, keymap->compat, true, XkbSI_AllOf | XkbSI_NoneOf);
-        CopyInterps(&info, keymap->compat, true, XkbSI_AnyOf);
-        CopyInterps(&info, keymap->compat, true, XkbSI_AnyOfOrNone);
-        CopyInterps(&info, keymap->compat, false, XkbSI_Exactly);
-        CopyInterps(&info, keymap->compat, false, XkbSI_AllOf | XkbSI_NoneOf);
-        CopyInterps(&info, keymap->compat, false, XkbSI_AnyOf);
-        CopyInterps(&info, keymap->compat, false, XkbSI_AnyOfOrNone);
-    }
-
-    for (i = 0, gcm = &info.groupCompat[0]; i < XkbNumKbdGroups; i++, gcm++) {
-        if ((gcm->fileID != 0) || (gcm->real_mods != 0) || (gcm->vmods != 0)) {
-            keymap->compat->groups[i].mask = gcm->real_mods;
-            keymap->compat->groups[i].real_mods = gcm->real_mods;
-            keymap->compat->groups[i].vmods = gcm->vmods;
-        }
-    }
-
-    if (info.leds != NULL) {
-        if (!CopyIndicatorMapDefs(keymap, info.leds, &unbound))
-            info.errorCount++;
-        info.leds = NULL;
-    }
 
-    if (!BindIndicators(keymap, unbound)) {
-        while (unbound) {
-            next = (LEDInfo *) unbound->defs.next;
-            free(unbound);
-            unbound = next;
-        }
-
-        goto err_info;
+        darray_append(info->keymap->sym_interpret, si->interp);
     }
-
-    ClearCompatInfo(&info, keymap);
-    return true;
-
-err_info:
-    ClearCompatInfo(&info, keymap);
-    return false;
-}
-
-static uint32_t
-VModsToReal(struct xkb_keymap *keymap, uint32_t vmodmask)
-{
-    uint32_t ret = 0;
-    int i;
-
-    if (!vmodmask)
-        return 0;
-
-    for (i = 0; i < XkbNumVirtualMods; i++) {
-        if (!(vmodmask & (1 << i)))
-            continue;
-        ret |= keymap->server->vmods[i];
-    }
-
-    return ret;
 }
 
 static void
-UpdateActionMods(struct xkb_keymap *keymap, union xkb_action *act,
-                 uint32_t rmodmask)
+CopyIndicatorMapDefs(CompatInfo *info)
 {
-    switch (act->type) {
-    case XkbSA_SetMods:
-    case XkbSA_LatchMods:
-    case XkbSA_LockMods:
-        if (act->mods.flags & XkbSA_UseModMapMods)
-            act->mods.real_mods = rmodmask;
-        act->mods.mask = act->mods.real_mods;
-        act->mods.mask |= VModsToReal(keymap, act->mods.vmods);
-        break;
-    case XkbSA_ISOLock:
-        if (act->iso.flags & XkbSA_UseModMapMods)
-            act->iso.real_mods = rmodmask;
-        act->iso.mask = act->iso.real_mods;
-        act->iso.mask |= VModsToReal(keymap, act->iso.vmods);
-        break;
-    default:
-        break;
-    }
-}
-
-/**
- * Find an interpretation which applies to this particular level, either by
- * finding an exact match for the symbol and modifier combination, or a
- * generic XKB_KEY_NoSymbol match.
- */
-static struct xkb_sym_interpret *
-FindInterpForKey(struct xkb_keymap *keymap, xkb_keycode_t key,
-                 uint32_t group, uint32_t level)
-{
-    struct xkb_sym_interpret *ret = NULL;
-    struct xkb_sym_interpret *interp;
-    const xkb_keysym_t *syms;
-    int num_syms;
-
-    num_syms = xkb_key_get_syms_by_level(keymap, key, group, level, &syms);
-    if (num_syms == 0)
-        return NULL;
-
-    darray_foreach(interp, keymap->compat->sym_interpret) {
-        uint32_t mods;
-        bool found;
-
-        if ((num_syms > 1 || interp->sym != syms[0]) &&
-            interp->sym != XKB_KEY_NoSymbol)
-            continue;
-
-        if (level == 0 || !(interp->match & XkbSI_LevelOneOnly))
-            mods = keymap->map->modmap[key];
-        else
-            mods = 0;
-
-        switch (interp->match & XkbSI_OpMask) {
-        case XkbSI_NoneOf:
-            found = !(interp->mods & mods);
-            break;
-        case XkbSI_AnyOfOrNone:
-            found = (!mods || (interp->mods & mods));
-            break;
-        case XkbSI_AnyOf:
-            found = !!(interp->mods & mods);
-            break;
-        case XkbSI_AllOf:
-            found = ((interp->mods & mods) == mods);
-            break;
-        case XkbSI_Exactly:
-            found = (interp->mods == mods);
-            break;
-        default:
-            found = false;
-            break;
+    LEDInfo *led;
+    xkb_led_index_t i;
+    struct xkb_indicator_map *im;
+    struct xkb_keymap *keymap = info->keymap;
+
+    darray_foreach(led, info->leds) {
+        /*
+         * Find the indicator with the given name, if it was already
+         * declared in keycodes.
+         */
+        for (i = 0; i < XKB_NUM_INDICATORS; i++)
+            if (keymap->indicators[i].name == led->im.name)
+                break;
+
+        /* Not previously declared; create it with next free index. */
+        if (i >= XKB_NUM_INDICATORS) {
+            log_dbg(keymap->ctx,
+                    "Indicator name \"%s\" was not declared in the keycodes section; "
+                    "Adding new indicator\n",
+                    xkb_atom_text(keymap->ctx, led->im.name));
+
+            for (i = 0; i < XKB_NUM_INDICATORS; i++)
+                if (keymap->indicators[i].name == XKB_ATOM_NONE)
+                    break;
+
+            /* Not place to put it; ignore. */
+            if (i >= XKB_NUM_INDICATORS) {
+                log_err(keymap->ctx,
+                        "Too many indicators (maximum is %d); "
+                        "Indicator name \"%s\" ignored\n",
+                        XKB_NUM_INDICATORS,
+                        xkb_atom_text(keymap->ctx, led->im.name));
+                continue;
+            }
         }
 
-        if (found && interp->sym != XKB_KEY_NoSymbol)
-            return interp;
-        else if (found && !ret)
-            ret = interp;
+        im = &keymap->indicators[i];
+        *im  = led->im;
+        if (im->groups != 0 && im->which_groups == 0)
+            im->which_groups = XKB_STATE_EFFECTIVE;
+        if (im->mods.mods != 0 && im->which_mods == 0)
+            im->which_mods = XKB_STATE_EFFECTIVE;
     }
-
-    return ret;
 }
 
-/**
- */
 static bool
-ApplyInterpsToKey(struct xkb_keymap *keymap, xkb_keycode_t key)
+CopyCompatToKeymap(struct xkb_keymap *keymap, CompatInfo *info)
 {
-#define INTERP_SIZE (8 * 4)
-    struct xkb_sym_interpret *interps[INTERP_SIZE];
-    union xkb_action *acts;
-    uint32_t vmodmask = 0;
-    int num_acts = 0;
-    int group, level;
-    int width = XkbKeyGroupsWidth(keymap, key);
-    int i;
-
-    /* If we've been told not to bind interps to this key, then don't. */
-    if (keymap->server->explicit[key] & XkbExplicitInterpretMask)
-        return true;
-
-    for (i = 0; i < INTERP_SIZE; i++)
-        interps[i] = NULL;
-
-    for (group = 0; group < XkbKeyNumGroups(keymap, key); group++) {
-        for (level = 0; level < XkbKeyGroupWidth(keymap, key, group); level++) {
-            i = (group * width) + level;
-            if (i >= INTERP_SIZE) /* XXX FIXME */
-                return false;
-            interps[i] = FindInterpForKey(keymap, key, group, level);
-            if (interps[i])
-                num_acts++;
-        }
-    }
-
-    if (num_acts)
-        num_acts = XkbKeyNumGroups(keymap, key) * width;
-    acts = XkbcResizeKeyActions(keymap, key, num_acts);
-    if (num_acts && !acts)
-        return false;
-
-    for (group = 0; group < XkbKeyNumGroups(keymap, key); group++) {
-        for (level = 0; level < XkbKeyGroupWidth(keymap, key, group); level++) {
-            struct xkb_sym_interpret *interp;
-
-            i = (group * width) + level;
-            interp = interps[i];
-
-            /* Infer default key behaviours from the base level. */
-            if (group == 0 && level == 0) {
-                if (!(keymap->server->explicit[key] & XkbExplicitAutoRepeatMask) &&
-                    (!interp || interp->flags & XkbSI_AutoRepeat))
-                        keymap->ctrls->per_key_repeat[key / 8] |= (1 << (key % 8));
-                if (!(keymap->server->explicit[key] & XkbExplicitBehaviorMask) &&
-                    interp && (interp->flags & XkbSI_LockingKey))
-                    keymap->server->behaviors[key].type = XkbKB_Lock;
-            }
-
-            if (!interp)
-                continue;
-
-            if ((group == 0 && level == 0) ||
-                !(interp->match & XkbSI_LevelOneOnly)) {
-                if (interp->virtual_mod != XkbNoModifier)
-                    vmodmask |= (1 << interp->virtual_mod);
-            }
-            acts[i] = interp->act;
-        }
+    keymap->compat_section_name = strdup_safe(info->name);
+
+    if (!darray_empty(info->interps)) {
+        /* Most specific to least specific. */
+        CopyInterps(info, true, MATCH_EXACTLY);
+        CopyInterps(info, true, MATCH_ALL);
+        CopyInterps(info, true, MATCH_ANY);
+        CopyInterps(info, true, MATCH_ANY_OR_NONE);
+        CopyInterps(info, false, MATCH_EXACTLY);
+        CopyInterps(info, false, MATCH_ALL);
+        CopyInterps(info, false, MATCH_ANY);
+        CopyInterps(info, false, MATCH_ANY_OR_NONE);
     }
 
-    if (!(keymap->server->explicit[key] & XkbExplicitVModMapMask))
-        keymap->server->vmodmap[key] = vmodmask;
+    CopyIndicatorMapDefs(info);
 
     return true;
-#undef INTERP_SIZE
 }
 
-/**
- * This collects a bunch of disparate functions which was done in the server
- * at various points that really should've been done within xkbcomp.  Turns out
- * your actions and types are a lot more useful when any of your modifiers
- * other than Shift actually do something ...
- */
 bool
-UpdateModifiersFromCompat(struct xkb_keymap *keymap)
+CompileCompatMap(XkbFile *file, struct xkb_keymap *keymap,
+                 enum merge_mode merge)
 {
-    xkb_keycode_t key;
-    int i;
-    struct xkb_key_type *type;
-    struct xkb_kt_map_entry *entry;
-
-    /* Find all the interprets for the key and bind them to actions,
-     * which will also update the vmodmap. */
-    for (key = keymap->min_key_code; key <= keymap->max_key_code; key++)
-        if (!ApplyInterpsToKey(keymap, key))
-            return false;
-
-    /* Update keymap->server->vmods, the virtual -> real mod mapping. */
-    for (i = 0; i < XkbNumVirtualMods; i++)
-        keymap->server->vmods[i] = 0;
-    for (key = keymap->min_key_code; key <= keymap->max_key_code; key++) {
-        if (!keymap->server->vmodmap[key])
-            continue;
-        for (i = 0; i < XkbNumVirtualMods; i++) {
-            if (!(keymap->server->vmodmap[key] & (1 << i)))
-                continue;
-            keymap->server->vmods[i] |= keymap->map->modmap[key];
-        }
-    }
-
-    /* Now update the level masks for all the types to reflect the vmods. */
-    darray_foreach(type, keymap->map->types) {
-        uint32_t mask = 0;
-        int j;
-        type->mods.mask = type->mods.real_mods;
-        type->mods.mask |= VModsToReal(keymap, type->mods.vmods);
-        for (j = 0; j < XkbNumVirtualMods; j++) {
-            if (!(type->mods.vmods & (1 << j)))
-                continue;
-            mask |= keymap->server->vmods[j];
-        }
+    CompatInfo info;
+    ActionsInfo *actions;
 
-        darray_foreach(entry, type->map)
-            entry->mods.mask = entry->mods.real_mods |
-                                VModsToReal(keymap, entry->mods.vmods);
-    }
+    actions = NewActionsInfo();
+    if (!actions)
+        return false;
 
-    /* Update action modifiers. */
-    for (key = keymap->min_key_code; key <= keymap->max_key_code; key++) {
-        union xkb_action *acts = XkbKeyActionsPtr(keymap, key);
-        for (i = 0; i < XkbKeyNumActions(keymap, key); i++) {
-            if (acts[i].any.type == XkbSA_NoAction)
-                continue;
-            UpdateActionMods(keymap, &acts[i], keymap->map->modmap[key]);
-        }
-    }
+    InitCompatInfo(&info, keymap, file->id, actions);
+    info.dflt.merge = merge;
+    info.ledDflt.merge = merge;
 
-    /* Update group modifiers. */
-    for (i = 0; i < XkbNumKbdGroups; i++) {
-        struct xkb_mods *group = &keymap->compat->groups[i];
-        group->mask = group->real_mods | VModsToReal(keymap, group->vmods);
-    }
+    HandleCompatMapFile(&info, file, merge);
+    if (info.errorCount != 0)
+        goto err_info;
 
-    /* Update vmod -> indicator maps. */
-    for (i = 0; i < XkbNumIndicators; i++) {
-        struct xkb_mods *led = &keymap->indicators->maps[i].mods;
-        led->mask = led->real_mods | VModsToReal(keymap, led->vmods);
-    }
+    if (!CopyCompatToKeymap(keymap, &info))
+        goto err_info;
 
+    ClearCompatInfo(&info);
+    FreeActionsInfo(actions);
     return true;
+
+err_info:
+    ClearCompatInfo(&info);
+    FreeActionsInfo(actions);
+    return false;
 }