2197d8482391b8616ab23fcb755078440ece50c0
[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 bool get_word(const char **begin, const char **end)
93 {
94         const char *cur = *end;
95         bool open_quote = false;
96
97         while ((*cur == ' ' || *cur == '\t') && *cur)
98                 ++cur;
99
100         if (*cur == '"')
101                 open_quote = true;
102
103         *begin = cur;
104
105         if (open_quote)
106         {
107                 ++cur;
108                 while (*cur != '"' && *cur)
109                         ++cur;
110                 if (*cur != '"')
111                         return false;
112                 ++cur;
113         }
114         else
115                 while ((*cur != ' ' && *cur != '\t') && *cur)
116                         ++cur;
117
118         *end = cur;
119
120         return (*end != *begin);
121 }
122
123
124 /**
125  * \brief Command arguments parser.
126  *
127  * Using the format pointed by the argument fmt
128  * parses the input string filling the array argv
129  * with input parameters of the correct type.
130  *
131  * \param fmt   Parameters format string.
132  * \param input Input string.
133  * \param argv  Array filled with parameters.
134  *
135  * \return False in case of errors, otherwise true.
136  */
137 static bool parseArgs(const char *fmt, const char *input, parms argv[])
138 {
139         const char *begin = input, *end = input;
140
141         while (*fmt)
142         {
143                 // Extract the argument
144                 if (!get_word(&begin, &end))
145                         return false;
146
147                 switch (*fmt)
148                 {
149                         case 'd':
150                                 (*argv++).l = atol(begin);
151                                 break;
152
153                         case 's':
154                                 (*argv).str.p = begin;
155                                 (*argv).str.sz = end - begin;
156                                 /* Remove the quotes from argument */
157                                 if (*begin == '"' && *(end - 1) == '"')
158                                 {
159                                         (*argv).str.p += 1;
160                                         (*argv).str.sz -= 2;
161                                 }
162                                 argv++;
163                                 break;
164
165                         default:
166                                 ASSERT2(0, "Unknown format for argument");
167                                 return false;
168                 }
169
170                 ++fmt;
171         }
172
173         /* check if there are remaining args */
174         if (get_word(&begin, &end))
175                 return false;
176
177         return true;
178 }
179
180 /// Hook provided by the parser for matching of command names (TAB completion) for readline
181 const char* parser_rl_match(UNUSED_ARG(void *,dummy), const char *word, int word_len)
182 {
183         HashIterator cur;
184         HashIterator end = ht_iter_end(&commands);
185         const char *found = NULL;
186
187         for (cur = ht_iter_begin(&commands);
188              !ht_iter_cmp(cur, end);
189              cur = ht_iter_next(cur))
190         {
191                 const struct CmdTemplate* cmdp = (const struct CmdTemplate*)ht_iter_get(cur);
192                 if (strncmp(cmdp->name, word, word_len) == 0)
193                 {
194                         // If there was another matching word, it means that we have a multiple
195                         //  match: then return NULL.
196                         if (found)
197                                 return NULL;
198
199                         found = cmdp->name;
200                 }
201         }
202
203         return found;
204 }
205
206 #if CONFIG_ENABLE_COMPAT_BEHAVIOUR
207 bool parser_get_cmd_id(const char* line, unsigned long* ID)
208 {
209         const char *begin = line, *end = line;
210         char *end2;
211
212         // The first word is the ID
213         if (!get_word(&begin, &end))
214                 return false;
215
216         *ID = strtoul(begin, &end2, 10);
217         if (end2 != end)
218                 return false;
219
220         return true;
221 }
222 #endif
223
224 /**
225  * Find the template for the command contained in the text line.
226  * The template can be used to tokenize the command and interpret
227  * it.
228  *
229  * This function can be used to find out which command is contained
230  * in a given text line without parsing all the parameters and
231  * executing it.
232  *
233  * \param input Text line to be processed (ASCIIZ)
234  *
235  * \return The command template associated with the command contained
236  * in the line, or NULL if the command is invalid.
237  */
238 const struct CmdTemplate* parser_get_cmd_template(const char *input)
239 {
240         const char *begin = input, *end = input;
241
242 #if CONFIG_ENABLE_COMPAT_BEHAVIOUR
243         // Skip the ID, and get the command
244         if (!get_word(&begin, &end))
245                 return NULL;
246 #endif
247         if (!get_word(&begin, &end))
248                 return NULL;
249
250         return (const struct CmdTemplate*)ht_find(&commands, begin, end-begin);
251 }
252
253 static const char *skip_to_params(const char *input, const struct CmdTemplate *cmdp)
254 {
255         const char *begin = input, *end = input;
256
257 #if CONFIG_ENABLE_COMPAT_BEHAVIOUR
258         // Skip the ID, and get the command
259         if (!get_word(&begin, &end))
260                 return NULL;
261 #endif
262         if (!get_word(&begin, &end))
263                 return NULL;
264
265         ASSERT2(strlen(cmdp->name) == (size_t)(end-begin), "Invalid command template specified");
266         ASSERT2(!strncmp(begin, cmdp->name, end-begin), "Invalid command template specified");
267
268         return end;
269 }
270
271 /**
272  * Extract the arguments for the command contained in the text line.
273  *
274  * The first argument will always be the command name, so the actual arguments
275  * will start at index 1.
276  *
277  * \param input Text line to be processed (ASCIIZ)
278  * \param cmdp Command template for this line
279  * \param args Will contain the extracted parameters
280  *
281  * \return True if everything OK, false in case of parsing error.
282  */
283 bool parser_get_cmd_arguments(const char* input, const struct CmdTemplate* cmdp, parms args[CONFIG_PARSER_MAX_ARGS])
284 {
285         input = skip_to_params(input, cmdp);
286         if (!input)
287                 return false;
288
289         args[0].str.p = cmdp->name;
290         if (!parseArgs(cmdp->arg_fmt, input, args + 1))
291                 return false;
292
293         return true;
294 }
295
296 static const void* get_key_from_command(const void* cmd, uint8_t* length)
297 {
298         const struct CmdTemplate* c = cmd;
299         *length = strlen(c->name);
300         return c->name;
301 }
302
303 /**
304  * \brief Command input handler.
305  *
306  * Process the input, calling the requested command (if found).
307  *
308  * \param input Text line to be processed (ASCIIZ)
309  *
310  * \return true if everything is OK, false in case of errors
311  */
312 bool parser_process_line(const char* input)
313 {
314         const struct CmdTemplate *cmdp;
315         parms args[CONFIG_PARSER_MAX_ARGS];
316
317         cmdp = parser_get_cmd_template(input);
318         if (!cmdp)
319                 return false;
320
321         if (!parser_get_cmd_arguments(input, cmdp, args))
322                 return false;
323
324         if (!parser_execute_cmd(cmdp, args))
325                 return false;
326
327         return true;
328 }
329
330 /**
331  * Register a new command into the parser
332  *
333  * \param cmd Command template describing the command
334  * \return true if registration was successful, false otherwise
335  */
336 bool parser_register_cmd(const struct CmdTemplate* cmd)
337 {
338         return ht_insert(&commands, cmd);
339 }
340
341 void parser_init(void)
342 {
343         // Initialize the hashtable used to store the command description
344         ht_init(&commands);
345 }