Added utilities for protocols
authormarco <marco@38d2e660-2303-0410-9eaa-f027e97ec537>
Thu, 1 Jun 2006 12:27:39 +0000 (12:27 +0000)
committermarco <marco@38d2e660-2303-0410-9eaa-f027e97ec537>
Thu, 1 Jun 2006 12:27:39 +0000 (12:27 +0000)
git-svn-id: https://src.develer.com/svnoss/bertos/trunk@653 38d2e660-2303-0410-9eaa-f027e97ec537

mware/cmd_hunk.h [new file with mode: 0755]
mware/hashtable.c
mware/hashtable.h
mware/parser.c [new file with mode: 0755]
mware/parser.h [new file with mode: 0755]
mware/readline.c [new file with mode: 0755]
mware/readline.h [new file with mode: 0755]

diff --git a/mware/cmd_hunk.h b/mware/cmd_hunk.h
new file mode 100755 (executable)
index 0000000..e0416be
--- /dev/null
@@ -0,0 +1,152 @@
+/*!
+ * \file
+ * <!--
+ * Copyright 2004 Giovanni Bajo
+ * Copyright 2004 Develer S.r.l. (http://www.develer.com/)
+ * All Rights Reserved.
+ * -->
+ * This module permits to create hunks for the functions that must be executed through
+ * RPC commands. For instance, given this code:
+ *
+ * \code
+ * ResultCode cmd_add(long a, long b, long* result);
+ * DECLARE_COMMAND_HUNK(add, (long)(long)(NIL), (long)(NIL));
+ * //                        ^ parameters       ^ return values
+ * \endcode
+ *
+ * The macro is expanded to:
+ *
+ * \code
+ * ResultCode cmd_add_hunk(params argv[], params results[])
+ * {
+ *    return cmd_add(argv[0].l, argv[1].l, &results[0].l);
+ * }
+ *
+ * const struct CmdTemplate cmd_add_template = 
+ * {
+ *    "add", "dd", "d", cmd_add_hunk
+ * };
+ * \endcode
+ *
+ * which is all the boilerplate needed to make the function ready for the RPC.
+ * The implementation uses the Boost Preprocessor Library (part of the Boost
+ * library, available at http://www.boost.org). The version we developed the
+ * code with is 1.31.
+ *
+ * \version $Id$
+ *
+ * \author Giovanni Bajo <rasky@develer.com>
+ *
+ * \brief Preprocessor magic to create hunks for the commands executed from the parser
+ *
+ */
+
+/*#*
+ *#* $Log$
+ *#* Revision 1.1  2006/06/01 12:27:39  marco
+ *#* Added utilities for protocols
+ *#*
+ *#* Revision 1.8  2005/01/12 20:06:31  rasky
+ *#* enum ResultCode è ora un typedef
+ *#*
+ *#* Revision 1.7  2004/12/14 15:10:53  user1
+ *#* Bernardo e Francesco: Assegnazione di un codice di ritorno ai processi
+ *#*
+ *#* Revision 1.6  2004/12/02 17:43:15  rasky
+ *#* Aggiunta la possibilità di settare le flag dei comandi con la nuova macro DECLARE_CMD_HUNK_FLAGS (altrimenti, il default è zero)
+ *#*
+ *#* Revision 1.5  2004/10/16 13:20:15  rasky
+ *#* Aggiunto supporto per alias di uno stesso comando
+ *#*
+ *#* Revision 1.4  2004/08/25 14:17:10  rasky
+ *#* Aggiornato il comment block dei log RCS
+ *#*
+ *#* Revision 1.3  2004/07/31 22:00:07  rasky
+ *#* Fixata una dipendenza
+ *#*
+ *#* Revision 1.2  2004/07/31 14:32:58  rasky
+ *#* args e results possono overlapparsi sempre grazie ai thunk generati: eliminati i due array separati per risparmiare memoria
+ *#*
+ *#* Revision 1.1  2004/05/22 16:29:16  rasky
+ *#* Generazione degli hunk dei comandi RPC tramite preprocessore
+ *#*
+ *#*/
+
+#ifndef CMD_HUNK_H
+#define CMD_HUNK_H
+
+#include "parser.h"
+
+// Bring in the Boost Preprocess Library
+#include <boost/preprocessor/library.hpp>
+
+#define HUNK_INDEX_FOR_NIL      0
+#define HUNK_INDEX_FOR_string   1
+#define HUNK_INDEX_FOR_long     2
+#define HUNK_ARRAY_LETTERS      (3, (NIL, s, l))
+#define HUNK_ARRAY_STRINGS      (3, ("", "s", "d"))
+
+// Transform int->l, float->f, etc.
+#define HUNK_TYPE_LETTER(s, _, type) \
+       BOOST_PP_CAT(HUNK_INDEX_FOR_, type) \
+       /**/
+
+#define HUNK_TRANSFORMER(_, array, elem) \
+       BOOST_PP_ARRAY_ELEM(elem, array) \
+       /**/
+
+#define HUNK_SEQ_TRANS_ARRAY(seq, array) \
+       BOOST_PP_SEQ_TRANSFORM(HUNK_TRANSFORMER, array, seq) \
+       /**/
+
+#define HUNK_PARAM(_, n, seq)    \
+       args_results[n+1]. BOOST_PP_SEQ_ELEM(n, seq) \
+       /**/
+
+#define HUNK_RESULT(_, n, seq)    \
+       &args_results[n]. BOOST_PP_SEQ_ELEM(n, seq) \
+       /**/
+
+#define HUNK_IDENTITY(_, dummy, x)  x
+#define CMD_HUNK_TEMPLATE(func)         cmd_##func###_template
+
+#define DECLARE_CMD_HUNK_2(func, name, param_types, result_types, flags)    \
+       static ResultCode cmd_##name##_hunk(parms args_results[]) \
+       { \
+               return cmd_##func( \
+                      BOOST_PP_ENUM(BOOST_PP_DEC(BOOST_PP_SEQ_SIZE(param_types)),  HUNK_PARAM,  HUNK_SEQ_TRANS_ARRAY(param_types, HUNK_ARRAY_LETTERS)) \
+                      BOOST_PP_COMMA_IF(BOOST_PP_AND(BOOST_PP_DEC(BOOST_PP_SEQ_SIZE(param_types)), BOOST_PP_DEC(BOOST_PP_SEQ_SIZE(result_types)))) \
+                      BOOST_PP_ENUM(BOOST_PP_DEC(BOOST_PP_SEQ_SIZE(result_types)), HUNK_RESULT, HUNK_SEQ_TRANS_ARRAY(result_types, HUNK_ARRAY_LETTERS)) \
+               ); \
+       } \
+       const struct CmdTemplate CMD_HUNK_TEMPLATE(name) = { \
+               #name, \
+               BOOST_PP_SEQ_FOR_EACH(HUNK_IDENTITY, _, HUNK_SEQ_TRANS_ARRAY(param_types, HUNK_ARRAY_STRINGS)),  \
+               BOOST_PP_SEQ_FOR_EACH(HUNK_IDENTITY, _, HUNK_SEQ_TRANS_ARRAY(result_types, HUNK_ARRAY_STRINGS)), \
+               cmd_##name##_hunk, \
+               flags \
+       } \
+       /**/
+
+#define DECLARE_CMD_HUNK(func, param_types, result_types)    \
+       DECLARE_CMD_HUNK_2(func, func, \
+                          BOOST_PP_SEQ_TRANSFORM(HUNK_TYPE_LETTER, _, param_types), \
+                          BOOST_PP_SEQ_TRANSFORM(HUNK_TYPE_LETTER, _, result_types), \
+                          0) \
+       /**/
+
+#define DECLARE_CMD_HUNK_NAME(func, name, param_types, result_types)    \
+       DECLARE_CMD_HUNK_2(func, name, \
+                          BOOST_PP_SEQ_TRANSFORM(HUNK_TYPE_LETTER, _, param_types), \
+                          BOOST_PP_SEQ_TRANSFORM(HUNK_TYPE_LETTER, _, result_types), \
+                          0) \
+       /**/
+
+#define DECLARE_CMD_HUNK_FLAGS(func, param_types, result_types, flags)    \
+       DECLARE_CMD_HUNK_2(func, func, \
+                          BOOST_PP_SEQ_TRANSFORM(HUNK_TYPE_LETTER, _, param_types), \
+                          BOOST_PP_SEQ_TRANSFORM(HUNK_TYPE_LETTER, _, result_types), \
+                          flags) \
+       /**/
+
+#endif
index bfc8bc0ec6a0b1510be0fb5aba68bbe8a583ee34..cd9a7b6e32fe39eb34639ddb9d9699eb8e04e174 100755 (executable)
 
 /*#*
  *#* $Log$
- *#* Revision 1.5  2005/04/11 19:10:28  bernie
- *#* Include top-level headers from cfg/ subdir.
- *#*
- *#* Revision 1.4  2004/12/08 09:42:30  bernie
- *#* Suppress warning.
- *#*
- *#* Revision 1.3  2004/10/03 20:43:22  bernie
- *#* Import changes from sc/firmware.
- *#*
- *#* Revision 1.12  2004/06/14 15:15:24  rasky
- *#* Cambiato key_data in un union invece di castare
- *#* Aggiunto un ASSERT sull'indice calcolata nella key_internal_get_ptr
- *#*
- *#* Revision 1.11  2004/06/14 15:09:04  rasky
- *#* Cambiati i messaggi di assert (è inutile citare il nome della funzione)
- *#*
- *#* Revision 1.10  2004/06/14 15:07:38  rasky
- *#* Convertito il loop di calc_hash a interi (per farlo ottimizzare maggiormente)
- *#*
- *#* Revision 1.9  2004/06/14 14:59:40  rasky
- *#* Rinominanta la macro di configurazione per rispettare il namespace, e aggiunta in un punto in cui mancava
- *#*
- *#* Revision 1.8  2004/06/12 15:18:05  rasky
- *#* Nuova hashtable con chiave esterna o interna a scelta, come discusso
- *#*
- *#* Revision 1.7  2004/06/04 17:16:31  rasky
- *#* Fixato un bug nel caso in cui la chiave ecceda la dimensione massima: il clamp non può essere fatto dentro la perform_lookup perché anche la ht_insert deve avere il valore clampato a disposizione per fare la memcpy
- *#*
- *#* Revision 1.6  2004/05/26 16:36:50  rasky
- *#* Aggiunto il rationale per l'interfaccia degli iteratori
- *#*
- *#* Revision 1.5  2004/05/24 15:28:20  rasky
- *#* Sistemata la documentazione, rimossa keycmp in favore della memcmp
+ *#* Revision 1.6  2006/06/01 12:27:39  marco
+ *#* Added utilities for protocols
  *#*
  *#*/
 
@@ -112,14 +81,14 @@ typedef const void** HashNodePtr;
 #define HT_HAS_INTERNAL_KEY(ht)        (CONFIG_HT_OPTIONAL_INTERNAL_KEY && ht->flags.key_internal)
 
 /*! For hash tables with internal keys, compute the pointer to the internal key for a given \a node. */
-INLINE uint8_t* key_internal_get_ptr(struct HashTable* ht, HashNodePtr node)
+INLINE uint8_t *key_internal_get_ptr(struct HashTable *ht, HashNodePtr node)
 {
        uint8_t* key_buf = ht->key_data.mem;
        size_t index;
 
        // Compute the index of the node and use it to move within the whole key buffer
        index = node - &ht->mem[0];
-       ASSERT(index < (1 << ht->max_elts_log2));
+       ASSERT(index < (size_t)(1 << ht->max_elts_log2));
        key_buf += index * (INTERNAL_KEY_MAX_LENGTH + 1);
 
        return key_buf;
index c6da451a7e88084ca1389bbb2b985d9d0e677fbc..45bea14674024fd324253e6473661bcabc2e2843 100755 (executable)
@@ -1,7 +1,7 @@
 /*!
  * \file
  * <!--
- * Copyright 2004 Develer S.r.l. (http://www.develer.com/)
+ * Copyright 2004, 2006 Develer S.r.l. (http://www.develer.com/)
  * Copyright 2004 Giovanni Bajo
  * All Rights Reserved.
  * -->
 
 /*#*
  *#* $Log$
- *#* Revision 1.6  2005/04/11 19:10:28  bernie
- *#* Include top-level headers from cfg/ subdir.
+ *#* Revision 1.7  2006/06/01 12:27:39  marco
+ *#* Added utilities for protocols
  *#*
- *#* Revision 1.5  2004/10/03 20:43:22  bernie
- *#* Import changes from sc/firmware.
- *#*
- *#* Revision 1.9  2004/06/14 15:15:24  rasky
- *#* Cambiato key_data in un union invece di castare
- *#* Aggiunto un ASSERT sull'indice calcolata nella key_internal_get_ptr
- *#*
- *#* Revision 1.8  2004/06/14 14:59:40  rasky
- *#* Rinominanta la macro di configurazione per rispettare il namespace, e aggiunta in un punto in cui mancava
- *#*
- *#* Revision 1.7  2004/06/12 15:18:05  rasky
- *#* Nuova hashtable con chiave esterna o interna a scelta, come discusso
- *#*
- *#* Revision 1.6  2004/05/26 16:33:31  rasky
- *#* Aggiunta interfaccia per visita della hashtable tramite iteratori
- *#*
- *#* Revision 1.5  2004/05/24 18:42:23  rasky
- *#* Fixato un commento doxygen
- *#*
- *#* Revision 1.4  2004/05/24 15:28:20  rasky
- *#* Sistemata la documentazione, rimossa keycmp in favore della memcmp
  *#*/
 
 #ifndef MWARE_HASHTABLE_H
@@ -77,7 +56,7 @@
  * Hook to get the key from \a data, which is an element of the hash table. The
  * key must be returned together with \a key_length (in words).
  */
-typedef const void* (*hook_get_key)(const void* data, uint8_t* key_length);
+typedef const void *(*hook_get_key)(const void *data, uint8_t *key_length);
 
 
 /*!
@@ -92,14 +71,14 @@ typedef const void* (*hook_get_key)(const void* data, uint8_t* key_length);
  */
 struct HashTable
 {
-       const void** mem;            //!< Buckets of data
+       const void **mem;            //!< Buckets of data
        uint16_t max_elts_log2;      //!< Log2 of the size of the table
        struct {
                bool key_internal : 1;   //!< true if the key is copied internally
        } flags;
        union {
                hook_get_key hook;       //!< Hook to get the key
-               uint8_tmem;            //!< Pointer to the key memory
+               uint8_t *mem;            //!< Pointer to the key memory
        } key_data;
 };
 
@@ -130,7 +109,7 @@ typedef struct
 /*! Exactly like \c DECLARE_HASHTABLE, but the variable will be declared as static. */
 #define DECLARE_HASHTABLE_STATIC(name, size, hook_gk) \
        static const void* name##_nodes[1 << UINT32_LOG2(size)]; \
-       static struct HashTable name = { name##_nodes, UINT32_LOG2(size), { false }, hook_gk }
+       static struct HashTable name = { name##_nodes, UINT32_LOG2(size), { false }, { hook_gk } }
 
 #if CONFIG_HT_OPTIONAL_INTERNAL_KEY
        /*! Declare a hash table with internal copies of the keys. This version does not
diff --git a/mware/parser.c b/mware/parser.c
new file mode 100755 (executable)
index 0000000..0b2c873
--- /dev/null
@@ -0,0 +1,371 @@
+/*!
+ * \file
+ * <!--
+ * Copyright 2003, 2006 Develer S.r.l. (http://www.develer.com/)
+ * All Rights Reserved.
+ * -->
+ *
+ * \version $Id$
+ *
+ * \author Bernardo Innocenti <bernie@develer.com>
+ * \author Stefano Fedrigo <aleph@develer.com>
+ * \author Giovanni Bajo <rasky@develer.com>
+ *
+ * \brief Serial protocol parser and commands.
+ *
+ * This file contains the serial protocol parser and
+ * the definition of the protocol commands. Commands are defined
+ * in a "CmdTemplate" type array, containing:
+ * - the name of the command,
+ * - the arguments it expects to receive,
+ * - the output values,
+ * - the name of the function implementing the command.
+ *
+ * The arguments and results are passed to command function
+ * using an union: the element of the union to use for each
+ * argument is determined by format strings present in the
+ * CmdTemplate table.
+ */
+
+/*#*
+ *#* $Log$
+ *#* Revision 1.1  2006/06/01 12:27:39  marco
+ *#* Added utilities for protocols
+ *#*
+ *#*/
+
+#include "parser.h"
+#include <drv/ser.h>
+
+#include <stdlib.h> // atol(), NULL
+#include <string.h> // strchr(), strcmp()
+
+#include <mware/hashtable.h>
+
+#define ARG_SEP_S " "
+#define ARG_SEP_C ' '
+
+#define MAX_COMMANDS_NUMBER  128  // 64
+
+//! Hashtable hook to extract the key from a command
+static const void* get_key_from_command(const void* cmd, uint8_t* length);
+
+//! Hashtable that handles the commands that can be executed
+DECLARE_HASHTABLE_STATIC(commands, MAX_COMMANDS_NUMBER, get_key_from_command);
+
+
+/*!
+ * \brief Tokenize one word at a time from a text.
+ *
+ * This function is similar to strtok, but does not use any implicit
+ * context, nor it does modify the input buffer in any form.
+ * The word is returned as a STL-like [begin,end) range.
+ *
+ * To extract the first word, make both begin and end point at the
+ * start of the text, and call the function. Then, subsequent
+ * calls will return the following words (assuming the begin/end
+ * variable are not modified between calls).
+ *
+ * \param begin Will contain the index of the first character of the word
+ * \param end Will contain the index of the character after the last
+ *     character of the word
+ *
+ * \return True if a word was extracted, false if we got to the end
+ * of the string without extracting any word.
+ */
+static bool get_word(const char **begin, const char **end)
+{
+       const char *cur = *end;
+
+       while ((*cur == ' ' || *cur == '\t') && *cur)
+               ++cur;
+
+       *begin = cur;
+
+       while ((*cur != ' ' && *cur != '\t') && *cur)
+               ++cur;
+
+       *end = cur;
+
+       return (*end != *begin);
+}
+
+
+/*!
+ * \brief Command arguments parser.
+ *
+ * Using the format pointed by the argument fmt
+ * parses the input string filling the array argv
+ * with input parameters of the correct type.
+ *
+ * \param fmt   Parameters format string.
+ * \param input Input string.
+ * \param argv  Array filled with parameters.
+ *
+ * \return False in case of errors, otherwise true.
+ */
+static bool parseArgs(const char *fmt, const char *input, parms argv[])
+{
+       const char *begin = input, *end = input;
+
+       while (*fmt)
+       {
+               // Extract the argument
+               if (!get_word(&begin, &end))
+                       return false;
+
+               switch (*fmt)
+               {
+                       case 'd':
+                               (*argv++).l = atol(begin);
+                               break;
+
+                       case 's':
+                               (*argv++).s = begin;
+                               break;
+
+                       default:
+                               ASSERT2(0, "Unknown format for argument");
+                               return false;
+               }
+
+               ++fmt;
+       }
+
+       /* check if there are remaining args */
+       if (get_word(&begin, &end))
+               return false;
+
+       return true;
+}
+
+
+#ifdef UNUSED_CODE
+/**
+ * \brief Command result formatting and printing.
+ *
+ * Prints out on device fd the values contained
+ * in the array result, using the format specified
+ * in fmt.
+ *
+ * \param ser     Serial handle.
+ * \param fmt     Values format string.
+ * \param result  Array containing result to be printed.
+ *
+ * \return -1 in case of errors, otherwise 0.
+ */
+static int printResult(struct Serial *ser, const char *fmt, parms result[])
+{
+       long n;
+       char repeat_cnt = 0;
+
+       while (*fmt)
+       {
+               if (*fmt >= '0' && *fmt <= '9')
+               {
+                       /* Collect repeat count digit (left to right order) */
+                       repeat_cnt = (repeat_cnt * 10) + (*fmt - '0');
+               }
+               else
+               {
+                       /* Set default repeat cnt of 1 when not specified */
+                       if (repeat_cnt == 0)
+                               repeat_cnt = 1;
+
+                       /* Loop repeat_cnt times */
+                       do
+                       {
+                               switch (*fmt)
+                               {
+                                       case 'd':
+                                               ser_printf(ser, ARG_SEP_S "%ld", (*result).l);
+                                               result++;
+                                               break;
+                                       case 'c':
+                                               ser_print(ser, ARG_SEP_S);
+                                               ser_print(ser, (*result).s);
+                                               result++;
+                                               break;
+                                       case 's':
+                                               ser_printf(ser, ARG_SEP_S "%s", (*result).s);
+                                               result++;
+                                               break;
+                                       case 'n':
+                                               n = (*result++).l;
+                                               ser_printf(ser, ARG_SEP_S "%ld", n);
+                                               while (n--) {
+                                                       ser_printf(ser, ARG_SEP_S "%ld", (*result).l);
+                                                       result++;
+                                               }
+                                               break;
+                                       default:
+                                               break;
+                               }
+                       }
+                       while (--repeat_cnt);
+               }
+
+               /* Skip to next format char */
+               ++fmt;
+
+       } /* while (*fmt) */
+
+
+       ser_print(ser, "\r\n");
+       return 0;
+}
+#endif /* UNUSED_CODE */
+
+//! Hook provided by the parser for matching of command names (TAB completion) for readline
+const char* parser_rl_match(UNUSED_ARG(void *,dummy), const char *word, int word_len)
+{
+       HashIterator cur;
+       HashIterator end = ht_iter_end(&commands);
+       const char *found = NULL;
+
+       for (cur = ht_iter_begin(&commands);
+            !ht_iter_cmp(cur, end);
+            cur = ht_iter_next(cur))
+       {
+               const struct CmdTemplate* cmdp = (const struct CmdTemplate*)ht_iter_get(cur);
+               if (strncmp(cmdp->name, word, word_len) == 0)
+               {
+                       // If there was another matching word, it means that we have a multiple
+                       //  match: then return NULL.
+                       if (found)
+                               return NULL;
+
+                       found = cmdp->name;
+               }
+       }
+
+       return found;
+}
+
+bool parser_get_cmd_id(const char* line, unsigned long* ID)
+{
+       const char *begin = line, *end = line;
+       char *end2;
+
+       // The first word is the ID
+       if (!get_word(&begin, &end))
+               return false;
+
+       *ID = strtoul(begin, &end2, 10);
+       if (end2 != end)
+               return false;
+
+       return true;
+}
+
+const struct CmdTemplate* parser_get_cmd_template(const char *input)
+{
+//     const struct CmdTemplate *cmdp;
+//     int cmdlen;
+       const char *begin = input, *end = input;
+
+       // Skip the ID, and get the command
+       if (!get_word(&begin, &end))
+               return NULL;
+       if (!get_word(&begin, &end))
+               return NULL;
+
+       return (const struct CmdTemplate*)ht_find(&commands, begin, end-begin);
+}
+
+static const char *skip_to_params(const char *input, const struct CmdTemplate *cmdp)
+{
+       const char *begin = input, *end = input;
+
+       // Skip the ID, and get the command
+       if (!get_word(&begin, &end))
+               return NULL;
+       if (!get_word(&begin, &end))
+               return NULL;
+
+       ASSERT2(strlen(cmdp->name) == (size_t)(end-begin), "Invalid command template specified");
+       ASSERT2(!strncmp(begin, cmdp->name, end-begin), "Invalid command template specified");
+
+       return end;
+}
+
+bool parser_get_cmd_arguments(const char* input, const struct CmdTemplate* cmdp, parms args[PARSER_MAX_ARGS])
+{
+       input = skip_to_params(input, cmdp);
+       if (!input)
+               return false;
+
+       args[0].s = cmdp->name;
+       if (!parseArgs(cmdp->arg_fmt, input, args + 1))
+               return false;
+
+       return true;
+}
+
+static const void* get_key_from_command(const void* cmd, uint8_t* length)
+{
+       const struct CmdTemplate* c = cmd;
+       *length = strlen(c->name);
+       return c->name;
+}
+
+bool parser_process_line(const char* input)
+{
+       const struct CmdTemplate *cmdp;
+       parms args[PARSER_MAX_ARGS];
+
+       cmdp = parser_get_cmd_template(input);
+       if (!cmdp)
+               return false;
+
+       if (!parser_get_cmd_arguments(input, cmdp, args))
+               return false;
+
+       if (!parser_execute_cmd(cmdp, args))
+               return false;
+
+       return true;
+}
+
+void parser_register_cmd(const struct CmdTemplate* cmd)
+{
+       ht_insert(&commands, cmd);
+}
+
+#if CONFIG_INTERNAL_COMMANDS
+static ResultCode cmd_help(void)
+{
+#ifdef _DEBUG
+
+       // FIXME: There is no way at the moment to access the serial port. Dump
+       //  this through JTAG for now
+       for (HashIterator iter = ht_iter_begin(&commands);
+               !ht_iter_cmp(iter, ht_iter_end(&commands));
+               iter = ht_iter_next(iter))
+       {
+               struct CmdTemplate* cmd = (struct CmdTemplate*)ht_iter_get(iter);
+               kprintf("%-20s", cmd->name);
+               for (unsigned j = 0; cmd->arg_fmt[j]; ++j)
+                       kprintf("%c ", 'a' + j);
+               kprintf("\r\n");
+       }
+#endif
+
+       return RC_OK;
+}
+
+#include "cmd_hunk.h"
+DECLARE_CMD_HUNK(help, (NIL), (NIL));
+
+#endif // CONFIG_INTERNAL_COMMANDS
+
+
+void parser_init(void)
+{
+       // Initialize the hashtable used to store the command description
+       ht_init(&commands);
+
+#if CONFIG_INTERNAL_COMMANDS
+       parser_register_cmd(&CMD_HUNK_TEMPLATE(help));
+#endif
+}
diff --git a/mware/parser.h b/mware/parser.h
new file mode 100755 (executable)
index 0000000..34f6a14
--- /dev/null
@@ -0,0 +1,163 @@
+/**
+ * \file
+ * Copyright 2003, 2006 Develer S.r.l. (http://www.develer.com/)
+ * All Rights Reserved.
+ *
+ * \version $Id$
+ *
+ * \author Bernardo Innocenti <bernie@develer.com>
+ * \author Stefano Fedrigo <aleph@develer.com>
+ * \author Giovanni Bajo <rasky@develer.com>
+ *
+ * \brief serial protocol parser and commands.
+ */
+
+/*#*
+ *#* $Log$
+ *#* Revision 1.1  2006/06/01 12:27:39  marco
+ *#* Added utilities for protocols
+ *#*
+ *#*/
+
+#ifndef PARSER_H
+#define PARSER_H
+
+#include <drv/ser.h>
+
+/** Max number of arguments and results for each command */
+#define PARSER_MAX_ARGS       8
+
+/**
+ * Error generated by the commands through the return code.
+ */
+typedef enum
+{
+       RC_ERROR  = -1, //!< Reply with error.
+       RC_OK     = 0,  //!< No reply (ignore reply arguments).
+       RC_REPLY  = 1,   //!< Reply command arguments.
+       RC_SKIP   = 2    //!< Skip following commands
+} ResultCode;
+
+/** union that contains parameters passed to and from commands */
+typedef union { long l; const char *s; } parms;
+/** pointer to commands */
+typedef ResultCode (*CmdFuncPtr)(parms args_results[]);
+
+/**
+ * Define a command that can be tokenized by the parser.
+ *
+ * The format strings are sequences of characters, one for each
+ * parameter/result. Valid characters are:
+ *
+ *  d - a long integer, in decimal format
+ *  s - a var string (in RAM)
+ *
+ * \note To create and fill an instance for this function, it is strongly
+ * advised to use \c DECLARE_CMD_HUNK (cmd_hunk.h).
+ */
+struct CmdTemplate
+{
+       const char *name;          //!< name of command
+       const char *arg_fmt;       //!< format string for the input
+       const char *result_fmt;    //!< format string for the output
+       CmdFuncPtr func;           //!< pointer to the handler function
+};
+
+/**
+ * Initialize the parser module
+ *
+ * \note This function must be called before any other function in this module
+ */
+void parser_init(void);
+
+
+/**
+ * Register a new command into the parser
+ *
+ * \param cmd Command template describing the command
+ *
+ */
+void parser_register_cmd(const struct CmdTemplate* cmd);
+
+
+/**
+ * Hook for readline to provide completion support for the commands
+ * registered in the parser.
+ *
+ * \note This is meant to be used with mware/readline.c. See the
+ * documentation there for a description of this hook.
+ */
+const char* parser_rl_match(void* dummy, const char* word, int word_len);
+
+
+/**
+ * \brief Command input handler.
+ *
+ * Process the input, calling the requested command
+ * (if found) and calling printResult() to give out
+ * the result (on device specified with parameter fd).
+ *
+ * \param line Text line to be processed (ASCIIZ)
+ *
+ * \return true if everything is OK, false in case of errors
+ */
+bool parser_process_line(const char* line);
+
+
+/*!
+ * Execute a command with its arguments, and fetch its results.
+ *
+ * \param templ Template of the command to be executed
+ * \param args Arguments for the command, and will contain the results
+ *
+ * \return False if the command returned an error, true otherwise
+ */
+INLINE bool parser_execute_cmd(const struct CmdTemplate* templ, parms args[PARSER_MAX_ARGS])
+{
+       return (templ->func(args) == 0);
+}
+
+
+/**
+ * Find the template for the command contained in the text line.
+ * The template can be used to tokenize the command and interpret
+ * it.
+ *
+ * This function can be used to find out which command is contained
+ * in a given text line without parsing all the parameters and
+ * executing it.
+ *
+ * \param line Text line to be processed (ASCIIZ)
+ *
+ * \return The command template associated with the command contained
+ * in the line, or NULL if the command is invalid.
+ */
+const struct CmdTemplate* parser_get_cmd_template(const char* line);
+
+
+/**
+ * Extract the arguments for the command contained in the text line.
+ *
+ * \param line Text line to be processed (ASCIIZ)
+ * \param templ Command template for this line
+ * \param args Will contain the extracted parameters
+ *
+ * \return True if everything OK, false in case of parsing error.
+ */
+bool parser_get_cmd_arguments(const char* line, const struct CmdTemplate* templ, parms args[PARSER_MAX_ARGS]);
+
+
+/**
+ * Extract the ID from the command text line.
+ *
+ * \param line Text line to be processed (ASCIIZ)
+ * \param ID Will contain the ID extracted.
+ *
+ * \return True if everything ok, false if there is no ID
+ *
+ */
+bool parser_get_cmd_id(const char* line, unsigned long* ID);
+
+
+#endif /* PARSER_H */
+
diff --git a/mware/readline.c b/mware/readline.c
new file mode 100755 (executable)
index 0000000..86c1dfd
--- /dev/null
@@ -0,0 +1,472 @@
+/*!
+ * \file
+ * <!--
+ * Copyright 2004 Develer S.r.l. (http://www.develer.com/)
+ * Copyright 2004 Giovanni Bajo
+ * All Rights Reserved.
+ * -->
+ *
+ * \brief Line editing support with history
+ *
+ * Rationale for basic implementation choices:
+ *
+ * \li The history is implemented storing consecutive ASCIIZ strings within an array of memory. When
+ * the history is full, the first (oldest) line is cancelled and the whole buffer is memmoved to
+ * overwrite it and make room. while this is is obviously not the fastest algorithm (which would
+ * require the use of a circular buffer) it is surely good enough for this module, which does not
+ * aim at fast performances (line editing does not require to be blazingly fast).
+ *
+ * \li The first character in the history is always \c \\0, and it is used as a guard. By 'wasting' it
+ * in this way, the code actually gets much simpler in that we remove many checks when moving 
+ * backward (\c i>0 and similar).
+ *
+ * \li While editing, the current index points to the position of the buffer which contains the
+ * last character typed in (exactly like a stack pointer). This also allows to simplify calculations
+ * and to make easier using the last byte of history.
+ *
+ * \li While editing, the current line is always kept null-terminated. This is important because
+ * if the user press ENTER, we must have room to add a \c \\0 to terminate the line. If the line
+ * is as long as the whole history buffer, there would not be space for it. By always keeping the
+ * \c \\0 at the end, we properly ensure this without making index checks harder.
+ *
+ * \li When removing a line from the history (see \c pop_history()), instead of updating all the
+ * indices we have around, we move backward the pointer to the history we use. This way, we don't
+ * have to update anything. This means that we keep two pointers to the history: \c real_history
+ * always points to the physical start, while \c history is the adjusted pointer (that is
+ * dereference to read/write to it).
+ *
+ * \todo Use up/down to move through history  The history line will be copied to the current line,
+ * making sure there is room for it.
+ *
+ * \version $Id$
+ *
+ * \author Giovanni Bajo <rasky@develer.com>
+ */
+
+/*#*
+ *#* $Log$
+ *#* Revision 1.1  2006/06/01 12:27:39  marco
+ *#* Added utilities for protocols
+ *#*
+ *#*/
+
+#include "readline.h"
+
+#include <cfg/compiler.h>
+#include <cfg/debug.h>
+
+#include <stdio.h>
+
+
+//! Enable compilation of the unit test code
+#define DEBUG_UNIT_TEST       0
+
+//! Enable dump of the history after each line
+#define DEBUG_DUMP_HISTORY    0
+
+
+/*! Special keys (escape sequences converted to a single code) */
+enum RL_KEYS {
+       SPECIAL_KEYS = 0x1000,
+
+       /*
+        * Three byte keys:
+        * #################
+        * UpArrow:     0x1B 0x5B 0X41
+        * DownArrow:   0x1B 0x5B 0X42
+        * RightArrow:  0x1B 0x5B 0x43
+        * LeftArrow:   0x1b 0x5B 0x44
+        * Beak(Pause): 0x1b 0x5B 0x50
+       */
+       KEY_UP_ARROW,
+       KEY_DOWN_ARROW,
+       KEY_LEFT_ARROW,
+       KEY_RIGHT_ARROW,
+       KEY_PAUSE,
+
+       /*
+        * Four byte keys:
+        * ################
+        * F1:          0x1b 0x5B 0x5B 0x41
+        * F2:          0x1b 0x5B 0x5B 0x42
+        * F3:          0x1b 0x5B 0x5B 0x43
+        * F4:          0x1b 0x5B 0x5B 0x44
+        * F5:          0x1b 0x5B 0x5B 0x45
+        * Ins:         0x1b 0x5B 0x32 0x7E
+        * Home:        0x1b 0x5B 0x31 0x7E
+        * PgUp:        0x1b 0x5B 0x35 0x7E
+        * Del:         0x1b 0x5B 0x33 0x7E
+        * End:         0x1b 0x5B 0x34 0x7E
+        * PgDn:        0x1b 0x5B 0x36 0x7E
+        */
+       KEY_F1, KEY_F2, KEY_F3, KEY_F4, KEY_F5,
+       KEY_INS, KEY_HOME, KEY_PGUP, KEY_DEL, KEY_END, KEY_PGDN,
+
+       /*
+        * Five byte keys:
+        * ################
+        * F6:          0x1b 0x5B 0x31 0x37 0x7E
+        * F7:          0x1b 0x5B 0x31 0x38 0x7E
+        * F8:          0x1b 0x5B 0x31 0x39 0x7E
+        * F9:          0x1b 0x5B 0x32 0x30 0x7E
+        * F10:         0x1b 0x5B 0x32 0x31 0x7E
+        * F11:         0x1b 0x5B 0x32 0x33 0x7E
+        * F12:         0x1b 0x5B 0x32 0x34 0x7E
+        */
+       KEY_F6, KEY_F7, KEY_F8, KEY_F9,
+       KEY_F10, KEY_F11, KEY_F12,
+};
+
+/*! Check if \a c is a separator between words.
+ *  \note Parameter \a c is evaluated multiple times
+ */
+#define IS_WORD_SEPARATOR(c) ((c) == ' ' || (c) == '\0')
+
+//! Write the string \a txt to the IO output (without any kind of termination)
+INLINE void rl_puts(const struct RLContext* ctx, const char* txt)
+{
+       if (!ctx->put)
+               return;
+
+       while (*txt)
+               ctx->put(*txt++, ctx->put_param);
+}
+
+//! Write character \a ch to the IO output.
+INLINE void rl_putc(const struct RLContext* ctx, char ch)
+{
+       if (ctx->put)
+               ctx->put(ch, ctx->put_param);
+}
+
+/*! Read a character from the IO into \a ch. This function also takes
+ *  care of converting the ANSI escape sequences into one of the codes
+ *  defined in \c RL_KEYS.
+ */
+static bool rl_getc(const struct RLContext* ctx, int* ch)
+{
+       int c = ctx->get(ctx->get_param);
+       if (c == EOF)
+               return false;
+
+       if (c == 0x1B)
+       {
+               // Unknown ESC sequence. Ignore it and read
+               //  return next character.
+               if (ctx->get(ctx->get_param) != 0x5B)
+                       return rl_getc(ctx, ch);
+
+               /* To be added:
+                       * Home:        0x1b 0x5B 0x31 0x7E
+                       * F6:          0x1b 0x5B 0x31 0x37 0x7E
+                       * F7:          0x1b 0x5B 0x31 0x38 0x7E
+                       * F8:          0x1b 0x5B 0x31 0x39 0x7E
+                       * Ins:         0x1b 0x5B 0x32 0x7E
+                       * F9:          0x1b 0x5B 0x32 0x30 0x7E
+                       * F10:         0x1b 0x5B 0x32 0x31 0x7E
+                       * F11:         0x1b 0x5B 0x32 0x33 0x7E
+                       * F12:         0x1b 0x5B 0x32 0x34 0x7E
+                       * Del:         0x1b 0x5B 0x33 0x7E
+                       * End:         0x1b 0x5B 0x34 0x7E
+                       * PgUp:        0x1b 0x5B 0x35 0x7E
+                       * PgDn:        0x1b 0x5B 0x36 0x7E
+               */
+
+               c = ctx->get(ctx->get_param);
+               switch (c)
+               {
+               case 0x41: c = KEY_UP_ARROW; break;
+               case 0x42: c = KEY_DOWN_ARROW; break;
+               case 0x43: c = KEY_RIGHT_ARROW; break;
+               case 0x44: c = KEY_LEFT_ARROW; break;
+               case 0x50: c = KEY_PAUSE; break;
+               case 0x5B:
+                       c = ctx->get(ctx->get_param);
+                       switch (c)
+                       {
+                       case 0x41: c = KEY_F1; break;
+                       case 0x42: c = KEY_F2; break;
+                       case 0x43: c = KEY_F3; break;
+                       case 0x44: c = KEY_F4; break;
+                       case 0x45: c = KEY_F5; break;
+                       default: return rl_getc(ctx, ch);
+                       }
+                       break;
+               default: return rl_getc(ctx, ch);
+               }
+       }
+
+       *ch = c;
+       return true;
+}
+
+INLINE void beep(struct RLContext* ctx)
+{
+       rl_putc(ctx, '\a');
+}
+
+static bool pop_history(struct RLContext* ctx, int total_len)
+{
+       // Compute the length of the first command (including terminator).
+       int len = strlen(ctx->real_history+1)+1;
+
+       // (the first byte of the history should always be 0)
+       ASSERT(ctx->real_history[0] == '\0');
+
+       // If it is the only one in the history, do nothing
+       if (len == total_len)
+               return false;
+
+       // Overwrite the first command with the second one
+       memmove(ctx->real_history, ctx->real_history+len, HISTORY_SIZE-len);
+
+       // Move back the ctx->buffer pointer so that all the indices are still valid
+       ctx->history -= len;
+
+       return true;
+}
+
+//! Check if index \a i points to the begin of the history.
+INLINE bool is_history_begin(struct RLContext* ctx, int i)
+{ return ctx->history + i == ctx->real_history; }
+
+//! Check if index \a i points to the (exclusive) end of history
+INLINE bool is_history_end(struct RLContext* ctx, int i)
+{ return ctx->history + i == ctx->real_history + HISTORY_SIZE; }
+
+//! Check if index \a i points to the (exclusive) end of history, or somewhere past the end.
+INLINE bool is_history_past_end(struct RLContext* ctx, int i)
+{ return ctx->history + i >= ctx->real_history + HISTORY_SIZE; }
+
+/*! Insert \a num_chars characters from \a ch into the history buffer at the
+ *  position indicated by \a curpos. If needed, remove old history to make room.
+ *  Returns true if everything was successful, false if there was no room to
+ *  add the characters.
+ *  \note \a num_chars can be 0, in which case we just make sure the line is
+ *  correctly zero-terminated (ASCIIZ format).
+ */
+static bool insert_chars(struct RLContext* ctx, size_t *curpos, const char* ch, int num_chars)
+{
+       ASSERT(!is_history_past_end(ctx, *curpos));
+
+       while (is_history_past_end(ctx, *curpos+num_chars+1))
+       {
+               if (!pop_history(ctx, *curpos))
+                       return false;
+       }
+
+       while (num_chars--)
+               ctx->history[++(*curpos)] = *ch++;
+
+       ASSERT(!is_history_past_end(ctx, *curpos + 1));
+       ctx->history[*curpos+1] = '\0';
+       return true;
+}
+
+//! Insert a single character \a ch into the buffer (with the same semantic of \c insert_chars())
+static bool insert_char(struct RLContext* ctx, size_t *curpos, char ch)
+{
+       return insert_chars(ctx, curpos, &ch, 1);
+}
+
+#if DEBUG_DUMP_HISTORY
+//! Dump the internal history of a context (used only for debug purposes)
+static void dump_history(struct RLContext* ctx)
+{
+       int k;
+       char buf[8];
+       ASSERT(ctx->real_history[0] == '\0');
+       rl_puts(ctx, "History dump:");
+       rl_puts(ctx, "\r\n");
+       for (k = 1;
+            ctx->real_history + k != ctx->history + ctx->history_pos + 1;
+            k += strlen(&ctx->real_history[k]) + 1)
+       {
+               rl_puts(ctx, &ctx->real_history[k]);
+               rl_puts(ctx, "\r\n");
+       }
+
+       sprintf(buf, "%d\r\n", ctx->history_pos + (ctx->history - ctx->real_history));
+       rl_puts(ctx, buf);
+}
+#endif /* DEBUG_DUMP_HISTORY */
+
+//! Complete the current word. Return false if no unambiguous completion was found
+static bool complete_word(struct RLContext *ctx, size_t *curpos)
+{
+       const char* completed_word;
+       size_t wstart;
+
+       // If the current character is a separator,
+       //  there is nothing to complete
+       wstart = *curpos;
+       if (IS_WORD_SEPARATOR(ctx->history[wstart]))
+       {
+               beep(ctx);
+               return false;
+       }
+
+       // Find the separator before the current word
+       do
+               --wstart;
+       while (!IS_WORD_SEPARATOR(ctx->history[wstart]));
+
+       // Complete the word through the hook
+       completed_word = ctx->match(ctx->match_param, ctx->history + wstart + 1, *curpos - wstart);
+       if (!completed_word)
+               return false;
+
+       // Move back the terminal cursor to the separator
+       while (*curpos != wstart)
+       {
+               rl_putc(ctx, '\b');
+               --*curpos;
+       }
+
+       // Insert the completed command
+       insert_chars(ctx, curpos, completed_word, strlen(completed_word));
+       rl_puts(ctx, completed_word);
+       insert_char(ctx, curpos, ' ');
+       rl_putc(ctx, ' ');
+
+       return true;
+}
+
+void rl_refresh(struct RLContext* ctx)
+{
+       rl_puts(ctx, "\r\n");
+       if (ctx->prompt)
+               rl_puts(ctx, ctx->prompt);
+       rl_puts(ctx, ctx->history + ctx->history_pos + 1);
+}
+
+const char* rl_readline(struct RLContext* ctx)
+{
+       size_t i = ctx->history_pos;
+
+       if (ctx->prompt)
+               rl_puts(ctx, ctx->prompt);
+
+       insert_chars(ctx, &i, NULL, 0);
+
+       while (1)
+       {
+               char ch;
+               int c;
+
+               ASSERT(ctx->history - ctx->real_history + i < HISTORY_SIZE);
+
+               if (!rl_getc(ctx, &c))
+                       return NULL;
+
+               // Just ignore special keys for now
+               if (c > SPECIAL_KEYS)
+                       continue;
+
+               if (c == '\t')
+               {
+                       // Ask the match hook if available
+                       if (!ctx->match)
+                               return false;
+
+                       complete_word(ctx, &i);
+                       continue;
+               }
+
+               if (c == '\r' || c == '\n')
+               {
+                       rl_puts(ctx, "\r\n");
+                       break;
+               }
+
+               // Backspace cancels a character, or it is ignored if at
+               //  the start of the line
+               if (c == '\b')
+               {
+                       if (ctx->history[i] != '\0')
+                       {
+                               --i;
+                               rl_puts(ctx, "\b \b");
+                       }
+                       continue;
+               }
+
+               // Add a character to the buffer, if possible
+               ch = (char)c;
+               ASSERT2(ch == c, "a special key was not properly handled");
+               if (insert_chars(ctx, &i, &ch, 1))
+                       rl_putc(ctx, ch);
+               else
+                       beep(ctx);
+       }
+
+       ctx->history_pos = i+1;
+       while (ctx->history[i] != '\0')
+               --i;
+
+       // Do not store empty lines in the history
+       if (i == ctx->history_pos - 1)
+               ctx->history_pos -= 1;
+
+#if DEBUG_DUMP_HISTORY
+       dump_history(ctx);
+#endif
+
+       // Since the current pointer now points to the separator, we need
+       //  to return the first character
+       return &ctx->history[i+1];
+}
+
+
+#if DEBUG_UNIT_TEST
+
+/*! Perform the unit test for the readline library */
+void rl_test(void);
+
+#if HISTORY_SIZE != 32
+       #error This test needs HISTORY_SIZE to be set at 32
+#endif
+
+static struct RLContext test_ctx;
+
+static char* test_getc_ptr;
+static int test_getc(void* data)
+{
+       return *test_getc_ptr++;
+}
+
+/*! Perform a readline test. The function pipes the characters from \a input_buffer
+ *  through the I/O to \c rl_readline(). After the whole string is sent, \c do_test()
+ *  checks if the current history within the context match \a expected_history.
+ */
+static bool do_test(char* input_buffer, char* expected_history)
+{
+       rl_init_ctx(&test_ctx);
+       rl_sethook_get(&test_ctx, test_getc, NULL);
+
+       test_getc_ptr = input_buffer;
+       while (*test_getc_ptr)
+               rl_readline(&test_ctx);
+
+       if (memcmp(test_ctx.real_history, expected_history, HISTORY_SIZE) != 0)
+       {
+               ASSERT2(0, "history compare failed");
+               return false;
+       }
+
+       return true;
+}
+
+void rl_test(void)
+{
+       char* test1_in = "a\nb\nc\nd\ne\nf\ng\nh\ni\nj\nk\nl\nm\nn\no\np\nq\nr\ns\nt\nu\nv\nw\nx\ny\nz\n";
+       char test1_hist[HISTORY_SIZE] = "\0l\0m\0n\0o\0p\0q\0r\0s\0t\0u\0v\0w\0x\0y\0z";
+
+       if (!do_test(test1_in, test1_hist))
+               return;
+
+       kprintf("rl_test successful\n");
+}
+
+#endif /* DEBUG_UNIT_TEST */
+
diff --git a/mware/readline.h b/mware/readline.h
new file mode 100755 (executable)
index 0000000..d875daa
--- /dev/null
@@ -0,0 +1,90 @@
+/*!
+ * \file
+ * <!--
+ * Copyright (C) 2004 Giovanni Bajo
+ * Copyright (C) 2004 Develer S.r.l. (http://www.develer.com/)
+ * All Rights Reserved.
+ * -->
+ *
+ * \brief Line editing support with history
+ *
+ * This file implements a kernel for line editing through a terminal, with history of the typed lines.
+ * Basic feature of this module:
+ *
+ * \li Abstracted from I/O. The user must provide hooks for getc and putc functions.
+ * \li Basic support for ANSI escape sequences for input of special codes.
+ * \li Support for command name completion (through a hook).
+ *
+ * \version $Id$
+ *
+ * \author Giovanni Bajo <rasky@develer.com>
+ */
+
+/*#*
+ *#* $Log$
+ *#* Revision 1.1  2006/06/01 12:27:39  marco
+ *#* Added utilities for protocols
+ *#*
+ *#*/
+
+#ifndef MWARE_READLINE_H
+#define MWARE_READLINE_H
+
+#include <cfg/compiler.h>
+
+#include <string.h>
+
+#define HISTORY_SIZE       1024
+
+typedef int (*getc_hook)(void* user_data);
+typedef void (*putc_hook)(char ch, void* user_data);
+typedef const char* (*match_hook)(void* user_data, const char* word, int word_len);
+
+struct RLContext
+{
+       getc_hook get;
+       void* get_param;
+
+       putc_hook put;
+       void* put_param;
+
+       match_hook match;
+       void* match_param;
+
+       const char* prompt;
+
+       char real_history[HISTORY_SIZE];
+       char* history;
+       size_t history_pos;
+};
+
+INLINE void rl_init_ctx(struct RLContext *ctx)
+{
+       memset(ctx, 0, sizeof(*ctx));
+       ctx->history = ctx->real_history;
+}
+
+INLINE void rl_clear_history(struct RLContext *ctx)
+{
+       memset(ctx->real_history, 0, sizeof(ctx->real_history));
+       ctx->history_pos = 0;
+       ctx->history = ctx->real_history;
+}
+
+INLINE void rl_sethook_get(struct RLContext* ctx, getc_hook get, void* get_param)
+{ ctx->get = get; ctx->get_param = get_param; }
+
+INLINE void rl_sethook_put(struct RLContext* ctx, putc_hook put, void* put_param)
+{ ctx->put = put; ctx->put_param = put_param; }
+
+INLINE void rl_sethook_match(struct RLContext* ctx, match_hook match, void* match_param)
+{ ctx->match = match; ctx->match_param = match_param; }
+
+INLINE void rl_setprompt(struct RLContext* ctx, const char* prompt)
+{ ctx->prompt = prompt; }
+
+const char* rl_readline(struct RLContext* ctx);
+
+void rl_refresh(struct RLContext* ctx);
+
+#endif /* MWARE_READLINE_H */