Помощь - Поиск - Пользователи - Календарь
Полная версия этой страницы: Command line parser
Форум разработчиков электроники ELECTRONIX.ru > Сайт и форум > В помощь начинающему > Программирование
Jenya7
До сих пор пользовался таким парсером
Код
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
         }
     }
}

Вопрос как сделать универсальный указатель на функцию чтоб могла возвращать значение и содержать любое количество аргументов.
А может у кого нибудь есть более интересное решение?
CrimsonPig
Цитата(Jenya7 @ Apr 13 2015, 08:03) *
Вопрос как сделать универсальный указатель на функцию чтоб могла возвращать значение и содержать любое количество аргументов.


Если чтоб еще и работало, то никак sm.gif
Можно сделать аналог функционального объекта (functor) (ну или сам функциональный объект если используем Ц++). Он инкапсулирует указатель на функцию и ее аргументы и в простейшем случае один метод, при вызове которого он вызывает функцию по указателю с данными аргументами.

Цитата(Jenya7 @ Apr 13 2015, 08:03) *
А может у кого нибудь есть более интересное решение?


Тупой перебор всех команд в цикле с strcmp это работает до какого-то предела, потом начинает тормозить.
Например, если таких команд больше 1000, то становится весело.
"Более интересное решение" это сделать парсер текстовых команд, которые представлены в виде дерева. Но это не сильно тривиально и могут возникнуть проблемы с добавлением\измненением этих команд в коде программы.

В качестве промежуточного варианта можно использовать хэширование - посчитать perfect hash команд заранее и построить hash table, в которой будут храниться указатели на функици - обработчики команд. Приходит команда, считаем ее хэш, вызываем функцию из хэш-таблицы, она парсит аргументы и делает другие полезные дела.





Jenya7
Цитата(CrimsonPig @ Apr 13 2015, 13:52) *
Если чтоб еще и работало, то никак sm.gif
Можно сделать аналог функционального объекта (functor) (ну или сам функциональный объект если используем Ц++). Он инкапсулирует указатель на функцию и ее аргументы и в простейшем случае один метод, при вызове которого он вызывает функцию по указателю с данными аргументами.



Тупой перебор всех команд в цикле с strcmp это работает до какого-то предела, потом начинает тормозить.
Например, если таких команд больше 1000, то становится весело.
"Более интересное решение" это сделать парсер текстовых команд, которые представлены в виде дерева. Но это не сильно тривиально и могут возникнуть проблемы с добавлением\измненением этих команд в коде программы.

В качестве промежуточного варианта можно использовать хэширование - посчитать perfect hash команд заранее и построить hash table, в которой будут храниться указатели на функици - обработчики команд. Приходит команда, считаем ее хэш, вызываем функцию из хэш-таблицы, она парсит аргументы и делает другие полезные дела.

Перебор по скорости не так уж плох. Кроме того комманды можно разбить на группы тогда перебор будет еще быстрее. Меня больше беспокоит размер функции - она уже отжирает здоровый кусок флэша а он у меня заканчивается.
CrimsonPig
Цитата(Jenya7 @ Apr 13 2015, 09:04) *
Перебор по скорости не так уж плох. Кроме того комманды можно разбить на группы тогда перебор будет еще быстрее. Меня больше беспокоит размер функции - она уже отжирает здоровый кусок флэша а он у меня заканчивается.


Тогда надо разобраться, что именно отжирает память. Если у вас 100 команд и у каждой разное число параметров и обрабатывать их надо индивидуально, тот тут ничего не сделаешь
Сергей Борщ
Чем не устраивает классический вариант - каждая функция получает первым параметром количество аргументов и вторым - массив указателей на аргументы?
Все функции имеют вид error_code_type (*function)(int argc, char *argv[]), поиск по названию в цикле, необходимое количество аргументов можно держать в массиве вместе с имененм и проверять соответствие фактическому количеству аргументов перед вызовом. Тогда и первый параметр функции не нужен.
_pv
передавайте указатели, нулевые - если параметра нет. ну и обратно можно так же, если функуция ничего не возвращает
Код
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.
Jenya7
Цитата(Сергей Борщ @ 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 на неиспользуемые.
A. Fig Lee
Я себе сделал "универсальный 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[]), поиск по названию в цикле, необходимое количество аргументов можно держать в массиве вместе с имененм и проверять соответствие фактическому количеству аргументов перед вызовом. Тогда и первый параметр функции не нужен.


Не всегда комманд лайн приходит в классической форме.
Частенько это стрим характеров, которые надо просто читать с сериал порта или сокета.
И с количеством параметров таким образом будут проблемы.
Jenya7
попробую соединить разные подходы вместе и сделать гибрид балансируя размер-скорость. спасибо за идеи.
k155la3
Цитата(Jenya7 @ Apr 14 2015, 08:15) *
попробую соединить разные подходы вместе и сделать гибрид балансируя размер-скорость. спасибо за идеи.


Вот еще в цикле обработки сообщений винды механизм используется sm.gif
SendMessage(WM_ICONERASEBKGND, (WPARAM) dc.GetSafeHdc(), 0);
а именно второй аргумент.
amaora
Мой вариант sh.c, с дополнением по именам команд и историей. Ни скорость ни размер кода не оптимизировал, если только по размеру занимаемой ram ужал немного. Параметры парсятся каждой функцией самостоятельно, слишком уж тяжело сделать универсальное решение с полной обработкой ошибок. Если же есть много похожих команд, например, для изменения значений переменных, то очевидно выгоднее парсить все в одном месте и держать список адресов этих переменных.

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

Есть еще microrl.

спасибо. попробую.
Для просмотра полной версии этой страницы, пожалуйста, пройдите по ссылке.
Invision Power Board © 2001-2025 Invision Power Services, Inc.