Change parser strings to include length.
[bertos.git] / bertos / mware / parser.c
1 /**
2  * \file
3  * <!--
4  * This file is part of BeRTOS.
5  *
6  * Bertos is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
19  *
20  * As a special exception, you may use this file as part of a free software
21  * library without restriction.  Specifically, if other files instantiate
22  * templates or use macros or inline functions from this file, or you compile
23  * this file and link it with other files to produce an executable, this
24  * file does not by itself cause the resulting executable to be covered by
25  * the GNU General Public License.  This exception does not however
26  * invalidate any other reasons why the executable file might be covered by
27  * the GNU General Public License.
28  *
29  * Copyright 2003, 2006 Develer S.r.l. (http://www.develer.com/)
30  * All Rights Reserved.
31  * -->
32  *
33  * \brief Channel protocol parser and commands.
34  *
35  * This file contains the channel protocol parser and
36  * the definition of the protocol commands. Commands are defined
37  * in a "CmdTemplate" type array, containing:
38  * - the name of the command,
39  * - the arguments it expects to receive,
40  * - the output values,
41  * - the name of the function implementing the command.
42  *
43  * The arguments and results are passed to command function
44  * using an union: the element of the union to use for each
45  * argument is determined by format strings present in the
46  * CmdTemplate table.
47  *
48  * \author Bernie Innocenti <bernie@codewiz.org>
49  * \author Stefano Fedrigo <aleph@develer.com>
50  * \author Giovanni Bajo <rasky@develer.com>
51  *
52  *
53  */
54
55
56 #include "parser.h"
57
58 #include "cfg/cfg_parser.h"
59
60 #include <io/kfile.h>
61 #include <struct/hashtable.h>
62
63 #include <stdlib.h> // atol(), NULL
64 #include <string.h> // strchr(), strcmp()
65
66 /// Hashtable hook to extract the key from a command
67 static const void* get_key_from_command(const void* cmd, uint8_t* length);
68
69 /// Hashtable that handles the commands that can be executed
70 DECLARE_HASHTABLE_STATIC(commands, CONFIG_MAX_COMMANDS_NUMBER, get_key_from_command);
71
72
73 /**
74  * \brief Tokenize one word at a time from a text.
75  *
76  * This function is similar to strtok, but does not use any implicit
77  * context, nor it does modify the input buffer in any form.
78  * The word is returned as a STL-like [begin,end) range.
79  *
80  * To extract the first word, make both begin and end point at the
81  * start of the text, and call the function. Then, subsequent
82  * calls will return the following words (assuming the begin/end
83  * variable are not modified between calls).
84  *
85  * \param begin Will contain the index of the first character of the word
86  * \param end Will contain the index of the character after the last
87  *     character of the word
88  *
89  * \return True if a word was extracted, false if we got to the end
90  * of the string without extracting any word.
91  */
92 static bool get_word(const char **begin, const char **end)
93 {
94         const char *cur = *end;
95
96         while ((*cur == ' ' || *cur == '\t') && *cur)
97                 ++cur;
98
99         *begin = cur;
100
101         while ((*cur != ' ' && *cur != '\t') && *cur)
102                 ++cur;
103
104         *end = cur;
105
106         return (*end != *begin);
107 }
108
109
110 /**
111  * \brief Command arguments parser.
112  *
113  * Using the format pointed by the argument fmt
114  * parses the input string filling the array argv
115  * with input parameters of the correct type.
116  *
117  * \param fmt   Parameters format string.
118  * \param input Input string.
119  * \param argv  Array filled with parameters.
120  *
121  * \return False in case of errors, otherwise true.
122  */
123 static bool parseArgs(const char *fmt, const char *input, parms argv[])
124 {
125         const char *begin = input, *end = input;
126
127         while (*fmt)
128         {
129                 // Extract the argument
130                 if (!get_word(&begin, &end))
131                         return false;
132
133                 switch (*fmt)
134                 {
135                         case 'd':
136                                 (*argv++).l = atol(begin);
137                                 break;
138
139                         case 's':
140                                 (*argv).str.p = begin;
141                                 (*argv).str.sz = end - begin;
142                                 argv++;
143                                 break;
144
145                         default:
146                                 ASSERT2(0, "Unknown format for argument");
147                                 return false;
148                 }
149
150                 ++fmt;
151         }
152
153         /* check if there are remaining args */
154         if (get_word(&begin, &end))
155                 return false;
156
157         return true;
158 }
159
160 /// Hook provided by the parser for matching of command names (TAB completion) for readline
161 const char* parser_rl_match(UNUSED_ARG(void *,dummy), const char *word, int word_len)
162 {
163         HashIterator cur;
164         HashIterator end = ht_iter_end(&commands);
165         const char *found = NULL;
166
167         for (cur = ht_iter_begin(&commands);
168              !ht_iter_cmp(cur, end);
169              cur = ht_iter_next(cur))
170         {
171                 const struct CmdTemplate* cmdp = (const struct CmdTemplate*)ht_iter_get(cur);
172                 if (strncmp(cmdp->name, word, word_len) == 0)
173                 {
174                         // If there was another matching word, it means that we have a multiple
175                         //  match: then return NULL.
176                         if (found)
177                                 return NULL;
178
179                         found = cmdp->name;
180                 }
181         }
182
183         return found;
184 }
185
186 #if CONFIG_ENABLE_COMPAT_BEHAVIOUR
187 bool parser_get_cmd_id(const char* line, unsigned long* ID)
188 {
189         const char *begin = line, *end = line;
190         char *end2;
191
192         // The first word is the ID
193         if (!get_word(&begin, &end))
194                 return false;
195
196         *ID = strtoul(begin, &end2, 10);
197         if (end2 != end)
198                 return false;
199
200         return true;
201 }
202 #endif
203
204 /**
205  * Find the template for the command contained in the text line.
206  * The template can be used to tokenize the command and interpret
207  * it.
208  *
209  * This function can be used to find out which command is contained
210  * in a given text line without parsing all the parameters and
211  * executing it.
212  *
213  * \param input Text line to be processed (ASCIIZ)
214  *
215  * \return The command template associated with the command contained
216  * in the line, or NULL if the command is invalid.
217  */
218 const struct CmdTemplate* parser_get_cmd_template(const char *input)
219 {
220         const char *begin = input, *end = input;
221
222 #if CONFIG_ENABLE_COMPAT_BEHAVIOUR
223         // Skip the ID, and get the command
224         if (!get_word(&begin, &end))
225                 return NULL;
226 #endif
227         if (!get_word(&begin, &end))
228                 return NULL;
229
230         return (const struct CmdTemplate*)ht_find(&commands, begin, end-begin);
231 }
232
233 static const char *skip_to_params(const char *input, const struct CmdTemplate *cmdp)
234 {
235         const char *begin = input, *end = input;
236
237 #if CONFIG_ENABLE_COMPAT_BEHAVIOUR
238         // Skip the ID, and get the command
239         if (!get_word(&begin, &end))
240                 return NULL;
241 #endif
242         if (!get_word(&begin, &end))
243                 return NULL;
244
245         ASSERT2(strlen(cmdp->name) == (size_t)(end-begin), "Invalid command template specified");
246         ASSERT2(!strncmp(begin, cmdp->name, end-begin), "Invalid command template specified");
247
248         return end;
249 }
250
251 /**
252  * Extract the arguments for the command contained in the text line.
253  *
254  * The first argument will always be the command name, so the actual arguments
255  * will start at index 1.
256  *
257  * \param input Text line to be processed (ASCIIZ)
258  * \param cmdp Command template for this line
259  * \param args Will contain the extracted parameters
260  *
261  * \return True if everything OK, false in case of parsing error.
262  */
263 bool parser_get_cmd_arguments(const char* input, const struct CmdTemplate* cmdp, parms args[CONFIG_PARSER_MAX_ARGS])
264 {
265         input = skip_to_params(input, cmdp);
266         if (!input)
267                 return false;
268
269         args[0].str.p = cmdp->name;
270         if (!parseArgs(cmdp->arg_fmt, input, args + 1))
271                 return false;
272
273         return true;
274 }
275
276 static const void* get_key_from_command(const void* cmd, uint8_t* length)
277 {
278         const struct CmdTemplate* c = cmd;
279         *length = strlen(c->name);
280         return c->name;
281 }
282
283 /**
284  * \brief Command input handler.
285  *
286  * Process the input, calling the requested command (if found).
287  *
288  * \param input Text line to be processed (ASCIIZ)
289  *
290  * \return true if everything is OK, false in case of errors
291  */
292 bool parser_process_line(const char* input)
293 {
294         const struct CmdTemplate *cmdp;
295         parms args[CONFIG_PARSER_MAX_ARGS];
296
297         cmdp = parser_get_cmd_template(input);
298         if (!cmdp)
299                 return false;
300
301         if (!parser_get_cmd_arguments(input, cmdp, args))
302                 return false;
303
304         if (!parser_execute_cmd(cmdp, args))
305                 return false;
306
307         return true;
308 }
309
310 /**
311  * Register a new command into the parser
312  *
313  * \param cmd Command template describing the command
314  * \return true if registration was successful, false otherwise
315  */
316 bool parser_register_cmd(const struct CmdTemplate* cmd)
317 {
318         return ht_insert(&commands, cmd);
319 }
320
321 void parser_init(void)
322 {
323         // Initialize the hashtable used to store the command description
324         ht_init(&commands);
325 }