@@ -0,0 +1,281 @@
#include "turtle.h"

#define min(X, Y) ((X) < (Y) ? (X) : (Y))
#define max(X, Y) ((X) > (Y) ? (X) : (Y))

void turtle_init(scene_turtle_t *st) {
scene_turtle_t t = { .fence = { .x1 = 0, .y1 = 0, .x2 = 3, .y2 = 63 },
.mode = TURTLE_BUMP,
.heading = 180,
.speed = 100,
.stepped = false,
.script_number = TEMP_SCRIPT };
memcpy(st, &t, sizeof(t));
turtle_set_x(st, 0);
turtle_set_y(st, 0);
st->last = st->position;
}

typedef struct {
QT x1, y1, x2, y2;
} Q_fence_t;

static inline Q_fence_t normalize_fence(turtle_fence_t in, turtle_mode_t mode) {
Q_fence_t out;

if (mode == TURTLE_WRAP) {
out.x1 = TO_Q(in.x1);
out.x2 = TO_Q((in.x2 + 1));
out.y1 = TO_Q(in.y1);
out.y2 = TO_Q((in.y2 + 1));
}
else {
out.x1 = TO_Q(in.x1) + (Q_1 >> 1);
out.x2 = TO_Q((in.x2 + 1)) - (Q_1 >> 1);
out.y1 = TO_Q(in.y1) + (Q_1 >> 1);
out.y2 = TO_Q((in.y2 + 1)) - (Q_1 >> 1);
}

return out;
}

void turtle_check_step(scene_turtle_t *t) {
turtle_position_t here;
turtle_resolve_position(t, &t->position, &here);
if (here.x != t->last.x || here.y != t->last.y) {
t->last = here;
t->stepped = true;
}
}

void turtle_normalize_position(scene_turtle_t *t, turtle_position_t *tp,
turtle_mode_t mode) {
Q_fence_t f = normalize_fence(t->fence, mode);

QT fxl = f.x2 - f.x1;
QT fyl = f.y2 - f.y1;

if (mode == TURTLE_WRAP) {
if (fxl > Q_1 && tp->x < f.x1)
tp->x = f.x2 + ((tp->x - f.x1) % fxl);
else if (fxl > Q_1 && tp->x > f.x2)
tp->x = f.x1 + ((tp->x - f.x1) % fxl);

if (fyl > Q_1 && tp->y < f.y1)
tp->y = f.y2 + ((tp->y - f.y1) % fyl);
else if (fyl > Q_1 && tp->y > f.y2)
tp->y = f.y1 + ((tp->y - f.y1) % fyl);
}
else if (mode == TURTLE_BOUNCE) {
// pretty sure you can do this with a % but I couldn't get it right
// what with the edge bounceback effectively changing the length
// so here's a crappy while() loop wavefolder. --burnsauce / sliderule
turtle_position_t last, here;
turtle_resolve_position(t, &t->position, &last);
while (tp->x > f.x2 || tp->x < f.x1) {
if (tp->x > f.x2) { // right fence
if (t->stepping) turtle_set_heading(t, 360 - t->heading);
// right extent minus how far past we are
tp->x = f.x2 - (tp->x - f.x2);
}
else if (tp->x < f.x1) { // left fence
if (t->stepping) turtle_set_heading(t, 360 - t->heading);
// left extent minus how far past we are
tp->x = f.x1 + (f.x1 - tp->x);
}
turtle_resolve_position(t, &t->position, &here);
if (here.x == last.x) break;
last = here;
}
while (tp->y > f.y2 || tp->y < f.y1) {
if (tp->y >= f.y2) { // top fence
if (t->stepping) turtle_set_heading(t, 180 - t->heading);
tp->y = f.y2 - (tp->y - f.y2);
}
else if (tp->y < f.y1) { // bottom fence
if (t->stepping) turtle_set_heading(t, 180 - t->heading);
tp->y = f.y1 + (f.y1 - tp->y);
}
turtle_resolve_position(t, &t->position, &here);
if (here.y == last.y) break;
last = here;
}
if (tp->x == f.x2) tp->x -= 1;
if (tp->y == f.y2) tp->y -= 1;
}
// either mode is TURTLE_BUMP or something above is broken
// both cases call for constraining the turtle to the fence
tp->x = min(f.x2 - 1, max(f.x1, tp->x));
tp->y = min(f.y2 - 1, max(f.y1, tp->y));
turtle_check_step(t);
}

// Produce Q0 positions in dst
void turtle_resolve_position(scene_turtle_t *t, turtle_position_t *src,
turtle_position_t *dst) {
dst->x = TO_I(src->x);
dst->y = TO_I(src->y);
}

uint8_t turtle_get_x(scene_turtle_t *st) {
turtle_position_t t;
turtle_resolve_position(st, &st->position, &t);
return t.x;
}

void turtle_set_x(scene_turtle_t *st, int16_t x) {
st->position.x = TO_Q(x) + Q_05; // standing in the middle of the cell
turtle_normalize_position(st, &st->position, TURTLE_BUMP);
}

uint8_t turtle_get_y(scene_turtle_t *st) {
turtle_position_t t;
turtle_resolve_position(st, &st->position, &t);
return t.y;
}

void turtle_set_y(scene_turtle_t *st, int16_t y) {
st->position.y = TO_Q(y) + Q_05;
turtle_normalize_position(st, &st->position, TURTLE_BUMP);
}

void turtle_move(scene_turtle_t *st, int16_t x, int16_t y) {
st->position.y += TO_Q(y);
st->position.x += TO_Q(x);
turtle_normalize_position(st, &st->position, st->mode);
}

/// http://www.coranac.com/2009/07/sines/
/// A sine approximation via a third-order approx.
/// @param x Angle (with 2^15 units/circle)
/// @return Sine value (Q12)
static inline int32_t _sin(int32_t x) {
// S(x) = x * ( (3<<p) - (x*x>>r) ) >> s
// n : Q-pos for quarter circle 13
// A : Q-pos for output 12
// p : Q-pos for parentheses intermediate 15
// r = 2n-p 11
// s = A-1-p-n 17

// int qN = 13, qA = 12, qP = 15, qR = 2 * qN - qP, qS = qN + qP + 1 - qA;
static const int qN = 13, qP = 15, qR = 11, qS = 17;

x = x << (30 - qN); // shift to full s32 range (Q13->Q30)

if ((x ^ (x << 1)) < 0) // test for quadrant 1 or 2
x = (1 << 31) - x;

x = x >> (30 - qN);

return x * ((3 << qP) - (x * x >> qR)) >> qS;
}

void turtle_step(scene_turtle_t *st) {
// watch out, it's a doozie ;)
QT dx = 0, dy = 0;
QT h1 = st->heading, h2 = st->heading;

h1 = ((h1 % 360) << 15) / 360;
h2 = (((h2 + 360 - 90) % 360) << 15) / 360;

int32_t dx_d_Q12 = (st->speed * _sin(h1)) / 100;
int32_t dy_d_Q12 = (st->speed * _sin(h2)) / 100;


if (dx_d_Q12 < 0)
// delta x = round(v *sin(heading))
dx = ((dx_d_Q12 >> (11 - Q_BITS)) - 1) >> 1;
else
dx = ((dx_d_Q12 >> (11 - Q_BITS)) + 1) >> 1;
if (dy_d_Q12 < 0)
dy = ((dy_d_Q12 >> (11 - Q_BITS)) - 1) >> 1;
else
dy = ((dy_d_Q12 >> (11 - Q_BITS)) + 1) >> 1;


st->position.x += dx;
st->position.y += dy;
st->stepping = true;
turtle_normalize_position(st, &st->position, st->mode);
st->stepping = false;
}

inline void turtle_correct_fence(scene_turtle_t *st) {
int16_t t;
st->fence.x1 = min(3, max(0, st->fence.x1));
st->fence.x2 = min(3, max(0, st->fence.x2));
st->fence.y1 = min(63, max(0, st->fence.y1));
st->fence.y2 = min(63, max(0, st->fence.y2));

if (st->fence.x1 > st->fence.x2) {
t = st->fence.x2;
st->fence.x2 = st->fence.x1;
st->fence.x1 = t;
}
if (st->fence.y1 > st->fence.y2) {
t = st->fence.y2;
st->fence.y2 = st->fence.y1;
st->fence.y1 = t;
}
turtle_normalize_position(st, &st->position, TURTLE_BUMP);
}

void turtle_set_fence(scene_turtle_t *st, int16_t x1, int16_t y1, int16_t x2,
int16_t y2) {
st->fence.x1 = min(3, max(0, x1));
st->fence.x2 = min(3, max(0, x2));
st->fence.y1 = min(63, max(0, y1));
st->fence.y2 = min(63, max(0, y2));
turtle_correct_fence(st);
}

turtle_mode_t turtle_get_mode(scene_turtle_t *st) {
return st->mode;
}

void turtle_set_mode(scene_turtle_t *st, turtle_mode_t m) {
if (m != TURTLE_WRAP && m != TURTLE_BUMP && m != TURTLE_BOUNCE)
m = TURTLE_BUMP;
st->mode = m;
turtle_normalize_position(st, &st->position, m);
}

uint16_t turtle_get_heading(scene_turtle_t *st) {
return st->heading;
}

void turtle_set_heading(scene_turtle_t *st, int16_t h) {
while (h < 0) h += 360;
st->heading = h % 360;
}

int16_t turtle_get_speed(scene_turtle_t *st) {
return st->speed;
}

void turtle_set_speed(scene_turtle_t *st, int16_t v) {
st->speed = v;
}

void turtle_set_script(scene_turtle_t *st, script_number_t sn) {
if (sn >= METRO_SCRIPT)
st->script_number = TEMP_SCRIPT;
else
st->script_number = sn;
st->stepped = false;
}

script_number_t turtle_get_script(scene_turtle_t *st) {
return st->script_number;
}

bool turtle_get_shown(scene_turtle_t *st) {
return st->shown;
}

void turtle_set_shown(scene_turtle_t *st, bool shown) {
st->shown = shown;
}

#undef min
#undef max
@@ -0,0 +1,99 @@
#ifndef _TURTLE_H_
#define _TURTLE_H_
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <string.h>

// In this code, we use signed fixed-point maths. This means that the int16_t
// is used to store a signed number with both an integer and a fraction part.
//
// Q_BITS represents how many are used for the fractional component. Because
// we work with signed integers, 1 bit facilitates the sign component.
// Therefore, INT_BITS = 16 - Q_BITS - 1
//
// The main thing we do in fixed-point is keep a fractional index into the
// pattern data, so it will be our main reference point.
//
// 63 is 6 bits, so Q_BITS = 16 - 6 - 1 = 9
// The notation for this system is Q6.9

#define Q_BITS 9
#define Q_1 (1 << Q_BITS) // 1.0
#define Q_05 (1 << (Q_BITS - 1)) // 0.5
#define Q_ROUND(X) \
((((X >> (Q_BITS - 1)) + 1) >> 1) << Q_BITS) // (int)(X + 0.5)
#define QT int32_t
#define TO_Q(X) (X << Q_BITS)
#define TO_I(X) ((X >> Q_BITS) & 0xFFFF)

// Can't include state.h first, so put script_number_t here
// really should be in a different header file?

typedef enum {
TT_SCRIPT_1 = 0,
TT_SCRIPT_2,
TT_SCRIPT_3,
TT_SCRIPT_4,
TT_SCRIPT_5,
TT_SCRIPT_6,
TT_SCRIPT_7,
TT_SCRIPT_8,
METRO_SCRIPT,
INIT_SCRIPT,
TEMP_SCRIPT
} script_number_t;

typedef struct {
int32_t x; // higher resolution to permit fixed-point math
int32_t y;
} turtle_position_t;

typedef struct {
uint8_t x1;
uint8_t y1;
uint8_t x2;
uint8_t y2;
} turtle_fence_t;

typedef enum { TURTLE_WRAP, TURTLE_BUMP, TURTLE_BOUNCE } turtle_mode_t;

typedef struct {
turtle_position_t position;
turtle_position_t last;
turtle_fence_t fence;
turtle_mode_t mode;
uint16_t heading;
int16_t speed;
script_number_t script_number;
bool stepping;
bool stepped;
bool shown;
} scene_turtle_t;

void turtle_init(scene_turtle_t*);
void turtle_normalize_position(scene_turtle_t*, turtle_position_t*,
turtle_mode_t);
void turtle_resolve_position(scene_turtle_t*, turtle_position_t*,
turtle_position_t*);
uint8_t turtle_get_x(scene_turtle_t*);
void turtle_set_x(scene_turtle_t*, int16_t);
uint8_t turtle_get_y(scene_turtle_t*);
void turtle_set_y(scene_turtle_t*, int16_t);
void turtle_move(scene_turtle_t*, int16_t, int16_t);
void turtle_step(scene_turtle_t*);
turtle_fence_t* turtle_get_fence(scene_turtle_t*);
void turtle_correct_fence(scene_turtle_t*);
void turtle_set_fence(scene_turtle_t*, int16_t, int16_t, int16_t, int16_t);
turtle_mode_t turtle_get_mode(scene_turtle_t*);
void turtle_set_mode(scene_turtle_t*, turtle_mode_t);
uint16_t turtle_get_heading(scene_turtle_t*);
void turtle_set_heading(scene_turtle_t*, int16_t);
int16_t turtle_get_speed(scene_turtle_t*);
void turtle_set_speed(scene_turtle_t*, int16_t);
script_number_t turtle_get_script(scene_turtle_t*);
void turtle_set_script(scene_turtle_t*, script_number_t);
void turtle_check_step(scene_turtle_t*);
bool turtle_get_shown(scene_turtle_t*);
void turtle_set_shown(scene_turtle_t*, bool);
#endif
@@ -2,17 +2,20 @@
CFLAGS = -std=c99 -g -Wall -fno-common -DSIM -I../src -I../libavr32/src

tests: main.o \
log.o \
match_token_tests.o op_mod_tests.o \
parser_tests.o process_tests.o \
turtle_tests.o \
../src/teletype.o ../src/command.o ../src/helpers.o \
../src/match_token.o ../src/scanner.o \
../src/state.o ../src/table.o \
../src/every.o ../src/match_token.o ../src/scanner.o \
../src/state.o ../src/table.o ../src/turtle.o \
../src/ops/op.o ../src/ops/ansible.c ../src/ops/controlflow.o \
../src/ops/delay.o ../src/ops/earthsea.o ../src/ops/hardware.o \
../src/ops/justfriends.o ../src/ops/meadowphysics.o \
../src/ops/metronome.o ../src/ops/maths.o ../src/ops/orca.o \
../src/ops/patterns.o ../src/ops/queue.o ../src/ops/stack.o \
../src/ops/telex.o ../src/ops/variables.o ../src/ops/whitewhale.c \
../src/ops/turtle.o \
../libavr32/src/euclidean/data.o ../libavr32/src/euclidean/euclidean.o \
../libavr32/src/util.o
$(CC) -o $@ $^ $(CFLAGS)
@@ -26,6 +29,9 @@ tests: main.o \
test: tests
@./tests | greatest/greenest

test-travis: tests
@./tests

clean:
rm -f tests
rm -rf tests.dSYM
@@ -0,0 +1,95 @@
#include <stdarg.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "log.h"

static inline log_t *get_log() {
static log_t log;
return &log;
}

#define L get_log()

void log_init() {
L->size = LOG_SIZE;
L->buf = calloc(LOG_SIZE, sizeof(char));
L->end = L->buf + LOG_SIZE;
}

void log_grow() {
char *new = calloc(L->size * 2, sizeof(char));
memcpy(new, L->buf, L->size);
free(L->buf);
L->buf = new;
L->size *= 2;
L->end = L->buf + L->size;
}

log_position_t *log_seek() {
log_position_t *here = get_log()->buf;
log_position_t *last = here;

while (*here != 0) {
last = here;
here += *here + 1;
}
return last;
}


log_position_t *log_seek_next() {
log_position_t *here = get_log()->buf;

while (*here != 0) here += *here + 1;
return here;
}

bool catting = false;

void lprintf(const char *format, ...) {
log_t *log = L;
va_list arg;
char buf[LOG_LINE_SIZE];
va_start(arg, format);
size_t len = vsnprintf(buf, LOG_LINE_SIZE, format, arg);
va_end(arg);
log_position_t *here = log_seek_next();
if (here + len + 2 > log->end) log_grow();
here = log_seek_next();
strcpy(here + 1, buf);
*here = len + 1;
catting = false;
}

void lcat(const char *str) {
log_position_t *(*seek)(); // Function ponters: useful!
if (catting)
seek = log_seek;
else
seek = log_seek_next;
log_position_t *here = seek();
if (here + strlen(str) + 1 > L->end) {
log_grow();
here = seek();
}

char *new = strcat(here + 1, str);
*here = strlen(new) + 1;
catting = true;
}

void log_clear() {
memset(L->buf, 0, L->size);
}

void log_print() {
puts("\n"); // two newlines, actually.
log_position_t *here = get_log()->buf;
while (*here != 0) {
puts(here + 1);
here += *here + 1;
}
}
@@ -0,0 +1,22 @@
#ifndef _LOG_H
#define _LOG_H

#define LOG_SIZE 2048
#define LOG_LINE_SIZE 255

typedef char log_position_t;

typedef struct {
char *buf;
char *end;
size_t size;
} log_t;


void log_init(void);
void lprintf(const char *, ...);
void lcat(const char *);
void log_print(void);
void log_clear(void);

#endif
@@ -9,6 +9,7 @@
#include "op_mod_tests.h"
#include "parser_tests.h"
#include "process_tests.h"
#include "turtle_tests.h"

void tele_metro_updated() {}
void tele_metro_reset() {}
@@ -37,6 +38,7 @@ int main(int argc, char **argv) {
RUN_SUITE(op_mod_suite);
RUN_SUITE(parser_suite);
RUN_SUITE(process_suite);
RUN_SUITE(turtle_suite);

GREATEST_MAIN_END();
}
@@ -39,6 +39,8 @@ TEST op_stack_size() {
// (needs dedicated initaliser)
exec_state_t es;
es_init(&es);
es_push(&es);
es_variables(&es)->script_number = 1;
command_state_t cs;
cs_init(&cs);

@@ -105,10 +107,10 @@ TEST mod_stack_size() {
for (int j = 0; j < mod->params + stack_extra; j++) cs_push(&cs, 0);

// execute func
const tele_command_t sub_command = {.length = 1,
.separator = 0,
.data = { {.tag = OP,
.value = E_OP_A } } };
const tele_command_t sub_command = { .length = 1,
.separator = 0,
.data = { { .tag = OP,
.value = E_OP_A } } };
mod->func(&ss, &es, &cs, &sub_command);

// check that the stack has the correct number of items in it
@@ -11,9 +11,11 @@
// correct (allows contiuation of state)
TEST process_helper_state(scene_state_t* ss, size_t n, char* lines[],
int16_t answer) {
process_result_t result = {.has_value = false, .value = 0 };
process_result_t result = { .has_value = false, .value = 0 };
exec_state_t es;
es_init(&es);
es_push(&es);
es_variables(&es)->script_number = 1;
for (size_t i = 0; i < n; i++) {
tele_command_t cmd;
char error_msg[TELE_ERROR_MSG_LENGTH];
@@ -231,9 +233,8 @@ TEST test_Q() {

// 1+2+3+4+5+6+7+8+9+10+11+12+13+14+15+16 = 136
// 136 / 16 = 8.5
// TODO fix Q.AVG to return 9 in this circumstance
char* test4[1] = { "Q.AVG" };
CHECK_CALL(process_helper_state(&ss, 1, test4, 8));
CHECK_CALL(process_helper_state(&ss, 1, test4, 9));

char* test5[2] = { "Q.AVG 5", "Q.AVG" };
CHECK_CALL(process_helper_state(&ss, 2, test5, 5));
@@ -278,6 +279,8 @@ TEST test_blank_command() {
ss_init(&ss);
exec_state_t es;
es_init(&es);
es_push(&es);
es_variables(&es)->script_number = 1;
tele_command_t cmd;
char error_msg[TELE_ERROR_MSG_LENGTH];

@@ -0,0 +1,302 @@
#include "turtle_tests.h"
#include <stdlib.h>
#include <string.h>
#include <unistd.h> // ssize_t

#include "greatest/greatest.h"

#include "log.h"
#include "teletype.h"

int32_t count = 0;

#define NEWTEST() count = 0;

static const char *error_message(error_t e) {
static const char *em[] = { "E_OK",
"E_PARSE",
"E_LENGTH",
"E_NEED_PARAMS",
"E_EXTRA_PARAMS",
"E_NO_MOD_HERE",
"E_MANY_PRE_SEP",
"E_NEED_PRE_SEP",
"E_PLACE_PRE_SEP",
"E_NO_SUB_SEP_IN_PRE",
"E_NOT_LEFT",
"E_NEED_SPACE_PRE_SEP",
"E_NEED_SPACE_SUB_SEP" };
return em[e];
}

// runs multiple lines of commands and then asserts that the final answer is
// correct (allows contiuation of state)
TEST process_helper_state(scene_state_t *ss, size_t n, char *lines[],
int16_t answer) {
count++;
process_result_t result = { .has_value = false, .value = 0 };
exec_state_t es;
memset(&es, 0, sizeof(es));
es_init(&es);
es_push(&es);
es_variables(&es)->script_number = 1;

log_clear();
lprintf("---- Test #%d ----", count);
lcat("Command: ");
// Format multi-line commands into one line
for (size_t i = 0; i < n; i++) {
lcat(lines[i]);
if (i < n - 1) lcat(" | ");
}

for (size_t i = 0; i < n; i++) {
tele_command_t cmd;
char error_msg[TELE_ERROR_MSG_LENGTH];
error_t error = parse(lines[i], &cmd, error_msg);
if (error != E_OK) {
lprintf("%s: %s", error_message(error), error_msg);
log_print();
FAILm("Parser failure.");
}
error = validate(&cmd, error_msg);
if (error != E_OK) {
lprintf("%s: %s", error_message(error), error_msg);
log_print();
FAILm("Validation failure");
}
result = process_command(ss, &es, &cmd);
}

if (result.has_value != true) {
lprintf("Expected a value, found none.");
log_print();
FAIL();
}
if (result.value != answer) {
lprintf("Value incorrect. Expected %d, got %d", answer, result.value);
log_print();
FAIL();
}

PASS();
}

// runs multiple lines of commands and then asserts that the final answer is
// correct
TEST process_helper(size_t n, char *lines[], int16_t answer) {
scene_state_t ss;
memset(&ss, 0, sizeof(ss));
ss_init(&ss);

CHECK_CALL(process_helper_state(&ss, n, lines, answer));

PASS();
}

TEST test_turtle_fence_normal() {
NEWTEST();
char *test1[2] = { "@F 1 2 3 4", "@FX1" };
CHECK_CALL(process_helper(2, test1, 1));
char *test2[2] = { "@F 1 2 3 4", "@FY1" };
CHECK_CALL(process_helper(2, test2, 2));
char *test3[2] = { "@F 1 2 3 4", "@FX2" };
CHECK_CALL(process_helper(2, test3, 3));
char *test4[2] = { "@F 1 2 3 4", "@FY2" };
CHECK_CALL(process_helper(2, test4, 4));
PASS();
}
TEST test_turtle_fence_swapped() {
NEWTEST();
char *test1[2] = { "@F 3 4 1 2", "@FX1" };
CHECK_CALL(process_helper(2, test1, 1));
char *test2[2] = { "@F 3 4 1 2", "@FY1" };
CHECK_CALL(process_helper(2, test2, 2));
char *test3[2] = { "@F 3 4 1 2", "@FX2" };
CHECK_CALL(process_helper(2, test3, 3));
char *test4[2] = { "@F 3 4 1 2", "@FY2" };
CHECK_CALL(process_helper(2, test4, 4));
PASS();
}
TEST test_turtle_fence_oob() {
NEWTEST();
char *test1[2] = { "@F -1 -1 4 100", "@FX1" };
CHECK_CALL(process_helper(2, test1, 0));
char *test2[2] = { "@F -1 -1 4 100", "@FY1" };
CHECK_CALL(process_helper(2, test2, 0));
char *test3[2] = { "@F -1 -1 4 100", "@FX2" };
CHECK_CALL(process_helper(2, test3, 3));
char *test4[2] = { "@F -1 -1 4 100", "@FY2" };
CHECK_CALL(process_helper(2, test4, 63));
PASS();
}
TEST test_turtle_fence_individual() {
NEWTEST();
char *test1[2] = { "@FX1 1", "@FX1" };
CHECK_CALL(process_helper(2, test1, 1));
char *test2[2] = { "@FY1 1", "@FY1" };
CHECK_CALL(process_helper(2, test2, 1));
char *test3[2] = { "@FX2 1", "@FX2" };
CHECK_CALL(process_helper(2, test3, 1));
char *test4[2] = { "@FY2 1", "@FY2" };
CHECK_CALL(process_helper(2, test4, 1));
PASS();
}
TEST test_turtle_fence_ind_swapped() {
NEWTEST();
char *test1[3] = { "@FX1 1", "@FX2 0", "@FX1" };
CHECK_CALL(process_helper(3, test1, 0));
char *test2[3] = { "@FY1 1", "@FY2 0", "@FY1" };
CHECK_CALL(process_helper(3, test2, 0));
char *test3[3] = { "@FX1 1", "@FX2 0", "@FX2" };
CHECK_CALL(process_helper(3, test3, 1));
char *test4[3] = { "@FY1 1", "@FY2 0", "@FY2" };
CHECK_CALL(process_helper(3, test4, 1));
PASS();
}
TEST test_turtle_fence_ind_oob() {
NEWTEST();
char *test1[2] = { "@FX1 -1", "@FX1" };
CHECK_CALL(process_helper(2, test1, 0));
char *test2[2] = { "@FY1 -1", "@FY1" };
CHECK_CALL(process_helper(2, test2, 0));
char *test3[2] = { "@FX2 4", "@FX2" };
CHECK_CALL(process_helper(2, test3, 3));
char *test4[2] = { "@FY2 63", "@FY2" };
CHECK_CALL(process_helper(2, test4, 63));
// TODO more tests
PASS();
}

TEST test_turtle_wrap() {
NEWTEST();
char *test1[3] = { "@WRAP 1", "@MOVE 0 -1", "@Y" };
CHECK_CALL(process_helper(3, test1, 63));
char *test1b[3] = { "@WRAP 1", "@MOVE 0 -1", "@X" };
CHECK_CALL(process_helper(3, test1b, 0));
char *test1c[3] = { "@WRAP 1", "@F 0 0 1 1; @MOVE 0 3", "@Y" };
CHECK_CALL(process_helper(3, test1c, 1));
char *test1d[3] = { "@WRAP 1", "@F 0 0 1 1; @MOVE 0 3", "@X" };
CHECK_CALL(process_helper(3, test1d, 0));
char *test1e[3] = { "@WRAP 1", "@STEP", "@X" };
CHECK_CALL(process_helper(3, test1e, 0));
char *test2[4] = { "@WRAP 1", "@FY2 1", "@MOVE 0 -1", "@Y" };
CHECK_CALL(process_helper(4, test2, 1));
char *test2b[4] = { "@WRAP 1", "@FY2 1", "@MOVE 0 -1", "@X" };
CHECK_CALL(process_helper(4, test2b, 0));
char *test3[3] = { "@WRAP 1", "@MOVE -1 0", "@X" };
CHECK_CALL(process_helper(3, test3, 3));
char *test4[4] = { "@WRAP 1", "@FX1 1", "@MOVE -1 0", "@X" };
CHECK_CALL(process_helper(4, test4, 3));
PASS();
}

TEST test_turtle_bounce() {
NEWTEST();
char *test1[3] = { "@BOUNCE 1", "@MOVE 0 -2", "@Y" };
CHECK_CALL(process_helper(3, test1, 2));
char *test2[3] = { "@BOUNCE 1", "@MOVE 0 64", "@Y" };
CHECK_CALL(process_helper(3, test2, 62));
char *test3[3] = { "@BOUNCE 1", "@MOVE -1 0", "@X" };
CHECK_CALL(process_helper(3, test3, 1));
char *test4[3] = { "@BOUNCE 1", "@MOVE -2 0", "@X" };
CHECK_CALL(process_helper(3, test4, 2));
char *test5[3] = { "@BOUNCE 1", "@MOVE 4 0", "@X" };
CHECK_CALL(process_helper(3, test5, 2));
char *test6[3] = { "@BOUNCE 1", "@MOVE 0 -1", "@Y" };
CHECK_CALL(process_helper(3, test6, 1));
char *test7[3] = { "@BOUNCE 1", "@MOVE 0 126", "@Y" };
CHECK_CALL(process_helper(3, test7, 0));
char *test8[3] = { "@BOUNCE 1", "@MOVE 0 130", "@Y" };
CHECK_CALL(process_helper(3, test8, 4));
char *test9[3] = { "@BOUNCE 1", "@MOVE 3 0", "@X" };
CHECK_CALL(process_helper(3, test9, 3));
char *test10[4] = { "@BOUNCE 1", "@F 0 0 1 1", "@STEP", "@Y" };
CHECK_CALL(process_helper(4, test10, 1));
char *test10b[4] = { "@BOUNCE 1", "@F 0 0 1 1", "@STEP", "@DIR" };
CHECK_CALL(process_helper(4, test10b, 180));

// The following tests reveal the charade that is the length between fences
#if 0
char *test10c[4] = { "@BOUNCE 1", "@F 0 0 1 1", "L 1 2: @STEP", "@DIR" };
CHECK_CALL(process_helper(4, test10c, 0));
char *test10d[4] = { "@BOUNCE 1", "@F 0 0 1 1", "L 1 3: @STEP", "@SPEED" };
CHECK_CALL(process_helper(4, test10d, 100));
char *test13[5] = {
"@F 0 0 1 1",
"@BOUNCE 1",
"@SPEED 300",
"@STEP",
"@DIR"
};
CHECK_CALL(process_helper(5, test13, 0));
char *test14[6] = {
"@F 0 0 1 1",
"@BOUNCE 1",
"@SPEED 400",
"@DIR 135",
"@STEP",
"@DIR"
};
CHECK_CALL(process_helper(6, test14, 315));
char *test15[6] = {
"@F 0 0 1 1",
"@BOUNCE 1",
"@SPEED 141",
"@DIR 135",
"@STEP",
"@Y"
};
CHECK_CALL(process_helper(6, test15, 1));
#endif

char *test12[4] = { "@BOUNCE 1; @SPEED 141", "@DIR 135", "@STEP", "@X" };
CHECK_CALL(process_helper(4, test12, 1));
PASS();
}

/*
TEST test_turtle_F() {
char *testX[Y] = {
"@",
"@",
"@",
"@",
"@",
"@"
};
CHECK_CALL(process_helper(Y, testX, xpct));
PASS();
}
*/

TEST test_turtle_vars() {
NEWTEST();
char *test1[2] = { "@X 1", "@X" };
CHECK_CALL(process_helper(2, test1, 1));
PASS();
}

TEST test_turtle_step() {
char *test1[2] = { "@STEP", "@Y" };
CHECK_CALL(process_helper(2, test1, 1));
char *test2[2] = { "@STEP", "@X" };
CHECK_CALL(process_helper(2, test2, 0));
PASS();
}

SUITE(turtle_suite) {
log_init();
RUN_TEST(test_turtle_fence_normal);
RUN_TEST(test_turtle_fence_swapped);
RUN_TEST(test_turtle_fence_oob);
RUN_TEST(test_turtle_fence_individual);
RUN_TEST(test_turtle_fence_ind_swapped);
RUN_TEST(test_turtle_fence_ind_oob);
RUN_TEST(test_turtle_wrap);
RUN_TEST(test_turtle_bounce);
RUN_TEST(test_turtle_vars);
RUN_TEST(test_turtle_step);
}
@@ -0,0 +1,8 @@
#ifndef _OP_TURTLE_TESTS_H_
#define _OP_TURTLE_TESTS_H_

#include "greatest/greatest.h"

SUITE_EXTERN(turtle_suite);

#endif
@@ -64,7 +64,24 @@ def _convert_struct_name_to_op_name(name):
"SYM_RIGHT_ANGLED_x2": ">>",
"SYM_AMPERSAND_x2": "&&",
"SYM_PIPE_x2": "||",
"M_SYM_EXCLAMATION": "M!"
"M_SYM_EXCLAMATION": "M!",
"TURTLE": "@",
"TURTLE_X": "@X",
"TURTLE_Y": "@Y",
"TURTLE_MOVE": "@MOVE",
"TURTLE_DIR": "@DIR",
"TURTLE_SPEED": "@SPEED",
"TURTLE_STEP": "@STEP",
"TURTLE_F": "@F",
"TURTLE_FX1": "@FX1",
"TURTLE_FX2": "@FX2",
"TURTLE_FY1": "@FY1",
"TURTLE_FY2": "@FY2",
"TURTLE_BUMP": "@BUMP",
"TURTLE_WRAP": "@WRAP",
"TURTLE_BOUNCE": "@BOUNCE",
"TURTLE_SCRIPT": "@SCRIPT",
"TURTLE_SHOW": "@SHOW"
}

if stripped in MAPPINGS:
@@ -23,7 +23,9 @@
autoescape=False,
loader=jinja2.FileSystemLoader(str(TEMPLATE_DIR)),
trim_blocks=True,
lstrip_blocks=True
lstrip_blocks=True,
cache_size=0,
auto_reload=True
)

# determines the order in which sections are displayed
@@ -37,6 +39,7 @@
"delay",
"stack",
"queue",
"turtle",
"ansible",
"whitewhale",
"meadowphysics",
@@ -136,7 +139,7 @@ def common_md():
sorted_ops = [kv[1] for kv in sorted(all_ops_dict.items())]
output += op_table_template.render(ops=sorted_ops)

output += "# Missing documentation\n\n"
output += "\n\n# Missing documentation\n\n"
missing_ops = all_ops - ops_with_docs
output += ", ".join([f"`{o}`" for o in sorted(missing_ops)]) + "\n\n"