untested arg parser for all but string

master
Ondřej Hruška 9 years ago
parent 77f14e1273
commit ea1b47e72f
  1. 2
      main.c
  2. 347
      scpi_parser.c

@ -61,7 +61,7 @@ void cmd_FREQ_cb(const SCPI_argval_t *args)
int main()
{
//const char *inp = "*IDN?\n";
const char *inp = "FREQ 123\n";
const char *inp = "FREQ 50\n";
for(int i=0;i<strlen(inp); i++) {
scpi_handle_byte(inp[i]);

@ -15,10 +15,11 @@ typedef enum {
PARS_ARG_BLOB_PREAMBLE,
// command with no args terminated by whitespace, discard whitespace until newline and then run callback.
// error on non-whitespace
PARS_DISCARD_WHITESPACE_ENDL,
PARS_TRAILING_WHITE,
PARS_DISCARD_LINE, // used after detecting error
} parser_state_t;
#define MAX_CHARBUF_LEN 256
/** parser internal state struct */
static struct {
@ -28,9 +29,11 @@ static struct {
parser_state_t state; // current parser internal state
// string buffer, chars collected here until recognized
char charbuf[256];
char charbuf[MAX_CHARBUF_LEN];
uint16_t charbuf_i;
int8_t blob_preamble_cnt; // preamble counter, if 0, was just #, must read count
// recognized complete command level strings (FUNCtion) - exact copy from command struct
char cur_levels[MAX_LEVEL_COUNT][MAX_CMD_LEN];
uint8_t cur_level_i; // next free level slot index
@ -41,7 +44,7 @@ static struct {
SCPI_argval_t args[MAX_PARAM_COUNT];
uint8_t arg_i; // next free argument slot index
} pstate = {
} pst = {
// defaults
.err_queue_i = 0,
.state = PARS_COMMAND,
@ -58,7 +61,15 @@ static void pars_cmd_space(void); // space ending a command
static void pars_cmd_newline(void); // LF
static void pars_cmd_semicolon(void); // semicolon right after a command
static bool pars_match_cmd(bool partial);
static void pars_arg_char(char c);
static void pars_arg_comma(void);
static void pars_arg_newline(void);
static bool try_match_cmd(const SCPI_command_t *cmd, bool partial);
static void charbuf_terminate(void);
static void charbuf_append(char c);
static void pars_run_callback(void);
static void arg_convert_value(void);
// char matching
#define INRANGE(c, a, b) ((c) >= (a) && (c) <= (b))
@ -70,38 +81,69 @@ static bool pars_match_cmd(bool partial);
#define IS_IDENT_CHAR(c) (IS_LCASE_CHAR((c)) || IS_UCASE_CHAR((c)) || IS_NUMBER_CHAR((c)) || (c) == '_' || (c) == '*' || (c) == '?')
#define IS_INT_CHAR(c) IS_NUMBER_CHAR((c))
#define IS_FLOAT_CHAR(c) (IS_NUMBER_CHAR((c)) || (c) == '.' || (c) == 'e' || (c) == 'E' || (e) == '+' || (e) == '-')
#define IS_FLOAT_CHAR(c) (IS_NUMBER_CHAR((c)) || (c) == '.' || (c) == 'e' || (c) == 'E' || (c) == '+' || (c) == '-')
#define CHAR_TO_LOWER(ucase) ((ucase) + 32)
#define CHAR_TO_UPPER(lcase) ((lcase) - 32)
/** Reset parser state. */
static void pars_reset_cmd(void)
{
pstate.state = PARS_COMMAND;
pstate.charbuf_i = 0;
pstate.cur_level_i = 0;
pstate.cmdbuf_kept = false;
pstate.matched_cmd = NULL;
pstate.arg_i = 0;
pst.state = PARS_COMMAND;
pst.charbuf_i = 0;
pst.cur_level_i = 0;
pst.cmdbuf_kept = false;
pst.matched_cmd = NULL;
pst.arg_i = 0;
}
/** Reset parser state, keep level (semicolon) */
static void pars_reset_cmd_keeplevel(void)
{
pstate.state = PARS_COMMAND;
pstate.charbuf_i = 0;
pst.state = PARS_COMMAND;
pst.charbuf_i = 0;
// rewind to last colon
if (pstate.cur_level_i > 0) {
pstate.cur_level_i--; // keep prev levels
if (pst.cur_level_i > 0) {
pst.cur_level_i--; // keep prev levels
}
pstate.cmdbuf_kept = true;
pst.cmdbuf_kept = true;
pst.matched_cmd = NULL;
pst.arg_i = 0;
}
/** Add a byte to charbuf, error on overflow */
static void charbuf_append(char c)
{
if (pst.charbuf_i >= MAX_CHARBUF_LEN) {
printf("ERROR string buffer overflow.\n");//TODO error
pst.state = PARS_DISCARD_LINE;
}
pstate.matched_cmd = NULL;
pstate.arg_i = 0;
pst.charbuf[pst.charbuf_i++] = c;
}
/** Terminate charbuf and rewind the pointer to start */
static void charbuf_terminate(void)
{
pst.charbuf[pst.charbuf_i] = '\0';
pst.charbuf_i = 0;
}
/** Run the matched command's callback with the arguments */
static void pars_run_callback(void)
{
if (pst.matched_cmd != NULL) {
pst.matched_cmd->callback(pst.args); // run
}
}
@ -110,26 +152,18 @@ void scpi_handle_byte(const uint8_t b)
// TODO handle blob here
const char c = (char) b;
switch (pstate.state) {
switch (pst.state) {
case PARS_COMMAND:
// Collecting command
if (pstate.charbuf_i == 0 && pstate.cur_level_i == 0) {
if (IS_WHITESPACE(c)) {
// leading whitespace is ignored
break;
}
}
if (IS_IDENT_CHAR(c)) {
// valid command char
if (pstate.charbuf_i < MAX_CMD_LEN) {
pstate.charbuf[pstate.charbuf_i++] = c;
if (pst.charbuf_i < MAX_CMD_LEN) {
charbuf_append(c);
} else {
printf("ERROR command part too long.\n");//TODO error
pstate.state = PARS_DISCARD_LINE;
pst.state = PARS_DISCARD_LINE;
}
} else {
@ -155,7 +189,7 @@ void scpi_handle_byte(const uint8_t b)
default:
printf("ERROR unexpected char '%c' in command.\n", c);//TODO error
pstate.state = PARS_DISCARD_LINE;
pst.state = PARS_DISCARD_LINE;
}
}
break;
@ -167,41 +201,225 @@ void scpi_handle_byte(const uint8_t b)
}
break;
// TODO
case PARS_TRAILING_WHITE:
if (IS_WHITESPACE(c)) break;
if (c == '\n') {
pars_run_callback();
pars_reset_cmd();
} else {
printf("ERROR unexpected char '%c' in trailing whitespace.\n", c);//TODO error
pst.state = PARS_DISCARD_LINE;
}
break; // whitespace discarded
case PARS_ARG:
if (IS_WHITESPACE(c)) break;
switch (c) {
case ',':
pars_arg_comma();
break;
case '\n':
pars_arg_newline();
break;
default:
pars_arg_char(c);
}
break;
case PARS_ARG_STR_APOS:
if (c == '\'') {
// end of string
pst.state = PARS_ARG; // next will be newline or comma (or ignored spaces)
} else {
charbuf_append(c);
}
break;
case PARS_ARG_STR_QUOT:
if (c == '"') {
// end of string
pst.state = PARS_ARG; // next will be newline or comma (or ignored spaces)
} else {
charbuf_append(c);
}
break;
// TODO more states
}
}
/** Non-whitespace and non-comma char received in arg. */
static void pars_arg_char(char c)
{
switch (pst.matched_cmd->params[pst.arg_i]) {
case SCPI_DT_STRING:
if (c == '\'') {
pst.state = PARS_ARG_STR_APOS;
} else if (c == '"') {
pst.state = PARS_ARG_STR_QUOT;
} else {
printf("ERROR unexpected char '%c', should be ' or \"\n", c);//TODO error
pst.state = PARS_DISCARD_LINE;
}
break;
case SCPI_DT_BLOB:
if (c == '#') {
pst.state = PARS_ARG_BLOB_PREAMBLE;
pst.blob_preamble_cnt = 0;
} else {
printf("ERROR unexpected char '%c', binary block should start with #\n", c);//TODO error
pst.state = PARS_DISCARD_LINE;
}
break;
case SCPI_DT_FLOAT:
if (!IS_FLOAT_CHAR(c)) {
printf("ERROR unexpected char '%c' in float.\n", c);//TODO error
pst.state = PARS_DISCARD_LINE;
} else {
charbuf_append(c);
}
break;
case SCPI_DT_INT:
if (!IS_FLOAT_CHAR(c)) {
printf("ERROR unexpected char '%d' in int.\n", c);//TODO error
pst.state = PARS_DISCARD_LINE;
} else {
charbuf_append(c);
}
break;
default:
charbuf_append(c);
break;
}
}
/** Received a comma while collecting an arg */
static void pars_arg_comma(void)
{
if (pst.arg_i == pst.matched_cmd->param_cnt - 1) {
// it was the last argument
// comma illegal
printf("ERROR unexpected comma after the last argument\n");//TODO error
pst.state = PARS_DISCARD_LINE;
return;
}
// Convert to the right type
arg_convert_value();
}
static void pars_arg_newline(void)
{
if (pst.arg_i < pst.matched_cmd->param_cnt - 1) {
// not the last arg yet - fail
printf("ERROR not enough arguments!\n");//TODO error
pst.state = PARS_DISCARD_LINE;
return;
}
arg_convert_value();
pars_run_callback();
pars_reset_cmd(); // start a new command
}
/** Convert BOOL, FLOAT or INT char to arg type and advance to next */
static void arg_convert_value(void)
{
charbuf_terminate();
SCPI_argval_t *dest = &pst.args[pst.arg_i];
int j;
switch (pst.matched_cmd->params[pst.arg_i]) {
case SCPI_DT_BOOL:
if (strcasecmp(pst.charbuf, "1") == 0) {
dest->BOOL = 1;
} else if (strcasecmp(pst.charbuf, "0") == 0) {
dest->BOOL = 0;
} else if (strcasecmp(pst.charbuf, "ON") == 0) {
dest->BOOL = 1;
} else if (strcasecmp(pst.charbuf, "OFF") == 0) {
dest->BOOL = 0;
} else {
printf("ERROR argument mismatch for type BOOL\n");//TODO error
pst.state = PARS_DISCARD_LINE;
}
break;
case SCPI_DT_FLOAT:
j = sscanf(pst.charbuf, "%f", &dest->FLOAT);
if (j == 0) {
printf("ERROR failed to convert %s to FLOAT\n", pst.charbuf);//TODO error
pst.state = PARS_DISCARD_LINE;
}
break;
case SCPI_DT_INT:
j = sscanf(pst.charbuf, "%d", &dest->INT);
if (j == 0) {
printf("ERROR failed to convert %s to INT\n", pst.charbuf);//TODO error
pst.state = PARS_DISCARD_LINE;
}
break;
// TODO string
default:
// impossible
printf("ERROR unexpected data type\n");//TODO error
pst.state = PARS_DISCARD_LINE;
}
// proceed to next argument
pst.arg_i++;
}
/** Colon received when collecting command parts */
static void pars_cmd_colon(void)
{
if (pstate.charbuf_i == 0) {
if (pst.charbuf_i == 0) {
// No command text before colon
if (pstate.cur_level_i == 0 || pstate.cmdbuf_kept) {
if (pst.cur_level_i == 0 || pst.cmdbuf_kept) {
// top level command starts with colon (or after semicolon - reset level)
pars_reset_cmd();
} else {
// colon after nothing - error
printf("ERROR unexpected colon in command.\n");//TODO error
pstate.state = PARS_DISCARD_LINE;
pst.state = PARS_DISCARD_LINE;
}
} else {
// internal colon - partial match
if (pars_match_cmd(true)) {
printf("OK partial cmd, last segment = %s\n", pstate.cur_levels[pstate.cur_level_i - 1]);
printf("OK partial cmd, last segment = %s\n", pst.cur_levels[pst.cur_level_i - 1]);
} else {
printf("ERROR no such command (colon).\n");//TODO error
pstate.state = PARS_DISCARD_LINE;
pst.state = PARS_DISCARD_LINE;
}
}
}
/** Newline received when collecting command - end command and execute. */
static void pars_cmd_newline(void)
{
if (pstate.cur_level_i == 0 && pstate.charbuf_i == 0) {
if (pst.cur_level_i == 0 && pst.charbuf_i == 0) {
// nothing before newline
pars_reset_cmd();
return;
@ -209,20 +427,41 @@ static void pars_cmd_newline(void)
// complete match
if (pars_match_cmd(false)) {
if (pstate.matched_cmd->param_cnt == 0) {
if (pst.matched_cmd->param_cnt == 0) {
// no param command - OK
pstate.matched_cmd->callback(pstate.args); // args are empty
pst.matched_cmd->callback(pst.args); // args are empty
} else {
printf("ERROR command missing arguments.\n");//TODO error
pars_reset_cmd();
}
} else {
printf("ERROR no such command (newline) %s.\n", pstate.charbuf);//TODO error
pstate.state = PARS_DISCARD_LINE;
printf("ERROR no such command (newline) %s.\n", pst.charbuf);//TODO error
pst.state = PARS_DISCARD_LINE;
}
}
/** Whitespace received when collecting command parts */
static void pars_cmd_space(void)
{
if (pst.cur_level_i == 0 && pst.charbuf_i == 0) {
// leading whitespace, ignore
return;
}
if (pars_match_cmd(false)) {
if (pst.matched_cmd->param_cnt == 0) {
// no commands
pst.state = PARS_TRAILING_WHITE;
} else {
pst.state = PARS_ARG;
}
} else {
printf("ERROR no such command (space) %s.\n", pst.charbuf);//TODO error
pst.state = PARS_DISCARD_LINE;
}
}
/** Check if chars equal, ignore case */
static bool char_equals_ci(char a, char b)
@ -285,18 +524,14 @@ static bool level_str_matches(const char *test, const char *pattern)
}
// proto
static bool try_match_cmd(const SCPI_command_t *cmd, bool partial);
static bool pars_match_cmd(bool partial)
{
// terminate segment
pstate.charbuf[pstate.charbuf_i] = '\0';
pstate.charbuf_i = 0; // rewind
charbuf_terminate(); // zero-end and rewind index
char *dest = pstate.cur_levels[pstate.cur_level_i++];
strcpy(dest, pstate.charbuf); // copy to level table
// copy to level table
char *dest = pst.cur_levels[pst.cur_level_i++];
strcpy(dest, pst.charbuf);
for (uint16_t i = 0; i < scpi_cmd_lang_len; i++) {
@ -313,7 +548,7 @@ static bool pars_match_cmd(bool partial)
return true;
} else {
// exact match found
pstate.matched_cmd = cmd;
pst.matched_cmd = cmd;
return true;
}
}
@ -326,22 +561,22 @@ static bool pars_match_cmd(bool partial)
/** Try to match current state to a given command */
static bool try_match_cmd(const SCPI_command_t *cmd, bool partial)
{
if (pstate.cur_level_i > cmd->level_cnt) return false; // command too short
if (pstate.cur_level_i == 0) return false; // nothing to match
if (pst.cur_level_i > cmd->level_cnt) return false; // command too short
if (pst.cur_level_i == 0) return false; // nothing to match
if (partial) {
if (pstate.cur_level_i == cmd->level_cnt) {
if (pst.cur_level_i == cmd->level_cnt) {
return false; // would be exact match
}
} else {
if (pstate.cur_level_i != cmd->level_cnt) {
if (pst.cur_level_i != cmd->level_cnt) {
return false; // can be only partial match
}
}
// check for match up to current index
for (uint8_t j = 0; j < pstate.cur_level_i; j++) {
if (!level_str_matches(pstate.cur_levels[j], cmd->levels[j])) {
for (uint8_t j = 0; j < pst.cur_level_i; j++) {
if (!level_str_matches(pst.cur_levels[j], cmd->levels[j])) {
return false;
}
}

Loading…
Cancel
Save