Обработка команд ASCII более надежным и безопасным способом - PullRequest
0 голосов
/ 06 февраля 2019

У меня есть модуль, который получает команды ASCII и затем соответствующим образом реагирует на них.Мне интересно, если это возможно, чтобы иметь более надежный и безопасный тип вызова функций-обработчиков.

В прошлом у меня был код, подобный следующему, который также очень похож на этот ответ: Обработка команд ASCII через RS232 во встроенном c

struct Command commands[] = {
{"command1", command1Handler}
{"command2", command2Handler}
 ...
};

//gets called when a new string has been received
void parseCmd(const char *input) {
//find the fitting command entry and call function pointer
}

bool command1Handler(const char *input) { }
bool command2Handler(const char *input) { }

Мне не нравится, что все функции-обработчики должны выполнять собственный анализ.Это кажется излишне повторяющимся и подверженным ошибкам.

Было бы здорово, если бы вместо этого мы могли сделать это следующим образом, где весь анализ выполняется в функции parseCmd:

struct Command commands[] = {
{"command1", command1HandlerSafe}
{"command2", command2HandlerSafe}
 ...
};

void parseCmd(const char *input) {
//1. find fitting command entry
//2. check that parameter number fits the expected number for the target function
//3. parse parameters and validate the types
//4. call function with parameters in their correct types
}

bool command1HandlerSafe(bool param1, const char *param2) { }
bool command2HandlerSafe(int param1) {}

IПодумайте, со старыми varargs в стиле C можно было бы выполнить синтаксический анализ в центральной функции, но это не принесло бы безопасность типов.

Редактировать: Между тем я придумал следующее решение, которое я считал несколько сбалансированнымхакерство и модульность:

class ParameterSet{

struct Param{
  const char *paramString;
  bool isInt();
  int toInt();
  float toFloat();
  ..
}

ParameterSet(const char *input);
Param at(size_t index);
size_t length();
char m_buffer[100];
Param m_params[10];
}

bool command1HandlerMoreSafe(const ParameterSet *paramSet);

Ответы [ 2 ]

0 голосов
/ 08 февраля 2019

Создание слоя абстракции вокруг этого может сделать вещи более сложными и, таким образом, подверженными ошибкам.Я бы не стал этого делать, если количество команд, которые вы должны обрабатывать, невелико, их необходимо поддерживать, и это одна из основных задач вашего приложения.

С предварительными условиями для сохранения типаБезопасный и сохраняющий разбор отдельно от алгоритмов, вы можете создать нечто похожее на следующий C-подобный псевдокод:

typedef enum
{
  INT,
  STR
} type_t;                 // for marking which type that is supported by the command

typedef struct
{
  type_t type;
  const char* text;       // what kind of text that is expected in case of strings
} arg_t;

typedef struct
{
  const char* name;       // the name of the command
  arg_t*      args;       // list of allowed arguments
  size_t      args_n;     // size of that list
  void (*callback)(void); // placeholder for callback functions of different types
} command_t;

Затем вы можете создавать функции обработчика обратного вызова, которые не заботятся о разборе, а только об ихвыделенная задача:

void cmd_branch (const char* str);
void cmd_kill   (int n);

Массив команд может выглядеть примерно так:

const command_t commands[] = 
{
  { // BRANCH [FAST][SLOW]
    .name="BRANCH", 
    .args=(entry_t[]){ {STR,"FAST"}, {STR,"SLOW"} },
    .args_n=2,
    .callback= (void(*)(void)) cmd_branch,
  },

  { // KILL [7][9]
    .name="KILL",
    .args=(entry_t[]){ {INT, NULL} },
    .args_n=1,
    .callback= (void(*)(void)) cmd_kill,
  }
};

Функция синтаксического анализа будет выполнять:

  • Найти какую командукоторый был получен при поиске в указанном выше списке (bsearch, если большой список).
  • Проверьте, какой тип аргументов поддерживает полученная команда
  • Соответствующий анализ аргументов
  • Вызовите соответствующую функцию с помощьюаргументы соответствующего типа

Так как в этом примере просто использовался какой-то указатель на фиктивную функцию типа (void(*)(void)), вам придетсят к правильному типу.Может быть сделано, например, C11 _Generic:

call(commands[i], int_val);

, который расширяется до:

#define call(command, arg) _Generic((arg), \
                                    int:         (void(*)(int))         command.callback, \
                                    const char*: (void(*)(const char*)) command.callback )(arg)
0 голосов
/ 06 февраля 2019

Один из способов сохранить интерфейсы обработки команд одинаковыми - использовать почтенный argv / argc интерфейс, который получает main().Предполагая, что полученные команды имеют некоторое понятие слов (возможно, разделенных пробелами), это может выглядеть следующим образом:

  • Получить строку ввода.
  • Анализировать вводв слова, где первое слово - это имя команды, а остальные слова - ее аргументы.
  • По ходу синтаксического анализа поместите указатель на строку, содержащую каждое слово в массиве, и ведите счетчик числа.элементов в массиве.
  • Используя первое слово, найдите указатель на командную функцию.Вы можете использовать что-то вроде bsearch(), если все команды известны во время компиляции.Возможно, хеш-таблица тоже подойдет.Как бы вы ни реализовали отображение, в результате получается указатель на функцию, которая принимает массив указателей на аргументы и счетчик количества элементов в массиве указателей.
  • Вызывает командную функцию через указатель ипередать массив проанализированных слов и количество, точно так же, как main() вызывается кодом запуска.
  • Оттуда каждая командная функция может иметь дело с тем, что конкретно означают ее аргументы, преобразовывая строковые представления во внутренние формы по мере необходимости..
...