From a5d4c124a53f74592228614fe5eecd3d2aa3ae2d Mon Sep 17 00:00:00 2001 From: marco Date: Thu, 1 Jun 2006 12:27:39 +0000 Subject: [PATCH] Added utilities for protocols git-svn-id: https://src.develer.com/svnoss/bertos/trunk@653 38d2e660-2303-0410-9eaa-f027e97ec537 --- mware/cmd_hunk.h | 152 +++++++++++++++ mware/hashtable.c | 39 +--- mware/hashtable.h | 35 +--- mware/parser.c | 371 ++++++++++++++++++++++++++++++++++++ mware/parser.h | 163 ++++++++++++++++ mware/readline.c | 472 ++++++++++++++++++++++++++++++++++++++++++++++ mware/readline.h | 90 +++++++++ 7 files changed, 1259 insertions(+), 63 deletions(-) create mode 100755 mware/cmd_hunk.h create mode 100755 mware/parser.c create mode 100755 mware/parser.h create mode 100755 mware/readline.c create mode 100755 mware/readline.h diff --git a/mware/cmd_hunk.h b/mware/cmd_hunk.h new file mode 100755 index 00000000..e0416be7 --- /dev/null +++ b/mware/cmd_hunk.h @@ -0,0 +1,152 @@ +/*! + * \file + * + * 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 + * + * \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 + +#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 diff --git a/mware/hashtable.c b/mware/hashtable.c index bfc8bc0e..cd9a7b6e 100755 --- a/mware/hashtable.c +++ b/mware/hashtable.c @@ -61,39 +61,8 @@ /*#* *#* $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; diff --git a/mware/hashtable.h b/mware/hashtable.h index c6da451a..45bea146 100755 --- a/mware/hashtable.h +++ b/mware/hashtable.h @@ -1,7 +1,7 @@ /*! * \file * @@ -31,30 +31,9 @@ /*#* *#* $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_t* mem; //!< 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 index 00000000..0b2c8738 --- /dev/null +++ b/mware/parser.c @@ -0,0 +1,371 @@ +/*! + * \file + * + * + * \version $Id$ + * + * \author Bernardo Innocenti + * \author Stefano Fedrigo + * \author Giovanni Bajo + * + * \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 + +#include // atol(), NULL +#include // strchr(), strcmp() + +#include + +#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 index 00000000..34f6a14d --- /dev/null +++ b/mware/parser.h @@ -0,0 +1,163 @@ +/** + * \file + * Copyright 2003, 2006 Develer S.r.l. (http://www.develer.com/) + * All Rights Reserved. + * + * \version $Id$ + * + * \author Bernardo Innocenti + * \author Stefano Fedrigo + * \author Giovanni Bajo + * + * \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 + +/** 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 index 00000000..86c1dfd8 --- /dev/null +++ b/mware/readline.c @@ -0,0 +1,472 @@ +/*! + * \file + * + * + * \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 + */ + +/*#* + *#* $Log$ + *#* Revision 1.1 2006/06/01 12:27:39 marco + *#* Added utilities for protocols + *#* + *#*/ + +#include "readline.h" + +#include +#include + +#include + + +//! 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 index 00000000..d875daa4 --- /dev/null +++ b/mware/readline.h @@ -0,0 +1,90 @@ +/*! + * \file + * + * + * \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 + */ + +/*#* + *#* $Log$ + *#* Revision 1.1 2006/06/01 12:27:39 marco + *#* Added utilities for protocols + *#* + *#*/ + +#ifndef MWARE_READLINE_H +#define MWARE_READLINE_H + +#include + +#include + +#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 */ -- 2.25.1