|
|
@ -1,11 +1,38 @@ |
|
|
|
#include <stdint.h> |
|
|
|
#include <stdint.h> |
|
|
|
#include <stdbool.h> |
|
|
|
#include <stdbool.h> |
|
|
|
#include <string.h> |
|
|
|
#include <string.h> |
|
|
|
#include "scpi_parser.h" |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#include <stdio.h> |
|
|
|
#include <stdio.h> |
|
|
|
#include <stdlib.h> |
|
|
|
#include <stdlib.h> |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#include "scpi_parser.h" |
|
|
|
|
|
|
|
#include "scpi_errors.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 { |
|
|
|
typedef enum { |
|
|
|
PARS_COMMAND = 0, |
|
|
|
PARS_COMMAND = 0, |
|
|
|
// collect generic arg, terminated with comma or newline. Leading and trailing whitespace ignored.
|
|
|
|
// collect generic arg, terminated with comma or newline. Leading and trailing whitespace ignored.
|
|
|
@ -19,17 +46,19 @@ typedef enum { |
|
|
|
PARS_DISCARD_LINE, // used after detecting error - drop all chars until \n
|
|
|
|
PARS_DISCARD_LINE, // used after detecting error - drop all chars until \n
|
|
|
|
} parser_state_t; |
|
|
|
} parser_state_t; |
|
|
|
|
|
|
|
|
|
|
|
#define MAX_CHARBUF_LEN 256 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** parser internal state struct */ |
|
|
|
|
|
|
|
|
|
|
|
/** Parser internal state struct */ |
|
|
|
static struct { |
|
|
|
static struct { |
|
|
|
char err_queue[ERR_QUEUE_LEN][MAX_ERROR_LEN]; |
|
|
|
char err_queue[ERR_QUEUE_LEN][MAX_ERROR_LEN + 1]; |
|
|
|
uint8_t err_queue_i; |
|
|
|
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
|
|
|
|
parser_state_t state; // current parser internal state
|
|
|
|
|
|
|
|
|
|
|
|
// string buffer, chars collected here until recognized
|
|
|
|
// string buffer, chars collected here until recognized
|
|
|
|
char charbuf[MAX_CHARBUF_LEN]; |
|
|
|
char charbuf[MAX_CHARBUF_LEN + 1]; |
|
|
|
uint16_t charbuf_i; |
|
|
|
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_cnt; // preamble counter, if 0, was just #, must read count. Used also for blob body.
|
|
|
@ -39,147 +68,126 @@ static struct { |
|
|
|
bool string_escape; // last char was backslash, next quote is literal
|
|
|
|
bool string_escape; // last char was backslash, next quote is literal
|
|
|
|
|
|
|
|
|
|
|
|
// recognized complete command level strings (FUNCtion) - exact copy from command struct
|
|
|
|
// recognized complete command level strings (FUNCtion) - exact copy from command struct
|
|
|
|
char cur_levels[MAX_LEVEL_COUNT][MAX_CMD_LEN]; |
|
|
|
char cur_levels[SCPI_MAX_LEVEL_COUNT][SCPI_MAX_CMD_LEN]; |
|
|
|
uint8_t cur_level_i; // next free level slot index
|
|
|
|
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)
|
|
|
|
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
|
|
|
|
const SCPI_command_t * matched_cmd; // command is put here after recognition, used as reference for args
|
|
|
|
|
|
|
|
|
|
|
|
SCPI_argval_t args[MAX_PARAM_COUNT]; |
|
|
|
SCPI_argval_t args[SCPI_MAX_PARAM_COUNT]; |
|
|
|
uint8_t arg_i; // next free argument slot index
|
|
|
|
uint8_t arg_i; // next free argument slot index
|
|
|
|
|
|
|
|
|
|
|
|
} pst = {0}; // initialized by all zeros
|
|
|
|
} pst = {0}; // initialized by all zeros
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// ---------------- PRIVATE PROTOTYPES ------------------
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Command parsing
|
|
|
|
static void pars_cmd_colon(void); // colon starting a command sub-segment
|
|
|
|
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_space(void); // space ending a command
|
|
|
|
static void pars_cmd_newline(void); // LF
|
|
|
|
static void pars_cmd_newline(void); // LF
|
|
|
|
static void pars_cmd_semicolon(void); // semicolon right after a command
|
|
|
|
static void pars_cmd_semicolon(void); // semicolon right after a command
|
|
|
|
static bool pars_match_cmd(bool partial); |
|
|
|
|
|
|
|
|
|
|
|
// 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_char(char c); |
|
|
|
static void pars_arg_comma(void); |
|
|
|
static void pars_arg_comma(void); |
|
|
|
static void pars_arg_newline(void); |
|
|
|
static void pars_arg_newline(void); |
|
|
|
static void pars_arg_semicolon(void); |
|
|
|
static void pars_arg_semicolon(void); |
|
|
|
static void pars_blob_preamble_char(uint8_t c); |
|
|
|
static void pars_blob_preamble_char(uint8_t c); |
|
|
|
|
|
|
|
static void arg_convert_value(void); |
|
|
|
|
|
|
|
|
|
|
|
static uint8_t cmd_param_count(const SCPI_command_t *cmd); |
|
|
|
|
|
|
|
static uint8_t cmd_level_count(const SCPI_command_t *cmd); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static bool try_match_cmd(const SCPI_command_t *cmd, bool partial); |
|
|
|
|
|
|
|
static void charbuf_terminate(void); |
|
|
|
static void charbuf_terminate(void); |
|
|
|
static void charbuf_append(char c); |
|
|
|
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)) |
|
|
|
|
|
|
|
#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) == '?') |
|
|
|
// Reset
|
|
|
|
#define IS_INT_CHAR(c) (IS_NUMBER_CHAR((c)) || (c) == '-' || (c) == '+' || IS_MULTIPLIER_CHAR((c))) |
|
|
|
static void pars_reset_cmd(void); |
|
|
|
#define IS_FLOAT_CHAR(c) (IS_NUMBER_CHAR((c)) || (c) == '.' || (c) == 'e' || (c) == 'E' || (c) == '+' || (c) == '-' || IS_MULTIPLIER_CHAR((c))) |
|
|
|
static void pars_reset_cmd_keeplevel(void); |
|
|
|
|
|
|
|
|
|
|
|
#define CHAR_TO_LOWER(ucase) ((ucase) + 32) |
|
|
|
|
|
|
|
#define CHAR_TO_UPPER(lcase) ((lcase) - 32) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// ---------------- BUILTIN SCPI COMMANDS ------------------
|
|
|
|
|
|
|
|
|
|
|
|
/** Reset parser state. */ |
|
|
|
static void builtin_cb_FOO(const SCPI_argval_t *args) |
|
|
|
static void pars_reset_cmd(void) |
|
|
|
|
|
|
|
{ |
|
|
|
{ |
|
|
|
pst.state = PARS_COMMAND; |
|
|
|
printf("Builtin FOO\n"); |
|
|
|
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) */ |
|
|
|
const SCPI_command_t scpi_commands_builtin[] = { |
|
|
|
static void pars_reset_cmd_keeplevel(void) |
|
|
|
|
|
|
|
{ |
|
|
|
{ |
|
|
|
pst.state = PARS_COMMAND; |
|
|
|
.levels = {"FOO"}, |
|
|
|
pst.charbuf_i = 0; |
|
|
|
.params = {}, |
|
|
|
|
|
|
|
.callback = builtin_cb_FOO |
|
|
|
|
|
|
|
}, |
|
|
|
|
|
|
|
{0} // end marker
|
|
|
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
// 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; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// ------------------- ERROR QUEUE ---------------------
|
|
|
|
|
|
|
|
|
|
|
|
static uint8_t cmd_param_count(const SCPI_command_t *cmd) |
|
|
|
void scpi_add_error(SCPI_error_t errno, const char *extra) |
|
|
|
{ |
|
|
|
{ |
|
|
|
for (uint8_t i = 0; i < MAX_PARAM_COUNT; i++) { |
|
|
|
bool added = true; |
|
|
|
if (cmd->params[i] == SCPI_DT_NONE) { |
|
|
|
if (pst.err_queue_used >= ERR_QUEUE_LEN) { |
|
|
|
return i; |
|
|
|
errno = E_DEV_QUEUE_OVERFLOW; |
|
|
|
} |
|
|
|
extra = NULL; |
|
|
|
} |
|
|
|
added = false; // replaced only
|
|
|
|
|
|
|
|
|
|
|
|
return MAX_PARAM_COUNT; |
|
|
|
// backtrack
|
|
|
|
|
|
|
|
pst.err_queue_w--; |
|
|
|
|
|
|
|
pst.err_queue_used--; |
|
|
|
|
|
|
|
if (pst.err_queue_w < 0) { |
|
|
|
|
|
|
|
pst.err_queue_w = ERR_QUEUE_LEN - 1; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static uint8_t cmd_level_count(const SCPI_command_t *cmd) |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
for (uint8_t i = 0; i < MAX_LEVEL_COUNT; i++) { |
|
|
|
|
|
|
|
if (cmd->levels[i][0] == 0) { |
|
|
|
|
|
|
|
return i; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return MAX_LEVEL_COUNT; |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
scpi_error_string(pst.err_queue[pst.err_queue_w], errno, extra); |
|
|
|
|
|
|
|
|
|
|
|
/** Add a byte to charbuf, error on overflow */ |
|
|
|
pst.err_queue_w++; |
|
|
|
static void charbuf_append(char c) |
|
|
|
pst.err_queue_used++; |
|
|
|
{ |
|
|
|
if (pst.err_queue_w >= ERR_QUEUE_LEN) { |
|
|
|
if (pst.charbuf_i >= MAX_CHARBUF_LEN) { |
|
|
|
pst.err_queue_w = 0; |
|
|
|
printf("ERROR string buffer overflow.\n");//TODO error
|
|
|
|
|
|
|
|
pst.state = PARS_DISCARD_LINE; |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
pst.charbuf[pst.charbuf_i++] = c; |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** Terminate charbuf and rewind the pointer to start */ |
|
|
|
void scpi_read_error(char *buf) |
|
|
|
static void charbuf_terminate(void) |
|
|
|
|
|
|
|
{ |
|
|
|
{ |
|
|
|
pst.charbuf[pst.charbuf_i] = '\0'; |
|
|
|
if (pst.err_queue_used == 0) { |
|
|
|
pst.charbuf_i = 0; |
|
|
|
scpi_error_string(buf, E_NO_ERROR, NULL); |
|
|
|
|
|
|
|
return; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
strcpy(buf, pst.err_queue[pst.err_queue_r++]); |
|
|
|
|
|
|
|
pst.err_queue_used--; |
|
|
|
|
|
|
|
|
|
|
|
/** Run the matched command's callback with the arguments */ |
|
|
|
if (pst.err_queue_r >= ERR_QUEUE_LEN) { |
|
|
|
static void pars_run_callback(void) |
|
|
|
pst.err_queue_r = 0; |
|
|
|
{ |
|
|
|
|
|
|
|
if (pst.matched_cmd != NULL) { |
|
|
|
|
|
|
|
pst.matched_cmd->callback(pst.args); // run
|
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void scpi_discard_blob(void) |
|
|
|
uint8_t scpi_error_count(void) |
|
|
|
{ |
|
|
|
{ |
|
|
|
pst.state = PARS_ARG_BLOB_DISCARD; |
|
|
|
return pst.err_queue_used; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// ----------------- INPUT PARSING ----------------
|
|
|
|
|
|
|
|
|
|
|
|
void scpi_handle_byte(const uint8_t b) |
|
|
|
void scpi_handle_byte(const uint8_t b) |
|
|
|
{ |
|
|
|
{ |
|
|
|
// TODO handle blob here
|
|
|
|
// TODO handle blob here
|
|
|
@ -192,7 +200,7 @@ void scpi_handle_byte(const uint8_t b) |
|
|
|
if (IS_IDENT_CHAR(c)) { |
|
|
|
if (IS_IDENT_CHAR(c)) { |
|
|
|
// valid command char
|
|
|
|
// valid command char
|
|
|
|
|
|
|
|
|
|
|
|
if (pst.charbuf_i < MAX_CMD_LEN) { |
|
|
|
if (pst.charbuf_i < SCPI_MAX_CMD_LEN) { |
|
|
|
charbuf_append(c); |
|
|
|
charbuf_append(c); |
|
|
|
} else { |
|
|
|
} else { |
|
|
|
printf("ERROR command part too long.\n");//TODO error
|
|
|
|
printf("ERROR command part too long.\n");//TODO error
|
|
|
@ -240,7 +248,7 @@ void scpi_handle_byte(const uint8_t b) |
|
|
|
|
|
|
|
|
|
|
|
if (c == '\n') { |
|
|
|
if (c == '\n') { |
|
|
|
if (pst.state != PARS_TRAILING_WHITE_NOCB) { |
|
|
|
if (pst.state != PARS_TRAILING_WHITE_NOCB) { |
|
|
|
pars_run_callback(); |
|
|
|
run_command_callback(); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
pars_reset_cmd(); |
|
|
|
pars_reset_cmd(); |
|
|
@ -334,6 +342,105 @@ void scpi_handle_byte(const uint8_t b) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// ------------------- 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) { |
|
|
|
|
|
|
|
printf("ERROR string buffer overflow.\n");//TODO error
|
|
|
|
|
|
|
|
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 */ |
|
|
|
/** Colon received when collecting command parts */ |
|
|
|
static void pars_cmd_colon(void) |
|
|
|
static void pars_cmd_colon(void) |
|
|
|
{ |
|
|
|
{ |
|
|
@ -351,7 +458,7 @@ static void pars_cmd_colon(void) |
|
|
|
|
|
|
|
|
|
|
|
} else { |
|
|
|
} else { |
|
|
|
// internal colon - partial match
|
|
|
|
// internal colon - partial match
|
|
|
|
if (pars_match_cmd(true)) { |
|
|
|
if (match_cmd(true)) { |
|
|
|
// ok
|
|
|
|
// ok
|
|
|
|
} else { |
|
|
|
} else { |
|
|
|
printf("ERROR no such command: %s\n", pst.charbuf);//TODO error
|
|
|
|
printf("ERROR no such command: %s\n", pst.charbuf);//TODO error
|
|
|
@ -361,6 +468,7 @@ static void pars_cmd_colon(void) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** Semiolon received when collecting command parts */ |
|
|
|
static void pars_cmd_semicolon(void) |
|
|
|
static void pars_cmd_semicolon(void) |
|
|
|
{ |
|
|
|
{ |
|
|
|
if (pst.cur_level_i == 0 && pst.charbuf_i == 0) { |
|
|
|
if (pst.cur_level_i == 0 && pst.charbuf_i == 0) { |
|
|
@ -370,10 +478,10 @@ static void pars_cmd_semicolon(void) |
|
|
|
return; |
|
|
|
return; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if (pars_match_cmd(false)) { |
|
|
|
if (match_cmd(false)) { |
|
|
|
if (cmd_param_count(pst.matched_cmd) == 0) { |
|
|
|
if (cmd_param_count(pst.matched_cmd) == 0) { |
|
|
|
// no param command - OK
|
|
|
|
// no param command - OK
|
|
|
|
pars_run_callback(); |
|
|
|
run_command_callback(); |
|
|
|
pars_reset_cmd_keeplevel(); // keep level - that's what semicolon does
|
|
|
|
pars_reset_cmd_keeplevel(); // keep level - that's what semicolon does
|
|
|
|
} else { |
|
|
|
} else { |
|
|
|
printf("ERROR command missing arguments.\n");//TODO error
|
|
|
|
printf("ERROR command missing arguments.\n");//TODO error
|
|
|
@ -396,10 +504,10 @@ static void pars_cmd_newline(void) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// complete match
|
|
|
|
// complete match
|
|
|
|
if (pars_match_cmd(false)) { |
|
|
|
if (match_cmd(false)) { |
|
|
|
if (cmd_param_count(pst.matched_cmd) == 0) { |
|
|
|
if (cmd_param_count(pst.matched_cmd) == 0) { |
|
|
|
// no param command - OK
|
|
|
|
// no param command - OK
|
|
|
|
pars_run_callback(); |
|
|
|
run_command_callback(); |
|
|
|
pars_reset_cmd(); |
|
|
|
pars_reset_cmd(); |
|
|
|
} else { |
|
|
|
} else { |
|
|
|
printf("ERROR command missing arguments.\n");//TODO error
|
|
|
|
printf("ERROR command missing arguments.\n");//TODO error
|
|
|
@ -420,7 +528,7 @@ static void pars_cmd_space(void) |
|
|
|
return; |
|
|
|
return; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if (pars_match_cmd(false)) { |
|
|
|
if (match_cmd(false)) { |
|
|
|
if (cmd_param_count(pst.matched_cmd) == 0) { |
|
|
|
if (cmd_param_count(pst.matched_cmd) == 0) { |
|
|
|
// no commands
|
|
|
|
// no commands
|
|
|
|
pst.state = PARS_TRAILING_WHITE; |
|
|
|
pst.state = PARS_TRAILING_WHITE; |
|
|
@ -500,7 +608,7 @@ static bool level_str_matches(const char *test, const char *pattern) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static bool pars_match_cmd(bool partial) |
|
|
|
static bool match_cmd(bool partial) |
|
|
|
{ |
|
|
|
{ |
|
|
|
charbuf_terminate(); // zero-end and rewind index
|
|
|
|
charbuf_terminate(); // zero-end and rewind index
|
|
|
|
|
|
|
|
|
|
|
@ -508,17 +616,30 @@ static bool pars_match_cmd(bool partial) |
|
|
|
char *dest = pst.cur_levels[pst.cur_level_i++]; |
|
|
|
char *dest = pst.cur_levels[pst.cur_level_i++]; |
|
|
|
strcpy(dest, pst.charbuf); |
|
|
|
strcpy(dest, pst.charbuf); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// First try builtin commands
|
|
|
|
|
|
|
|
if (match_any_cmd_from_array(scpi_commands_builtin, partial)) { |
|
|
|
|
|
|
|
return true; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// User commands
|
|
|
|
|
|
|
|
return match_any_cmd_from_array(scpi_commands, partial); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static bool match_any_cmd_from_array(const SCPI_command_t arr[], bool partial) |
|
|
|
|
|
|
|
{ |
|
|
|
for (uint16_t i = 0; i < 0xFFFF; i++) { |
|
|
|
for (uint16_t i = 0; i < 0xFFFF; i++) { |
|
|
|
|
|
|
|
|
|
|
|
const SCPI_command_t *cmd = &scpi_cmd_lang[i]; |
|
|
|
const SCPI_command_t *cmd = &arr[i]; |
|
|
|
if (cmd->levels[0][0] == 0) break; // end marker
|
|
|
|
if (cmd->levels[0][0] == 0) break; // end marker
|
|
|
|
|
|
|
|
|
|
|
|
if (cmd_level_count(cmd) > MAX_LEVEL_COUNT) { |
|
|
|
if (cmd_level_count(cmd) > SCPI_MAX_LEVEL_COUNT) { |
|
|
|
// FAIL, too deep. Bad config
|
|
|
|
// FAIL, too deep. Bad config
|
|
|
|
continue; |
|
|
|
continue; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if (try_match_cmd(cmd, partial)) { |
|
|
|
if (match_cmd_do(cmd, partial)) { |
|
|
|
if (partial) { |
|
|
|
if (partial) { |
|
|
|
// match found, OK
|
|
|
|
// match found, OK
|
|
|
|
return true; |
|
|
|
return true; |
|
|
@ -535,7 +656,7 @@ static bool pars_match_cmd(bool partial) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** Try to match current state to a given command */ |
|
|
|
/** Try to match current state to a given command */ |
|
|
|
static bool try_match_cmd(const SCPI_command_t *cmd, bool partial) |
|
|
|
static bool match_cmd_do(const SCPI_command_t *cmd, bool partial) |
|
|
|
{ |
|
|
|
{ |
|
|
|
const uint8_t level_cnt = cmd_level_count(cmd); |
|
|
|
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 > level_cnt) return false; // command too short
|
|
|
@ -562,6 +683,18 @@ static bool try_match_cmd(const SCPI_command_t *cmd, bool partial) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** 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. */ |
|
|
|
/** Non-whitespace and non-comma char received in arg. */ |
|
|
|
static void pars_arg_char(char c) |
|
|
|
static void pars_arg_char(char c) |
|
|
|
{ |
|
|
|
{ |
|
|
@ -645,7 +778,7 @@ static void pars_arg_newline(void) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
arg_convert_value(); |
|
|
|
arg_convert_value(); |
|
|
|
pars_run_callback(); |
|
|
|
run_command_callback(); |
|
|
|
|
|
|
|
|
|
|
|
pars_reset_cmd(); // start a new command
|
|
|
|
pars_reset_cmd(); // start a new command
|
|
|
|
} |
|
|
|
} |
|
|
@ -661,7 +794,7 @@ static void pars_arg_semicolon(void) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
arg_convert_value(); |
|
|
|
arg_convert_value(); |
|
|
|
pars_run_callback(); |
|
|
|
run_command_callback(); |
|
|
|
|
|
|
|
|
|
|
|
pars_reset_cmd_keeplevel(); // start a new command, keep level
|
|
|
|
pars_reset_cmd_keeplevel(); // start a new command, keep level
|
|
|
|
} |
|
|
|
} |
|
|
@ -709,7 +842,7 @@ static void arg_convert_value(void) |
|
|
|
break; |
|
|
|
break; |
|
|
|
|
|
|
|
|
|
|
|
case SCPI_DT_STRING: |
|
|
|
case SCPI_DT_STRING: |
|
|
|
if (strlen(pst.charbuf) > MAX_STRING_LEN) { |
|
|
|
if (strlen(pst.charbuf) > SCPI_MAX_STRING_LEN) { |
|
|
|
printf("ERROR string too long.\n");//TODO error
|
|
|
|
printf("ERROR string too long.\n");//TODO error
|
|
|
|
pst.state = PARS_DISCARD_LINE; |
|
|
|
pst.state = PARS_DISCARD_LINE; |
|
|
|
} else { |
|
|
|
} else { |
|
|
@ -728,12 +861,13 @@ static void arg_convert_value(void) |
|
|
|
pst.arg_i++; |
|
|
|
pst.arg_i++; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static void pars_blob_preamble_char(uint8_t c) |
|
|
|
static void pars_blob_preamble_char(uint8_t c) |
|
|
|
{ |
|
|
|
{ |
|
|
|
if (pst.blob_cnt == 0) { |
|
|
|
if (pst.blob_cnt == 0) { |
|
|
|
if (!INRANGE(c, '1', '9')) { |
|
|
|
if (!INRANGE(c, '1', '9')) { |
|
|
|
printf("ERROR expected ASCII 1-9 after #\n");//TODO error
|
|
|
|
printf("ERROR expected ASCII 1-9 after #\n");//TODO error
|
|
|
|
pst.state = PARS_DISCARD_LINE; |
|
|
|
pst.state = PARS_DISCARD_LINE;// not enough to remove the blob containing \n
|
|
|
|
return; |
|
|
|
return; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
@ -759,7 +893,7 @@ static void pars_blob_preamble_char(uint8_t c) |
|
|
|
sscanf(pst.charbuf, "%d", &pst.blob_len); |
|
|
|
sscanf(pst.charbuf, "%d", &pst.blob_len); |
|
|
|
|
|
|
|
|
|
|
|
pst.args[pst.arg_i].BLOB_LEN = pst.blob_len; |
|
|
|
pst.args[pst.arg_i].BLOB_LEN = pst.blob_len; |
|
|
|
pars_run_callback(); |
|
|
|
run_command_callback(); |
|
|
|
|
|
|
|
|
|
|
|
// Call handler, enter special blob mode
|
|
|
|
// Call handler, enter special blob mode
|
|
|
|
pst.state = PARS_ARG_BLOB_BODY; |
|
|
|
pst.state = PARS_ARG_BLOB_BODY; |
|
|
|