working command matching, tbd trailing space and args

master
Ondřej Hruška 9 years ago
parent 588bb22270
commit 09587ee41e
  1. 13
      main.c
  2. BIN
      main.elf
  3. 167
      scpi_parser.c
  4. 2
      scpi_parser.h

@ -1,4 +1,5 @@
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "scpi_parser.h"
@ -15,7 +16,7 @@ const SCPI_command_t scpi_cmd_lang[4] = {
.callback = cmd_aIDNq_cb
},
{
.level_cnt = 2, .levels = {"APPLy", "SINe"},
.level_cnt = 3, .levels = {"APPLy", "SINe"},
.param_cnt = 3, .params = {SCPI_DT_INT, SCPI_DT_FLOAT, SCPI_DT_FLOAT},
.callback = cmd_APPL_SIN_cb
},
@ -57,9 +58,15 @@ void cmd_FREQ_cb(const SCPI_argval_t *args)
int main(int argc, const char**argv)
int main()
{
const char *inp = argv[1];
//const char *inp = "*IDN?\n";
const char *inp = "FREQ 123\n";
for(int i=0;i<strlen(inp); i++) {
scpi_handle_byte(inp[i]);
}
// printf("%d\n", char_equals_ci('a','A'));
// printf("%d\n", char_equals_ci('z','z'));

Binary file not shown.

@ -8,10 +8,14 @@
typedef enum {
PARS_COMMAND,
PARS_ARG, // 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.
PARS_ARG,
PARS_ARG_STR_APOS, // collect arg - string with single quotes
PARS_ARG_STR_QUOT, // collect arg - string with double quotes
PARS_ARG_BLOB_PREAMBLE,
// command with no args terminated by whitespace, discard whitespace until newline and then run callback.
// error on non-whitespace
PARS_DISCARD_WHITESPACE_ENDL,
PARS_DISCARD_LINE, // used after detecting error
} parser_state_t;
@ -19,42 +23,42 @@ typedef enum {
/** parser internal state struct */
static struct {
char err_queue[ERR_QUEUE_LEN][MAX_ERROR_LEN];
uint8_t err_queue_ptr;
uint8_t err_queue_i;
parser_state_t state; // current parser internal state
// string buffer, chars collected here until recognized
char charbuf[256];
uint16_t charbuf_ptr;
uint16_t charbuf_i;
// recognized complete command level strings (FUNCtion) - exact copy from command struct
char cur_levels[MAX_LEVEL_COUNT][MAX_CMD_LEN];
uint8_t cur_level_ptr; // 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)
SCPI_command_t *detected_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];
uint8_t arg_ptr; // next free argument slot index
uint8_t arg_i; // next free argument slot index
} pstate = {
// defaults
.err_queue_ptr = 0,
.err_queue_i = 0,
.state = PARS_COMMAND,
.charbuf_ptr = 0,
.cur_level_ptr = 0,
.charbuf_i = 0,
.cur_level_i = 0,
.cmdbuf_kept = false,
.detected_cmd = NULL,
.arg_ptr = 0
.matched_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
static void pars_match_level(void); // match charbuf content to a level, advance level ptr (or fail)
static bool pars_match_cmd(bool partial);
// char matching
#define INRANGE(c, a, b) ((c) >= (a) && (c) <= (b))
@ -64,39 +68,44 @@ static void pars_match_level(void); // match charbuf content to a level, advance
#define IS_UCASE_CHAR(c) INRANGE((c), 'A', 'Z')
#define IS_NUMBER_CHAR(c) INRANGE((c), '0', '9')
#define IS_IDENT_CHAR(c) (IS_LCASE_CHAR((c)) || IS_UCASE_CHAR((c)) || IS_NUMBER_CHAR((c)) || (c) == '_')
#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 CHAR_TO_LOWER(ucase) ((ucase) + 32)
#define CHAR_TO_UPPER(lcase) ((lcase) - 32)
static void pars_reset_cmd(void)
{
pstate.state = PARS_COMMAND;
pstate.charbuf_ptr = 0;
pstate.cur_level_ptr = 0;
pstate.charbuf_i = 0;
pstate.cur_level_i = 0;
pstate.cmdbuf_kept = false;
pstate.detected_cmd = NULL;
pstate.arg_ptr = 0;
pstate.matched_cmd = NULL;
pstate.arg_i = 0;
}
static void pars_reset_cmd_keeplevel(void)
{
pstate.state = PARS_COMMAND;
pstate.charbuf_ptr = 0;
pstate.charbuf_i = 0;
// rewind to last colon
if (pstate.cur_level_ptr > 0) {
pstate.cur_level_ptr--; // keep prev levels
if (pstate.cur_level_i > 0) {
pstate.cur_level_i--; // keep prev levels
}
pstate.cmdbuf_kept = true;
pstate.detected_cmd = NULL;
pstate.arg_ptr = 0;
pstate.matched_cmd = NULL;
pstate.arg_i = 0;
}
void scpi_receive_byte(const uint8_t b)
void scpi_handle_byte(const uint8_t b)
{
// TODO handle blob here
const char c = (char) b;
@ -105,7 +114,7 @@ void scpi_receive_byte(const uint8_t b)
case PARS_COMMAND:
// Collecting command
if (pstate.charbuf_ptr == 0 && pstate.cur_level_ptr == 0) {
if (pstate.charbuf_i == 0 && pstate.cur_level_i == 0) {
if (IS_WHITESPACE(c)) {
// leading whitespace is ignored
break;
@ -116,8 +125,8 @@ void scpi_receive_byte(const uint8_t b)
if (IS_IDENT_CHAR(c)) {
// valid command char
if (pstate.charbuf_ptr < MAX_CMD_LEN) {
pstate.charbuf[pstate.charbuf_ptr++] = c;
if (pstate.charbuf_i < MAX_CMD_LEN) {
pstate.charbuf[pstate.charbuf_i++] = c;
} else {
printf("ERROR command part too long.\n");//TODO error
pstate.state = PARS_DISCARD_LINE;
@ -127,7 +136,7 @@ void scpi_receive_byte(const uint8_t b)
// invalid or delimiter
if (IS_WHITESPACE(c)) {
// pars_cmd_space(); // whitespace in command - end of command?
pars_cmd_space(); // whitespace in command - end of command, start of args (?)
break;
}
@ -137,7 +146,7 @@ void scpi_receive_byte(const uint8_t b)
break;
case '\n': // line terminator
// pars_cmd_newline();
pars_cmd_newline();
break;
case ';': // ends a command, does not reset cmd path.
@ -159,16 +168,17 @@ void scpi_receive_byte(const uint8_t b)
break;
// TODO
}
}
static void pars_cmd_colon(void)
{
if (pstate.charbuf_ptr == 0) {
if (pstate.charbuf_i == 0) {
// No command text before colon
if (pstate.cur_level_ptr == 0 || pstate.cmdbuf_kept) {
if (pstate.cur_level_i == 0 || pstate.cmdbuf_kept) {
// top level command starts with colon (or after semicolon - reset level)
pars_reset_cmd();
} else {
@ -178,13 +188,40 @@ static void pars_cmd_colon(void)
}
} else {
// internal colon
pars_match_level();
// 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]);
} else {
printf("ERROR no such command (colon).\n");//TODO error
pstate.state = PARS_DISCARD_LINE;
}
}
}
static void pars_cmd_newline(void)
{
if (pstate.cur_level_i == 0 && pstate.charbuf_i == 0) {
// nothing before newline
pars_reset_cmd();
return;
}
// complete match
if (pars_match_cmd(false)) {
if (pstate.matched_cmd->param_cnt == 0) {
// no param command - OK
pstate.matched_cmd->callback(pstate.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;
}
}
#define CHAR_TO_LOWER(ucase) ((ucase) + 32)
#define CHAR_TO_UPPER(lcase) ((lcase) - 32)
/** Check if chars equal, ignore case */
@ -248,12 +285,66 @@ 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 void pars_match_level(void)
static bool pars_match_cmd(bool partial)
{
// terminate segment
pstate.charbuf[pstate.charbuf_ptr] = '\0';
pstate.charbuf[pstate.charbuf_i] = '\0';
pstate.charbuf_i = 0; // rewind
char *dest = pstate.cur_levels[pstate.cur_level_i++];
strcpy(dest, pstate.charbuf); // copy to level table
for (uint16_t i = 0; i < scpi_cmd_lang_len; i++) {
const SCPI_command_t *cmd = &scpi_cmd_lang[i];
if (cmd->level_cnt > MAX_LEVEL_COUNT) {
// FAIL, too deep. Bad config
continue;
}
//TODO
if (try_match_cmd(cmd, partial)) {
if (partial) {
// match found, OK
return true;
} else {
// exact match found
pstate.matched_cmd = cmd;
return true;
}
}
}
return false;
}
/** 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 (partial) {
if (pstate.cur_level_i == cmd->level_cnt) {
return false; // would be exact match
}
} else {
if (pstate.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])) {
return false;
}
}
return true;
}

@ -49,4 +49,4 @@ extern const uint16_t scpi_cmd_lang_len; // number of commands
extern const SCPI_command_t scpi_cmd_lang[];
// --------------- functions --------------------
void scpi_handle_byte(const uint8_t b);

Loading…
Cancel
Save