diff --git a/.gitignore b/.gitignore index bbf313b..f87ed06 100644 --- a/.gitignore +++ b/.gitignore @@ -30,3 +30,5 @@ # Debug files *.dSYM/ + +*.autosave diff --git a/main.c b/main.c index be7a26f..bc0296f 100644 --- a/main.c +++ b/main.c @@ -111,13 +111,6 @@ const char *scpi_device_identifier(void) return "FEL CVUT,DDS1,0,0.1"; } - -const char *scpi_device_version(void) -{ - return "0.0.1"; -} - - int main() { char buf[256]; diff --git a/source/scpi_builtins.c b/source/scpi_builtins.c index 5417e64..f48fd73 100644 --- a/source/scpi_builtins.c +++ b/source/scpi_builtins.c @@ -193,7 +193,7 @@ static void builtin_SYST_ERR_CLEAR(const SCPI_argval_t *args) static void builtin_SYST_VERSq(const SCPI_argval_t *args) { - scpi_send_string(scpi_device_version()); + scpi_send_string("1999.0"); // implemented SCPI version } diff --git a/source/scpi_builtins.h b/source/scpi_builtins.h index 666b611..d39521b 100644 --- a/source/scpi_builtins.h +++ b/source/scpi_builtins.h @@ -19,8 +19,5 @@ extern __attribute__((weak)) void scpi_user_TSTq(void); /** Get device *IDN? string. */ extern const char *scpi_device_identifier(void); -/** Get device version string. */ -extern const char *scpi_device_version(void); - // Provides: // const SCPI_command_t scpi_commands_builtin[]; diff --git a/source/scpi_parser.c b/source/scpi_parser.c index 49af10e..0f53346 100644 --- a/source/scpi_parser.c +++ b/source/scpi_parser.c @@ -122,7 +122,7 @@ static void pars_reset_cmd_keeplevel(void); // ------------------- MESSAGE SEND ------------------ /** Send string, no \r\n */ -void scpi_send_string_no_eol(const char *message) +void scpi_send_string_raw(const char *message) { char c; while ((c = *message++) != 0) { @@ -134,7 +134,7 @@ void scpi_send_string_no_eol(const char *message) /** Send a message to master. Trailing newline is added. */ void scpi_send_string(const char *message) { - scpi_send_string_no_eol(message); + scpi_send_string_raw(message); scpi_send_byte_impl('\r'); scpi_send_byte_impl('\n'); diff --git a/source/scpi_parser.c.autosave b/source/scpi_parser.c.autosave deleted file mode 100644 index 0f53346..0000000 --- a/source/scpi_parser.c.autosave +++ /dev/null @@ -1,983 +0,0 @@ -#include -#include -#include - -#include -#include - -#include "scpi_parser.h" -#include "scpi_errors.h" -#include "scpi_builtins.h" -#include "scpi_regs.h" - -// Config -#define ERR_QUEUE_LEN 4 -#define MAX_ERROR_LEN 255 -#define MAX_CHARBUF_LEN 255 - - -// Char matching -#define INRANGE(c, a, b) ((c) >= (a) && (c) <= (b)) -#define IS_WHITESPACE(c) (INRANGE((c), 0, 9) || INRANGE((c), 11, 32)) - -#define IS_LCASE_CHAR(c) INRANGE((c), 'a', 'z') -#define IS_UCASE_CHAR(c) INRANGE((c), 'A', 'Z') -#define IS_NUMBER_CHAR(c) INRANGE((c), '0', '9') -#define IS_MULTIPLIER_CHAR(c) ((c) == 'k' || (c) == 'M' || (c) == 'G' || (c) == 'm' || (c) == 'u' || (c) == 'n' || (c) == 'p') - -#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)) || (c) == '-' || (c) == '+' || IS_MULTIPLIER_CHAR((c))) -#define IS_FLOAT_CHAR(c) (IS_NUMBER_CHAR((c)) || (c) == '.' || (c) == 'e' || (c) == 'E' || (c) == '+' || (c) == '-' || IS_MULTIPLIER_CHAR((c))) - -#define CHAR_TO_LOWER(ucase) ((ucase) + 32) -#define CHAR_TO_UPPER(lcase) ((lcase) - 32) - - - -/** Parser internal state enum */ -typedef enum { - PARS_COMMAND = 0, - // collect generic arg, terminated with comma or newline. Leading and trailing whitespace ignored. - PARS_ARG, // generic argument (bool, float...) - PARS_ARG_STRING, // collect arg - string (special treatment for quotes) - PARS_ARG_BLOB_PREAMBLE, // #nDDD - PARS_ARG_BLOB_DISCARD, // discard blob - same as BLOB_BODY, but no callback or buffering - PARS_ARG_BLOB_BODY, // blob body, callback for each group - PARS_TRAILING_WHITE, // command ready to run, waiting for end, only whitespace allowed - PARS_TRAILING_WHITE_NOCB, // discard whitespace until end of line, don't run callback on success - PARS_DISCARD_LINE, // used after detecting error - drop all chars until \n -} parser_state_t; - - - -/** Parser internal state struct */ -static struct { - char err_queue[ERR_QUEUE_LEN][MAX_ERROR_LEN + 1]; - int8_t err_queue_r; - int8_t err_queue_w; - int8_t err_queue_used; // signed for backtracking - - parser_state_t state; // current parser internal state - - // string buffer, chars collected here until recognized - char charbuf[MAX_CHARBUF_LEN + 1]; - uint16_t charbuf_i; - - int32_t blob_cnt; // preamble counter, if 0, was just #, must read count. Used also for blob body. - int32_t blob_len; // total blob length to read - - char string_quote; // symbol used to quote string - bool string_escape; // last char was backslash, next quote is literal - - // recognized complete command level strings (FUNCtion) - exact copy from command struct - char cur_levels[SCPI_MAX_LEVEL_COUNT][SCPI_MAX_CMD_LEN]; - uint8_t cur_level_i; // next free level slot index - - bool cmdbuf_kept; // set to 1 after semicolon - cur_levels is kept (removed last part) - - const SCPI_command_t * matched_cmd; // command is put here after recognition, used as reference for args - - SCPI_argval_t args[SCPI_MAX_PARAM_COUNT]; - uint8_t arg_i; // next free argument slot index - -} pst = {0}; // initialized by all zeros - - -// buffer for error messages -static char ebuf[256]; - -// ---------------- PRIVATE PROTOTYPES ------------------ - -// Command parsing -static void pars_cmd_colon(void); // colon starting a command sub-segment -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 - -// Command properties (find length of array) -static uint8_t cmd_param_count(const SCPI_command_t *cmd); -static uint8_t cmd_level_count(const SCPI_command_t *cmd); - -static bool match_cmd(bool partial); -static bool match_any_cmd_from_array(const SCPI_command_t arr[], bool partial); -static bool match_cmd_do(const SCPI_command_t *cmd, bool partial); -static void run_command_callback(void); - -// Argument parsing -static void pars_arg_char(char c); -static void pars_arg_comma(void); -static void pars_arg_newline(void); -static void pars_arg_semicolon(void); -static void pars_blob_preamble_char(uint8_t c); -static void arg_convert_value(void); - -static void charbuf_terminate(void); -static void charbuf_append(char c); - -// Reset -static void pars_reset_cmd(void); -static void pars_reset_cmd_keeplevel(void); - - -// ------------------- MESSAGE SEND ------------------ - -/** Send string, no \r\n */ -void scpi_send_string_raw(const char *message) -{ - char c; - while ((c = *message++) != 0) { - scpi_send_byte_impl(c); - } -} - - -/** Send a message to master. Trailing newline is added. */ -void scpi_send_string(const char *message) -{ - scpi_send_string_raw(message); - - scpi_send_byte_impl('\r'); - scpi_send_byte_impl('\n'); -} - - -// ------------------- ERROR QUEUE --------------------- - -void scpi_add_error(SCPI_error_t errno, const char *extra) -{ - bool added = true; - if (pst.err_queue_used >= ERR_QUEUE_LEN) { - errno = E_DEV_QUEUE_OVERFLOW; - extra = NULL; - added = false; // replaced only - - // backtrack - pst.err_queue_w--; - pst.err_queue_used--; - if (pst.err_queue_w < 0) { - pst.err_queue_w = ERR_QUEUE_LEN - 1; - } - } - - scpi_error_string(pst.err_queue[pst.err_queue_w], errno, extra); - - pst.err_queue_w++; - pst.err_queue_used++; - if (pst.err_queue_w >= ERR_QUEUE_LEN) { - pst.err_queue_w = 0; - } - - scpi_status_update(); -} - - -void scpi_read_error(char *buf) -{ - if (pst.err_queue_used == 0) { - scpi_error_string(buf, E_NO_ERROR, NULL); - return; - } - - strcpy(buf, pst.err_queue[pst.err_queue_r++]); - pst.err_queue_used--; - - if (pst.err_queue_r >= ERR_QUEUE_LEN) { - pst.err_queue_r = 0; - } - - scpi_status_update(); -} - - -void scpi_clear_errors(void) -{ - pst.err_queue_r = 0; - pst.err_queue_w = 0; - pst.err_queue_used = 0; - - scpi_status_update(); -} - - -uint8_t scpi_error_count(void) -{ - return pst.err_queue_used; -} - - -static void err_no_such_command() -{ - char *b = ebuf; - for (int i = 0; i < pst.cur_level_i; i++) { - if (i > 0) b += sprintf(b, ":"); - b += sprintf(b, "%s", pst.cur_levels[i]); - } - - scpi_add_error(E_CMD_UNDEFINED_HEADER, ebuf); -} - - -static void err_no_such_command_partial() -{ - char *b = ebuf; - for (int i = 0; i < pst.cur_level_i; i++) { - b += sprintf(b, "%s:", pst.cur_levels[i]); - } - - scpi_add_error(E_CMD_UNDEFINED_HEADER, ebuf); -} - - -// ----------------- INPUT PARSING ---------------- - -void scpi_handle_byte(const uint8_t b) -{ - const char c = (char) b; - - switch (pst.state) { - case PARS_COMMAND: - // Collecting command - - if (IS_IDENT_CHAR(c)) { - // valid command char - - if (pst.charbuf_i < SCPI_MAX_CMD_LEN) { - charbuf_append(c); - } else { - scpi_add_error(E_CMD_PROGRAM_MNEMONIC_TOO_LONG, NULL); - pst.state = PARS_DISCARD_LINE; - } - - } else { - // invalid or delimiter - - if (IS_WHITESPACE(c)) { - pars_cmd_space(); // whitespace in command - end of command, start of args (?) - break; - } - - switch (c) { - case ':': - pars_cmd_colon(); // end of a section - break; - - case '\n': // line terminator - pars_cmd_newline(); - break; - - case ';': // ends a command, does not reset cmd path. - pars_cmd_semicolon(); - break; - - default: - sprintf(ebuf, "Unexpected '%c' in command.", c); - scpi_add_error(E_CMD_INVALID_CHARACTER, ebuf); - pst.state = PARS_DISCARD_LINE; - } - } - break; - - case PARS_DISCARD_LINE: - // drop it. Clear state on newline. - if (c == '\r' || c == '\n') { - pars_reset_cmd(); - } - break; - - case PARS_TRAILING_WHITE: - case PARS_TRAILING_WHITE_NOCB: - if (IS_WHITESPACE(c)) break; - - if (c == '\n') { - if (pst.state != PARS_TRAILING_WHITE_NOCB) { - run_command_callback(); - } - - pars_reset_cmd(); - } else { - sprintf(ebuf, "Unexpected '%c' in trailing whitespace.", c); - scpi_add_error(E_CMD_INVALID_CHARACTER, ebuf); - pst.state = PARS_DISCARD_LINE; - } - - break; // whitespace discarded - - case PARS_ARG: - if (IS_WHITESPACE(c)) break; // discard - - switch (c) { - case ',': - pars_arg_comma(); - break; - - case '\n': - pars_arg_newline(); - break; - - case ';': - pars_arg_semicolon(); - break; - - default: - pars_arg_char(c); - } - break; - - case PARS_ARG_STRING: - // string - - if (c == pst.string_quote && !pst.string_escape) { - // end of string - pst.state = PARS_ARG; // next will be newline or comma (or ignored spaces) - } else if (c == '\n') { - scpi_add_error(E_CMD_STRING_DATA_ERROR, "String not terminated (unexpected newline)."); - - pst.state = PARS_DISCARD_LINE; - } else { - if (pst.string_escape) { - charbuf_append(c); - pst.string_escape = false; - } else { - if (c == '\\') { - pst.string_escape = true; - } else { - charbuf_append(c); - } - } - } - break; - - case PARS_ARG_BLOB_PREAMBLE: - // # - pars_blob_preamble_char(c); - break; - - case PARS_ARG_BLOB_BODY: - // binary blob body with callback on buffer full - - charbuf_append(c); - pst.blob_cnt++; - - if (pst.charbuf_i >= pst.matched_cmd->blob_chunk) { - charbuf_terminate(); - - if (pst.matched_cmd->blob_callback != NULL) { - pst.matched_cmd->blob_callback((uint8_t *)pst.charbuf); - } - } - - if (pst.blob_cnt == pst.blob_len) { - pst.state = PARS_TRAILING_WHITE_NOCB; // discard trailing whitespace until newline - } - - break; - - case PARS_ARG_BLOB_DISCARD: - // binary blob, discard incoming data - - pst.blob_cnt++; - - if (pst.blob_cnt == pst.blob_len) { - pst.state = PARS_DISCARD_LINE; - } - - break; - } -} - - - -// ------------------- RESET INTERNAL STATE ------------------ - - -// public // -/** Discard the rest of the currently processed blob */ -void scpi_discard_blob(void) -{ - if (pst.state == PARS_ARG_BLOB_BODY) { - pst.state = PARS_ARG_BLOB_DISCARD; - } -} - - -/** Reset parser state. */ -static void pars_reset_cmd(void) -{ - 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; - pst.string_escape = false; -} - - -/** Reset parser state, keep level (semicolon) */ -static void pars_reset_cmd_keeplevel(void) -{ - pst.state = PARS_COMMAND; - pst.charbuf_i = 0; - - // rewind to last colon - 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; - pst.string_escape = false; -} - - -// ------------------- COMMAND HELPERS ------------------- - -/** Get param count from command struct */ -static uint8_t cmd_param_count(const SCPI_command_t *cmd) -{ - for (uint8_t i = 0; i < SCPI_MAX_PARAM_COUNT; i++) { - if (cmd->params[i] == SCPI_DT_NONE) { - return i; - } - } - - return SCPI_MAX_PARAM_COUNT; -} - - -/** Get level count from command struct */ -static uint8_t cmd_level_count(const SCPI_command_t *cmd) -{ - for (uint8_t i = 0; i < SCPI_MAX_LEVEL_COUNT; i++) { - if (cmd->levels[i][0] == 0) { - return i; - } - } - - return SCPI_MAX_LEVEL_COUNT; -} - - - -// ----------------- CHAR BUFFER HELPERS ------------------- - -/** Add a byte to charbuf, error on overflow */ -static void charbuf_append(char c) -{ - if (pst.charbuf_i >= MAX_CHARBUF_LEN) { - scpi_add_error(E_DEV_INPUT_BUFFER_OVERRUN, NULL); - pst.state = PARS_DISCARD_LINE; - } - - 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; -} - - - -// ----------------- PARSING COMMANDS --------------- - -/** Colon received when collecting command parts */ -static void pars_cmd_colon(void) -{ - if (pst.charbuf_i == 0) { - // No command text before colon - - 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 - scpi_add_error(E_CMD_SYNTAX_ERROR, "Unexpected colon."); - - pst.state = PARS_DISCARD_LINE; - } - - } else { - // internal colon - partial match - if (match_cmd(true)) { - // ok - } else { - // error - err_no_such_command_partial(); - - pst.state = PARS_DISCARD_LINE; - } - } -} - - -/** Semiolon received when collecting command parts */ -static void pars_cmd_semicolon(void) -{ - if (pst.cur_level_i == 0 && pst.charbuf_i == 0) { - // nothing before semicolon - scpi_add_error(E_CMD_SYNTAX_ERROR, "Semicolon not preceded by command."); - pars_reset_cmd(); - return; - } - - if (match_cmd(false)) { - int req_cnt = cmd_param_count(pst.matched_cmd); - - if (req_cnt == 0) { - // no param command - OK - run_command_callback(); - pars_reset_cmd_keeplevel(); // keep level - that's what semicolon does - } else { - sprintf(ebuf, "Required %d, got 0.", req_cnt); - scpi_add_error(E_CMD_MISSING_PARAMETER, ebuf); - pars_reset_cmd(); - } - } else { - err_no_such_command(); - pst.state = PARS_DISCARD_LINE; - } -} - - -/** Newline received when collecting command - end command and execute. */ -static void pars_cmd_newline(void) -{ - if (pst.cur_level_i == 0 && pst.charbuf_i == 0) { - // nothing before newline - pars_reset_cmd(); - return; - } - - // complete match - if (match_cmd(false)) { - int req_cnt = cmd_param_count(pst.matched_cmd); - - if (req_cnt == 0) { - // no param command - OK - run_command_callback(); - pars_reset_cmd(); - } else { - // error - sprintf(ebuf, "Required %d, got 0.", req_cnt); - scpi_add_error(E_CMD_MISSING_PARAMETER, ebuf); - - pars_reset_cmd(); - } - - } else { - err_no_such_command(); - 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 (match_cmd(false)) { - if (cmd_param_count(pst.matched_cmd) == 0) { - // no commands - pst.state = PARS_TRAILING_WHITE; - } else { - pst.state = PARS_ARG; - } - } else { - // error - err_no_such_command(); - pst.state = PARS_DISCARD_LINE; - } -} - - -/** Check if chars equal, ignore case */ -static bool char_equals_ci(char a, char b) -{ - if (IS_LCASE_CHAR(a)) { - - if (IS_LCASE_CHAR(b)) { - return a == b; - } else if (IS_UCASE_CHAR(b)) { - return a == CHAR_TO_LOWER(b); - } else { - return false; - } - - } else if (IS_UCASE_CHAR(a)) { - - if (IS_UCASE_CHAR(b)) { - return a == b; - } else if (IS_LCASE_CHAR(b)) { - return a == CHAR_TO_UPPER(b); - } else { - return false; - } - - } else { - return a == b; // exact match, not letters - } -} - - -/** Check if command matches a pattern */ -static bool level_str_matches(const char *test, const char *pattern) -{ - const uint8_t testlen = strlen(test); - uint8_t pat_i, tst_i; - bool long_started = false; - for (pat_i = 0, tst_i = 0; pat_i < strlen(pattern); pat_i++) { - if (tst_i > testlen) return false; // not match - - const char pat_c = pattern[pat_i]; - const char tst_c = test[tst_i]; // may be at the \0 terminator - - if (IS_LCASE_CHAR(pat_c)) { - // optional char - if (char_equals_ci(pat_c, tst_c)) { - tst_i++; // advance test string - long_started = true; - } else { - if (long_started) return false; // once long variant started, it must be completed. - } - - continue; // next pi - tc stays in place - } else { - // require exact match (case insensitive) - if (char_equals_ci(pat_c, tst_c)) { - tst_i++; - } else { - return false; - } - } - } - - return (tst_i >= testlen); -} - - - -static bool match_cmd(bool partial) -{ - charbuf_terminate(); // zero-end and rewind index - - // copy to level table - char *dest = pst.cur_levels[pst.cur_level_i++]; - strcpy(dest, pst.charbuf); - - - // User commands are checked first, can override builtin commands - if (match_any_cmd_from_array(scpi_commands, partial)) { - return true; - } - - // Try the built-in commands - return match_any_cmd_from_array(scpi_commands_builtin, partial); -} - - -static bool match_any_cmd_from_array(const SCPI_command_t arr[], bool partial) -{ - for (uint16_t i = 0; i < 0xFFFF; i++) { - - const SCPI_command_t *cmd = &arr[i]; - if (cmd->levels[0][0] == 0) break; // end marker - - if (cmd_level_count(cmd) > SCPI_MAX_LEVEL_COUNT) { - // FAIL, too deep. Bad config - continue; - } - - if (match_cmd_do(cmd, partial)) { - if (partial) { - // match found, OK - return true; - } else { - // exact match found - pst.matched_cmd = cmd; - return true; - } - } - } - - return false; -} - - -/** Try to match current state to a given command */ -static bool match_cmd_do(const SCPI_command_t *cmd, bool partial) -{ - const uint8_t level_cnt = cmd_level_count(cmd); - if (pst.cur_level_i > level_cnt) return false; // command too short - if (pst.cur_level_i == 0) return false; // nothing to match - - if (partial) { - if (pst.cur_level_i == level_cnt) { - return false; // would be exact match - } - } else { - if (pst.cur_level_i != level_cnt) { - return false; // can be only partial match - } - } - - // check for match up to current index - for (uint8_t j = 0; j < pst.cur_level_i; j++) { - if (!level_str_matches(pst.cur_levels[j], cmd->levels[j])) { - return false; - } - } - - return true; -} - - -/** Run the matched command's callback with the arguments */ -static void run_command_callback(void) -{ - if (pst.matched_cmd != NULL) { - pst.matched_cmd->callback(pst.args); // run - } -} - - - -// ---------------------- PARSING ARGS -------------------------- - -/** 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_FLOAT: - if (!IS_FLOAT_CHAR(c)) { - sprintf(ebuf, "'%c' not allowed in FLOAT.", c); - scpi_add_error(E_CMD_INVALID_CHARACTER_IN_NUMBER, ebuf); - - pst.state = PARS_DISCARD_LINE; - } else { - charbuf_append(c); - } - break; - - case SCPI_DT_INT: - if (!IS_INT_CHAR(c)) { - sprintf(ebuf, "'%c' not allowed in INT.", c); - scpi_add_error(E_CMD_INVALID_CHARACTER_IN_NUMBER, ebuf); - - pst.state = PARS_DISCARD_LINE; - } else { - charbuf_append(c); - } - break; - - case SCPI_DT_STRING: - if (c == '\'' || c == '"') { - pst.state = PARS_ARG_STRING; - pst.string_quote = c; - pst.string_escape = false; - } else { - scpi_add_error(E_CMD_INVALID_STRING_DATA, "Invalid quote, or chars after string."); - pst.state = PARS_DISCARD_LINE; - } - break; - - case SCPI_DT_BLOB: - if (c == '#') { - pst.state = PARS_ARG_BLOB_PREAMBLE; - pst.blob_cnt = 0; - } else { - scpi_add_error(E_CMD_INVALID_BLOCK_DATA, "Block data must start with #"); - pst.state = PARS_DISCARD_LINE; - } - break; - - default: - charbuf_append(c); - break; - } -} - - -/** Received a comma while collecting an arg */ -static void pars_arg_comma(void) -{ - if (pst.arg_i == cmd_param_count(pst.matched_cmd) - 1) { - // it was the last argument - scpi_add_error(E_CMD_UNEXPECTED_NUMBER_OF_PARAMETERS, "Comma after last argument."); - pst.state = PARS_DISCARD_LINE; - return; - } - - if (pst.charbuf_i == 0) { - scpi_add_error(E_CMD_SYNTAX_ERROR, "Missing command before comma."); - pst.state = PARS_DISCARD_LINE; - return; - } - - // Convert to the right type - - arg_convert_value(); -} - - -// line ended with \n or ; -static void pars_arg_eol_do(bool keep_levels) -{ - int req_cnt = cmd_param_count(pst.matched_cmd); - - if (pst.arg_i < req_cnt - 1) { - // not the last arg yet - fail - - if (pst.charbuf_i > 0) pst.arg_i++; // acknowledge the last arg - - sprintf(ebuf, "Required %d, got %d.", req_cnt, pst.arg_i); - scpi_add_error(E_CMD_MISSING_PARAMETER, ebuf); - - pst.state = PARS_DISCARD_LINE; - return; - } - - arg_convert_value(); - run_command_callback(); - - if (keep_levels) { - pars_reset_cmd_keeplevel(); - } else { - pars_reset_cmd(); // start a new command - } -} - - -static void pars_arg_newline(void) -{ - pars_arg_eol_do(false); -} - - -static void pars_arg_semicolon(void) -{ - pars_arg_eol_do(true); -} - - -/** 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 { - sprintf(ebuf, "Invalid BOOL value: '%s'", pst.charbuf); - scpi_add_error(E_CMD_NUMERIC_DATA_ERROR, ebuf); - - pst.state = PARS_DISCARD_LINE; - } - break; - - case SCPI_DT_FLOAT: - j = sscanf(pst.charbuf, "%f", &dest->FLOAT); - if (j == 0 || pst.charbuf[0] == '\0') { //fail or empty buffer - sprintf(ebuf, "Invalid FLOAT value: '%s'", pst.charbuf); - scpi_add_error(E_CMD_NUMERIC_DATA_ERROR, ebuf); - - pst.state = PARS_DISCARD_LINE; - } - break; - - case SCPI_DT_INT: - j = sscanf(pst.charbuf, "%d", &dest->INT); - - if (j == 0 || pst.charbuf[0] == '\0') { //fail or empty buffer - sprintf(ebuf, "Invalid INT value: '%s'", pst.charbuf); - scpi_add_error(E_CMD_NUMERIC_DATA_ERROR, ebuf); - - pst.state = PARS_DISCARD_LINE; - } - break; - - case SCPI_DT_STRING: - if (strlen(pst.charbuf) > SCPI_MAX_STRING_LEN) { - scpi_add_error(E_CMD_INVALID_STRING_DATA, "String too long."); - - pst.state = PARS_DISCARD_LINE; - } else { - strcpy(dest->STRING, pst.charbuf); // copy the string - } - - break; - - default: - // impossible - scpi_add_error(E_DEV_SYSTEM_ERROR, "Unexpected argument data type."); - pst.state = PARS_DISCARD_LINE; - } - - // proceed to next argument - pst.arg_i++; -} - - -static void pars_blob_preamble_char(uint8_t c) -{ - if (pst.blob_cnt == 0) { - if (!INRANGE(c, '1', '9')) { - sprintf(ebuf, "Unexpected '%c' in binary data preamble.", c); - scpi_add_error(E_CMD_BLOCK_DATA_ERROR, ebuf); - - pst.state = PARS_DISCARD_LINE;// (but not enough to remove the blob containing \n) - return; - } - - pst.blob_cnt = c - '0'; // 1-9 - } else { - if (c == '\n') { - scpi_add_error(E_CMD_BLOCK_DATA_ERROR, "Unexpected newline in binary data preamble."); - - pars_reset_cmd(); - return; - } - - if (!IS_NUMBER_CHAR(c)) { - sprintf(ebuf, "Unexpected '%c' in binary data preamble.", c); - scpi_add_error(E_CMD_BLOCK_DATA_ERROR, ebuf); - - pst.state = PARS_DISCARD_LINE; - return; - } - - charbuf_append(c); - if (--pst.blob_cnt == 0) { - // end of preamble sequence - charbuf_terminate(); - - sscanf(pst.charbuf, "%d", &pst.blob_len); - - pst.args[pst.arg_i].BLOB_LEN = pst.blob_len; - run_command_callback(); - - // Call handler, enter special blob mode - pst.state = PARS_ARG_BLOB_BODY; - pst.blob_cnt = 0; - } - } -}