From cb1202003373b344cefeecb584139fafadb273b4 Mon Sep 17 00:00:00 2001 From: eunhhu Date: Sun, 4 Oct 2026 11:50:55 +0100 Subject: Let a library declare records, written LIBRARY.NAME (005's 4) A library declares records as it declares fns; the app writes them store.User in its lists, rows fields and record values, and they are User in its module: Pure's record names are identifiers, one namespace with states. So the task list with its record moved into a library builds the same module, byte for byte, and a migration module of the same library reads and changes what the app stored. Only the library records an app's lists and rows hold go into its module, after the app's own, library by library in declaration order; a record value is matched to its declaration, not its name, and two records of one name in a module are refused. --- tests/check.py | 88 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 87 insertions(+), 1 deletion(-) (limited to 'tests/check.py') diff --git a/tests/check.py b/tests/check.py index df670ee..fdb7060 100644 --- a/tests/check.py +++ b/tests/check.py @@ -915,7 +915,7 @@ def libraries(aup, pure_run, work): assert shown['views'] == views, (source, shown['views']) bad = [ ('use loopa\n', 'loopb.aup:1:5', "library 'loopa' uses itself through its uses"), - ('use stateful\n', 'stateful.aup:2:1', 'a library declares only fn and use'), + ('use stateful\n', 'stateful.aup:2:1', 'a library declares only fn, record and use'), ('use peek\nstate x = 1\nview v = peek.f(1)\n', 'peek.aup:1:15', "'x' is not a parameter; a library fn reads only its parameters (in fn 'f' called at 3:15)"), ('use shapes\nview v = shapes.volume(1)\n', 'main.aup:2:17', "library 'shapes' has no fn 'volume'"), @@ -926,6 +926,91 @@ def libraries(aup, pure_run, work): return len(good), len(bad) + +def library_records(aup, pure_run, 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.""" + import re + import struct + lib = work / 'record-libs' + lib.mkdir(parents=True, exist_ok=True) + env = {'PATH': '/usr/bin:/bin'} + + def build(name, source): + path = lib / f'{name}.aup' + path.write_text(source) + return subprocess.run([str(aup), 'build', str(path), '-o', str(lib / f'{name}.pmod')], capture_output=True, + text=True, env=env, timeout=60) + + # The task list, its record moved into a library: the same module. + tasks = (ROOT / 'examples' / 'tasks.aup').read_text() + record = re.search(r'record Task \{.*?\n\}\n', tasks, re.S).group(0) + (lib / 'words.aup').write_text((ROOT / 'examples' / 'words.aup').read_text()) + (lib / 'taskrecords.aup').write_text(record) + moved = 'use taskrecords\n' + tasks.replace(record, '').replace('list of Task', 'list of taskrecords.Task') \ + .replace('Task {', 'taskrecords.Task {') + assert build('tasks-before', tasks).returncode == 0 and build('tasks-moved', moved).returncode == 0 + assert (lib / 'tasks-before.pmod').read_bytes() == (lib / 'tasks-moved.pmod').read_bytes() + # Only the records used: one of three. + (lib / 'people.aup').write_text('record Person { name: text }\nrecord Team { name: text, size = 0 }\n' + 'record Badge { label: text }\nfn big(n) = n > 10\n') + assert build('teams', 'use people\nstate teams: list of people.Team\ninput add = text\n' + 'on add { teams.append(people.Team { name: add, size: 12 }) }\n' + 'view big = count(t in teams where people.big(t.size))\n').returncode == 0 + module = (lib / 'teams.pmod').read_bytes() + assert struct.unpack_from('