|
|
  |
Command line parser |
|
|
|
Apr 13 2015, 07:03
|
Профессионал
    
Группа: Участник
Сообщений: 1 778
Регистрация: 29-03-12
Пользователь №: 71 075

|
До сих пор пользовался таким парсером Код void PARSER_ParseUart(char *str) { str = ToLower(str); if(strlen(str) == 0) goto exit; char *command; char *argument1=""; char *argument2=""; char *argument3=""; command = strtok(str," "); argument1 = strtok (NULL, " "); argument2 = strtok (NULL, " "); argument3 = strtok (NULL, " "); if(strcmp(command,"command1")==0) { if(argument1 == NULL) { UART_SendInt(UART0,FunctionReadsValue()); } else { int val1 = atoi(argument1); FunctionWritesValue(val1); } } else if(strcmp(command,"command2")==0) { if(argument1 == NULL) { UART_SendInt(UART0,FunctionReadsValue()); } else if (argument2 == NULL || argument3 == NULL) { FunctionDoSmething(); } else { FunctionDoAnotherThing(); } } //and so on……………… } В принципе я этим способом доволен, работает четко. Но сейчас количество команд перевалило за 100. Функция стала монструозной к тому же жрет много флэша. Поэтому я решил замутить что то вроде этого Код struct command { char *name; //command name char mode; //0-read, 1- read/write int minval; int maxval; char val_changed; void (*fp) (void); //function pointer };
struct command commands[10];
struct command commands[] = { {"imax", 1, 1000, 10000, 0, get_imax}, {"inow", 0, 1000, 10000, 0, get_inow}, {"top", 1, 100, 2500, 0, get_top}, {"pos", 0, 100000, 100000, 0, get_pos}, };
void get_command(char *com) { int I; for (i = 0; i < sizeof(commands); i++) { if(strcmp(commands[i].name, com) == 0) { //do something } } } Вопрос как сделать универсальный указатель на функцию чтоб могла возвращать значение и содержать любое количество аргументов. А может у кого нибудь есть более интересное решение?
|
|
|
|
|
Apr 13 2015, 07:52
|

Местный
  
Группа: Участник
Сообщений: 329
Регистрация: 23-04-14
Пользователь №: 81 502

|
Цитата(Jenya7 @ Apr 13 2015, 08:03)  Вопрос как сделать универсальный указатель на функцию чтоб могла возвращать значение и содержать любое количество аргументов. Если чтоб еще и работало, то никак  Можно сделать аналог функционального объекта (functor) (ну или сам функциональный объект если используем Ц++). Он инкапсулирует указатель на функцию и ее аргументы и в простейшем случае один метод, при вызове которого он вызывает функцию по указателю с данными аргументами. Цитата(Jenya7 @ Apr 13 2015, 08:03)  А может у кого нибудь есть более интересное решение? Тупой перебор всех команд в цикле с strcmp это работает до какого-то предела, потом начинает тормозить. Например, если таких команд больше 1000, то становится весело. "Более интересное решение" это сделать парсер текстовых команд, которые представлены в виде дерева. Но это не сильно тривиально и могут возникнуть проблемы с добавлением\измненением этих команд в коде программы. В качестве промежуточного варианта можно использовать хэширование - посчитать perfect hash команд заранее и построить hash table, в которой будут храниться указатели на функици - обработчики команд. Приходит команда, считаем ее хэш, вызываем функцию из хэш-таблицы, она парсит аргументы и делает другие полезные дела.
|
|
|
|
|
Apr 13 2015, 08:04
|
Профессионал
    
Группа: Участник
Сообщений: 1 778
Регистрация: 29-03-12
Пользователь №: 71 075

|
Цитата(CrimsonPig @ Apr 13 2015, 13:52)  Если чтоб еще и работало, то никак  Можно сделать аналог функционального объекта (functor) (ну или сам функциональный объект если используем Ц++). Он инкапсулирует указатель на функцию и ее аргументы и в простейшем случае один метод, при вызове которого он вызывает функцию по указателю с данными аргументами. Тупой перебор всех команд в цикле с strcmp это работает до какого-то предела, потом начинает тормозить. Например, если таких команд больше 1000, то становится весело. "Более интересное решение" это сделать парсер текстовых команд, которые представлены в виде дерева. Но это не сильно тривиально и могут возникнуть проблемы с добавлением\измненением этих команд в коде программы. В качестве промежуточного варианта можно использовать хэширование - посчитать perfect hash команд заранее и построить hash table, в которой будут храниться указатели на функици - обработчики команд. Приходит команда, считаем ее хэш, вызываем функцию из хэш-таблицы, она парсит аргументы и делает другие полезные дела. Перебор по скорости не так уж плох. Кроме того комманды можно разбить на группы тогда перебор будет еще быстрее. Меня больше беспокоит размер функции - она уже отжирает здоровый кусок флэша а он у меня заканчивается.
|
|
|
|
|
Apr 13 2015, 10:05
|
Гуру
     
Группа: Свой
Сообщений: 2 563
Регистрация: 8-04-05
Из: Nsk
Пользователь №: 3 954

|
передавайте указатели, нулевые - если параметра нет. ну и обратно можно так же, если функуция ничего не возвращает Код typedef struct { const char * name; int* (*func) (int * arg1, int * arg2, int * arg3); } tCmd;
tCmd commands[] = { {"cmd1", cmd1}, {"cmd2", cmd2}, {"cmd3", cmd3}, }
int * cmd1(int * arg1 = 0, int * arg2 = 0, int * arg3 = 0){ static int ret; if (arg2){ return 0; //допустим если есть второй агрумент значит что-то записали и возвращать ничего не будем. } else { ret = GetSomeValue(); return &ret; } } если в массиве commands команды хранить отсортированными по алфавиту, бинарный поиск по нему будет заметно быстрее вместо последовательного перебора с strcmp.
|
|
|
|
|
Apr 13 2015, 11:09
|
Профессионал
    
Группа: Участник
Сообщений: 1 778
Регистрация: 29-03-12
Пользователь №: 71 075

|
Цитата(Сергей Борщ @ Apr 13 2015, 16:01)  Чем не устраивает классический вариант - каждая функция получает первым параметром количество аргументов и вторым - массив указателей на аргументы? Все функции имеют вид error_code_type (*function)(int argc, char *argv[]), поиск по названию в цикле, необходимое количество аргументов можно держать в массиве вместе с имененм и проверять соответствие фактическому количеству аргументов перед вызовом. Тогда и первый параметр функции не нужен. так мне нужно чтобы функция вернула реальное значение Цитата(_pv @ Apr 13 2015, 16:05)  передавайте указатели, нулевые - если параметра нет. ну и обратно можно так же, если функуция ничего не возвращает Код typedef struct { const char * name; int* (*func) (int * arg1, int * arg2, int * arg3); } tCmd;
tCmd commands[] = { {"cmd1", cmd1}, {"cmd2", cmd2}, {"cmd3", cmd3}, }
int * cmd1(int * arg1 = 0, int * arg2 = 0, int * arg3 = 0){ static int ret; if (arg2){ return 0; //допустим если есть второй агрумент значит что-то записали и возвращать ничего не будем. } else { ret = GetSomeValue(); return &ret; } } если в массиве commands команды хранить отсортированными по алфавиту, бинарный поиск по нему будет заметно быстрее вместо последовательного перебора с strcmp. да действительно. как это я не подумал об этом. у меня правда есть функции которые возвращают float и double но я могу держать несколько указателей и передавать NULL на неиспользуемые.
|
|
|
|
|
Apr 13 2015, 11:49
|

Знающий
   
Группа: Участник
Сообщений: 974
Регистрация: 4-04-08
Из: далека
Пользователь №: 36 467

|
Я себе сделал "универсальный command line парсер" Примерно так. Хедер: Код #define PARAM_CHAR 0x01 #define PARAM_STRING 0x02 #define PARAM_UINT8 0x04 #define PARAM_UINT16 0x08 #define PARAM_UINT32 0x10 #define PARAM_ARRAY_CNT 0x40 #define PARAM_ARRAY 0x80
typedef struct sCmd { const char *name; void *params; void *parsed; const char *help; //help string char *stoppedAt; uint8_t num_parsed; }tCmd;
int8_t process_cmd(char *line, tCmd *cmd); И пользую в сурсе: Код #define CMD_PING "ping " #define CMD_ADDR "addr" #define CMD_SEND "send " // panId is used current, first 0x04X dest addr, then arbitrary number of bytes #define CMD_ACK "ack" #define CMD_RESET "reset" #define CMD_ID "id" #define CMD_VERSION "version" #define CMD_HELP "help"
uint8_t paramsPing[] = {PARAM_UINT16, PARAM_UINT16, 0}; uint8_t paramsAddr[] = {PARAM_UINT16, 0}; uint8_t paramsSend[] = {PARAM_UINT16, PARAM_UINT16, PARAM_ARRAY_CNT, PARAM_ARRAY | PARAM_UINT8, 0}; //... tCmd cmds[] = { {CMD_ADDR, paramsAddr, &parsedAddr, "[address]"}, {CMD_SEND, paramsSend, &parsedSend, "panId address byte byte ..."}, {CMD_ACK, NULL, NULL, NULL}, {CMD_RESET, NULL, NULL, NULL}, {CMD_PING, paramsPing, parsedPing, "destinationPanID destinationAddr"}, {CMD_ID, NULL, NULL, NULL}, {CMD_VERSION, NULL, NULL, NULL}, {CMD_HELP, NULL, NULL, NULL}, {NULL, NULL, NULL, NULL} };
void mailn_loop() { int indx = = process_cmd((char*) read_buffer, &cmds[0]); switch (index) { case -1: { printf( "Unknown cmd\n>"); } break; case 0: // empty string break; case 1: // 1- based index.. if (cmds[index - 1].num_parsed) { cmd_set_addr(parsedAddr); } else { cmd_get_addr(); } cmds[index-1].num_parsed = 0; //...... } } Код фактически не растет с добавлением комманд, только структуры добавляются. И не болит голова у дятла с разборкой параметров. Указываешь тип параметра, если сможет, распарсит. Цитата(Сергей Борщ @ Apr 13 2015, 06:01)  Чем не устраивает классический вариант - каждая функция получает первым параметром количество аргументов и вторым - массив указателей на аргументы? Все функции имеют вид error_code_type (*function)(int argc, char *argv[]), поиск по названию в цикле, необходимое количество аргументов можно держать в массиве вместе с имененм и проверять соответствие фактическому количеству аргументов перед вызовом. Тогда и первый параметр функции не нужен. Не всегда комманд лайн приходит в классической форме. Частенько это стрим характеров, которые надо просто читать с сериал порта или сокета. И с количеством параметров таким образом будут проблемы.
--------------------
Верить нельзя никому, даже себе. Мне - можно.
|
|
|
|
|
Apr 25 2015, 12:21
|
Местный
  
Группа: Участник
Сообщений: 421
Регистрация: 2-01-08
Пользователь №: 33 778

|
Мой вариант sh.c, с дополнением по именам команд и историей. Ни скорость ни размер кода не оптимизировал, если только по размеру занимаемой ram ужал немного. Параметры парсятся каждой функцией самостоятельно, слишком уж тяжело сделать универсальное решение с полной обработкой ошибок. Если же есть много похожих команд, например, для изменения значений переменных, то очевидно выгоднее парсить все в одном месте и держать список адресов этих переменных. Есть еще microrl.
|
|
|
|
|
Apr 26 2015, 10:03
|
Профессионал
    
Группа: Участник
Сообщений: 1 778
Регистрация: 29-03-12
Пользователь №: 71 075

|
Цитата(amaora @ Apr 25 2015, 18:21)  Мой вариант sh.c, с дополнением по именам команд и историей. Ни скорость ни размер кода не оптимизировал, если только по размеру занимаемой ram ужал немного. Параметры парсятся каждой функцией самостоятельно, слишком уж тяжело сделать универсальное решение с полной обработкой ошибок. Если же есть много похожих команд, например, для изменения значений переменных, то очевидно выгоднее парсить все в одном месте и держать список адресов этих переменных. Есть еще microrl. спасибо. попробую.
|
|
|
|
|
  |
1 чел. читают эту тему (гостей: 1, скрытых пользователей: 0)
Пользователей: 0
|
|
|