From 15f5d1387f2ed7cd9ab73da7ba2ac0353a666f5b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Hru=C5=A1ka?= Date: Wed, 2 Dec 2015 09:39:51 +0100 Subject: [PATCH] initial --- out | Bin 0 -> 7784 bytes scpi.pro | 10 ++ scpi_parser.c | 310 +++++++++++++++++++++++++++++++++++++++++++++++++ style.astylerc | 13 +++ 4 files changed, 333 insertions(+) create mode 100755 out create mode 100644 scpi.pro create mode 100644 scpi_parser.c create mode 100644 style.astylerc diff --git a/out b/out new file mode 100755 index 0000000000000000000000000000000000000000..a08552088155674e59eb3d0989a4ce5e67e2965f GIT binary patch literal 7784 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 IbM9~dziS1`hyVZp literal 0 HcmV?d00001 diff --git a/scpi.pro b/scpi.pro new file mode 100644 index 0000000..ae59836 --- /dev/null +++ b/scpi.pro @@ -0,0 +1,10 @@ +TEMPLATE = app +CONFIG += console +CONFIG -= app_bundle +CONFIG -= qt + +SOURCES += \ + scpi_parser.c + +DISTFILES += \ + style.astylerc diff --git a/scpi_parser.c b/scpi_parser.c new file mode 100644 index 0000000..12e8bbb --- /dev/null +++ b/scpi_parser.c @@ -0,0 +1,310 @@ +#include +#include +#include +#include +#include +//#include "scpi_parser.h" + +#define MAX_CMD_LEN 12 +#define MAX_STRING_LEN 12 +#define MAX_LEVEL_COUNT 4 +#define MAX_PARAM_COUNT 4 + +#define ERR_QUEUE_LEN 4 +#define MAX_ERROR_LEN 100 + +/** Argument data types */ +typedef enum { + SCPI_DT_NONE = 0, + SCPI_DT_FLOAT, // float with support for scientific notation + SCPI_DT_INT, // integer (may be signed) + SCPI_DT_BOOL, // 0, 1, ON, OFF + SCPI_DT_STRING, // quoted string, max 12 chars; no escapes. + SCPI_DT_BLOB, // binary block, callback: uint32_t holding number of bytes +} SCPI_datatype_t; + + +/** Arguemnt value (union) */ +typedef union { + float FLOAT; + int32_t INT; + bool BOOL; + char STRING[MAX_STRING_LEN]; + uint32_t BLOB; +} SCPI_argval_t; + + +/** SCPI command preset */ +typedef struct { + const uint8_t level_cnt; // number of used command levels (colons + 1) + const char levels[MAX_LEVEL_COUNT][MAX_CMD_LEN]; // up to 4 parts + + const bool quest; // command ends with a question mark + + const uint8_t param_cnt; // parameter count + const SCPI_datatype_t params[MAX_PARAM_COUNT]; // parameter types (0 for unused) + + // called when the command is completed. BLOB arg must be last in the argument list, + // and only the first part is collected. + void (*callback)(const SCPI_argval_t * args); +} SCPI_command_t; + + +typedef enum { + PARS_COMMAND, + PARS_ARG, // collect generic arg, terminated with comma or newline. Leading and trailing whitespace ignored. + PARS_ARG_STR_APOS, // collect arg - string with single quotes + PARS_ARG_STR_QUOT, // collect arg - string with double quotes + PARS_ARG_BLOB_PREAMBLE, + PARS_DISCARD_LINE, // used after detecting error +} parser_state_t; + + +/** parser internal state struct */ +static struct { + char err_queue[ERR_QUEUE_LEN][MAX_ERROR_LEN]; + uint8_t err_queue_used; + + parser_state_t state; // current parser internal state + + // string buffer, chars collected here until recognized + char charbuf[256]; + uint16_t charbuf_ptr; + + // command buffer, built from recognized parts and colons. + char cmdbuf[(MAX_CMD_LEN + 1)*MAX_LEVEL_COUNT + 1]; // :COMMAND for each level + zero terminator + uint16_t cmdbuf_ptr; + bool cmdbuf_kept; // set to 1 after semicolon + + parser_state_t *detected_cmd; // command is put here after recognition, used as reference for args + uint8_t arg_i; // current argument index (0-based) +} pstate = { + // defaults + .err_queue_used = 0, + .state = PARS_COMMAND, + .charbuf_ptr = 0, + .cmdbuf_ptr = 0, + .cmdbuf_kept = false, + .detected_cmd = NULL, + .arg_i = 0 +}; + + + +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 + +#define INRANGE(x, a, b) ((x) >= (a) && (x) <= (b)) +#define IS_WHITE(x) (INRANGE((x), 0, 9) || INRANGE((x), 11, 32)) + + +static void pars_reset_cmd(void) +{ + pstate.state = PARS_COMMAND; + pstate.charbuf_ptr = 0; + pstate.cmdbuf_ptr = 0; + pstate.cmdbuf_kept = false; + pstate.detected_cmd = NULL; + pstate.arg_i = 0; +} + +static void pars_reset_cmd_keeplevel(void) +{ + pstate.state = PARS_COMMAND; + pstate.charbuf_ptr = 0; + // rewind to last colon + + pstate.cmdbuf[pstate.cmdbuf_ptr] = 0; // terminate + const char *cp = strrchr(pstate.cmdbuf, ':'); // find last colon + pstate.cmdbuf_ptr = (uint16_t) (cp - pstate.cmdbuf + 1); // location of one char past the colon + + // ↑ FIXME may be broken + pstate.cmdbuf_kept = true; + + pstate.detected_cmd = NULL; + pstate.arg_i = 0; +} + + +void scpi_receive_byte(const uint8_t b) +{ + // TODO handle blob here + const char c = (char) b; + + switch (pstate.state) { + case PARS_COMMAND: + // Collecting command + + if (pstate.charbuf_ptr == 0) { + if (IS_WHITE(c)) { + // leading whitespace is ignored + break; + } + } + + + if (INRANGE(c, 'a', 'z') || INRANGE(c, 'A', 'Z') || INRANGE(c, '0', '9') || c == '_') { + // valid command char + if (pstate.charbuf_ptr < MAX_CMD_LEN) { + pstate.charbuf[pstate.charbuf_ptr++] = c; + } + } else { + // invalid or delimiter + + if (IS_WHITE(c)) { + pars_cmd_space(); // whitespace in command - end of command? + 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: + printf("ERROR unexpected char '%c' in command.\n", c);//TODO error + pstate.state = PARS_DISCARD_LINE; + } + } + break; + + case PARS_DISCARD_LINE: + // drop it. Clear state on newline. + if (c == '\r' || c == '\n') { + pars_reset_cmd(); + } + break; + + // TODO + } +} + + +// whitespace (INRANGE(c, 0, 9) || INRANGE(c, 11, 32)) + + +static void pars_cmd_colon(void) +{ + if (pstate.charbuf_ptr == 0) { + // No command text before colon + + // TODO FIXME should keep parsed command parts in array rather than a combined string + + if (pstate.cmdbuf_ptr == 0 || pstate.cmdbuf_kept) { + // top level command starts with optional 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; + } + + } else { + while(pstate.charbuf_ptr > 0 && IS_WHITE(pstate.charbuf[pstate.charbuf_ptr - 1])) { + pstate.charbuf_ptr--; + } + + if (pstate.charbuf_ptr){ + printf("ERROR only whitespace before colon.\n");//TODO error + pstate.state = PARS_DISCARD_LINE; // this error shouldn't happen + return; + } + + pstate.charbuf[pstate.charbuf_ptr] = '\0'; // terminator + + } +} + + +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 + + + + + + + + + + + + + + + +//----------------------- TESTS ---------------------- + +void cmd_aIDNq_cb(const SCPI_argval_t *args); +void cmd_APPL_SIN_cb(const SCPI_argval_t *args); +void cmd_APPL_TRI_cb(const SCPI_argval_t *args); +void cmd_FREQ_cb(const SCPI_argval_t *args); + + +const SCPI_command_t lang[] = { + { + .level_cnt = 1, .levels = {"*IDN"}, + .quest = true, + .param_cnt = 0, .params = {}, + .callback = cmd_aIDNq_cb + }, + { + .level_cnt = 2, .levels = {"APPLy", "SINe"}, + .quest = false, + .param_cnt = 3, .params = {SCPI_DT_INT, SCPI_DT_FLOAT, SCPI_DT_FLOAT}, + .callback = cmd_APPL_SIN_cb + }, + { + .level_cnt = 2, .levels = {"APPLy", "TRIangle"}, + .quest = false, + .param_cnt = 3, .params = {SCPI_DT_INT, SCPI_DT_FLOAT, SCPI_DT_FLOAT}, + .callback = cmd_APPL_TRI_cb + }, + { + .level_cnt = 1, .levels = {"FREQuency"}, + .quest = false, + .param_cnt = 1, .params = {SCPI_DT_INT}, + .callback = cmd_FREQ_cb + }, +}; + + +int main(int argc, const char**argv) +{ + const char *inp = argv[1]; +} + + +void cmd_aIDNq_cb(const SCPI_argval_t *args) +{ + printf("cb *IDN?\n"); +} + + +void cmd_APPL_SIN_cb(const SCPI_argval_t *args) +{ + printf("cb APPLy:SINe\n"); +} + + +void cmd_APPL_TRI_cb(const SCPI_argval_t *args) +{ + printf("cb APPLy:TRIangle\n"); +} + + +void cmd_FREQ_cb(const SCPI_argval_t *args) +{ + printf("cb FREQuency\n"); +} diff --git a/style.astylerc b/style.astylerc new file mode 100644 index 0000000..0569d9a --- /dev/null +++ b/style.astylerc @@ -0,0 +1,13 @@ +style=kr +indent=tab +max-instatement-indent=60 + +convert-tabs + +indent-switches + +pad-oper +unpad-paren +pad-header + +verbose