From 31e4f8aab2d4bac7daaa4c849244f9789ded73d4 Mon Sep 17 00:00:00 2001 From: eunhhu Date: Sun, 4 Oct 2026 11:57:04 +0100 Subject: Write a library's record as the app does in every hint, and post its rows over HTTP The hints of keyed lists, has and a whole item named a library's record by its plain name, which the app cannot write: they say store.User now. library_records posts a message whose rows are a library's records to pure-serve (make check builds it) and reads them back. --- Makefile | 2 +- compiler/compile.c | 8 ++++---- tests/check.py | 36 +++++++++++++++++++++++++++++------- 3 files changed, 34 insertions(+), 12 deletions(-) diff --git a/Makefile b/Makefile index 1056019..a75fab5 100644 --- a/Makefile +++ b/Makefile @@ -16,6 +16,6 @@ $(BUILD)/aup: compiler/main.c compiler/compile.c compiler/aup.h | $(BUILD) $(CC) $(CPPFLAGS) $(CFLAGS) compiler/main.c compiler/compile.c $(LDFLAGS) $(LDLIBS) -o $@ check: $(BUILD)/aup - $(MAKE) -C $(PURE) build/pure-run build/pure-edit-module + $(MAKE) -C $(PURE) build/pure-run build/pure-edit-module build/pure-serve $(MAKE) -C $(PURE)/prototypes/connections BUILD=build/tree STORE=store-tree.c build/tree/pure-edit python3 tests/check.py --aup $(BUILD)/aup --pure-run $(PURE)/build/pure-run --pure $(PURE) diff --git a/compiler/compile.c b/compiler/compile.c index d2a3c74..e0d694e 100644 --- a/compiler/compile.c +++ b/compiler/compile.c @@ -2309,7 +2309,7 @@ static Kind value(Compiler *c, const Expr *e, bool handler, int height) { app_name(c, e, e->member); const char *key = keyed_by(c, list); if (!key) fail_at(c, e->line, e->column, "'%s' has no key; declare it as list of %s by a field to ask has", - e->member, record_of(c, list)->name); + e->member, record_shown(c, c->module, record_of(c, list))); if (e->count != 1) fail_at(c, e->line, e->column, "%s.has takes one key", e->member); Type type = field_kind(record_of(c, list), (unsigned)c->list_key[list] - 1); Kind given = wanted(c, e->args[0], handler, height, type); @@ -2735,7 +2735,7 @@ static void statements(Compiler *c, const Stmt *s) { fail_at(c, s->line, s->column, "a for does not add to the list it walks; add to another list, or after the for"); if (s->kind == S_INSERT && keyed_by(c, list)) fail_at(c, s->line, s->column, "'%s' is keyed by %s: %s.add(%s { ... }) places an item by its key", s->name, - keyed_by(c, list), s->name, record_of(c, list)->name); + keyed_by(c, list), s->name, record_shown(c, c->module, record_of(c, list))); if (s->kind == S_REMOVE) item_of(c, s->first, true, 0, list); else index_of(c, s->first, true, 0, list); if (s->kind == S_INSERT) record_fields(c, s->value, list, 1); @@ -2759,7 +2759,7 @@ static void statements(Compiler *c, const Stmt *s) { int list = list_state(c, s->name, s->line, s->column, NULL); if (c->walking && list == c->walk_list) fail_at(c, s->line, s->column, "a for does not add to the list it walks; add to another list, or after the for"); - const char *key = keyed_by(c, list), *record = record_of(c, list)->name; + const char *key = keyed_by(c, list), *record = record_shown(c, c->module, record_of(c, list)); if (s->kind == S_APPEND && key) fail_at(c, s->line, s->column, "'%s' is keyed by %s: %s.add(%s { ... }) places an item by its key", s->name, key, s->name, record); @@ -2779,7 +2779,7 @@ static void statements(Compiler *c, const Stmt *s) { int key = c->list_key[list] - 1; for (unsigned k = 0; k < r->fields; ++k) if (!values[k]) fail_at(c, s->value->line, s->value->column, "%s[...] = %s { ... } needs '%s'", s->name, - r->name, r->field_names[k]); + record_shown(c, c->module, r), r->field_names[k]); if (key >= 0) { /* require tasks[at].key == the value's key */ Expr *item = node(c, E_ITEM, s->line, s->column), *same = node(c, E_BINARY, s->line, s->column); memcpy(item->name, s->name, NAME); diff --git a/tests/check.py b/tests/check.py index fdb7060..79da1ab 100644 --- a/tests/check.py +++ b/tests/check.py @@ -927,14 +927,17 @@ def libraries(aup, pure_run, work): -def library_records(aup, pure_run, work): +def library_records(aup, pure_run, pure, work): """A library declares records (docs/design/002, Pure's review 005): an app writes them LIBRARY.NAME, and they are NAME in its module, so the task list with its record moved into a library builds the same module, byte for byte. Only the records the app's lists and rows hold go into its module. A second module using the same library (a migration) reads - and changes what the app stored, and the app reads it after. Names are - one namespace in a module, however they were written.""" + and changes what the app stored, and the app reads it after; a message's + rows of a library's record arrive over HTTP (pure-serve). Names are one + namespace in a module, however they were written; hints write a + library's record as the app does.""" + import http.client import re import struct lib = work / 'record-libs' @@ -978,10 +981,26 @@ def library_records(aup, pure_run, work): run(pure_run, lib / 'migrate.pmod', '--store', store, stdin='down bump\nup bump\n') shown = json.loads(run(pure_run, lib / 'main.pmod', '--store', store, stdin='show\n').stdout.splitlines()[-1]) assert shown['views'] == {'count': 3, 'points': 30}, shown - # A message's rows of a library's record. + # A message's rows of a library's record, posted as JSON to pure-serve. assert build('rows', 'use store\ninput load = message {\n rows: list of store.User\n}\nstate n = 0\n' - 'on load {\n n = n + load.rows.count\n}\nview loaded = n\n').returncode == 0, \ - build('rows', '').stderr + 'on load {\n n = n + sum(r.points for r in load.rows)\n}\nview loaded = n\n').returncode == 0 + host = subprocess.Popen([str(pure / 'build' / 'pure-serve'), '--module', str(lib / 'rows.pmod'), '--store', + str(lib / 'rows-store'), '--port', '0'], stdout=subprocess.PIPE, stderr=subprocess.PIPE) + try: + port = json.loads(host.stdout.readline())['port'] + body = json.dumps({'rows': [{'name': 'a', 'points': 4}, {'name': 'b', 'points': 5}]}).encode() + connection = http.client.HTTPConnection('127.0.0.1', port, timeout=10) + connection.request('POST', '/inputs/load', body, {'Content-Type': 'application/json'}) + answer = connection.getresponse() + assert answer.status == 200, (answer.status, answer.getheader('X-Pure-Error'), answer.read()) + answer.read() + connection.request('GET', '/views/loaded') + loaded = connection.getresponse().read() + connection.close() + assert loaded == b'9', loaded + finally: + host.terminate() + host.communicate(timeout=30) (lib / 'other.aup').write_text('record User { name: text }\n') (lib / 'twofold.aup').write_text('record Item { n = 0 }\nfn Item = 1\n') (lib / 'stateful.aup').write_text('record R { n = 0 }\nstate x = 0\n') @@ -1001,6 +1020,9 @@ def library_records(aup, pure_run, work): 'main.aup:4:21', "'users' holds store.User, not User"), ('use store\nstate users: list of store.User\ninput j = text\non j { users.append(store.Nope { name: j }) }\n', 'main.aup:4:27', "library 'store' has no record 'Nope'"), + ('use store\nstate users: list of store.User by name\ninput j = text\n' + 'on j { users.append(store.User { name: j }) }\n', 'main.aup:4:8', + "'users' is keyed by name: users.add(store.User { ... }) places an item by its key"), ('use twofold\n', 'twofold.aup:2:4', "'Item' is already declared as a record"), ('use stateful\n', 'stateful.aup:2:1', 'a library declares only fn, record and use'), ] @@ -1709,7 +1731,7 @@ def main(): report['keyed_snapshots'], report['keyed_failed_events'] = keyed(aup, pure_run, work, range(args.seeds)) report['stored_snapshots'], report['stored_restarts'] = stored_runs(aup, pure_run, work, range(args.text_seeds)) report['library_programs'], report['library_diagnostics'] = libraries(aup, pure_run, work) - report['library_records'] = library_records(aup, pure_run, work) + report['library_records'] = library_records(aup, pure_run, args.pure.resolve(), work) report['editor_parity_operations'], report['editor_parity_outcomes'] = \ editor_parity(aup, args.pure.resolve(), work, range(args.editor_seeds)) print(json.dumps(report)) -- cgit v1.2.3