doc: Move function documentation to .c file.
[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 <io/kfile.h>
59 #include <struct/hashtable.h>
60
61 #include <stdlib.h> // atol(), NULL
62 #include <string.h> // strchr(), strcmp()
63
64 /// Hashtable hook to extract the key from a command
65 static const void* get_key_from_command(const void* cmd, uint8_t* length);
66
67 /// Hashtable that handles the commands that can be executed
68 DECLARE_HASHTABLE_STATIC(commands, CONFIG_MAX_COMMANDS_NUMBER, get_key_from_command);
69
70
71 /**
72  * \brief Tokenize one word at a time from a text.
73  *
74  * This function is similar to strtok, but does not use any implicit
75  * context, nor it does modify the input buffer in any form.
76  * The word is returned as a STL-like [begin,end) range.
77  *
78  * To extract the first word, make both begin and end point at the
79  * start of the text, and call the function. Then, subsequent
80  * calls will return the following words (assuming the begin/end
81  * variable are not modified between calls).
82  *
83  * \param begin Will contain the index of the first character of the word
84  * \param end Will contain the index of the character after the last
85  *     character of the word
86  *
87  * \return True if a word was extracted, false if we got to the end
88  * of the string without extracting any word.
89  */
90 static bool get_word(const char **begin, const char **end)
91 {
92         const char *cur = *end;
93
94         while ((*cur == ' ' || *cur == '\t') && *cur)
95                 ++cur;
96
97         *begin = cur;
98
99         while ((*cur != ' ' && *cur != '\t') && *cur)
100                 ++cur;
101
102         *end = cur;
103
104         return (*end != *begin);
105 }
106
107
108 /**
109  * \brief Command arguments parser.
110  *
111  * Using the format pointed by the argument fmt
112  * parses the input string filling the array argv
113  * with input parameters of the correct type.
114  *
115  * \param fmt   Parameters format string.
116  * \param input Input string.
117  * \param argv  Array filled with parameters.
118  *
119  * \return False in case of errors, otherwise true.
120  */
121 static bool parseArgs(const char *fmt, const char *input, parms argv[])
122 {
123         const char *begin = input, *end = input;
124
125         while (*fmt)
126         {
127                 // Extract the argument
128                 if (!get_word(&begin, &end))
129                         return false;
130
131                 switch (*fmt)
132                 {
133                         case 'd':
134                                 (*argv++).l = atol(begin);
135                                 break;
136
137                         case 's':
138                                 (*argv++).s = begin;
139                                 break;
140
141                         default:
142                                 ASSERT2(0, "Unknown format for argument");
143                                 return false;
144                 }
145
146                 ++fmt;
147         }
148
149         /* check if there are remaining args */
150         if (get_word(&begin, &end))
151                 return false;
152
153         return true;
154 }
155
156 /// Hook provided by the parser for matching of command names (TAB completion) for readline
157 const char* parser_rl_match(UNUSED_ARG(void *,dummy), const char *word, int word_len)
158 {
159         HashIterator cur;
160         HashIterator end = ht_iter_end(&commands);
161         const char *found = NULL;
162
163         for (cur = ht_iter_begin(&commands);
164              !ht_iter_cmp(cur, end);
165              cur = ht_iter_next(cur))
166         {
167                 const struct CmdTemplate* cmdp = (const struct CmdTemplate*)ht_iter_get(cur);
168                 if (strncmp(cmdp->name, word, word_len) == 0)
169                 {
170                         // If there was another matching word, it means that we have a multiple
171                         //  match: then return NULL.
172                         if (found)
173                                 return NULL;
174
175                         found = cmdp->name;
176                 }
177         }
178
179         return found;
180 }
181
182 #if CONFIG_ENABLE_COMPAT_BEHAVIOUR
183 bool parser_get_cmd_id(const char* line, unsigned long* ID)
184 {
185         const char *begin = line, *end = line;
186         char *end2;
187
188         // The first word is the ID
189         if (!get_word(&begin, &end))
190                 return false;
191
192         *ID = strtoul(begin, &end2, 10);
193         if (end2 != end)
194                 return false;
195
196         return true;
197 }
198 #endif
199
200 /**
201  * Find the template for the command contained in the text line.
202  * The template can be used to tokenize the command and interpret
203  * it.
204  *
205  * This function can be used to find out which command is contained
206  * in a given text line without parsing all the parameters and
207  * executing it.
208  *
209  * \param line Text line to be processed (ASCIIZ)
210  *
211  * \return The command template associated with the command contained
212  * in the line, or NULL if the command is invalid.
213  */
214 const struct CmdTemplate* parser_get_cmd_template(const char *input)
215 {
216         const char *begin = input, *end = input;
217
218 #if CONFIG_ENABLE_COMPAT_BEHAVIOUR
219         // Skip the ID, and get the command
220         if (!get_word(&begin, &end))
221                 return NULL;
222 #endif
223         if (!get_word(&begin, &end))
224                 return NULL;
225
226         return (const struct CmdTemplate*)ht_find(&commands, begin, end-begin);
227 }
228
229 static const char *skip_to_params(const char *input, const struct CmdTemplate *cmdp)
230 {
231         const char *begin = input, *end = input;
232
233 #if CONFIG_ENABLE_COMPAT_BEHAVIOUR
234         // Skip the ID, and get the command
235         if (!get_word(&begin, &end))
236                 return NULL;
237 #endif
238         if (!get_word(&begin, &end))
239                 return NULL;
240
241         ASSERT2(strlen(cmdp->name) == (size_t)(end-begin), "Invalid command template specified");
242         ASSERT2(!strncmp(begin, cmdp->name, end-begin), "Invalid command template specified");
243
244         return end;
245 }
246
247 /**
248  * Extract the arguments for the command contained in the text line.
249  *
250  * The first argument will always be the command name, so the actual arguments
251  * will start at index 1.
252  *
253  * \param line Text line to be processed (ASCIIZ)
254  * \param templ Command template for this line
255  * \param args Will contain the extracted parameters
256  *
257  * \return True if everything OK, false in case of parsing error.
258  */
259 bool parser_get_cmd_arguments(const char* input, const struct CmdTemplate* cmdp, parms args[CONFIG_PARSER_MAX_ARGS])
260 {
261         input = skip_to_params(input, cmdp);
262         if (!input)
263                 return false;
264
265         args[0].s = cmdp->name;
266         if (!parseArgs(cmdp->arg_fmt, input, args + 1))
267                 return false;
268
269         return true;
270 }
271
272 static const void* get_key_from_command(const void* cmd, uint8_t* length)
273 {
274         const struct CmdTemplate* c = cmd;
275         *length = strlen(c->name);
276         return c->name;
277 }
278
279 /**
280  * \brief Command input handler.
281  *
282  * Process the input, calling the requested command (if found).
283  *
284  * \param line Text line to be processed (ASCIIZ)
285  *
286  * \return true if everything is OK, false in case of errors
287  */
288 bool parser_process_line(const char* input)
289 {
290         const struct CmdTemplate *cmdp;
291         parms args[CONFIG_PARSER_MAX_ARGS];
292
293         cmdp = parser_get_cmd_template(input);
294         if (!cmdp)
295                 return false;
296
297         if (!parser_get_cmd_arguments(input, cmdp, args))
298                 return false;
299
300         if (!parser_execute_cmd(cmdp, args))
301                 return false;
302
303         return true;
304 }
305
306 /**
307  * Register a new command into the parser
308  *
309  * \param cmd Command template describing the command
310  */
311 void parser_register_cmd(const struct CmdTemplate* cmd)
312 {
313         ht_insert(&commands, cmd);
314 }
315
316 void parser_init(void)
317 {
318         // Initialize the hashtable used to store the command description
319         ht_init(&commands);
320 }