From 8d0901ba060165ad0622a3be09b09e3acd4a8899 Mon Sep 17 00:00:00 2001 From: eunhhu Date: Sun, 4 Oct 2026 14:43:11 +0100 Subject: Let a view compute in its body, on a reader's own lists (018 stage 3) --- core/test-module.c | 109 ++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 107 insertions(+), 2 deletions(-) (limited to 'core/test-module.c') 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(); -- cgit v1.2.3