From ad7180ce29873ab47ea5e803b1ee70498bf948eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Hru=C5=A1ka?= Date: Sat, 5 Dec 2015 17:48:33 +0100 Subject: [PATCH] SYST:ERR:CODE subsystem --- source/scpi_builtins.c | 92 ++++ source/scpi_parser.c | 12 +- source/scpi_parser.c.autosave | 983 ++++++++++++++++++++++++++++++++++ source/scpi_parser.h | 3 + 4 files changed, 1087 insertions(+), 3 deletions(-) create mode 100644 source/scpi_parser.c.autosave diff --git a/source/scpi_builtins.c b/source/scpi_builtins.c index 39d9cf5..5417e64 100644 --- a/source/scpi_builtins.c +++ b/source/scpi_builtins.c @@ -123,6 +123,74 @@ static void builtin_SYST_ERR_NEXTq(const SCPI_argval_t *args) } +// optional +static void builtin_SYST_ERR_ALLq(const SCPI_argval_t *args) +{ + int cnt = 0; + while (scpi_error_count()) { + scpi_read_error(sbuf); + if (cnt++ > 0) scpi_send_string_raw(","); + scpi_send_string_raw(sbuf); + } + + scpi_send_string_raw("\r\n"); // eol +} + + +static void builtin_SYST_ERR_CODE_NEXTq(const SCPI_argval_t *args) +{ + scpi_read_error(sbuf); + + // end at comma + for (int i = 0; i < 256; i++) { + if (sbuf[i] == ',') { + sbuf[i] = 0; + break; + } + } + + scpi_send_string(sbuf); +} + + +// optional +static void builtin_SYST_ERR_CODE_ALLq(const SCPI_argval_t *args) +{ + int cnt = 0; + while (scpi_error_count()) { + scpi_read_error(sbuf); + if (cnt++ > 0) scpi_send_string_raw(","); + + // end at comma + for (int i = 0; i < 256; i++) { + if (sbuf[i] == ',') { + sbuf[i] = 0; + break; + } + } + + scpi_send_string_raw(sbuf); + } + + scpi_send_string_raw("\r\n"); // eol +} + + +// optional +static void builtin_SYST_ERR_COUNq(const SCPI_argval_t *args) +{ + sprintf(sbuf, "%d", scpi_error_count()); + scpi_send_string(sbuf); +} + + +// optional, custom +static void builtin_SYST_ERR_CLEAR(const SCPI_argval_t *args) +{ + scpi_clear_errors(); +} + + static void builtin_SYST_VERSq(const SCPI_argval_t *args) { scpi_send_string(scpi_device_version()); @@ -270,6 +338,30 @@ const SCPI_command_t scpi_commands_builtin[] = { .levels = {"SYSTem", "ERRor", "NEXT?"}, .callback = builtin_SYST_ERR_NEXTq }, + { + .levels = {"SYSTem", "ERRor", "ALL?"}, // optional + .callback = builtin_SYST_ERR_ALLq + }, + { + .levels = {"SYSTem", "ERRor", "CLEAR"}, // custom, added for convenience + .callback = builtin_SYST_ERR_CLEAR + }, + { + .levels = {"SYSTem", "ERRor", "COUNt?"}, // optional + .callback = builtin_SYST_ERR_COUNq + }, + { + .levels = {"SYSTem", "ERRor", "CODE?"}, // optional + .callback = builtin_SYST_ERR_CODE_NEXTq + }, + { + .levels = {"SYSTem", "ERRor", "CODE", "NEXT?"}, // optional + .callback = builtin_SYST_ERR_CODE_NEXTq + }, + { + .levels = {"SYSTem", "ERRor", "CODE", "ALL?"}, // optional + .callback = builtin_SYST_ERR_CODE_ALLq + }, { .levels = {"SYSTem", "VERSion?"}, .callback = builtin_SYST_VERSq diff --git a/source/scpi_parser.c b/source/scpi_parser.c index 24c0dc9..49af10e 100644 --- a/source/scpi_parser.c +++ b/source/scpi_parser.c @@ -121,20 +121,26 @@ static void pars_reset_cmd_keeplevel(void); // ------------------- MESSAGE SEND ------------------ -/** Send a message to master. Trailing newline is added. */ -void scpi_send_string(const char *message) +/** Send string, no \r\n */ +void scpi_send_string_no_eol(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_no_eol(message); scpi_send_byte_impl('\r'); scpi_send_byte_impl('\n'); } - // ------------------- ERROR QUEUE --------------------- void scpi_add_error(SCPI_error_t errno, const char *extra) diff --git a/source/scpi_parser.c.autosave b/source/scpi_parser.c.autosave new file mode 100644 index 0000000..0f53346 --- /dev/null +++ b/source/scpi_parser.c.autosave @@ -0,0 +1,983 @@ +#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; + } + } +} diff --git a/source/scpi_parser.h b/source/scpi_parser.h index 3930f72..af279b7 100644 --- a/source/scpi_parser.h +++ b/source/scpi_parser.h @@ -97,6 +97,9 @@ void scpi_discard_blob(void); /** Send a string to master. \r\n is added. */ void scpi_send_string(const char *message); +/** Send a string without a line terminator */ +void scpi_send_string_raw(const char *message); + /** Clear the error queue */ void scpi_clear_errors(void);