@@ -1,6 +1,7 @@
#include <stdint.h> // types
#include <stdio.h> // printf
#include <string.h>
#include <unistd.h> // ssize_t

#include "helpers.h"
#include "ops/op.h"
@@ -26,9 +27,10 @@ static const char *errordesc[] = { "OK",
"NOT ENOUGH PARAMS",
"TOO MANY PARAMS",
"MOD NOT ALLOWED HERE",
"EXTRA SEPARATOR",
"NEED SEPARATOR",
"BAD SEPARATOR",
"EXTRA PRE SEPARATOR",
"NEED PRE SEPARATOR",
"BAD PRE SEPARATOR",
"NO SUB SEP IN PRE",
"MOVE LEFT" };

const char *tele_error(error_t e) {
@@ -102,8 +104,9 @@ error_t validate(const tele_command_t *c, char error_msg[ERROR_MSG_LENGTH]) {
tele_word_t word_type = c->data[idx].tag;
int16_t word_value = c->data[idx].value;
// A first_cmd is either at the beginning of the command or immediately
// after the SEP
bool first_cmd = idx == 0 || c->data[idx - 1].tag == SEP;
// after the PRE_SEP or COMMAND_SEP
bool first_cmd = idx == 0 || c->data[idx - 1].tag == PRE_SEP ||
c->data[idx - 1].tag == SUB_SEP;

if (word_type == NUMBER) { stack_depth++; }
else if (word_type == OP) {
@@ -136,7 +139,7 @@ error_t validate(const tele_command_t *c, char error_msg[ERROR_MSG_LENGTH]) {
if (idx != 0)
mod_error = E_NO_MOD_HERE;
else if (c->separator == -1)
mod_error = E_NEED_SEP;
mod_error = E_NEED_PRE_SEP;
else if (stack_depth < tele_mods[word_value]->params)
mod_error = E_NEED_PARAMS;
else if (stack_depth > tele_mods[word_value]->params)
@@ -149,17 +152,24 @@ error_t validate(const tele_command_t *c, char error_msg[ERROR_MSG_LENGTH]) {

stack_depth = 0;
}
else if (word_type == SEP) {
else if (word_type == PRE_SEP) {
sep_count++;
if (sep_count > 1)
return E_MANY_SEP;
else if (idx == 0)
return E_PLACE_SEP;

if (stack_depth > 1)
return E_EXTRA_PARAMS;
else
stack_depth = 0;
if (sep_count > 1) return E_MANY_PRE_SEP;

if (idx == 0) return E_PLACE_PRE_SEP;

if (stack_depth > 1) return E_EXTRA_PARAMS;

// reset the stack depth
stack_depth = 0;
}
else if (word_type == SUB_SEP) {
if (sep_count > 0) return E_NO_SUB_SEP_IN_PRE;

if (stack_depth > 1) return E_EXTRA_PARAMS;

// reset the stack depth
stack_depth = 0;
}
}

@@ -177,52 +187,99 @@ process_result_t run_script(size_t script_no) {
exec_state_t es;
es_init(&es);
for (size_t i = 0; i < tele_get_script_l(script_no); i++) {
result = process(&es, tele_get_script_c(script_no, i));
result = process_command(&es, tele_get_script_c(script_no, i));
}
return result;
}

process_result_t run_command(const tele_command_t *cmd) {
exec_state_t es;
es_init(&es);
return process(&es, cmd);
return process_command(&es, cmd);
}


/////////////////////////////////////////////////////////////////
// PROCESS //////////////////////////////////////////////////////

process_result_t process(exec_state_t *es, const tele_command_t *c) {
// run a single command inside a given exec_state
process_result_t process_command(exec_state_t *es, const tele_command_t *c) {
command_state_t cs;
cs_init(&cs);

// if the command has a MOD, only process it
// allow the MOD to deal with processing the remainder
int16_t idx = c->separator == -1 ? c->length : c->separator;

while (idx--) { // process from right to left
// 1. Do we have a PRE seperator?
// ------------------------------
// if we do then only process the PRE part, the MOD will determine if the
// POST should be run and take care of running it
ssize_t start_idx = 0;
ssize_t end_idx = c->separator == -1 ? c->length : c->separator;

// 2. Determine the location of all the SUB commands
// -------------------------------------------------
// an array of structs to hold the start and end of each sub command
struct sub_idx {
ssize_t start;
ssize_t end;
} subs[COMMAND_MAX_LENGTH];

ssize_t sub_len = 0;
ssize_t sub_start = 0;

// iterate through c->data to find all the SUB_SEPs and add to the array
for (ssize_t idx = start_idx; idx < end_idx; idx++) {
tele_word_t word_type = c->data[idx].tag;
int16_t word_value = c->data[idx].value;
if (word_type == SUB_SEP && idx > sub_start) {
subs[sub_len].start = sub_start;
subs[sub_len].end = idx - 1;
sub_len++;
sub_start = idx + 1;
}
}

if (word_type == NUMBER) { cs_push(&cs, word_value); }
else if (word_type == OP) {
const tele_op_t *op = tele_ops[word_value];
// the last sub command won't have been added, manually add it here
if (end_idx > sub_start) {
subs[sub_len].start = sub_start;
subs[sub_len].end = end_idx - 1;
sub_len++;
}

// if we're in the first command position, and there is a set fn
// pointer and we have enough params, then run set, else run get
if (idx == 0 && op->set != NULL &&
cs_stack_size(&cs) >= op->params + 1)
op->set(op->data, &scene_state, es, &cs);
else
op->get(op->data, &scene_state, es, &cs);
}
else if (word_type == MOD) {
tele_command_t sub_command;
copy_sub_command(&sub_command, c);
tele_mods[word_value]->func(&scene_state, es, &cs, &sub_command);
// 3. Loop through each sub command and execute it
// -----------------------------------------------
// iterate through sub commands from left to right
for (ssize_t sub_idx = 0; sub_idx < sub_len; sub_idx++) {
const ssize_t sub_start = subs[sub_idx].start;
const ssize_t sub_end = subs[sub_idx].end;

// as we are using a stack based language, we must process commands from
// right to left
for (ssize_t idx = sub_end; idx >= sub_start; idx--) {
const tele_word_t word_type = c->data[idx].tag;
const int16_t word_value = c->data[idx].value;

if (word_type == NUMBER) { cs_push(&cs, word_value); }
else if (word_type == OP) {
const tele_op_t *op = tele_ops[word_value];

// if we're in the first command position, and there is a set fn
// pointer and we have enough params, then run set, else run get
if (idx == sub_start && op->set != NULL &&
cs_stack_size(&cs) >= op->params + 1)
op->set(op->data, &scene_state, es, &cs);
else
op->get(op->data, &scene_state, es, &cs);
}
else if (word_type == MOD) {
tele_command_t post_command;
copy_post_command(&post_command, c);
tele_mods[word_value]->func(&scene_state, es, &cs,
&post_command);
}
}
}

// 4. Return
// ---------
// sometimes we have single value left of the stack, if so return it
if (cs_stack_size(&cs)) {
process_result_t o = {.has_value = true, .value = cs_pop(&cs) };
return o;
@@ -21,9 +21,10 @@ typedef enum {
E_NEED_PARAMS,
E_EXTRA_PARAMS,
E_NO_MOD_HERE,
E_MANY_SEP,
E_NEED_SEP,
E_PLACE_SEP,
E_MANY_PRE_SEP,
E_NEED_PRE_SEP,
E_PLACE_PRE_SEP,
E_NO_SUB_SEP_IN_PRE,
E_NOT_LEFT
} error_t;

@@ -38,7 +39,7 @@ error_t parse(const char *cmd, tele_command_t *out,
error_t validate(const tele_command_t *c, char error_msg[ERROR_MSG_LENGTH]);
process_result_t run_script(size_t script_no);
process_result_t run_command(const tele_command_t *cmd);
process_result_t process(exec_state_t *es, const tele_command_t *c);
process_result_t process_command(exec_state_t *es, const tele_command_t *c);

void tele_tick(uint8_t);

@@ -167,6 +167,20 @@ TEST should_parse_and_validate() {
PASS();
}

TEST parser_test_sub_commands() {
ASSERT_EQ(parse_and_validate_helper("X 1 ; Y 2"), E_OK);
ASSERT_EQ(parse_and_validate_helper("X 1 ; Y 2 ; ; "), E_OK);
ASSERT_EQ(parse_and_validate_helper("IF 1 : X 1 ; Y 2"), E_OK);
ASSERT_EQ(parse_and_validate_helper("IF 1 ; Z 1 : X 1"),
E_NO_SUB_SEP_IN_PRE);
ASSERT_EQ(parse_and_validate_helper("IF 1 ; Z 1 : X 1 ; Y 2"),
E_NO_SUB_SEP_IN_PRE);
// possibly the following should return E_NO_SUB_SEP_IN_PRE one day
ASSERT_EQ(parse_and_validate_helper("Z 1 ; IF 1 : X 1"), E_NO_MOD_HERE);

PASS();
}

// This test asserts that the parser always returns the correct op, it does this
// by starting with the op in question, extracting the name and running that
// through the parser. Then asserting that only 1 op is returned in
@@ -204,6 +218,7 @@ TEST parser_should_return_mod() {

SUITE(parser_suite) {
RUN_TEST(should_parse_and_validate);
RUN_TEST(parser_test_sub_commands);
RUN_TEST(parser_should_return_op);
RUN_TEST(parser_should_return_mod);
}
@@ -16,7 +16,7 @@ TEST process_helper(size_t n, char* lines[], int16_t answer) {
error_t error = parse(lines[i], &cmd, error_msg);
if (error != E_OK) { FAIL(); }
if (validate(&cmd, error_msg) != E_OK) { FAIL(); }
result = process(&es, &cmd);
result = process_command(&es, &cmd);
}
ASSERT_EQ(result.has_value, true);
ASSERT_EQ(result.value, answer);
@@ -217,6 +217,20 @@ TEST test_X() {
PASS();
}

TEST test_sub_commands() {
char* test1[2] = { "X 10 ; Y 20 ; Z 30", "ADD X ADD Y Z" };
CHECK_CALL(process_helper(2, test1, 60));

char* test2[2] = { "IF 1 : X 1 ; Y 2 ; Z 3", "ADD X ADD Y Z" };
CHECK_CALL(process_helper(2, test2, 6));

char* test3[3] = { "X 0 ; Y 0 ; Z 0", "IF 0 : X 1 ; Y 2 ; Z 3",
"ADD X ADD Y Z" };
CHECK_CALL(process_helper(3, test3, 0));

PASS();
}

SUITE(process_suite) {
RUN_TEST(test_numbers);
RUN_TEST(test_ADD);
@@ -228,4 +242,5 @@ SUITE(process_suite) {
RUN_TEST(test_Q);
RUN_TEST(test_PN);
RUN_TEST(test_X);
RUN_TEST(test_sub_commands);
}