aboutsummaryrefslogtreecommitdiffstats
path: root/prototypes
diff options
context:
space:
mode:
Diffstat (limited to 'prototypes')
-rw-r--r--prototypes/connections/module.c156
-rw-r--r--prototypes/connections/module.h26
-rw-r--r--prototypes/connections/pure-run.c52
-rw-r--r--prototypes/connections/test-module.c260
4 files changed, 395 insertions, 99 deletions
diff --git a/prototypes/connections/module.c b/prototypes/connections/module.c
index 2e5375c..4b5d15d 100644
--- a/prototypes/connections/module.c
+++ b/prototypes/connections/module.c
@@ -12,9 +12,9 @@ enum { STATE_BYTES = 16, COLLECTION_BYTES = 12, INPUT_BYTES = 16, FIELD_BYTES =
enum { T_NUM = 1, T_BYTES = 2, T_TEXT = 3, T_CURSOR = 4, T_FIELD = 5, T_INDEX = 6, T_ITEM = 7 };
enum { FROM_PAYLOAD, FROM_DOCUMENT, FROM_COPY, FROM_CONSTANT, FROM_TEXT, FROM_ARENA };
/* Where an opcode may appear. */
-enum { IN_VIEW = 1, IN_HANDLER = 2, IN_MESSAGE = 4, IN_KEYED = 8, IN_TEXT = 16 };
+enum { IN_VIEW = 1, IN_HANDLER = 2, IN_MESSAGE = 4, IN_KEYED = 8, IN_TEXT = 16, IN_ROW = 32 };
enum { READ = IN_VIEW | IN_HANDLER };
-static const unsigned char magic[8] = {'P','U','R','E','M','O','D','4'};
+static const unsigned char magic[8] = {'P','U','R','E','M','O','D','5'};
/* Operand types popped (bottom to top), the type pushed, and where allowed. */
static const struct { unsigned char pops, in[3], out, where; } signature[PURE_OP_COUNT] = {
@@ -39,8 +39,8 @@ static const struct { unsigned char pops, in[3], out, where; } signature[PURE_OP
[PURE_OP_MAX] = {2, {T_NUM, T_NUM}, T_NUM, READ},
[PURE_OP_CLAMP] = {3, {T_NUM, T_NUM, T_NUM}, T_NUM, READ},
[PURE_OP_STORE] = {1, {T_NUM}, 0, IN_HANDLER},
- [PURE_OP_JZ] = {1, {T_NUM}, 0, IN_HANDLER},
- [PURE_OP_JMP] = {0, {0}, 0, IN_HANDLER},
+ [PURE_OP_JZ] = {1, {T_NUM}, 0, READ},
+ [PURE_OP_JMP] = {0, {0}, 0, READ},
[PURE_OP_FIELD] = {0, {0}, T_FIELD, IN_MESSAGE},
[PURE_OP_DOC] = {0, {0}, T_BYTES, IN_KEYED},
[PURE_OP_DOC_SIZE] = {0, {0}, T_NUM, IN_KEYED},
@@ -81,6 +81,7 @@ static const struct { unsigned char pops, in[3], out, where; } signature[PURE_OP
[PURE_OP_INDEX_FIRST] = {0, {0}, T_INDEX, READ},
[PURE_OP_INDEX_LAST] = {0, {0}, T_INDEX, READ},
[PURE_OP_INDEX_NUM] = {1, {T_INDEX}, T_NUM, READ},
+ [PURE_OP_ROW] = {0, {0}, T_INDEX, IN_ROW},
};
static uint32_t read16(const unsigned char *p) { return (uint32_t)p[0] | (uint32_t)p[1] << 8; }
@@ -151,7 +152,7 @@ static bool uses_arg(uint8_t op) {
case PURE_OP_TEXT_LINE: case PURE_OP_TEXT_SET: case PURE_OP_CUR_COUNT: case PURE_OP_LIST_COUNT:
case PURE_OP_LIST_APPEND: case PURE_OP_LIST_INSERT: case PURE_OP_LIST_REMOVE: case PURE_OP_ITEM_GET:
case PURE_OP_ITEM_SET: case PURE_OP_INDEX_LOAD: case PURE_OP_INDEX_STORE: case PURE_OP_INDEX_FIRST:
- case PURE_OP_INDEX_LAST:
+ case PURE_OP_INDEX_LAST: case PURE_OP_ROW:
return true;
default: return false;
}
@@ -170,35 +171,62 @@ static unsigned char field_type(const PureModule *m, uint32_t list, uint32_t fie
return m->record_field_text[m->record[m->state_ref[list]].first + field] ? T_TEXT : T_NUM;
}
+/* The stack a branch brings to its target, while the target is ahead. */
+typedef struct { uint32_t target; int height; unsigned char types[PURE_MODULE_STACK], owners[PURE_MODULE_STACK]; } Join;
+
+static bool same_stack(const Join *join, int height, const unsigned char *types, const unsigned char *owners) {
+ return join->height == height && !memcmp(join->types, types, (size_t)height) &&
+ !memcmp(join->owners, owners, (size_t)height);
+}
+
/* Types and heights are checked per instruction; a cursor's type records
* which text it belongs to and an index's type which list. Branches go
- * forward within the block and every join happens with an empty operand
- * stack, so control flow never merges typed values. Handlers end empty,
- * views with one value of their type. */
-static bool verify_block(const PureModule *m, PureBlock block, int view, uint32_t event,
+ * forward within the block. Every path into a join brings the same stack
+ * (types and owners), so a value may cross a join, as in a conditional
+ * expression; at most 8 joins with a non-empty stack are ahead at once.
+ * Handlers end empty, views with one value of their type. A column (row
+ * is its list) may read the row it is evaluated for. */
+static bool verify_block(const PureModule *m, PureBlock block, int view, int row, uint32_t event,
char *error, size_t size, unsigned index) {
if (block.start > m->code_count || block.length > m->code_count - block.start)
return reject(error, size, "code range outside module", index);
if (view >= 0 && !block.length) return reject(error, size, "empty view", index);
int input = view >= 0 ? -1 : event_input(event);
const PureInputShape *shape = input >= 0 ? &m->input[input] : NULL;
- unsigned where = view >= 0 ? IN_VIEW : IN_HANDLER;
+ unsigned where = view >= 0 ? IN_VIEW | (row >= 0 ? IN_ROW : 0) : IN_HANDLER;
if (shape && shape->kind == PURE_KIND_MESSAGE) where |= IN_MESSAGE | (shape->keyed ? IN_KEYED : 0);
if (shape && shape->kind == PURE_KIND_TEXT) where |= IN_TEXT;
unsigned char types[PURE_MODULE_STACK], owners[PURE_MODULE_STACK], target[PURE_MODULE_CODE + 1] = {0};
+ Join joins[PURE_MODULE_JOINS];
+ unsigned open = 0;
int height = 0;
bool reachable = true;
for (uint32_t i = 0; i <= block.length; ++i) {
uint32_t pc = block.start + i;
- if (!reachable && target[i]) { reachable = true; height = 0; }
- else if (target[i] && height) return reject(error, size, "join with a non-empty stack", pc);
+ /* Branches here brought an empty stack (target) or the stack of a join. */
+ unsigned join = open;
+ for (unsigned k = 0; k < open; ++k) if (joins[k].target == pc) join = k;
+ if (join < open) {
+ if (target[i] || (reachable && !same_stack(&joins[join], height, types, owners)))
+ return reject(error, size, "join with different stacks", pc);
+ height = joins[join].height;
+ memcpy(types, joins[join].types, (size_t)height);
+ memcpy(owners, joins[join].owners, (size_t)height);
+ joins[join] = joins[--open];
+ reachable = true;
+ } else if (target[i]) {
+ if (reachable && height) return reject(error, size, "join with different stacks", pc);
+ height = 0;
+ reachable = true;
+ }
if (i == block.length) break;
if (!reachable) return reject(error, size, "unreachable instruction", pc);
const PureOp *op = &m->code[pc];
if (op->op >= PURE_OP_COUNT) return reject(error, size, "unknown opcode", pc);
unsigned allowed = signature[op->op].where;
if (!(allowed & where)) {
- if (view >= 0) return reject(error, size, "view with an effect or branch", pc);
+ if (allowed & IN_ROW) return reject(error, size, "row outside a column", pc);
+ if (view >= 0) return reject(error, size, "view with an effect", pc);
if (allowed & IN_KEYED) return reject(error, size, "document operation outside a keyed message handler", pc);
if (allowed & IN_TEXT) return reject(error, size, "text input outside a text handler", pc);
return reject(error, size, "message operation outside a message handler", pc);
@@ -228,6 +256,7 @@ static bool verify_block(const PureModule *m, PureBlock block, int view, uint32_
item_field(arg) < m->record[m->state_ref[list_of(arg)]].count;
break;
case PURE_OP_INDEX_LOAD: case PURE_OP_INDEX_STORE: known = state_of(m, arg, PURE_STATE_INDEX); break;
+ case PURE_OP_ROW: known = (int)arg == row; break;
default: break;
}
if (!known) return reject(error, size, "operand names no such state, field, constant or unit", pc);
@@ -275,7 +304,7 @@ static bool verify_block(const PureModule *m, PureBlock block, int view, uint32_
if (height == PURE_MODULE_STACK) return reject(error, size, "stack overflow", pc);
owners[height] = (unsigned char)(op->op == PURE_OP_CUR_LOAD || op->op == PURE_OP_INDEX_LOAD ? m->state_ref[arg] :
op->op == PURE_OP_CUR_START || op->op == PURE_OP_CUR_END ||
- op->op == PURE_OP_INDEX_FIRST || op->op == PURE_OP_INDEX_LAST ? arg :
+ op->op == PURE_OP_INDEX_FIRST || op->op == PURE_OP_INDEX_LAST || op->op == PURE_OP_ROW ? arg :
op->op == PURE_OP_INDEX_MOVE ? index_owner :
owner >= 0 ? (unsigned)owner : 0);
types[height++] = out;
@@ -283,8 +312,20 @@ static bool verify_block(const PureModule *m, PureBlock block, int view, uint32_
if (op->op == PURE_OP_JZ || op->op == PURE_OP_JMP) {
if (arg <= pc || arg > block.start + block.length)
return reject(error, size, "branch not forward within its block", pc);
- if (height) return reject(error, size, "branch with a non-empty stack", pc);
- target[arg - block.start] = 1;
+ unsigned join = open;
+ for (unsigned k = 0; k < open; ++k) if (joins[k].target == arg) join = k;
+ if (!height) {
+ if (join < open) return reject(error, size, "join with different stacks", pc);
+ target[arg - block.start] = 1;
+ } else if (join < open) {
+ if (!same_stack(&joins[join], height, types, owners)) return reject(error, size, "join with different stacks", pc);
+ } else {
+ if (target[arg - block.start]) return reject(error, size, "join with different stacks", pc);
+ if (open == PURE_MODULE_JOINS) return reject(error, size, "more than 8 joins ahead", pc);
+ joins[open] = (Join){.target = arg, .height = height};
+ memcpy(joins[open].types, types, (size_t)height);
+ memcpy(joins[open++].owners, owners, (size_t)height);
+ }
if (op->op == PURE_OP_JMP) reachable = false;
}
}
@@ -446,12 +487,22 @@ PureResult pure_module_load(const void *bytes, size_t size, PureModule *out,
(out->state_type[i] == PURE_STATE_LIST && out->state_ref[i] >= out->records) ||
(out->state_type[i] == PURE_STATE_INDEX && !state_of(out, out->state_ref[i], PURE_STATE_LIST)))
return reject(error, error_size, "state reference", i), PURE_INVALID;
+ /* A table names its list in place of code; its columns follow it. */
for (uint32_t i = 0; i < out->views; ++i, at += VIEW_BYTES) {
if (!name_at(text, strings, read32(at), true, out->view_names[i]))
return reject(error, error_size, "view name", i), PURE_INVALID;
- if (read32(at + 4) > PURE_VIEW_TEXT) return reject(error, error_size, "view type", i), PURE_INVALID;
- out->view_type[i] = (uint8_t)read32(at + 4);
+ out->view_type[i] = at[4];
+ out->view_table[i] = (uint16_t)read16(at + 6);
out->view_code[i] = (PureBlock){read32(at + 8), read32(at + 12)};
+ uint32_t table = out->view_table[i];
+ if (at[4] > PURE_VIEW_TABLE || at[5] || (table && (table > i || out->view_type[table - 1] != PURE_VIEW_TABLE ||
+ at[4] == PURE_VIEW_TABLE)))
+ return reject(error, error_size, "view type", i), PURE_INVALID;
+ if (at[4] == PURE_VIEW_TABLE) {
+ if (!state_of(out, out->view_code[i].start, PURE_STATE_LIST) || out->view_code[i].length)
+ return reject(error, error_size, "table list", i), PURE_INVALID;
+ out->view_list[i] = (uint16_t)out->view_code[i].start;
+ } else if (table) out->view_list[i] = out->view_list[table - 1];
}
for (uint32_t i = 0; i < out->handlers; ++i, at += HANDLER_BYTES) {
uint32_t event = read32(at);
@@ -472,14 +523,22 @@ PureResult pure_module_load(const void *bytes, size_t size, PureModule *out,
for (uint32_t i = 0; i < out->records; ++i) names[count++] = out->record_names[i];
for (uint32_t i = 0; i < out->collections; ++i) names[count++] = out->collection_names[i];
for (uint32_t i = 0; i < out->inputs; ++i) names[count++] = out->input_names[i];
- for (uint32_t i = 0; i < out->views; ++i) names[count++] = out->view_names[i];
+ for (uint32_t i = 0; i < out->views; ++i) if (!out->view_table[i]) names[count++] = out->view_names[i];
for (size_t i = 0; i < count; ++i)
for (size_t k = 0; k < i; ++k)
if (!strcmp(names[i], names[k])) return reject(error, error_size, "duplicate name", (unsigned)i), PURE_INVALID;
+ /* Column names are unique within their table. */
for (uint32_t i = 0; i < out->views; ++i)
- if (!verify_block(out, out->view_code[i], out->view_type[i], 0, error, error_size, i)) return PURE_INVALID;
+ for (uint32_t k = 0; k < i; ++k)
+ if (out->view_table[i] && out->view_table[k] == out->view_table[i] &&
+ !strcmp(out->view_names[i], out->view_names[k]))
+ return reject(error, error_size, "duplicate column", i), PURE_INVALID;
+ for (uint32_t i = 0; i < out->views; ++i)
+ if (out->view_type[i] != PURE_VIEW_TABLE &&
+ !verify_block(out, out->view_code[i], out->view_type[i], out->view_table[i] ? out->view_list[i] : -1, 0,
+ error, error_size, i)) return PURE_INVALID;
for (uint32_t i = 0; i < out->handlers; ++i)
- if (!verify_block(out, out->handler[i].code, -1, out->handler[i].event, error, error_size, i))
+ if (!verify_block(out, out->handler[i].code, -1, -1, out->handler[i].event, error, error_size, i))
return PURE_INVALID;
return PURE_OK;
}
@@ -492,7 +551,17 @@ static int find(const char (*names)[PURE_MODULE_NAME], unsigned count, const cha
int pure_module_state(const PureModule *m, const char *name) { return find(m->state_names, m->states, name); }
int pure_module_input(const PureModule *m, const char *name) { return find(m->input_names, m->inputs, name); }
-int pure_module_view(const PureModule *m, const char *name) { return find(m->view_names, m->views, name); }
+int pure_module_view(const PureModule *m, const char *name) {
+ for (uint32_t i = 0; i < m->views; ++i)
+ if (!m->view_table[i] && !strcmp(m->view_names[i], name)) return (int)i;
+ return -1;
+}
+
+int pure_module_column(const PureModule *m, uint32_t table, const char *name) {
+ for (uint32_t i = 0; i < m->views; ++i)
+ if (m->view_table[i] == table + 1 && !strcmp(m->view_names[i], name)) return (int)i;
+ return -1;
+}
/* ---- Execution ------------------------------------------------------ */
@@ -506,6 +575,7 @@ typedef struct {
const PureInputShape *shape; /* message, or NULL */
PureArena *arena;
double dt;
+ size_t row; /* the row a column is evaluated for */
bool modified, exists;
} Event;
@@ -917,6 +987,11 @@ static PureResult run(const PureModule *m, double *values, PureBlock block, Even
continue;
}
case PURE_OP_INDEX_NUM: ++pc; continue; /* the position itself */
+ case PURE_OP_ROW:
+ stack[sp].num = (double)ev->row;
+ stack[sp++].bytes.owner = (uint16_t)op->arg;
+ ++pc;
+ continue;
default: break;
}
b = stack[--sp].num;
@@ -1133,11 +1208,11 @@ PureResult pure_app_reply(const PureApp *app, bool (*visit)(void *, PureSpan), v
}
/* A view that fails reports it by its result; the fault stays the last event's. */
-static PureResult evaluate(PureApp *app, uint32_t view, Slot *out) {
+static PureResult evaluate(PureApp *app, uint32_t view, size_t row, Slot *out) {
double values[PURE_MODULE_STATES];
memcpy(values, app->values, sizeof(values));
app->view_arena.used = 0;
- Event ev = {.app = app, .arena = &app->view_arena};
+ Event ev = {.app = app, .arena = &app->view_arena, .row = row};
PureFault fault = app->fault;
uint32_t fault_pc = app->fault_pc;
PureResult result = run(app->module, values, app->module->view_code[view], &ev, out);
@@ -1146,18 +1221,43 @@ static PureResult evaluate(PureApp *app, uint32_t view, Slot *out) {
return result;
}
+/* A view outside a table (column false), or a column and a row of its table. */
+static PureResult view_of(PureApp *app, uint32_t view, bool column, size_t row, uint8_t type, Slot *slot) {
+ const PureModule *m = app->module;
+ if (view >= m->views || m->view_type[view] != type || (m->view_table[view] != 0) != column) return PURE_INVALID;
+ if (column && row >= pure_list_count(app->lists[m->view_list[view]])) return PURE_NOT_FOUND;
+ return evaluate(app, view, row, slot);
+}
+
bool pure_app_view(PureApp *app, uint32_t view, double *out) {
- if (view >= app->module->views || app->module->view_type[view] != PURE_VIEW_NUM) return false;
Slot slot;
- if (evaluate(app, view, &slot) != PURE_OK) return false;
+ if (view_of(app, view, false, 0, PURE_VIEW_NUM, &slot) != PURE_OK) return false;
*out = slot.num;
return true;
}
PureResult pure_app_view_text(PureApp *app, uint32_t view, bool (*visit)(void *, PureSpan), void *context) {
- if (view >= app->module->views || app->module->view_type[view] != PURE_VIEW_TEXT) return PURE_INVALID;
Slot slot;
- PureResult result = evaluate(app, view, &slot);
+ PureResult result = view_of(app, view, false, 0, PURE_VIEW_TEXT, &slot);
+ return result == PURE_OK ? visit_value(app, &app->view_arena, slot.bytes, visit, context) : result;
+}
+
+size_t pure_app_rows(const PureApp *app, uint32_t table) {
+ const PureModule *m = app->module;
+ if (table >= m->views || m->view_type[table] != PURE_VIEW_TABLE) return 0;
+ return pure_list_count(app->lists[m->view_list[table]]);
+}
+
+bool pure_app_cell(PureApp *app, uint32_t column, size_t row, double *out) {
+ Slot slot;
+ if (view_of(app, column, true, row, PURE_VIEW_NUM, &slot) != PURE_OK) return false;
+ *out = slot.num;
+ return true;
+}
+
+PureResult pure_app_cell_text(PureApp *app, uint32_t column, size_t row, bool (*visit)(void *, PureSpan), void *context) {
+ Slot slot;
+ PureResult result = view_of(app, column, true, row, PURE_VIEW_TEXT, &slot);
return result == PURE_OK ? visit_value(app, &app->view_arena, slot.bytes, visit, context) : result;
}
diff --git a/prototypes/connections/module.h b/prototypes/connections/module.h
index 6774cca..749f9d0 100644
--- a/prototypes/connections/module.h
+++ b/prototypes/connections/module.h
@@ -8,18 +8,18 @@
#include <stddef.h>
#include <stdint.h>
-/* Pure module format v4 (docs/design/008-module-format.md). A toolchain such
+/* Pure module format v5 (docs/design/008-module-format.md). A toolchain such
* as aup produces it; Pure verifies it completely before anything executes:
* operand types (including which text a cursor and which list an index
- * belongs to), stack heights, forward-only branches, names, records,
- * constants and effects. */
+ * belongs to), stack heights and the stack at every join, forward-only
+ * branches, names, records, tables, constants and effects. */
enum {
- PURE_MODULE_STATES = 32, PURE_MODULE_INPUTS = 32, PURE_MODULE_VIEWS = 24,
+ PURE_MODULE_STATES = 32, PURE_MODULE_INPUTS = 32, PURE_MODULE_VIEWS = 64,
PURE_MODULE_HANDLERS = 64, PURE_MODULE_CODE = 512, PURE_MODULE_STACK = 64,
PURE_MODULE_NAME = 32, PURE_MODULE_STRINGS = 32768, PURE_MODULE_HEADER = 40,
PURE_MODULE_FIELDS = 32, PURE_MODULE_COLLECTIONS = 1, PURE_MODULE_CONSTANTS = 64,
PURE_MODULE_CONSTANT_BYTES = 8192, PURE_MODULE_RECORDS = 8, PURE_MODULE_RECORD_FIELDS = 64,
- PURE_RECORD_FIELDS = 16
+ PURE_RECORD_FIELDS = 16, PURE_MODULE_JOINS = 8
};
typedef enum {
@@ -36,7 +36,7 @@ typedef enum {
PURE_OP_TEXT_SET, PURE_OP_TEXT_FROM, PURE_OP_BYTES_FROM, PURE_OP_CUR_COUNT,
PURE_OP_LIST_COUNT, PURE_OP_LIST_APPEND, PURE_OP_LIST_INSERT, PURE_OP_LIST_REMOVE,
PURE_OP_ITEM_GET, PURE_OP_ITEM_SET, PURE_OP_INDEX_LOAD, PURE_OP_INDEX_STORE,
- PURE_OP_INDEX_MOVE, PURE_OP_INDEX_FIRST, PURE_OP_INDEX_LAST, PURE_OP_INDEX_NUM,
+ PURE_OP_INDEX_MOVE, PURE_OP_INDEX_FIRST, PURE_OP_INDEX_LAST, PURE_OP_INDEX_NUM, PURE_OP_ROW,
PURE_OP_COUNT
} PureOpcode;
@@ -45,7 +45,9 @@ typedef enum { PURE_FIELD_U32 = 1, PURE_FIELD_BYTES = 2 } PureFieldType;
typedef enum {
PURE_STATE_NUM = 0, PURE_STATE_TEXT = 1, PURE_STATE_CURSOR = 2, PURE_STATE_LIST = 3, PURE_STATE_INDEX = 4
} PureStateType;
-typedef enum { PURE_VIEW_NUM = 0, PURE_VIEW_TEXT = 1 } PureViewType;
+/* A table view has one row per item of a list; its columns are number or
+ * text views evaluated for one row at a time. */
+typedef enum { PURE_VIEW_NUM = 0, PURE_VIEW_TEXT = 1, PURE_VIEW_TABLE = 2 } PureViewType;
typedef enum { PURE_UNIT_CHAR = 0, PURE_UNIT_LINE = 1 } PureUnit;
typedef enum { PURE_EDGE_DOWN, PURE_EDGE_UP } PureEdge;
typedef enum {
@@ -81,6 +83,8 @@ typedef struct {
PureField field[PURE_MODULE_FIELDS];
double initial[PURE_MODULE_STATES];
uint8_t view_type[PURE_MODULE_VIEWS];
+ uint16_t view_table[PURE_MODULE_VIEWS]; /* a column's table view + 1, or 0 */
+ uint16_t view_list[PURE_MODULE_VIEWS]; /* the list of a table or column */
PureBlock view_code[PURE_MODULE_VIEWS];
PureHandler handler[PURE_MODULE_HANDLERS];
PureOp code[PURE_MODULE_CODE];
@@ -96,10 +100,11 @@ typedef struct {
/* Decode and verify. On failure out is unspecified and error names the rule. */
PureResult pure_module_load(const void *bytes, size_t size, PureModule *out,
char *error, size_t error_size);
-/* Index of a name, or -1. */
+/* Index of a name, or -1. Views are those outside tables. */
int pure_module_state(const PureModule *, const char *name);
int pure_module_input(const PureModule *, const char *name);
int pure_module_view(const PureModule *, const char *name);
+int pure_module_column(const PureModule *, uint32_t table, const char *name);
/* Keyed collections live in a data host (for example the tree store). One
* event opens at most one document, the message's key, at its current
@@ -163,6 +168,11 @@ void pure_app_finish(PureApp *);
bool pure_app_view(PureApp *, uint32_t view, double *out);
/* A text view, lent in order until the next view evaluation. */
PureResult pure_app_view_text(PureApp *, uint32_t view, bool (*visit)(void *, PureSpan), void *context);
+/* The rows of a table view (its list's items), or 0 for another view. A
+ * host evaluates only the cells it shows. */
+size_t pure_app_rows(const PureApp *, uint32_t table);
+bool pure_app_cell(PureApp *, uint32_t column, size_t row, double *out);
+PureResult pure_app_cell_text(PureApp *, uint32_t column, size_t row, bool (*visit)(void *, PureSpan), void *context);
/* Switch to another verified module. States keep their value by name (text
* states keep their content, cursors their position in that text); new states
* start at their initial value. The module must outlive the app. */
diff --git a/prototypes/connections/pure-run.c b/prototypes/connections/pure-run.c
index 7f9f316..8a9788f 100644
--- a/prototypes/connections/pure-run.c
+++ b/prototypes/connections/pure-run.c
@@ -8,7 +8,8 @@
* JSON snapshot per command. Not a product interface.
* down NAME | up NAME | frame [N] | type NAME HEX|- | replace PATH | show
* Text states and text views print as JSON strings; cursors as byte positions;
- * lists as arrays of objects; indices as positions. A failed view is null. */
+ * lists as arrays of objects; indices as positions; a table view as an array
+ * with an object of its columns per row. A failed view or cell is null. */
static const char *const fault_names[] = {"none", "divide", "non_finite", "clamp", "limit", "not_found", "encoding"};
static bool load(const char *path, PureModule *module) {
@@ -67,6 +68,27 @@ static void show_list(const PureModule *m, const PureList *list, const PureRecor
putchar(']');
}
+/* A number or text view, or a column at a row. */
+static void show_value(PureApp *app, uint32_t view, size_t row) {
+ const PureModule *m = app->module;
+ static Collected text;
+ text.used = 0;
+ text.fits = true;
+ double value;
+ bool column = m->view_table[view];
+ if (m->view_type[view] == PURE_VIEW_TEXT) {
+ PureResult result = column ? pure_app_cell_text(app, view, row, collect, &text)
+ : pure_app_view_text(app, view, collect, &text);
+ if (result != PURE_OK || !text.fits) printf("null");
+ else {
+ putchar('"');
+ json_span(NULL, (PureSpan){text.bytes, text.used});
+ putchar('"');
+ }
+ } else if (column ? pure_app_cell(app, view, row, &value) : pure_app_view(app, view, &value)) printf("%.17g", value);
+ else printf("null");
+}
+
static void show(PureApp *app, const char *failure) {
const PureModule *m = app->module;
printf("{\"states\":{");
@@ -80,21 +102,23 @@ static void show(PureApp *app, const char *failure) {
else printf("%.17g", app->values[i]);
}
printf("},\"views\":{");
+ bool first = true;
for (uint32_t i = 0; i < m->views; ++i) {
- printf("%s\"%s\":", i ? "," : "", m->view_names[i]);
- double value;
- static Collected text;
- text.used = 0;
- text.fits = true;
- if (m->view_type[i] == PURE_VIEW_TEXT) {
- if (pure_app_view_text(app, i, collect, &text) != PURE_OK || !text.fits) printf("null");
- else {
- putchar('"');
- json_span(NULL, (PureSpan){text.bytes, text.used});
- putchar('"');
+ if (m->view_table[i]) continue;
+ printf("%s\"%s\":", first ? "" : ",", m->view_names[i]);
+ first = false;
+ if (m->view_type[i] != PURE_VIEW_TABLE) { show_value(app, i, 0); continue; }
+ putchar('[');
+ for (size_t row = 0; row < pure_app_rows(app, i); ++row) {
+ printf("%s{", row ? "," : "");
+ for (uint32_t c = 0, shown = 0; c < m->views; ++c) {
+ if (m->view_table[c] != i + 1) continue;
+ printf("%s\"%s\":", shown++ ? "," : "", m->view_names[c]);
+ show_value(app, c, row);
}
- } else if (pure_app_view(app, i, &value)) printf("%.17g", value);
- else printf("null");
+ putchar('}');
+ }
+ putchar(']');
}
printf("},\"tick\":%llu,\"successfulEvents\":%llu,\"fault\":\"%s\",\"error\":%s%s%s}\n",
(unsigned long long)app->ticks, (unsigned long long)app->events, fault_names[app->fault],
diff --git a/prototypes/connections/test-module.c b/prototypes/connections/test-module.c
index 8867e0a..3c12d49 100644
--- a/prototypes/connections/test-module.c
+++ b/prototypes/connections/test-module.c
@@ -23,11 +23,14 @@ typedef struct { const char *name; uint32_t type, ref; } T;
* default is the constant numbered initial. */
typedef struct { const char *name; uint32_t text; double initial; } RF;
typedef struct { const char *name; const RF *fields; size_t nfields; } R;
+/* A further view: a table (type 2, start = list state) or a column of the
+ * view numbered table - 1. */
+typedef struct { const char *name; uint32_t type, table, start, length; } W;
typedef struct {
const S *s; size_t ns; const I *in; size_t ni; const V *v; size_t nv;
const H *h; size_t nh; const O *o; size_t no; const char *const *c; size_t nc;
const T *t; size_t nt; const char *const *k; size_t nk; const V *tv; size_t ntv;
- const R *r; size_t nr;
+ const R *r; size_t nr; const W *w; size_t nw;
} Spec;
static unsigned char *put32(unsigned char *p, uint32_t v) {
@@ -42,7 +45,7 @@ static unsigned char *put_double(unsigned char *p, double value) {
return put32(p + 4, (uint32_t)(bits >> 32));
}
-/* Test-side encoder for format v4; the toolchain has its own. */
+/* Test-side encoder for format v5; the toolchain has its own. */
static size_t build(unsigned char *out, Spec spec) {
unsigned char strings[4096], blob[4096];
size_t used = 0, blob_used = 0;
@@ -59,6 +62,7 @@ static size_t build(unsigned char *out, Spec spec) {
}
for (size_t i = 0; i < spec.nv; ++i) text[names++] = spec.v[i].name;
for (size_t i = 0; i < spec.ntv; ++i) text[names++] = spec.tv[i].name;
+ for (size_t i = 0; i < spec.nw; ++i) text[names++] = spec.w[i].name;
size_t record_names = names, record_fields = 0;
for (size_t i = 0; i < spec.nr; ++i) {
text[names++] = spec.r[i].name;
@@ -77,8 +81,8 @@ static size_t build(unsigned char *out, Spec spec) {
for (size_t i = 0; i < spec.nk; ++i) { memcpy(blob + blob_used, spec.k[i], strlen(spec.k[i])); blob_used += strlen(spec.k[i]); }
unsigned char *p = out;
memset(p, 0, 40);
- memcpy(p, "PUREMOD4", 8);
- size_t states = spec.ns + spec.nt, views = spec.nv + spec.ntv;
+ memcpy(p, "PUREMOD5", 8);
+ size_t states = spec.ns + spec.nt, views = spec.nv + spec.ntv + spec.nw;
p[8] = (unsigned char)states; p[9] = (unsigned char)(states >> 8);
p[10] = (unsigned char)spec.ni; p[12] = (unsigned char)views; p[14] = (unsigned char)spec.nh;
put32(p + 16, (uint32_t)spec.no);
@@ -128,6 +132,13 @@ static size_t build(unsigned char *out, Spec spec) {
}
for (size_t i = 0; i < spec.nv; ++i) { p = put32(p, offset[n++]); p = put32(p, 0); p = put32(p, spec.v[i].start); p = put32(p, spec.v[i].length); }
for (size_t i = 0; i < spec.ntv; ++i) { p = put32(p, offset[n++]); p = put32(p, 1); p = put32(p, spec.tv[i].start); p = put32(p, spec.tv[i].length); }
+ for (size_t i = 0; i < spec.nw; ++i) {
+ const W *w = &spec.w[i];
+ p = put32(p, offset[n++]);
+ *p++ = (unsigned char)w->type; *p++ = 0; *p++ = (unsigned char)w->table; *p++ = (unsigned char)(w->table >> 8);
+ p = put32(p, w->start);
+ p = put32(p, w->length);
+ }
for (size_t i = 0; i < spec.nh; ++i) { p = put32(p, spec.h[i].event); p = put32(p, spec.h[i].start); p = put32(p, spec.h[i].length); }
for (size_t i = 0; i < spec.no; ++i) {
*p++ = spec.o[i].op; *p++ = 0; *p++ = 0; *p++ = 0;
@@ -149,7 +160,7 @@ static PureResult load(Spec spec, PureModule *out) {
}
#define LEN(a) (sizeof(a) / sizeof(*(a)))
-#define SPEC(s, in, v, h, o) ((Spec){s, LEN(s), in, LEN(in), v, LEN(v), h, LEN(h), o, LEN(o), NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0})
+#define SPEC(s, in, v, h, o) ((Spec){s, LEN(s), in, LEN(in), v, LEN(v), h, LEN(h), o, LEN(o), NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0})
enum { C = PURE_OP_CONST, L = PURE_OP_LOAD, DT = PURE_OP_DT, ADD = PURE_OP_ADD,
MUL = PURE_OP_MUL, DIV = PURE_OP_DIV, GT = PURE_OP_GT, MIN = PURE_OP_MIN,
@@ -204,7 +215,7 @@ static void behaviour(void) {
static const V no_views[1];
static const H no_handlers[1];
static const O no_code[1];
- CHECK(load((Spec){next_states, 2, no_inputs, 0, no_views, 0, no_handlers, 0, no_code, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0}, &module_b) == PURE_OK);
+ CHECK(load((Spec){next_states, 2, no_inputs, 0, no_views, 0, no_handlers, 0, no_code, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0}, &module_b) == PURE_OK);
CHECK(pure_app_replace(&app, &module_b) == PURE_OK);
CHECK(app.module == &module_b && app.values[0] == 7 && app.values[1] == 25);
CHECK(pure_app_replace(&app, &module_a) == PURE_OK);
@@ -280,28 +291,28 @@ static void rejections(void) {
for (int i = 0; i < 65; ++i) overflow[i] = (O){C, 0, 1};
overflow[65] = (O){ST, 0, 0};
const Rejection cases[] = {
- {"duplicate name", {dup, 2, i1, 0, clash, 0, none, 0, load_a, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0}, PURE_INVALID},
- {"state name", {bad_name, 1, i1, 0, clash, 0, none, 0, load_a, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0}, PURE_INVALID},
- {"state name", {long_name, 1, i1, 0, clash, 0, none, 0, load_a, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0}, PURE_INVALID},
- {"non-finite initial value", {non_finite, 1, i1, 0, clash, 0, none, 0, load_a, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0}, PURE_INVALID},
- {"input key", {s1, 1, bad_key, 1, clash, 0, none, 0, load_a, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0}, PURE_INVALID},
- {"duplicate key", {s1, 1, dup_key, 2, clash, 0, none, 0, load_a, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0}, PURE_INVALID},
- {"duplicate name", {s1, 1, i1, 0, clash, 1, none, 0, load_a, 1, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0}, PURE_INVALID},
- {"view with an effect", {s1, 1, i1, 0, v_store, 1, none, 0, store, 3, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0}, PURE_INVALID},
- {"view with an effect", {s1, 1, i1, 0, v_dt, 1, none, 0, store, 3, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0}, PURE_INVALID},
- {"empty view", {s1, 1, i1, 0, v_empty, 1, none, 0, store, 3, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0}, PURE_INVALID},
- {"handler event", {s1, 1, i1, 1, clash, 0, h_bad_event, 1, store, 3, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0}, PURE_INVALID},
- {"branch not forward", {s1, 1, i1, 0, clash, 0, h_two, 1, back, 2, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0}, PURE_INVALID},
- {"branch not forward", {s1, 1, i1, 0, clash, 0, h_two, 1, beyond, 2, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0}, PURE_INVALID},
- {"stack underflow", {s1, 1, i1, 0, clash, 0, h_all, 1, under, 1, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0}, PURE_INVALID},
- {"join with a non-empty stack", {s1, 1, i1, 0, clash, 0, h_five, 1, join, 5, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0}, PURE_INVALID},
- {"handler must leave no value", {s1, 1, i1, 0, clash, 0, h_two, 1, leftover, 2, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0}, PURE_INVALID},
- {"no such state", {s1, 1, i1, 0, clash, 0, h_two, 1, unknown_state, 2, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0}, PURE_INVALID},
- {"non-canonical operand", {s1, 1, i1, 0, clash, 0, h_two, 1, nan_const, 2, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0}, PURE_INVALID},
- {"non-canonical operand", {s1, 1, i1, 0, clash, 0, h0, 1, noncanon, 3, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0}, PURE_INVALID},
- {"unreachable instruction", {s1, 1, i1, 0, clash, 0, h_four, 1, unreachable, 4, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0}, PURE_INVALID},
- {"unknown opcode", {s1, 1, i1, 0, clash, 0, h_two, 1, bad_op, 2, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0}, PURE_INVALID},
- {"stack overflow", {s1, 1, i1, 0, clash, 0, (H[]){{0, 0, 66}}, 1, overflow, 66, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0}, PURE_INVALID},
+ {"duplicate name", {dup, 2, i1, 0, clash, 0, none, 0, load_a, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0}, PURE_INVALID},
+ {"state name", {bad_name, 1, i1, 0, clash, 0, none, 0, load_a, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0}, PURE_INVALID},
+ {"state name", {long_name, 1, i1, 0, clash, 0, none, 0, load_a, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0}, PURE_INVALID},
+ {"non-finite initial value", {non_finite, 1, i1, 0, clash, 0, none, 0, load_a, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0}, PURE_INVALID},
+ {"input key", {s1, 1, bad_key, 1, clash, 0, none, 0, load_a, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0}, PURE_INVALID},
+ {"duplicate key", {s1, 1, dup_key, 2, clash, 0, none, 0, load_a, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0}, PURE_INVALID},
+ {"duplicate name", {s1, 1, i1, 0, clash, 1, none, 0, load_a, 1, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0}, PURE_INVALID},
+ {"view with an effect", {s1, 1, i1, 0, v_store, 1, none, 0, store, 3, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0}, PURE_INVALID},
+ {"view with an effect", {s1, 1, i1, 0, v_dt, 1, none, 0, store, 3, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0}, PURE_INVALID},
+ {"empty view", {s1, 1, i1, 0, v_empty, 1, none, 0, store, 3, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0}, PURE_INVALID},
+ {"handler event", {s1, 1, i1, 1, clash, 0, h_bad_event, 1, store, 3, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0}, PURE_INVALID},
+ {"branch not forward", {s1, 1, i1, 0, clash, 0, h_two, 1, back, 2, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0}, PURE_INVALID},
+ {"branch not forward", {s1, 1, i1, 0, clash, 0, h_two, 1, beyond, 2, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0}, PURE_INVALID},
+ {"stack underflow", {s1, 1, i1, 0, clash, 0, h_all, 1, under, 1, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0}, PURE_INVALID},
+ {"join with different stacks", {s1, 1, i1, 0, clash, 0, h_five, 1, join, 5, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0}, PURE_INVALID},
+ {"handler must leave no value", {s1, 1, i1, 0, clash, 0, h_two, 1, leftover, 2, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0}, PURE_INVALID},
+ {"no such state", {s1, 1, i1, 0, clash, 0, h_two, 1, unknown_state, 2, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0}, PURE_INVALID},
+ {"non-canonical operand", {s1, 1, i1, 0, clash, 0, h_two, 1, nan_const, 2, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0}, PURE_INVALID},
+ {"non-canonical operand", {s1, 1, i1, 0, clash, 0, h0, 1, noncanon, 3, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0}, PURE_INVALID},
+ {"unreachable instruction", {s1, 1, i1, 0, clash, 0, h_four, 1, unreachable, 4, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0}, PURE_INVALID},
+ {"unknown opcode", {s1, 1, i1, 0, clash, 0, h_two, 1, bad_op, 2, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0}, PURE_INVALID},
+ {"stack overflow", {s1, 1, i1, 0, clash, 0, (H[]){{0, 0, 66}}, 1, overflow, 66, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0}, PURE_INVALID},
};
for (size_t i = 0; i < LEN(cases); ++i) {
PureResult result = load(cases[i].spec, &module_a);
@@ -313,9 +324,9 @@ static void rejections(void) {
static S many[33];
static char many_names[33][8];
for (unsigned i = 0; i < 33; ++i) { snprintf(many_names[i], 8, "s%u", i % 100u); many[i] = (S){many_names[i], 0}; }
- CHECK(load((Spec){many, 33, i1, 0, clash, 0, none, 0, load_a, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0}, &module_a) == PURE_LIMIT);
+ CHECK(load((Spec){many, 33, i1, 0, clash, 0, none, 0, load_a, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0}, &module_a) == PURE_LIMIT);
static unsigned char bytes[65536];
- size_t size = build(bytes, (Spec){s1, 1, i1, 0, clash, 0, none, 0, load_a, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0});
+ size_t size = build(bytes, (Spec){s1, 1, i1, 0, clash, 0, none, 0, load_a, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0});
CHECK(pure_module_load(bytes, size - 1, &module_a, error, sizeof(error)) == PURE_INVALID);
CHECK(pure_module_load(bytes, size + 1, &module_a, error, sizeof(error)) == PURE_INVALID);
bytes[0] = 'X';
@@ -527,7 +538,7 @@ static void messages(void) {
};
static const H handlers[] = {{1, 0, 2}, {3, 2, 12}, {5, 14, 6}, {7, 20, 14}};
static const V views[] = {{"count", 0, 0}};
- Spec spec = {states, 1, inputs, 5, views, 0, handlers, 4, code, LEN(code), collections, 1, NULL, 0, NULL, 0, NULL, 0, NULL, 0};
+ Spec spec = {states, 1, inputs, 5, views, 0, handlers, 4, code, LEN(code), collections, 1, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0};
editor_spec = spec;
PureResult loaded = load(spec, &module_a);
if (loaded != PURE_OK) { fprintf(stderr, "%s\n", error); abort(); }
@@ -593,23 +604,28 @@ static void messages(void) {
static const I unkeyed[] = {{"note", NULL, PURE_KIND_MESSAGE, false, NULL, 0}};
static const O doc_read[] = {{DOC, 0, 0}, {REPLY, 0, 0}};
static const H note_handler[] = {{1, 0, 2}};
- bad = (Spec){states, 1, unkeyed, 1, views, 0, note_handler, 1, doc_read, 2, collections, 1, NULL, 0, NULL, 0, NULL, 0, NULL, 0};
+ bad = (Spec){states, 1, unkeyed, 1, views, 0, note_handler, 1, doc_read, 2, collections, 1, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0};
CHECK(load(bad, &module_b) == PURE_INVALID && strstr(error, "document operation outside"));
static const F tail_first[] = {{"insert", 2}, {"start", 1}};
static const I misplaced[] = {{"edit", NULL, PURE_KIND_MESSAGE, true, tail_first, 2}};
- bad = (Spec){states, 1, misplaced, 1, views, 0, NULL, 0, NULL, 0, collections, 1, NULL, 0, NULL, 0, NULL, 0, NULL, 0};
+ bad = (Spec){states, 1, misplaced, 1, views, 0, NULL, 0, NULL, 0, collections, 1, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0};
CHECK(load(bad, &module_b) == PURE_INVALID && strstr(error, "message field"));
static const H even[] = {{2, 0, 0}};
- bad = (Spec){states, 1, inputs, 1, views, 0, even, 1, NULL, 0, collections, 1, NULL, 0, NULL, 0, NULL, 0, NULL, 0};
+ bad = (Spec){states, 1, inputs, 1, views, 0, even, 1, NULL, 0, collections, 1, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0};
CHECK(load(bad, &module_b) == PURE_INVALID && strstr(error, "handler event"));
static const V field_view[] = {{"v", 0, 1}};
static const O field_only[] = {{FIELD, 0, 0}};
- bad = (Spec){states, 1, inputs, 2, field_view, 1, NULL, 0, field_only, 1, collections, 1, NULL, 0, NULL, 0, NULL, 0, NULL, 0};
+ bad = (Spec){states, 1, inputs, 2, field_view, 1, NULL, 0, field_only, 1, collections, 1, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0};
CHECK(load(bad, &module_b) == PURE_INVALID && strstr(error, "view with an effect"));
+ /* A value may cross a join when every path brings the same stack. */
static const O branch_value[] = {{C, 0, 1}, {C, 0, 1}, {JZ, 3, 0}, {ST, 0, 0}};
+ static const O branch_mismatch[] = {{C, 0, 1}, {C, 0, 1}, {JZ, 4, 0}, {C, 0, 2}, {ST, 0, 0}};
static const H frame_handler[] = {{0, 0, 4}};
- bad = (Spec){states, 1, inputs, 0, views, 0, frame_handler, 1, branch_value, 4, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0};
- CHECK(load(bad, &module_b) == PURE_INVALID && strstr(error, "branch with a non-empty stack"));
+ bad = (Spec){states, 1, inputs, 0, views, 0, frame_handler, 1, branch_value, 4, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0};
+ CHECK(load(bad, &module_b) == PURE_OK);
+ static const H frame_five[] = {{0, 0, 5}};
+ bad.o = branch_mismatch; bad.no = 5; bad.h = frame_five;
+ CHECK(load(bad, &module_b) == PURE_INVALID && strstr(error, "join with different stacks"));
}
/* Damaged message modules either fail to load or run messages safely. */
@@ -726,7 +742,7 @@ static void texts(void) {
static const V number_views[] = {{"chars", 37, 2}, {"offset", 39, 2}, {"line_no", 41, 2}};
static const V text_views[] = {{"line", 43, 2}, {"header", 45, 9}, {"ratio", 54, 4}};
Spec spec = {nums, 1, inputs, LEN(inputs), number_views, 3, handlers, LEN(handlers), code, LEN(code),
- NULL, 0, textual, 3, constants, LEN(constants), text_views, 3, NULL, 0};
+ NULL, 0, textual, 3, constants, LEN(constants), text_views, 3, NULL, 0, NULL, 0};
text_spec = spec;
if (load(spec, &module_a) != PURE_OK) { fprintf(stderr, "%s\n", error); abort(); }
PureApp app;
@@ -761,7 +777,7 @@ static void texts(void) {
static const S fresh[] = {{"fresh", 7}};
static const T kept[] = {{"note", 1, 0}, {"at", 2, 1}};
static const char *const other[] = {"ignored"};
- Spec next = {fresh, 1, inputs, 0, number_views, 0, handlers, 0, code, 0, NULL, 0, kept, 2, other, 1, text_views, 0, NULL, 0};
+ Spec next = {fresh, 1, inputs, 0, number_views, 0, handlers, 0, code, 0, NULL, 0, kept, 2, other, 1, text_views, 0, NULL, 0, NULL, 0};
CHECK(load(next, &module_b) == PURE_OK);
CHECK(pure_app_input(&app, 2, PURE_EDGE_DOWN) == PURE_OK && app.values[2] == 3);
CHECK(pure_app_replace(&app, &module_b) == PURE_OK);
@@ -772,7 +788,7 @@ static void texts(void) {
static const T two[] = {{"one", 1, 0}, {"two", 1, 0}, {"c", 2, 1}};
static const O wrong_text[] = {{CUL, 3, 0}, {TK, 0, 0}, {TINS, 2, 0}};
static const H frame[] = {{0, 0, 3}};
- Spec bad = {nums, 1, inputs, 0, number_views, 0, frame, 1, wrong_text, 3, NULL, 0, two, 3, constants, 1, text_views, 0, NULL, 0};
+ Spec bad = {nums, 1, inputs, 0, number_views, 0, frame, 1, wrong_text, 3, NULL, 0, two, 3, constants, 1, text_views, 0, NULL, 0, NULL, 0};
CHECK(load(bad, &module_b) == PURE_INVALID && strstr(error, "cursor of another text"));
static const O input_in_frame[] = {{CUL, 3, 0}, {TI, 0, 0}, {TINS, 1, 0}};
bad.o = input_in_frame;
@@ -786,13 +802,13 @@ static void texts(void) {
CHECK(load(bad, &module_b) == PURE_INVALID && strstr(error, "no such state"));
static const V text_as_number[] = {{"v", 0, 1}};
static const O constant_only[] = {{TK, 0, 0}};
- bad = (Spec){nums, 1, inputs, 0, text_as_number, 1, NULL, 0, constant_only, 1, NULL, 0, two, 3, constants, 1, NULL, 0, NULL, 0};
+ bad = (Spec){nums, 1, inputs, 0, text_as_number, 1, NULL, 0, constant_only, 1, NULL, 0, two, 3, constants, 1, NULL, 0, NULL, 0, NULL, 0};
CHECK(load(bad, &module_b) == PURE_INVALID && strstr(error, "view must leave one value of its type"));
static const T cursor_on_number[] = {{"c", 2, 0}};
- bad = (Spec){nums, 1, inputs, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0, cursor_on_number, 1, constants, 1, NULL, 0, NULL, 0};
+ bad = (Spec){nums, 1, inputs, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0, cursor_on_number, 1, constants, 1, NULL, 0, NULL, 0, NULL, 0};
CHECK(load(bad, &module_b) == PURE_INVALID && strstr(error, "state reference"));
static const char *const broken[] = {"\xc0\xaf"};
- bad = (Spec){nums, 1, inputs, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0, broken, 1, NULL, 0, NULL, 0};
+ bad = (Spec){nums, 1, inputs, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0, broken, 1, NULL, 0, NULL, 0, NULL, 0};
CHECK(load(bad, &module_b) == PURE_INVALID && strstr(error, "text constant"));
}
@@ -908,7 +924,7 @@ static void lists(void) {
static const V number_views[] = {{"count", 46, 1}, {"position", 47, 2}, {"done", 49, 2}};
static const V text_views[] = {{"current", 51, 2}};
Spec spec = {nums, 1, inputs, LEN(inputs), number_views, 3, handlers, LEN(handlers), code, LEN(code),
- NULL, 0, states, 2, constants, 3, text_views, 1, records, 1};
+ NULL, 0, states, 2, constants, 3, text_views, 1, records, 1, NULL, 0};
list_spec = spec;
if (load(spec, &module_a) != PURE_OK) { fprintf(stderr, "%s\n", error); abort(); }
PureApp app;
@@ -951,7 +967,7 @@ static void lists(void) {
* fields their default. */
static const S no_nums[] = {{"made", 0}};
Spec same = {no_nums, 1, inputs, 0, number_views, 0, handlers, 0, code, 0, NULL, 0, states, 2, constants, 3,
- text_views, 0, records, 1};
+ text_views, 0, records, 1, NULL, 0};
CHECK(load(same, &module_b) == PURE_OK);
const PureList *before = app.lists[1];
CHECK(pure_app_replace(&app, &module_b) == PURE_OK && app.lists[1] == before && app.values[2] == 2);
@@ -980,7 +996,7 @@ static void lists(void) {
static const O other_index[] = {{XL, 3, 0}, {LR, 2, 0}};
static const H frame[] = {{0, 0, 2}};
Spec bad = {nums, 1, inputs, 0, number_views, 0, frame, 1, other_index, 2, NULL, 0, two_lists, 3, constants, 3,
- text_views, 0, records, 1};
+ text_views, 0, records, 1, NULL, 0};
CHECK(load(bad, &module_b) == PURE_INVALID && strstr(error, "index of another list"));
static const O swapped[] = {{C, 0, 0}, {TK, 0, 0}, {LA, 1, 0}};
static const H frame3[] = {{0, 0, 3}};
@@ -997,11 +1013,11 @@ static void lists(void) {
CHECK(load(bad, &module_b) == PURE_INVALID && strstr(error, "operand type"));
static const V append_view[] = {{"v", 0, 3}};
bad = (Spec){nums, 1, inputs, 0, append_view, 1, NULL, 0, (O[]){{TK, 0, 0}, {C, 0, 0}, {LA, 1, 0}}, 3, NULL, 0,
- two_lists, 3, constants, 3, NULL, 0, records, 1};
+ two_lists, 3, constants, 3, NULL, 0, records, 1, NULL, 0};
CHECK(load(bad, &module_b) == PURE_INVALID && strstr(error, "view with an effect"));
static const T index_of_number[] = {{"ia", 4, 0}};
bad = (Spec){nums, 1, inputs, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0, index_of_number, 1, constants, 3, NULL, 0,
- records, 1};
+ records, 1, NULL, 0};
CHECK(load(bad, &module_b) == PURE_INVALID && strstr(error, "state reference"));
static const T list_of_nothing[] = {{"a", 3, 1}};
bad.t = list_of_nothing;
@@ -1073,6 +1089,150 @@ static void list_mutations(void) {
printf("{\"list_module_mutations\":20000,\"accepted_after_damage\":%u}\n", accepted);
}
+static Spec table_spec;
+
+static const char *cell_text(PureApp *app, uint32_t column, size_t row) {
+ static Text t;
+ t.size = 0;
+ t.text[0] = 0;
+ CHECK(pure_app_cell_text(app, column, row, text_span, &t) == PURE_OK);
+ return t.text;
+}
+
+enum { ROW = PURE_OP_ROW, EQ = PURE_OP_EQ, LT = PURE_OP_LT };
+
+/* A table view has a row per item; its columns read the row and may branch.
+ * A value crosses a join when every path brings the same stack. */
+static void tables(void) {
+ static const S nums[] = {{"n", 0}};
+ static const T states[] = {{"tasks", 3, 0}, {"at", 4, 1}};
+ static const RF task_fields[] = {{"title", 1, 0}, {"done", 0, 0}};
+ static const R records[] = {{"task", task_fields, 2}};
+ static const char *const constants[] = {"", "x", " ", "empty"};
+ static const I inputs[] = {{"typed", NULL, PURE_KIND_TEXT, false, NULL, 0}, KEY("toggle", "Space")};
+ static const O code[] = {
+ {TI, 0, 0}, {C, 0, 0}, {LA, 1, 0}, {XE, 1, 0}, {XS, 2, 0}, /* 0 typed */
+ {XL, 2, 0}, {XL, 2, 0}, {IG, DONE, 0}, {NOT, 0, 0}, {IS, DONE, 0}, /* 5 toggle */
+ {LC, 1, 0}, {C, 0, 0}, {GT, 0, 0}, {JZ, 17, 0}, {XL, 2, 0}, {IG, TITLE, 0}, {JMP, 18, 0},
+ {TK, 3, 0}, /* 10 current */
+ {ROW, 1, 0}, {IG, TITLE, 0}, /* 18 title */
+ {ROW, 1, 0}, {IG, DONE, 0}, {JZ, 25, 0}, {TK, 1, 0}, {JMP, 26, 0}, {TK, 2, 0}, /* 20 mark */
+ {ROW, 1, 0}, {XN, 0, 0}, /* 26 pos */
+ {ROW, 1, 0}, {XN, 0, 0}, {XL, 2, 0}, {XN, 0, 0}, {EQ, 0, 0}, /* 28 here */
+ };
+ static const H handlers[] = {{1, 0, 5}, {3, 5, 5}};
+ static const V text_views[] = {{"current", 10, 8}};
+ static const W table[] = {{"board", 2, 0, 1, 0}, {"title", 1, 2, 18, 2}, {"mark", 1, 2, 20, 6},
+ {"pos", 0, 2, 26, 2}, {"here", 0, 2, 28, 5}};
+ Spec spec = {nums, 1, inputs, 2, NULL, 0, handlers, 2, code, LEN(code), NULL, 0, states, 2, constants, 4,
+ text_views, 1, records, 1, table, 5};
+ table_spec = spec;
+ if (load(spec, &module_a) != PURE_OK) { fprintf(stderr, "%s\n", error); abort(); }
+ CHECK(pure_module_view(&module_a, "board") == 1 && pure_module_view(&module_a, "title") == -1 &&
+ pure_module_column(&module_a, 1, "title") == 2 && pure_module_column(&module_a, 1, "current") == -1);
+ PureApp app;
+ CHECK(pure_app_start(&app, &module_a, (PureData){0}) == PURE_OK);
+ double value;
+ CHECK(pure_app_rows(&app, 1) == 0 && !strcmp(view_text(&app, 0), "empty")); /* the else branch */
+ CHECK(pure_app_cell_text(&app, 2, 0, text_span, &(Text){{0}, 0}) == PURE_NOT_FOUND);
+ CHECK(pure_app_text(&app, 0, (PureSpan){(const unsigned char *)"a", 1}) == PURE_OK);
+ CHECK(pure_app_text(&app, 0, (PureSpan){(const unsigned char *)"\xeb\xb9\xb5", 3}) == PURE_OK);
+ CHECK(pure_app_input(&app, 1, PURE_EDGE_DOWN) == PURE_OK);
+ CHECK(pure_app_rows(&app, 1) == 2 && !strcmp(view_text(&app, 0), "\xeb\xb9\xb5"));
+ CHECK(!strcmp(cell_text(&app, 2, 0), "a") && !strcmp(cell_text(&app, 2, 1), "\xeb\xb9\xb5"));
+ CHECK(!strcmp(cell_text(&app, 3, 0), " ") && !strcmp(cell_text(&app, 3, 1), "x"));
+ CHECK(pure_app_cell(&app, 4, 1, &value) && value == 1 && pure_app_cell(&app, 5, 0, &value) && value == 0 &&
+ pure_app_cell(&app, 5, 1, &value) && value == 1);
+ CHECK(!pure_app_cell(&app, 4, 2, &value) && pure_app_cell_text(&app, 2, 2, text_span, &(Text){{0}, 0}) == PURE_NOT_FOUND);
+ /* Columns are read by row, other views without one. */
+ CHECK(!pure_app_view(&app, 4, &value) && pure_app_view_text(&app, 2, text_span, &(Text){{0}, 0}) == PURE_INVALID);
+ CHECK(pure_app_cell_text(&app, 0, 0, text_span, &(Text){{0}, 0}) == PURE_INVALID && pure_app_rows(&app, 0) == 0);
+ CHECK(pure_app_view_text(&app, 1, text_span, &(Text){{0}, 0}) == PURE_INVALID);
+ pure_app_stop(&app);
+
+ /* Verification of rows, tables and joins. */
+ Spec bad = spec;
+ static const V row_view[] = {{"current", 18, 2}};
+ bad.tv = row_view;
+ CHECK(load(bad, &module_b) == PURE_INVALID && strstr(error, "row outside a column"));
+ static const H row_handler[] = {{3, 26, 2}};
+ bad = spec; bad.h = row_handler; bad.nh = 1;
+ CHECK(load(bad, &module_b) == PURE_INVALID && strstr(error, "row outside a column"));
+ static const W twice[] = {{"board", 2, 0, 1, 0}, {"title", 1, 2, 18, 2}, {"title", 1, 2, 18, 2}};
+ bad = spec; bad.w = twice; bad.nw = 3;
+ CHECK(load(bad, &module_b) == PURE_INVALID && strstr(error, "duplicate column"));
+ static const W two_tables[] = {{"board", 2, 0, 1, 0}, {"title", 1, 2, 18, 2}, {"other", 2, 0, 1, 0},
+ {"title", 1, 4, 18, 2}, {"tasks", 0, 4, 26, 2}};
+ bad = spec; bad.w = two_tables; bad.nw = 5;
+ CHECK(load(bad, &module_b) == PURE_OK); /* column names belong to their table */
+ static const W not_a_table[] = {{"title", 1, 1, 18, 2}};
+ bad = spec; bad.w = not_a_table; bad.nw = 1;
+ CHECK(load(bad, &module_b) == PURE_INVALID && strstr(error, "view type"));
+ static const W over_number[] = {{"board", 2, 0, 0, 0}};
+ bad = spec; bad.w = over_number; bad.nw = 1;
+ CHECK(load(bad, &module_b) == PURE_INVALID && strstr(error, "table list"));
+ static const O other_row[] = {{ROW, 2, 0}, {XN, 0, 0}};
+ static const W other_row_column[] = {{"board", 2, 0, 1, 0}, {"pos", 0, 1, 0, 2}};
+ bad = spec; bad.o = other_row; bad.no = 2; bad.h = NULL; bad.nh = 0; bad.tv = NULL; bad.ntv = 0;
+ bad.w = other_row_column; bad.nw = 2;
+ CHECK(load(bad, &module_b) == PURE_INVALID && strstr(error, "no such state"));
+ static const O typed_join[] = {{L, 0, 0}, {JZ, 4, 0}, {TK, 0, 0}, {JMP, 5, 0}, {C, 0, 1}};
+ static const V mixed[] = {{"current", 0, 5}};
+ bad = spec; bad.o = typed_join; bad.no = 5; bad.h = NULL; bad.nh = 0; bad.tv = mixed; bad.w = NULL; bad.nw = 0;
+ CHECK(load(bad, &module_b) == PURE_INVALID && strstr(error, "join with different stacks"));
+ /* At most 8 joins with a value may be ahead at once. */
+ for (unsigned open = 8; open <= 9; ++open) {
+ static O nested[1 + 2 * 9 + 9 + 1];
+ unsigned n = 0;
+ nested[n++] = (O){C, 0, 1};
+ for (unsigned k = 0; k < open; ++k) { nested[n++] = (O){C, 0, 0}; nested[n++] = (O){JZ, 1 + 2 * open + k, 0}; }
+ for (unsigned k = 0; k < open; ++k) nested[n++] = (O){NEG, 0, 0};
+ nested[n++] = (O){ST, 0, 0};
+ H frame = {0, 0, n};
+ bad = spec; bad.o = nested; bad.no = n; bad.h = &frame; bad.nh = 1; bad.tv = NULL; bad.ntv = 0; bad.w = NULL; bad.nw = 0;
+ CHECK(load(bad, &module_b) == (open == 8 ? PURE_OK : PURE_INVALID));
+ }
+ CHECK(strstr(error, "more than 8 joins ahead"));
+}
+
+/* Damaged table modules either fail to load or evaluate every cell safely. */
+static void table_mutations(void) {
+ static unsigned char valid[65536], bytes[65536];
+ size_t size = build(valid, table_spec);
+ uint32_t state = 0xc2b2ae35u;
+ unsigned accepted = 0;
+ for (unsigned round = 0; round < 20000; ++round) {
+ memcpy(bytes, valid, size);
+ unsigned flips = 1 + next_random(&state) % 3;
+ for (unsigned f = 0; f < flips; ++f)
+ bytes[next_random(&state) % size] ^= (unsigned char)(1u << (next_random(&state) % 8));
+ if (pure_module_load(bytes, size, &module_b, error, sizeof(error)) != PURE_OK) continue;
+ ++accepted;
+ PureApp app;
+ if (pure_app_start(&app, &module_b, (PureData){0}) != PURE_OK) continue;
+ for (unsigned e = 0; e < 12; ++e) {
+ uint32_t input = next_random(&state) % 2;
+ if (module_b.input[input].kind == PURE_KIND_TEXT) pure_app_text(&app, input, (PureSpan){(const unsigned char *)"t", 1});
+ else if (module_b.input[input].kind == PURE_KIND_KEY) pure_app_input(&app, input, PURE_EDGE_DOWN);
+ for (uint32_t v = 0; v < module_b.views; ++v) {
+ double value;
+ if (!module_b.view_table[v]) {
+ if (module_b.view_type[v] == PURE_VIEW_TEXT) pure_app_view_text(&app, v, ignore_span, NULL);
+ else pure_app_view(&app, v, &value);
+ continue;
+ }
+ size_t rows = pure_app_rows(&app, module_b.view_table[v] - 1u);
+ for (size_t row = 0; row <= rows; ++row) {
+ if (module_b.view_type[v] == PURE_VIEW_TEXT) pure_app_cell_text(&app, v, row, ignore_span, NULL);
+ else if (pure_app_cell(&app, v, row, &value)) CHECK(isfinite(value));
+ }
+ }
+ }
+ pure_app_stop(&app);
+ }
+ printf("{\"table_module_mutations\":20000,\"accepted_after_damage\":%u}\n", accepted);
+}
+
int main(void) {
behaviour();
faults();
@@ -1084,7 +1244,9 @@ int main(void) {
text_mutations();
lists();
list_mutations();
+ tables();
+ table_mutations();
printf("{\"sizeof_module\":%zu,\"sizeof_app\":%zu}\n", sizeof(PureModule), sizeof(PureApp));
- puts("{\"module_tests\":\"verified-load-atomic-events-faults-signed-zero-if-else-replace-by-name-rejections-mutations-messages-documents-typed-verification-text-cursors-utf8-views-rollback-replace-lists-indices-undo-migration\"}");
+ puts("{\"module_tests\":\"verified-load-atomic-events-faults-signed-zero-if-else-replace-by-name-rejections-mutations-messages-documents-typed-verification-text-cursors-utf8-views-rollback-replace-lists-indices-undo-migration-joins-tables-columns\"}");
return 0;
}