diff --git a/main.c b/main.c index 4a5e0d2..19a1dcf 100644 --- a/main.c +++ b/main.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= (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 + } + + 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.cmdbuf_kept = true; + 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; +} + - pstate.matched_cmd = NULL; - pstate.arg_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; } }