reorg, improve

master
Ondřej Hruška 9 years ago
parent 15f5d1387f
commit 3bef041547
  1. 64
      main.c
  2. 6
      scpi.pro
  3. 207
      scpi_parser.c
  4. 48
      scpi_parser.h

@ -0,0 +1,64 @@
#include <stdio.h>
#include <stdlib.h>
#include "scpi_parser.h"
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 scpi_cmd_lang[] = {
{
.level_cnt = 1, .levels = {"*IDN?"},
.param_cnt = 0, .params = {},
.callback = cmd_aIDNq_cb
},
{
.level_cnt = 2, .levels = {"APPLy", "SINe"},
.param_cnt = 3, .params = {SCPI_DT_INT, SCPI_DT_FLOAT, SCPI_DT_FLOAT},
.callback = cmd_APPL_SIN_cb
},
{
.level_cnt = 2, .levels = {"APPLy", "TRIangle"},
.param_cnt = 3, .params = {SCPI_DT_INT, SCPI_DT_FLOAT, SCPI_DT_FLOAT},
.callback = cmd_APPL_TRI_cb
},
{
.level_cnt = 1, .levels = {"FREQuency"},
.param_cnt = 1, .params = {SCPI_DT_INT},
.callback = cmd_FREQ_cb
},
};
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");
}
int main(int argc, const char**argv)
{
const char *inp = argv[1];
printf("cmd lang len = %d\n", (int) sizeof(scpi_cmd_lang)/sizeof(SCPI_command_t));
}

@ -4,7 +4,11 @@ CONFIG -= app_bundle
CONFIG -= qt CONFIG -= qt
SOURCES += \ SOURCES += \
scpi_parser.c scpi_parser.c \
main.c
DISTFILES += \ DISTFILES += \
style.astylerc style.astylerc
HEADERS += \
scpi_parser.h

@ -1,53 +1,10 @@
#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"
#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 { typedef enum {
@ -63,7 +20,7 @@ typedef enum {
/** 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];
uint8_t err_queue_used; uint8_t err_queue_ptr;
parser_state_t state; // current parser internal state parser_state_t state; // current parser internal state
@ -71,22 +28,25 @@ static struct {
char charbuf[256]; char charbuf[256];
uint16_t charbuf_ptr; uint16_t charbuf_ptr;
// command buffer, built from recognized parts and colons. // recognized complete command level strings (FUNCtion) - exact copy from command struct
char cmdbuf[(MAX_CMD_LEN + 1)*MAX_LEVEL_COUNT + 1]; // :COMMAND for each level + zero terminator char cur_levels[MAX_LEVEL_COUNT][MAX_CMD_LEN];
uint16_t cmdbuf_ptr; uint8_t cur_level_ptr; // next free level slot index
bool cmdbuf_kept; // set to 1 after semicolon
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
parser_state_t *detected_cmd; // command is put here after recognition, used as reference for args SCPI_argval_t args[MAX_PARAM_COUNT];
uint8_t arg_i; // current argument index (0-based) uint8_t arg_ptr; // next free argument slot index
} pstate = { } pstate = {
// defaults // defaults
.err_queue_used = 0, .err_queue_ptr = 0,
.state = PARS_COMMAND, .state = PARS_COMMAND,
.charbuf_ptr = 0, .charbuf_ptr = 0,
.cmdbuf_ptr = 0, .cur_level_ptr = 0,
.cmdbuf_kept = false, .cmdbuf_kept = false,
.detected_cmd = NULL, .detected_cmd = NULL,
.arg_i = 0 .arg_ptr = 0
}; };
@ -95,19 +55,24 @@ 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 void pars_match_level(void); // match charbuf content to a level, advance level ptr (or fail)
#define INRANGE(x, a, b) ((x) >= (a) && (x) <= (b)) // char matching
#define IS_WHITE(x) (INRANGE((x), 0, 9) || INRANGE((x), 11, 32)) #define INRANGE(c, a, b) ((c) >= (a) && (c) <= (b))
#define IS_WHITESPACE(c) (INRANGE((c), 0, 9) || INRANGE((c), 11, 32))
#define IS_IDENT_CHAR(c) (INRANGE((c), 'a', 'z') || INRANGE((c), 'A', 'Z') || INRANGE((c), '0', '9') || (c) == '_')
#define IS_INT_CHAR(c) INRANGE((c), '0', '9')
#define IS_FLOAT_CHAR(c) (IS_INT_CHAR((c)) || (c) == '.' || (c) == 'e' || (c) == 'E' || (e) == '+' || (e) == '-')
static void pars_reset_cmd(void) static void pars_reset_cmd(void)
{ {
pstate.state = PARS_COMMAND; pstate.state = PARS_COMMAND;
pstate.charbuf_ptr = 0; pstate.charbuf_ptr = 0;
pstate.cmdbuf_ptr = 0; pstate.cur_level_ptr = 0;
pstate.cmdbuf_kept = false; pstate.cmdbuf_kept = false;
pstate.detected_cmd = NULL; pstate.detected_cmd = NULL;
pstate.arg_i = 0; pstate.arg_ptr = 0;
} }
static void pars_reset_cmd_keeplevel(void) static void pars_reset_cmd_keeplevel(void)
@ -116,15 +81,14 @@ static void pars_reset_cmd_keeplevel(void)
pstate.charbuf_ptr = 0; pstate.charbuf_ptr = 0;
// rewind to last colon // rewind to last colon
pstate.cmdbuf[pstate.cmdbuf_ptr] = 0; // terminate if (pstate.cur_level_ptr > 0) {
const char *cp = strrchr(pstate.cmdbuf, ':'); // find last colon pstate.cur_level_ptr--; // keep prev levels
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.cmdbuf_kept = true;
pstate.detected_cmd = NULL; pstate.detected_cmd = NULL;
pstate.arg_i = 0; pstate.arg_ptr = 0;
} }
@ -137,23 +101,28 @@ void scpi_receive_byte(const uint8_t b)
case PARS_COMMAND: case PARS_COMMAND:
// Collecting command // Collecting command
if (pstate.charbuf_ptr == 0) { if (pstate.charbuf_ptr == 0 && pstate.cur_level_ptr == 0) {
if (IS_WHITE(c)) { if (IS_WHITESPACE(c)) {
// leading whitespace is ignored // leading whitespace is ignored
break; break;
} }
} }
if (INRANGE(c, 'a', 'z') || INRANGE(c, 'A', 'Z') || INRANGE(c, '0', '9') || c == '_') { if (IS_IDENT_CHAR(c)) {
// valid command char // valid command char
if (pstate.charbuf_ptr < MAX_CMD_LEN) { if (pstate.charbuf_ptr < MAX_CMD_LEN) {
pstate.charbuf[pstate.charbuf_ptr++] = c; pstate.charbuf[pstate.charbuf_ptr++] = c;
} else {
printf("ERROR command part too long.\n");//TODO error
pstate.state = PARS_DISCARD_LINE;
} }
} else { } else {
// invalid or delimiter // invalid or delimiter
if (IS_WHITE(c)) { if (IS_WHITESPACE(c)) {
pars_cmd_space(); // whitespace in command - end of command? pars_cmd_space(); // whitespace in command - end of command?
break; break;
} }
@ -198,10 +167,8 @@ static void pars_cmd_colon(void)
if (pstate.charbuf_ptr == 0) { if (pstate.charbuf_ptr == 0) {
// No command text before colon // No command text before colon
// TODO FIXME should keep parsed command parts in array rather than a combined string if (pstate.cur_level_ptr == 0 || pstate.cmdbuf_kept) {
// top level command starts with colon (or after semicolon - reset level)
if (pstate.cmdbuf_ptr == 0 || pstate.cmdbuf_kept) {
// top level command starts with optional colon (or after semicolon reset level)
pars_reset_cmd(); pars_reset_cmd();
} else { } else {
// colon after nothing - error // colon after nothing - error
@ -210,101 +177,17 @@ static void pars_cmd_colon(void)
} }
} else { } else {
while(pstate.charbuf_ptr > 0 && IS_WHITE(pstate.charbuf[pstate.charbuf_ptr - 1])) { // internal colon
pstate.charbuf_ptr--; pars_match_level();
} }
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
static void pars_match_level(void)
//----------------------- 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]; // terminate segment
} pstate.charbuf[pstate.charbuf_ptr] = '\0';
//TODO
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");
}

@ -0,0 +1,48 @@
#pragma once
#include <stdint.h>
#include <stdbool.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 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;
extern const SCPI_command_t scpi_cmd_lang[];
Loading…
Cancel
Save