diff options
Diffstat (limited to 'prototypes')
| -rw-r--r-- | prototypes/connections/module.c | 23 | ||||
| -rw-r--r-- | prototypes/connections/module.h | 13 | ||||
| -rw-r--r-- | prototypes/connections/pure-run.c | 12 | ||||
| -rw-r--r-- | prototypes/connections/test-module.c | 72 |
4 files changed, 109 insertions, 11 deletions
diff --git a/prototypes/connections/module.c b/prototypes/connections/module.c index 7f22c49..73a9ed6 100644 --- a/prototypes/connections/module.c +++ b/prototypes/connections/module.c @@ -97,6 +97,8 @@ static const struct { unsigned char pops, in[3], out, where, arg, of; } signatur [PURE_OP_TEXT_COMPARE] = {2, {T_TEXT, T_TEXT}, T_NUM, READ, A_RELATION, O_ANY}, [PURE_OP_LIST_FIND] = {1, {T_KEY}, T_INDEX, READ, A_KEYED, O_ARG}, [PURE_OP_LIST_HAS] = {1, {T_KEY}, T_NUM, READ, A_KEYED, O_ANY}, + [PURE_OP_NOW] = {0, {0}, T_NUM, IN_HANDLER, A_NONE, O_ANY}, + [PURE_OP_FLOOR] = NUMS(1), [PURE_OP_MOD] = NUMS(2), }; #undef NUMS #undef STATE @@ -446,6 +448,12 @@ PURE_COLD PureResult pure_module_load(const void *bytes, size_t size, PureModule REFUSE("text input shape", i); continue; } + if (shape->kind == PURE_KIND_TIMER) { + shape->period = read32(at + 8); + if (shape->keyed || shape->field_start || shape->field_count || !shape->period || + shape->period > PURE_TIMER_LONGEST) REFUSE("timer input", i); + continue; + } if (shape->kind != PURE_KIND_MESSAGE || read32(at + 8) || shape->field_start != next_field || shape->field_count > out->fields - next_field) REFUSE("message shape", i); /* The prefix holds the fixed fields and the variable fields' lengths @@ -771,6 +779,8 @@ static PureResult run(const PureModule *m, double *values, PureBlock block, Even case PURE_OP_CONST: stack[sp++].num = op->value; continue; case PURE_OP_LOAD: stack[sp++].num = values[arg]; continue; case PURE_OP_DT: stack[sp++].num = ev->dt; continue; + case PURE_OP_NOW: stack[sp++].num = app->now; continue; + case PURE_OP_FLOOR: top->num = floor(top->num); continue; case PURE_OP_STORE: case PURE_OP_CUR_STORE: values[arg] = stack[--sp].num; continue; case PURE_OP_JZ: if (stack[--sp].num == 0) pc = arg; continue; case PURE_OP_JMP: pc = arg; continue; @@ -1069,6 +1079,12 @@ static PureResult run(const PureModule *m, double *values, PureBlock block, Even case PURE_OP_AND: value = a != 0 && b != 0; break; case PURE_OP_OR: value = a != 0 || b != 0; break; case PURE_OP_MIN: value = minimum(a, b); break; + case PURE_OP_MOD: /* the divisor's sign, as floor division leaves it (a zero too) */ + if (b == 0) { kind = PURE_FAULT_DIVIDE; goto failed; } + value = fmod(a, b); + if (value != 0 && (value < 0) != (b < 0)) value += b; + else if (value == 0) value = copysign(0.0, b); + break; default: value = maximum(a, b); break; } if (!isfinite(value)) { kind = PURE_FAULT_NON_FINITE; goto failed; } @@ -1266,6 +1282,13 @@ PureResult pure_app_frame(PureApp *app) { return dispatch(app, 0, &ev); } +PureResult pure_app_tick(PureApp *app, uint32_t input) { + if (input >= app->module->inputs || app->module->input[input].kind != PURE_KIND_TIMER) return PURE_INVALID; + pure_app_finish(app); + Event ev = {.app = app, .arena = &app->arena}; + return dispatch(app, 1 + 2 * input, &ev); +} + PureResult pure_app_input(PureApp *app, uint32_t input, PureEdge edge) { if (input >= app->module->inputs || app->module->input[input].kind != PURE_KIND_KEY || (edge != PURE_EDGE_DOWN && edge != PURE_EDGE_UP)) return PURE_INVALID; diff --git a/prototypes/connections/module.h b/prototypes/connections/module.h index 52671f0..95483c7 100644 --- a/prototypes/connections/module.h +++ b/prototypes/connections/module.h @@ -38,6 +38,7 @@ typedef enum { 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_ROW, PURE_OP_PICK, PURE_OP_SLIDE, PURE_OP_FOLD, PURE_OP_TEXT_COMPARE, PURE_OP_LIST_FIND, PURE_OP_LIST_HAS, + PURE_OP_NOW, PURE_OP_FLOOR, PURE_OP_MOD, PURE_OP_COUNT } PureOpcode; /* FOLD's argument: the kind (bits 0-7), the list state (8-15) and the length @@ -50,7 +51,9 @@ typedef enum { PURE_FOLD_COUNT, PURE_FOLD_SUM, PURE_FOLD_MAX, PURE_FOLD_MIN, PUR * comparisons (LT LE GT GE EQ NE); texts order by their bytes. */ typedef enum { PURE_LESS, PURE_LESS_EQUAL, PURE_GREATER, PURE_GREATER_EQUAL, PURE_EQUAL, PURE_NOT_EQUAL } PureRelation; -typedef enum { PURE_KIND_KEY = 1, PURE_KIND_MESSAGE = 2, PURE_KIND_TEXT = 3 } PureInputKind; +/* A timer ticks every `period` milliseconds while a host runs the app. */ +typedef enum { PURE_KIND_KEY = 1, PURE_KIND_MESSAGE = 2, PURE_KIND_TEXT = 3, PURE_KIND_TIMER = 4 } PureInputKind; +enum { PURE_TIMER_LONGEST = 86400000 }; /* A message field: a u32le or a binary64 (fixed, at its offset in the * payload's prefix), or bytes or text of any length. The prefix also holds * each variable field's u32le length but the last one's (its offset is @@ -86,7 +89,8 @@ typedef struct { uint8_t kind; bool keyed; uint16_t field_start, field_count; - uint32_t prefix; /* fixed payload bytes before an optional bytes tail */ + uint32_t prefix; /* fixed payload bytes before the variable fields */ + uint32_t period; /* a timer's, in milliseconds */ } PureInputShape; /* A record type: fields numbered from first, each a number or text. */ @@ -198,6 +202,9 @@ typedef struct { PureFault fault; uint32_t fault_pc; uint64_t ticks, events; + /* The time events see as now, in milliseconds since 1970 (UTC). The host + * sets it, so a script can replay any time; views never see it. */ + double now; uint64_t changes; /* counts every change of states, never back: what a filter's rows are fresh for */ struct PureRows { uint32_t table; uint64_t changes; size_t *items, count, capacity; } *rows; /* a filtered table's items */ unsigned row_tables; @@ -220,6 +227,8 @@ bool pure_app_defer(PureApp *, bool deferred); /* Leaves unsaved frame changes unsaved: call pure_app_save first to keep them. */ void pure_app_stop(PureApp *); PureResult pure_app_frame(PureApp *); +/* A timer input's tick: an event saved like a key's. */ +PureResult pure_app_tick(PureApp *, uint32_t input); PureResult pure_app_input(PureApp *, uint32_t input, PureEdge); /* Point an index state at the item at position (0 first), as a user pointing * at a row: an event with no handler, saved like a key event. NOT_FOUND past diff --git a/prototypes/connections/pure-run.c b/prototypes/connections/pure-run.c index d97d277..ef36965 100644 --- a/prototypes/connections/pure-run.c +++ b/prototypes/connections/pure-run.c @@ -10,11 +10,12 @@ * states live in that tree store: it starts from them, and frame changes are * saved at the end of input or by save. --batch N defers saving and saves * every N commands (and at the end), so N events share one commit. - * down NAME | up NAME | frame [N] | type NAME HEX|- | replace PATH | save | show + * down NAME | up NAME | frame [N] | type NAME HEX|- | tick NAME | time MS | replace PATH | save | show * Text states and text views print as JSON strings; cursors as byte positions; * 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", "exists"}; +/* time MS sets the time events see (milliseconds since 1970); tick NAME is a timer's tick. */ static bool load(const char *path, PureModule *module) { FILE *file = fopen(path, "rb"); @@ -169,6 +170,15 @@ int main(int argc, char **argv) { text[i] = (unsigned char)(hex(data[2 * i]) << 4 | hex(data[2 * i + 1])); if (input < 0) failure = "unknown input"; else if (pure_app_text(&app, (uint32_t)input, (PureSpan){text, length}) != PURE_OK) failure = "event failed"; + } else if (!strcmp(command, "tick")) { + int input = pure_module_input(app.module, argument); + if (input < 0) failure = "unknown input"; + else if (pure_app_tick(&app, (uint32_t)input) != PURE_OK) failure = "event failed"; + } else if (!strcmp(command, "time")) { + char *end; + double now = strtod(argument, &end); + if (!*argument || *end || !(now >= 0 && now < 1e15)) failure = "time"; + else app.now = now; } else if (!strcmp(command, "frame")) { long count = *argument ? strtol(argument, NULL, 10) : 1; if (count < 1 || count > 600) failure = "frame count"; diff --git a/prototypes/connections/test-module.c b/prototypes/connections/test-module.c index c881678..257c632 100644 --- a/prototypes/connections/test-module.c +++ b/prototypes/connections/test-module.c @@ -27,6 +27,8 @@ typedef struct { const char *name; uint32_t type; } F; /* kind 0 is a key input; otherwise PURE_KIND_MESSAGE with fields. */ typedef struct { const char *name, *key; int kind; bool keyed; const F *fields; size_t nfields; } I; #define KEY(name, key) {name, key, 0, false, NULL, 0} +/* A timer's period (milliseconds) rides in nfields. */ +#define TIMER(name, period) {name, NULL, PURE_KIND_TIMER, false, NULL, period} typedef struct { const char *name; uint32_t start, length; } V; typedef struct { uint32_t event, start, length; } H; typedef struct { uint8_t op; uint32_t arg; double value; } O; @@ -73,7 +75,8 @@ static size_t build(unsigned char *out, Spec spec) { for (size_t i = 0; i < spec.ni; ++i) { text[names++] = spec.in[i].name; if (!spec.in[i].kind) text[names++] = spec.in[i].key; - for (size_t f = 0; f < spec.in[i].nfields; ++f) text[names++] = spec.in[i].fields[f].name; + for (size_t f = 0; spec.in[i].kind != PURE_KIND_TIMER && f < spec.in[i].nfields; ++f) + text[names++] = spec.in[i].fields[f].name; } 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; @@ -92,7 +95,7 @@ static size_t build(unsigned char *out, Spec spec) { used += length; } size_t fields = 0; - for (size_t i = 0; i < spec.ni; ++i) fields += spec.in[i].nfields; + for (size_t i = 0; i < spec.ni; ++i) fields += spec.in[i].kind == PURE_KIND_TIMER ? 0 : spec.in[i].nfields; 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); @@ -121,16 +124,17 @@ static size_t build(unsigned char *out, Spec spec) { const I *in = &spec.in[i]; p = put32(p, offset[n++]); *p++ = (unsigned char)(in->kind ? in->kind : 1); *p++ = in->keyed; *p++ = 0; *p++ = 0; - p = put32(p, in->kind ? 0 : offset[n++]); + bool timer = in->kind == PURE_KIND_TIMER; + p = put32(p, timer ? (uint32_t)in->nfields : in->kind ? 0 : offset[n++]); uint32_t first = in->kind == PURE_KIND_MESSAGE ? field_start : 0; /* only messages have fields */ *p++ = (unsigned char)first; *p++ = (unsigned char)(first >> 8); - *p++ = (unsigned char)in->nfields; *p++ = 0; - for (size_t f = 0; f < in->nfields; ++f) field_offsets[nf++] = offset[n++]; - field_start += (uint32_t)in->nfields; + *p++ = (unsigned char)(timer ? 0 : in->nfields); *p++ = 0; + for (size_t f = 0; !timer && f < in->nfields; ++f) field_offsets[nf++] = offset[n++]; + field_start += timer ? 0 : (uint32_t)in->nfields; } nf = 0; for (size_t i = 0; i < spec.ni; ++i) - for (size_t f = 0; f < spec.in[i].nfields; ++f) { + for (size_t f = 0; spec.in[i].kind != PURE_KIND_TIMER && f < spec.in[i].nfields; ++f) { p = put32(p, field_offsets[nf++]); p = put32(p, spec.in[i].fields[f].type); } @@ -2259,6 +2263,57 @@ static void message_fields(void) { CHECK(load(bad, &module_b) == PURE_INVALID && strstr(error, "operand type")); } +/* A timer's tick sees the time the host set; floor and a floored modulo + * take it apart. now is for handlers only. */ +enum { NOW = PURE_OP_NOW, FLOOR = PURE_OP_FLOOR, MOD = PURE_OP_MOD }; +static void timers(void) { + static const S nums[] = {{"clock", 0}, {"seconds", 0}, {"ticks", 0}, {"rest", 0}}; + static const I inputs[] = {TIMER("tick", 1000), KEY("left", "KeyL"), KEY("zero", "KeyZ")}; + static const O code[] = { + {NOW, 0, 0}, {ST, 0, 0}, {NOW, 0, 0}, {C, 0, 1000}, {DIV, 0, 0}, {FLOOR, 0, 0}, {C, 0, 60}, {MOD, 0, 0}, + {ST, 1, 0}, {L, 2, 0}, {C, 0, 1}, {ADD, 0, 0}, {ST, 2, 0}, /* 0 tick */ + {C, 0, -7}, {C, 0, 3}, {MOD, 0, 0}, {C, 0, 7}, {C, 0, -3}, {MOD, 0, 0}, {C, 0, 10}, {MUL, 0, 0}, + {ADD, 0, 0}, {ST, 3, 0}, /* 13 left: 2 + -20 */ + {C, 0, 1}, {C, 0, 0}, {MOD, 0, 0}, {ST, 3, 0}, /* 23 zero */ + }; + static const H handlers[] = {{1, 0, 13}, {3, 13, 10}, {5, 23, 4}}; + static const V one_view[] = {{"v", 0, 1}}; + Spec spec = SPEC(nums, inputs, one_view, handlers, code); + spec.nv = 0; + if (load(spec, &module_a) != PURE_OK) { fprintf(stderr, "%s\n", error); abort(); } + CHECK(module_a.input[0].kind == PURE_KIND_TIMER && module_a.input[0].period == 1000); + PureApp app; + CHECK(pure_app_start(&app, &module_a, (PureData){0}) == PURE_OK); + app.now = 1700000123456.0; + CHECK(pure_app_tick(&app, 0) == PURE_OK && app.values[0] == 1700000123456.0 && + app.values[1] == (double)(1700000123 % 60) && app.values[2] == 1); + CHECK(pure_app_tick(&app, 1) == PURE_INVALID && pure_app_input(&app, 0, PURE_EDGE_DOWN) == PURE_INVALID); + CHECK(pure_app_input(&app, 1, PURE_EDGE_DOWN) == PURE_OK && app.values[3] == 2 - 20); + /* A zero takes the divisor's sign too. */ + static const O zeros[] = {{C, 0, -6}, {C, 0, 3}, {MOD, 0, 0}, {ST, 0, 0}, {C, 0, 6}, {C, 0, -3}, {MOD, 0, 0}, {ST, 1, 0}}; + static const H zero_handler[] = {{3, 0, 8}}; + Spec signs = spec; + signs.o = zeros; signs.no = 8; signs.h = zero_handler; signs.nh = 1; + CHECK(load(signs, &module_b) == PURE_OK); + PureApp zero; + CHECK(pure_app_start(&zero, &module_b, (PureData){0}) == PURE_OK && pure_app_input(&zero, 1, PURE_EDGE_DOWN) == PURE_OK); + CHECK(zero.values[0] == 0 && !signbit(zero.values[0]) && zero.values[1] == 0 && signbit(zero.values[1])); + pure_app_stop(&zero); + CHECK(pure_app_input(&app, 2, PURE_EDGE_DOWN) == PURE_INVALID && app.fault == PURE_FAULT_DIVIDE); + pure_app_stop(&app); + /* A timer's period is 1 ms to a day, and it has nothing else; now is for handlers. */ + static const I no_period[] = {TIMER("tick", 0)}, too_long[] = {TIMER("tick", PURE_TIMER_LONGEST + 1)}; + static const O now_view[] = {{NOW, 0, 0}}; + Spec bad = spec; + bad.in = no_period; bad.ni = 1; bad.nh = 1; bad.o = code; bad.no = 13; + CHECK(load(bad, &module_b) == PURE_INVALID && strstr(error, "timer input")); + bad.in = too_long; + CHECK(load(bad, &module_b) == PURE_INVALID && strstr(error, "timer input")); + bad = SPEC(nums, inputs, one_view, handlers, now_view); + bad.nh = 0; + CHECK(load(bad, &module_b) == PURE_INVALID && strstr(error, "view with an effect")); +} + int main(void) { behaviour(); faults(); @@ -2266,6 +2321,7 @@ int main(void) { mutations(); messages(); message_fields(); + timers(); message_mutations(); texts(); marks(); @@ -2283,6 +2339,6 @@ int main(void) { stored_states(); deferred(); 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-joins-tables-columns-pick-slide-stored-equivalence-crashes-frames-messages-migration-deferred-bursts-pointing-marks-folds-filters-keyed-lists-text-order-message-fields\"}"); + 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-pick-slide-stored-equivalence-crashes-frames-messages-migration-deferred-bursts-pointing-marks-folds-filters-keyed-lists-text-order-message-fields-timers-now\"}"); return 0; } |
