aboutsummaryrefslogtreecommitdiffstats
path: root/core
diff options
context:
space:
mode:
Diffstat (limited to 'core')
-rw-r--r--core/module.c112
-rw-r--r--core/module.h9
-rw-r--r--core/test-module.c109
3 files changed, 201 insertions, 29 deletions
diff --git a/core/module.c b/core/module.c
index e90684e..ffcc9cb 100644
--- a/core/module.c
+++ b/core/module.c
@@ -21,8 +21,9 @@ enum { FROM_PAYLOAD, FROM_DOCUMENT, FROM_COPY, FROM_CONSTANT, FROM_TEXT, FROM_AR
/* Where an opcode may appear. */
/* IN_ARGUMENTS: fields read where nothing is done with them (a view's arguments, a message's fields in a fold). */
/* IN_LOCALS: a handler's locals, read in its walks and the folds in them. */
+/* IN_COMPUTE: what a view's body may do too (018): loops, locals, and changes to an event's lists alone. */
enum { IN_VIEW = 1, IN_HANDLER = 2, IN_MESSAGE = 4, IN_KEYED = 8, IN_TEXT = 16, IN_ROW = 32, IN_OUTER = 64, IN_ARGUMENTS = 128,
- IN_LOCALS = 256 };
+ IN_LOCALS = 256, IN_COMPUTE = 512 };
enum { READ = IN_VIEW | IN_HANDLER };
/* What an argument names: nothing (it is 0), a state of one type (A_STATE +
* the type), a list and one of its record's fields, a message field, the
@@ -37,8 +38,10 @@ enum { A_NONE, A_STATE, A_ITEM = A_STATE + PURE_STATE_I64 + 1, A_FIELD, A_COLLEC
* or a value and whether to include it (a bool): a number (FOLD_TWO) or an
* i64 (FOLD_I64). A table's filter is verified as a column that leaves a
* bool: these stand for a view type. A walk's block (EACH) is verified as
- * its handler's code that may read the item it runs for (WALK). */
-enum { FOLD_ONE = 100, FOLD_TWO, FOLD_I64, FILTER, WALK };
+ * its handler's code that may read the item it runs for (WALK). In a view's
+ * body a loop's block leaves nothing either (STEPS), and a walk's reads its
+ * item (STEPS_WALK); neither changes a state. */
+enum { FOLD_ONE = 100, FOLD_TWO, FOLD_I64, FILTER, WALK, STEPS, STEPS_WALK };
/* The text or list an opcode works on: its cursor and index operands belong
* to it, and so does a cursor or index it pushes. O_ANY: no rule. O_OPERAND:
* it pushes a position in its operand's text or list. O_ARG: the argument.
@@ -94,11 +97,11 @@ static const struct { unsigned char pops, in[3], out; unsigned short where; unsi
[PURE_OP_CUR_COUNT] = {1, {T_CURSOR}, T_NUM, READ, A_UNIT, O_ANY},
/* Record operations also take as many values as the list's record has fields. */
[PURE_OP_LIST_COUNT] = {0, {0}, T_NUM, READ, STATE(LIST), O_ANY},
- [PURE_OP_LIST_APPEND] = {0, {0}, 0, IN_HANDLER, STATE(LIST), O_ANY},
- [PURE_OP_LIST_INSERT] = {1, {T_INDEX}, 0, IN_HANDLER, STATE(LIST), O_ARG},
- [PURE_OP_LIST_REMOVE] = {1, {T_INDEX}, 0, IN_HANDLER, STATE(LIST), O_ARG},
+ [PURE_OP_LIST_APPEND] = {0, {0}, 0, IN_HANDLER | IN_COMPUTE, STATE(LIST), O_ANY},
+ [PURE_OP_LIST_INSERT] = {1, {T_INDEX}, 0, IN_HANDLER | IN_COMPUTE, STATE(LIST), O_ARG},
+ [PURE_OP_LIST_REMOVE] = {1, {T_INDEX}, 0, IN_HANDLER | IN_COMPUTE, STATE(LIST), O_ARG},
[PURE_OP_ITEM_GET] = {1, {T_INDEX}, T_ITEM, READ, A_ITEM, O_LIST},
- [PURE_OP_ITEM_SET] = {1, {T_INDEX}, 0, IN_HANDLER, A_ITEM, O_LIST},
+ [PURE_OP_ITEM_SET] = {1, {T_INDEX}, 0, IN_HANDLER | IN_COMPUTE, A_ITEM, O_LIST},
[PURE_OP_INDEX_LOAD] = {0, {0}, T_INDEX, READ, STATE(INDEX), O_REF},
[PURE_OP_INDEX_STORE] = {1, {T_INDEX}, 0, IN_HANDLER, STATE(INDEX), O_REF},
[PURE_OP_INDEX_MOVE] = {2, {T_INDEX, T_NUM}, T_INDEX, READ, A_NONE, O_OPERAND},
@@ -117,12 +120,12 @@ static const struct { unsigned char pops, in[3], out; unsigned short where; unsi
[PURE_OP_LIST_HAS] = {1, {T_KEY}, T_BOOL, READ, A_KEYED, O_ANY},
[PURE_OP_NOW] = {0, {0}, T_NUM, IN_HANDLER, A_NONE, O_ANY},
[PURE_OP_SEND] = {0, {0}, 0, IN_HANDLER, A_OUTPUT, O_ANY},
- [PURE_OP_EACH] = {0, {0}, 0, IN_HANDLER, A_EACH, O_ANY},
+ [PURE_OP_EACH] = {0, {0}, 0, IN_HANDLER | IN_COMPUTE, A_EACH, O_ANY},
[PURE_OP_LOCAL_LOAD] = {0, {0}, T_LOCAL, IN_LOCALS, A_LOCAL, O_ANY},
- [PURE_OP_LOCAL_STORE] = {1, {T_LOCAL}, 0, IN_HANDLER, A_LOCAL, O_ANY},
+ [PURE_OP_LOCAL_STORE] = {1, {T_LOCAL}, 0, IN_HANDLER | IN_COMPUTE, A_LOCAL, O_ANY},
/* LOOP takes its low and high, two numbers or two i64s (the operands are its argument's). */
- [PURE_OP_LOOP] = {0, {0}, 0, IN_HANDLER, A_LOOP, O_ANY},
- [PURE_OP_BREAK] = {0, {0}, 0, IN_HANDLER, A_NONE, O_ANY},
+ [PURE_OP_LOOP] = {0, {0}, 0, IN_HANDLER | IN_COMPUTE, A_LOOP, O_ANY},
+ [PURE_OP_BREAK] = {0, {0}, 0, IN_HANDLER | IN_COMPUTE, A_NONE, O_ANY},
[PURE_OP_FLOOR] = NUMS(1), [PURE_OP_MOD] = NUMS(2),
[PURE_OP_I64_ADD] = I64S(2), [PURE_OP_I64_SUB] = I64S(2), [PURE_OP_I64_MUL] = I64S(2), [PURE_OP_I64_DIV] = I64S(2),
[PURE_OP_I64_MOD] = I64S(2), [PURE_OP_I64_NEG] = I64S(1), [PURE_OP_I64_MIN] = I64S(2), [PURE_OP_I64_MAX] = I64S(2),
@@ -422,16 +425,21 @@ PURE_COLD static const char *verify_code(const PureModule *m, PureBlock block, i
uint32_t event, bool locals, unsigned nest, uint32_t walked, uint32_t *at,
unsigned char *target) {
#define REFUSE(why) do { *at = pc; return why; } while (0)
- bool handler = view < 0 || view == WALK, folding = view == FOLD_ONE || view == FOLD_TWO || view == FOLD_I64;
+ bool steps = view == STEPS || view == STEPS_WALK, handler = view < 0 || view == WALK || steps;
+ bool folding = view == FOLD_ONE || view == FOLD_TWO || view == FOLD_I64;
+ /* A view outside a table computes (locals: its code reads them): a body, and the loops in it. */
+ bool body = steps || (!handler && !folding && view != FILTER && row < 0 && locals);
if (!handler && !block.length) return "empty view";
/* A handler's message or text; a view's arguments, or the message of the handler a fold is in. */
int input = event_input(event);
const PureInputShape *shape = input >= 0 ? &m->input[input] : NULL;
- unsigned where = !handler ? IN_VIEW | (row >= 0 ? IN_ROW : 0) | (folding && outer >= 0 ? IN_OUTER : 0)
+ unsigned where = steps ? IN_VIEW | IN_COMPUTE | (view == STEPS_WALK ? IN_ROW : 0)
+ : !handler ? IN_VIEW | (row >= 0 ? IN_ROW : 0) | (folding && outer >= 0 ? IN_OUTER : 0) | (body ? IN_COMPUTE : 0)
: IN_HANDLER | (view == WALK ? IN_ROW : 0);
- if (shape && handler && shape->kind == PURE_KIND_MESSAGE) where |= IN_MESSAGE | (shape->keyed ? IN_KEYED : 0);
- if (shape && handler && shape->kind == PURE_KIND_TEXT) where |= IN_TEXT;
- if (shape && !handler && (shape->kind == PURE_KIND_MESSAGE || shape->kind == PURE_KIND_ARGUMENTS)) where |= IN_ARGUMENTS;
+ if (shape && handler && !steps && shape->kind == PURE_KIND_MESSAGE) where |= IN_MESSAGE | (shape->keyed ? IN_KEYED : 0);
+ if (shape && handler && !steps && shape->kind == PURE_KIND_TEXT) where |= IN_TEXT;
+ if (shape && (!handler || steps) && (shape->kind == PURE_KIND_MESSAGE || shape->kind == PURE_KIND_ARGUMENTS))
+ where |= IN_ARGUMENTS;
if (locals) where |= IN_LOCALS;
unsigned char types[PURE_MODULE_STACK], owners[PURE_MODULE_STACK];
Join joins[PURE_MODULE_JOINS];
@@ -467,7 +475,7 @@ PURE_COLD static const char *verify_code(const PureModule *m, PureBlock block, i
if (allowed & IN_LOCALS) REFUSE("local outside a handler");
if (view == FILTER) REFUSE("filter with an effect");
if (folding) REFUSE("fold with an effect");
- if (!handler) REFUSE("view with an effect");
+ if (!handler || steps) REFUSE("view with an effect");
if (allowed & IN_KEYED) REFUSE("document operation outside a keyed message handler");
if (allowed & IN_TEXT) REFUSE("text input outside a text handler");
REFUSE("message operation outside a message handler");
@@ -492,6 +500,11 @@ PURE_COLD static const char *verify_code(const PureModule *m, PureBlock block, i
if (op->op == PURE_OP_BREAK && height) REFUSE("break with values on the stack");
if (op->op == PURE_OP_ITEM_SET && m->state_key[list_of(arg)] == item_field(arg) + 1)
REFUSE("change of a keyed list's key");
+ /* A view's body changes an event's lists alone: its states are a snapshot's. */
+ if (body && (op->op == PURE_OP_LIST_APPEND || op->op == PURE_OP_LIST_INSERT || op->op == PURE_OP_LIST_REMOVE ||
+ op->op == PURE_OP_ITEM_SET) &&
+ !(m->state_flags[op->op == PURE_OP_ITEM_SET ? list_of(arg) : arg] & PURE_LIST_ROWS >> 8))
+ REFUSE("view changes a state");
/* PICK copies the value arg below the top; SLIDE drops arg values under the top. */
if (kind == A_DEPTH) {
bool pick = op->op == PURE_OP_PICK;
@@ -515,10 +528,10 @@ PURE_COLD static const char *verify_code(const PureModule *m, PureBlock block, i
unsigned how = arg & PURE_FOLD_KIND;
uint32_t list = arg >> 8 & 0xFF;
/* A fold in a walk reads the walk's item as a fold in a column reads its row; a loop's block is its block's code. */
- const char *why = kind == A_EACH ? verify_block(m, (PureBlock){pc + 1, skip}, WALK, (int)list, -1, event, locals,
- nest + 1, walked | UINT32_C(1) << (list & 31), &inner)
- : kind == A_LOOP ? verify_block(m, (PureBlock){pc + 1, skip}, view, row, outer, event, locals,
- nest + 1, walked, &inner)
+ const char *why = kind == A_EACH ? verify_block(m, (PureBlock){pc + 1, skip}, body ? STEPS_WALK : WALK, (int)list, -1,
+ event, locals, nest + 1, walked | UINT32_C(1) << (list & 31), &inner)
+ : kind == A_LOOP ? verify_block(m, (PureBlock){pc + 1, skip}, body && !steps ? STEPS : view, row,
+ outer, event, locals, nest + 1, walked, &inner)
: verify_block(m, (PureBlock){pc + 1, skip},
how == PURE_FOLD_COUNT || how == PURE_FOLD_FIRST ? FOLD_ONE :
arg & PURE_FOLD_INTEGER ? FOLD_I64 : FOLD_TWO,
@@ -1280,10 +1293,21 @@ PURE_COLD PureResult pure_module_load(const void *bytes, size_t size, PureModule
bad = i;
bool table = out->view_type[i] == PURE_VIEW_TABLE;
uint32_t event = out->view_arguments[i] ? 2u * out->view_arguments[i] - 1u : 0; /* the arguments' input, as an arrival */
+ bool computes = !table && !out->view_table[i]; /* a view outside a table may have a body */
if ((!table || out->view_code[i].length) &&
(why = verify_block(out, out->view_code[i], table ? FILTER : out->view_type[i],
- table || out->view_table[i] ? out->view_list[i] : -1, -1, event, false, 0, 0, &bad)))
+ table || out->view_table[i] ? out->view_list[i] : -1, -1, event, computes, 0, 0, &bad)))
goto refused;
+ if (computes && !locals_agree(out, out->view_code[i])) { why = "local used as two things"; goto refused; }
+ for (uint32_t pc = out->view_code[i].start; computes && pc < out->view_code[i].start + out->view_code[i].length; ++pc) {
+ const PureOp *op = &out->code[pc];
+ if (op->op == PURE_OP_LOCAL_LOAD || op->op == PURE_OP_LOCAL_STORE || op->op == PURE_OP_LOOP || op->op == PURE_OP_EACH)
+ out->view_body[i] = true;
+ if (op->op == PURE_OP_LIST_APPEND || op->op == PURE_OP_LIST_INSERT || op->op == PURE_OP_LIST_REMOVE)
+ out->view_lists[i] |= UINT32_C(1) << (op->arg & 31);
+ if (op->op == PURE_OP_ITEM_SET) out->view_lists[i] |= UINT32_C(1) << (list_of(op->arg) & 31);
+ }
+ if (out->view_lists[i]) out->view_body[i] = true;
}
for (uint32_t i = 0; i < out->handlers; ++i) {
bad = i;
@@ -3102,9 +3126,12 @@ void pure_snapshot_release(PureSnapshot *snapshot) {
}
}
+static void let_own_go(PureApp *app);
+
void pure_app_stop(PureApp *app) {
pure_app_finish(app);
close_burst(app, true); /* unsaved changes are dropped with the app */
+ let_own_go(app);
for (uint32_t i = 0; i < PURE_MODULE_STATES && !app->reading; ++i) {
pure_draft_close(app->texts[i]);
pure_list_free(app->lists[i]);
@@ -3567,22 +3594,47 @@ static bool argue(const PureApp *app, uint32_t view, Event *ev) {
return true;
}
+/* The event's lists a view's body changes, empty: a reader's own (its
+ * states are a snapshot's, which other readers read), the app's otherwise
+ * (empty between events). */
+static bool own_lists(PureApp *app, uint32_t lists) {
+ const PureModule *m = app->module;
+ for (uint32_t left = lists; app->reading && left; left &= left - 1) {
+ unsigned i = (unsigned)__builtin_ctz(left);
+ if (!app->own[i]) {
+ if (!(app->own[i] = new_list(m, m->state_ref[i]))) return false;
+ (void)pure_list_keyed(app->own[i], m->state_key[i] ? (int)m->state_key[i] - 1 : -1);
+ }
+ app->lists[i] = app->own[i];
+ }
+ return true;
+}
+
static PureResult evaluate(PureApp *app, uint32_t view, size_t row, Slot *out) {
- PureValue values[PURE_MODULE_STATES];
+ const PureModule *m = app->module;
+ PureValue values[PURE_MODULE_STATES], locals[PURE_MODULE_LOCALS];
memcpy(values, app->values, sizeof(values));
app->view_arena.used = 0;
Event ev = {.app = app, .arena = &app->view_arena, .row = row};
if (!argue(app, view, &ev)) return PURE_INVALID;
+ if (m->view_body[view]) { /* a body computes: its locals from 0, its lists its own, its additions counted */
+ memset(locals, 0, sizeof(locals));
+ ev.locals = locals;
+ if (!own_lists(app, m->view_lists[view])) return PURE_ALLOCATION;
+ app->grown = app->spent = 0;
+ app->room = app->memory > PURE_GROWTH_DEFAULT ? app->memory : PURE_GROWTH_DEFAULT;
+ }
PureFault fault = app->fault;
uint32_t fault_pc = app->fault_pc;
- PureBlock code = app->module->view_code[view];
+ PureBlock code = m->view_code[view];
app->fault = PURE_FAULT_NONE;
app->work = 0;
bool counted = charge(app, code.length);
- PureResult result = counted ? run(app->module, values, code, &ev, out) : PURE_LIMIT;
+ PureResult result = counted ? run(m, values, code, &ev, out) : PURE_LIMIT;
if (result != PURE_OK) app->view_fault = counted ? app->fault : PURE_FAULT_WORK; /* why it has no value */
app->fault = fault;
app->fault_pc = fault_pc;
+ for (uint32_t left = m->view_lists[view]; left; left &= left - 1) pure_list_clear(app->lists[__builtin_ctz(left)]);
return result;
}
@@ -3843,8 +3895,16 @@ PureSnapshot *pure_save_snapshot(PureApp *app, uint32_t runtime) {
return made;
}
+/* A reader's own lists go: its next views compute in new ones. */
+static void let_own_go(PureApp *app) {
+ for (uint32_t i = 0; i < PURE_MODULE_STATES; ++i) {
+ pure_list_free(app->own[i]);
+ app->own[i] = NULL;
+ }
+}
+
void pure_app_read(PureApp *reader, const PureSnapshot *snapshot) {
- if (reader->module != snapshot->module) drop_passes(reader);
+ if (reader->module != snapshot->module) drop_passes(reader), let_own_go(reader); /* laid out for its module */
reader->module = snapshot->module;
reader->reading = true;
reader->changes = snapshot->changes; /* what the row cache is fresh for */
diff --git a/core/module.h b/core/module.h
index e71559b..c5b7ba0 100644
--- a/core/module.h
+++ b/core/module.h
@@ -175,7 +175,10 @@ enum { PURE_CURSOR_STAYS = 1, PURE_LIST_ROWS = 256 };
enum { PURE_STATE_TRANSIENT = 256 };
/* A table view has one row per item of a list, or per item its filter
* keeps; its columns are number, bool, i64 or text views evaluated for one
- * row at a time. */
+ * row at a time. A view outside a table may compute (docs/design/018): its
+ * code may hold LOOP, EACH, BREAK and locals, and change an event's lists,
+ * but no state; each evaluation begins with its locals 0 and those lists
+ * empty (a reader's own: readers of one snapshot compute apart). */
typedef enum { PURE_VIEW_NUM = 0, PURE_VIEW_TEXT = 1, PURE_VIEW_TABLE = 2, PURE_VIEW_BOOL = 3, PURE_VIEW_I64 = 4 } PureViewType;
typedef enum { PURE_UNIT_CHAR = 0, PURE_UNIT_LINE = 1 } PureUnit;
typedef enum { PURE_EDGE_DOWN, PURE_EDGE_UP } PureEdge;
@@ -259,6 +262,9 @@ typedef struct {
PureBlock view_code[PURE_MODULE_VIEWS];
uint8_t view_plan[PURE_MODULE_VIEWS]; /* a table's filter runs a leaf of its list at once (as a fold may) */
uint8_t view_arguments[PURE_MODULE_VIEWS]; /* the input of a view's arguments + 1 (a column's: its table's), or 0 */
+ /* Set at load: a view's code computes (its locals and loops: a body), and the event's lists it changes, a bit each. */
+ bool view_body[PURE_MODULE_VIEWS];
+ uint32_t view_lists[PURE_MODULE_VIEWS];
PureShape shape[PURE_MODULE_SHAPES];
uint8_t shapes;
PureRange range[PURE_MODULE_RANGES];
@@ -413,6 +419,7 @@ typedef struct {
size_t memory; /* the budget of what the states hold (0: none): pure_app_memory */
uint64_t work_budget, work; /* pure_app_work's budget (0: PURE_WORK_DEFAULT), and what the unit running has used */
int64_t grown, spent; /* what the event running has added to the states (and its lists), as stored and in all */
+ PureList *own[PURE_MODULE_STATES]; /* a reader's: the event's lists its views compute in, its own */
size_t room; /* what it may hold for that in all: PURE_GROWTH_DEFAULT, or the memory budget if larger */
PureFault view_fault; /* why the last view evaluated had no value (pure_app_view_fault) */
char why[128]; /* why the last start or replace was refused, when it says (else empty; a failed start keeps it) */
diff --git a/core/test-module.c b/core/test-module.c
index 0f9cda9..9bf34ff 100644
--- a/core/test-module.c
+++ b/core/test-module.c
@@ -1429,7 +1429,7 @@ static void lists(void) {
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, NULL, 0};
- CHECK(load(bad, &module_b) == PURE_INVALID && strstr(error, "view with an effect"));
+ CHECK(load(bad, &module_b) == PURE_INVALID && strstr(error, "view changes a state")); /* a body changes its event's lists alone */
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, NULL, 0};
@@ -3262,8 +3262,10 @@ static void locals(void) {
static const O other_list[] = {{XF, L_XS, 0}, {LOCAL_S, LOCAL(0, 3), 0}};
static const O far_slot[] = {{C, 0, 1}, {LOCAL_S, LOCAL(64, -1), 0}};
static const H first2[] = {{1, 0, 2}}, first4[] = {{1, 0, 4}}, first5[] = {{1, 0, 5}};
+ /* A view outside a table computes (018): its locals begin at 0. */
+ CHECK(load(local_spec(view_local, 1, NULL, 0, local_view, 1), &module_b) == PURE_OK && module_b.view_body[0]);
+ pure_module_release(&module_b);
const struct { Spec spec; const char *reason; } refused[] = {
- {local_spec(view_local, 1, NULL, 0, local_view, 1), "local outside a handler"},
{local_spec(fold_store, LEN(fold_store), first5, 1, NULL, 0), "fold with an effect"},
{local_spec(two_things, LEN(two_things), first4, 1, NULL, 0), "local used as two things"},
{local_spec(other_list, LEN(other_list), first2, 1, NULL, 0), "no such state"},
@@ -5453,6 +5455,108 @@ static void loops(void) {
puts("{\"loops\":\"count-break-nested-walks-event-lists-work-memory-refusals\"}");
}
+/* Views that compute (docs/design/018): a body's locals begin at 0 and
+ * the event's lists it fills begin empty at each evaluation, on the app and
+ * on readers of one snapshot at once (each its own lists); its loops count
+ * as a view's work, its lists as an event's growth. */
+typedef struct { PureSnapshot *snapshot; double total, distinct; unsigned rounds; } BodyReads;
+
+static void *read_bodies(void *context) {
+ BodyReads *r = context;
+ PureApp reader = {0};
+ for (unsigned k = 0; k < r->rounds; ++k) {
+ pure_app_read(&reader, r->snapshot);
+ double total, distinct;
+ CHECK(pure_app_view(&reader, 1, &distinct) && distinct == r->distinct);
+ CHECK(pure_app_view(&reader, 0, &total) && total == r->total);
+ }
+ CHECK(reader.own[3] && !pure_list_count(reader.own[3]));
+ pure_app_stop(&reader);
+ return NULL;
+}
+
+static void view_bodies(void) {
+ enum { N, XS = 2, SEEN, LOOP = PURE_OP_LOOP, LL = PURE_OP_LOCAL_LOAD, LS = PURE_OP_LOCAL_STORE, HAS_ = PURE_OP_LIST_HAS,
+ EACH_ = PURE_OP_EACH, MOD_ = PURE_OP_MOD };
+ static const S nums[] = {{"n", 0}, {"m", 0}};
+ static const T states[] = {{"xs", 3, 0}, {"let.seen", 3 | (uint32_t)(PURE_LIST_ROWS | 1) << 8, 0}};
+ static const RF fields[] = {{"value", 0, 0}};
+ static const R records[] = {{"row", fields, 1}};
+ static const I inputs[] = {KEY("add", "KeyA")};
+ static const O code[] = {
+ /* add: xs.append(row { n mod 7 }); n = n + 1 (8) */
+ {L, N, 0}, {C, 0, 7}, {MOD_, 0, 0}, {LA, XS, 0}, {L, N, 0}, {C, 0, 1}, {ADD, 0, 0}, {ST, N, 0},
+ /* total at 8: s = 0; for i in 0 .. xs.count { s = s + (xs.first + i).value }; s (13) */
+ {C, 0, 0}, {LS, 1, 0}, {C, 0, 0}, {LC, XS, 0}, {LOOP, 0 | 7u << 16, 0},
+ {LL, 1, 0}, {XF, XS, 0}, {LL, 0, 0}, {XM, 0, 0}, {IG, XS, 0}, {ADD, 0, 0}, {LS, 1, 0}, {LL, 1, 0},
+ /* distinct at 21: for x in xs { if !seen.has(x.value) { seen.add(row { x.value }) } }; seen.count (10) */
+ {EACH_, WALKING(XS, 8), 0}, {ROW, XS, 0}, {IG, XS, 0}, {HAS_, SEEN, 0}, {NOT, 0, 0}, {JZ, 30, 0}, {ROW, XS, 0},
+ {IG, XS, 0}, {LA, SEEN, 0}, {LC, SEEN, 0},
+ /* spin at 31: for i in 0 .. 1e15 { }; 1 (4) */
+ {C, 0, 0}, {C, 0, 1e15}, {LOOP, 0, 0}, {C, 0, 1},
+ /* fill at 35: for i in 0 .. 1e6 { seen.add(row { i }) }; seen.count (6) */
+ {C, 0, 0}, {C, 0, 1e6}, {LOOP, 0 | 2u << 16, 0}, {LL, 0, 0}, {LA, SEEN, 0}, {LC, SEEN, 0},
+ };
+ static const H handlers[] = {{1, 0, 8}};
+ static const V views[] = {{"total", 8, 13}, {"distinct", 21, 10}, {"spin", 31, 4}, {"fill", 35, 6}};
+ Spec spec = {nums, 2, inputs, 1, views, 4, handlers, 1, code, LEN(code), NULL, 0, states, 2, NULL, 0, NULL, 0,
+ records, 1, NULL, 0};
+ CHECK(load(spec, &module_a) == PURE_OK);
+ CHECK(module_a.view_body[0] && module_a.view_body[1] && !module_a.view_lists[0] && module_a.view_lists[1] == 1u << SEEN);
+ char *path = new_store();
+ PureApp app;
+ CHECK(pure_app_start(&app, &module_a, pure_tree_data(path)) == PURE_OK);
+ double total = 0, value;
+ for (int k = 0; k < 20; ++k) {
+ CHECK(pure_app_input(&app, 0, PURE_EDGE_DOWN) == PURE_OK);
+ total += k % 7;
+ }
+ for (int round = 0; round < 2; ++round) /* each evaluation begins anew */
+ CHECK(number_view(&app, 0) == total && number_view(&app, 1) == 7 && !pure_list_count(app.lists[SEEN]));
+ CHECK(!pure_app_view(&app, 2, &value) && pure_app_view_fault(&app) == PURE_FAULT_WORK);
+ CHECK(number_view(&app, 3) == 1e6 && !pure_list_count(app.lists[SEEN]));
+ pure_app_memory(&app, 4096);
+ CHECK(!pure_app_view(&app, 3, &value) && pure_app_view_fault(&app) == PURE_FAULT_MEMORY && !pure_list_count(app.lists[SEEN]));
+ pure_app_memory(&app, 0);
+ CHECK(pure_app_input(&app, 0, PURE_EDGE_DOWN) == PURE_OK); /* the app goes on */
+ total += 20 % 7;
+ /* Readers of one snapshot compute at once, each in its own lists. */
+ BodyReads reads = {pure_app_snapshot(&app), total, 7, 2000};
+ CHECK(reads.snapshot);
+ pthread_t threads[3];
+ for (int t = 0; t < 3; ++t) CHECK(!pthread_create(&threads[t], NULL, read_bodies, &reads));
+ for (int t = 0; t < 3; ++t) pthread_join(threads[t], NULL);
+ pure_snapshot_release(reads.snapshot);
+ pure_app_stop(&app);
+ remove_store(path);
+
+ /* A body changes no state. */
+ static const O stores[] = {{C, 0, 0}, {C, 0, 2}, {LOOP, 0 | 2u << 16, 0}, {C, 0, 1}, {ST, N, 0}, {C, 0, 1}};
+ static const O appends[] = {{C, 0, 1}, {LA, XS, 0}, {C, 0, 1}};
+ static const O breaks[] = {{PURE_OP_BREAK, 0, 0}, {C, 0, 1}};
+ const struct { const O *code; size_t count; const char *why; } refused[] = {
+ {stores, LEN(stores), "view with an effect"},
+ {appends, LEN(appends), "view changes a state"},
+ {breaks, LEN(breaks), "break outside a loop"},
+ };
+ for (size_t i = 0; i < LEN(refused); ++i) {
+ V one[] = {{"v", 0, (uint32_t)refused[i].count}};
+ Spec bad = spec;
+ bad.o = refused[i].code;
+ bad.no = refused[i].count;
+ bad.h = NULL;
+ bad.nh = 0;
+ bad.v = one;
+ bad.nv = 1;
+ if (load(bad, &module_b) == PURE_OK || !strstr(error, refused[i].why)) {
+ fprintf(stderr, "body case %zu: expected '%s', got '%s'\n", i, refused[i].why, error);
+ abort();
+ }
+ }
+ pure_module_release(&module_a);
+ puts("{\"view_bodies\":\"locals-loops-own-lists-readers-work-memory-refusals\"}");
+}
+
int main(void) {
number_texts();
behaviour();
@@ -5477,6 +5581,7 @@ int main(void) {
view_arguments();
locals();
loops();
+ view_bodies();
ranges();
keyed();
keyed_mutations();