HelenOS sources

root/uspace/srv/hid/input/gsp.c

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. trans_key_hash
  2. trans_hash
  3. trans_key_equal
  4. gsp_init
  5. gsp_insert_defs
  6. gsp_insert_seq
  7. gsp_step
  8. trans_lookup
  9. trans_insert
  10. trans_new

/*
 * Copyright (c) 2009 Jiri Svoboda
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * - Redistributions of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 * - Redistributions in binary form must reproduce the above copyright
 *   notice, this list of conditions and the following disclaimer in the
 *   documentation and/or other materials provided with the distribution.
 * - The name of the author may not be used to endorse or promote products
 *   derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

/**
 * @addtogroup kbdgen generic
 * @ingroup  input
 * @{
 */
/** @file
 * @brief Generic scancode parser.
 *
 * The scancode parser is a simple finite state machine. It is described
 * using sequences of input symbols (scancodes) and the corresponding output
 * value (mods, key pair). When the parser recognizes a sequence,
 * it outputs the value and restarts. If a transition is undefined,
 * the parser restarts, too.
 *
 * Apart from precise values, GSP_DEFAULT allows to catch general cases.
 * I.e. if we knew that after 0x1b 0x4f there always follow two more
 * scancodes, we can define (0x1b, 0x4f, GSP_DEFAULT, GSP_DEFAULT, GSP_END)
 * with null output. This will force the parser to read the entire sequence,
 * not leaving garbage on the input if it does not recognize the specific
 * sequence.
 */

#include <adt/hash_table.h>
#include <adt/hash.h>
#include <stdbool.h>
#include <stdlib.h>
#include <stdio.h>
#include "gsp.h"

/*
 * Transition function hash table operations.
 */
typedef struct {
        int old_state;
        int input;
} trans_key_t;

static size_t trans_key_hash(const void *key)
{
        const trans_key_t *trans_key = key;
        return hash_combine(trans_key->input, trans_key->old_state);
}

static size_t trans_hash(const ht_link_t *item)
{
        gsp_trans_t *t = hash_table_get_inst(item, gsp_trans_t, link);
        return hash_combine(t->input, t->old_state);
}

static bool trans_key_equal(const void *key, const ht_link_t *item)
{
        const trans_key_t *trans_key = key;
        gsp_trans_t *t = hash_table_get_inst(item, gsp_trans_t, link);

        return trans_key->input == t->input && trans_key->old_state == t->old_state;
}

static const hash_table_ops_t trans_ops = {
        .hash = trans_hash,
        .key_hash = trans_key_hash,
        .key_equal = trans_key_equal,
        .equal = NULL,
        .remove_callback = NULL
};

static gsp_trans_t *trans_lookup(gsp_t *p, int state, int input);
static void trans_insert(gsp_t *p, gsp_trans_t *t);
static gsp_trans_t *trans_new(void);

/** Initialize scancode parser. */
void gsp_init(gsp_t *p)
{
        p->states = 1;
        hash_table_create(&p->trans, 0, 0, &trans_ops);
}

/** Insert a series of definitions into the parser.
 *
 * @param p     The parser.
 * @param defs  Definition list. Each definition starts with two output values
 *              (mods, key) and continues with a sequence of input values
 *              terminated with GSP_END. The definition list is terminated
 *              with two zeroes (0, 0) for output values.
 */
int gsp_insert_defs(gsp_t *p, const int *defs)
{
        unsigned mods, key;
        const int *dp;
        int rc;

        dp = defs;

        while (true) {
                /* Read the output values. */
                mods = *dp++;
                key = *dp++;
                if (key == 0)
                        break;

                /* Insert one sequence. */
                rc = gsp_insert_seq(p, dp, mods, key);
                if (rc != 0)
                        return rc;

                /* Skip to the next definition. */
                while (*dp != GSP_END)
                        ++dp;
                ++dp;
        }

        return 0;
}

/** Insert one sequence into the parser.
 *
 * @param p     The parser.
 * @param seq   Sequence of input values terminated with GSP_END.
 * @param mods  Corresponsing output value.
 * @param key   Corresponsing output value.
 */
int gsp_insert_seq(gsp_t *p, const int *seq, unsigned mods, unsigned key)
{
        int state;
        gsp_trans_t *t;

        state = 0;
        t = NULL;

        /* Input sequence must be non-empty. */
        if (*seq == GSP_END)
                return -1;

        while (*(seq + 1) != GSP_END) {
                t = trans_lookup(p, state, *seq);
                if (t == NULL) {
                        /* Create new state. */
                        t = trans_new();
                        t->old_state = state;
                        t->input = *seq;
                        t->new_state = p->states++;

                        t->out_mods = 0;
                        t->out_key = 0;

                        trans_insert(p, t);
                }
                state = t->new_state;
                ++seq;
        }

        /* Process the last transition. */
        t = trans_lookup(p, state, *seq);
        if (t != NULL) {
                exit(1);
                return -1;      /* Conflicting definition. */
        }

        t = trans_new();
        t->old_state = state;
        t->input = *seq;
        t->new_state = 0;

        t->out_mods = mods;
        t->out_key = key;

        trans_insert(p, t);

        return 0;
}

/** Compute one parser step.
 *
 * Computes the next state and output values for a given state and input.
 * This handles everything including restarts and default branches.
 *
 * @param p             The parser.
 * @param state         Old state.
 * @param input         Input symbol (scancode).
 * @param mods          Output value (modifier).
 * @param key           Output value (key).
 * @return              New state.
 */
int gsp_step(gsp_t *p, int state, int input, unsigned *mods, unsigned *key)
{
        gsp_trans_t *t;

        t = trans_lookup(p, state, input);
        if (t == NULL) {
                t = trans_lookup(p, state, GSP_DEFAULT);
        }

        if (t == NULL) {
                printf("gsp_step: not found, state=%d, input=0x%x\n",
                    state, input);
                *mods = 0;
                *key = 0;
                return 0;
        }

        *mods = t->out_mods;
        *key = t->out_key;

        return t->new_state;
}

/** Transition function lookup.
 *
 * Returns the value of the transition function for the given state
 * and input. Note that the transition must be specified precisely,
 * to obtain the default branch use input = GSP_DEFAULT.
 *
 * @param p             Parser.
 * @param state         Current state.
 * @param input         Input value.
 * @return              The transition or @c NULL if not defined.
 */
static gsp_trans_t *trans_lookup(gsp_t *p, int state, int input)
{
        ht_link_t *item;

        trans_key_t key = {
                .input = input,
                .old_state = state
        };

        item = hash_table_find(&p->trans, &key);
        if (item == NULL)
                return NULL;

        return hash_table_get_inst(item, gsp_trans_t, link);
}

/** Define a new transition.
 *
 * @param p     The parser.
 * @param t     Transition with all fields defined.
 */
static void trans_insert(gsp_t *p, gsp_trans_t *t)
{
        hash_table_insert(&p->trans, &t->link);
}

/** Allocate transition structure. */
static gsp_trans_t *trans_new(void)
{
        gsp_trans_t *t;

        t = malloc(sizeof(gsp_trans_t));
        if (t == NULL) {
                printf("Memory allocation failed.\n");
                exit(1);
        }

        return t;
}

/**
 * @}
 */

/* [<][>][^][v][top][bottom][index][help] */
HelenOS homepage, sources at GitHub