diff options
| author | eunhhu <[email protected]> | 2026-09-25 15:05:58 +0100 |
|---|---|---|
| committer | eunhhu <[email protected]> | 2026-09-25 15:05:58 +0100 |
| commit | 91aa0afcd34cff345f1f7293d82fec7d00082d8b (patch) | |
| tree | d96b3c7c20f756562e596ded34acf35a95a57327 /tests | |
| parent | f35ded451b1da147613ece18e2b334d0966b24f1 (diff) | |
| download | aup-91aa0afcd34cff345f1f7293d82fec7d00082d8b.tar.gz aup-91aa0afcd34cff345f1f7293d82fec7d00082d8b.zip | |
Add conditional expressions and table views
if c { a } else { b }, with else-if chains, is an expression anywhere a
value goes. Only the chosen branch runs, every branch gives the same type,
and nesting that would pass Pure's limit of 8 value-carrying joins is a
compile error.
view name = each row in list { column = value ... } declares a table with
a row per item; columns read their row as an index of the list. An index
now reads and writes its item's fields directly (at.title, at.done = 1),
so 'position' cannot name a record field.
examples/tasks.aup gains conditional views and a board table and still
matches its reference model; a new check confirms the unchosen branch is
never computed.
Diffstat (limited to 'tests')
| -rw-r--r-- | tests/check.py | 67 |
1 files changed, 61 insertions, 6 deletions
diff --git a/tests/check.py b/tests/check.py index f4f3b92..73b7572 100644 --- a/tests/check.py +++ b/tests/check.py @@ -14,10 +14,11 @@ mark exactly as a reference Python model under random typing (ASCII, Korean, emoji, combining marks, NUL, newlines), moves and deletions; string escapes and number text follow their stated rules. -7. List model: examples/tasks.aup keeps its list, index, views and failed - events exactly as a reference Python model under random appends, moves, - toggles, copies, renames and removals; replacing it with a module whose - record changed keeps each item's fields by name. +7. List model: examples/tasks.aup keeps its list, index, views (conditional + ones and a table with a row per task) and failed events exactly as a + reference Python model under random appends, moves, toggles, copies, + renames and removals; replacing it with a module whose record changed + keeps each item's fields by name. 5. Editor parity: the document editor written in aup, run by pure-edit-module, answers random show/edit/apply operations exactly as pure-edit running the original editor.pure (stdout, stderr, exit code), on the same tree store @@ -36,6 +37,7 @@ ROOT = HERE.parent RECORD = 'record T {\n a = 0\n}\n' ON_K = 'input k = key KeyK\non k.down {\n' +LIST = RECORD + 'state xs: list of T\nstate at: index of xs\n' DIAGNOSTICS = [ ('state x = 1\nstate x = 2\n', '2:7', "already declared as a state"), @@ -125,6 +127,25 @@ DIAGNOSTICS = [ (RECORD + 'state n: text\nstate at: cursor in n\n' + ON_K + ' n.insert(at, T { a: 1 })\n}\n', '8:16', 'a record value goes into a list'), ('state n: text\nstate at: cursor in n\n' + ON_K + ' n.remove(at)\n}\n', '5:3', 'a text removes a range'), + ('record T {\n position = 0\n}\n', '2:3', "'position' is where an index is"), + (LIST + 'view v = at.b\n', '6:10', "record 'T' has no field 'b'"), + (LIST + ON_K + ' at.a = "x"\n}\n', '8:10', 'expected a number, not text'), + (LIST + 'state n = 0\n' + ON_K + ' n.a = 1\n}\n', '9:3', 'expected an index, not a number'), + # conditional expressions + ('view v = if 1 { 2 }\n', '1:20', 'an if expression needs else'), + ('view v = if 1 { 2 } else if 0 { 3 }\n', '1:36', 'an if expression needs else'), + ('view v = if 1 { 2 } else { "x" }\n', '1:28', 'the branches of if give a number and text'), + ('state a: text\nstate b: text\nstate x: cursor in a\nstate y: cursor in b\n' + ON_K + ' x = if 1 { x } else { y }\n}\n', + '7:25', 'the branches of if give cursors of different texts'), + ('view v = ' + '1 + (if 1 { ' * 9 + '1' + ' } else { 0 })' * 9 + '\n', '1:111', 'conditional expressions nest too deeply'), + # tables + ('view t = each x in missing {\n a = 1\n}\n', '1:20', "unknown list 'missing'"), + (LIST + 'view t = each x in xs {\n}\n', '7:1', 'a table needs at least one column'), + (LIST + 'view t = each x in xs {\n a = 1\n a = 2\n}\n', '8:3', "duplicate column 'a'"), + (LIST + 'view t = each x in xs {\n a = x.a\n}\nview v = x.a\n', '9:10', "unknown state 'x'"), + (LIST + 'view t = each at in xs {\n a = 1\n}\n', '6:15', "'at' is already declared as a list index"), + (LIST + 'view t = each x in xs {\n c = x\n}\n', '7:7', 'a column is a number or text, not an index'), + ('view each = 1\n', '1:6', "'each' is reserved"), ] ACCEPTED = [ @@ -150,6 +171,18 @@ ACCEPTED = [ 'record Row {\n label: text\n}\nstate rows: list of Row\nstate focus: index of rows\ninput next = key Tab\n' 'on next.down {\n if focus.position + 1 < rows.count {\n focus = focus + 1\n } else {\n focus = rows.first\n }\n}\n' 'view summary = "{rows.count} rows, focus {focus.position}"\n', + # conditionals: else-if chains, on the next line, inside arithmetic, over cursors, seven deep + 'state n: text = "ab"\nstate at: cursor in n\nstate x = 2\ninput k = key KeyK\n' + 'on k.down {\n x = 1 + if x > 1 { 10 } else if x > 0 { 20 }\n else { 30 }\n' + ' at = if x > 5 { n.end } else { n.start }\n}\n' + 'view size = if x > 1 {\n "big"\n} else {\n "small"\n}\n' + 'view deep = ' + '1 + (if 1 { ' * 7 + '1' + ' } else { 0 })' * 7 + '\n', + # a table whose columns read their row, other states and the row's position + 'record Cell {\n label: text = "-"\n weight = 1\n}\nstate cells: list of Cell\nstate at: index of cells\n' + 'input add = key KeyA\non add.down {\n cells.append(Cell { weight: cells.count })\n at.label = "#{at.weight}"\n}\n' + 'view grid = each cell in cells {\n label = cell.label\n heavy = cell.weight > 2\n' + ' here = if cell.position == at.position { ">" } else { " " }\n}\n' + 'view other = each cell in cells { weight = cell.weight }\n', ] @@ -460,9 +493,13 @@ class TasksModel: def expected(self): item = self.tasks[self.at] if self.tasks else None + board = [{'title': task['title'], 'mark': 'x' if task['done'] else ' ', 'here': int(i == self.at)} + for i, task in enumerate(self.tasks)] return ({'tasks': [dict(task) for task in self.tasks], 'at': self.at, 'made': self.made}, - {'count': len(self.tasks), 'position': self.at, 'current': item and item['title'], - 'status': f'{self.at + 1}/{len(self.tasks)}', 'finished': item and item['done']}) + {'count': len(self.tasks), 'position': self.at, + 'current': item['title'] if item else '(empty)', + 'status': f'{self.at + 1}/{len(self.tasks)}' if item else 'empty', + 'finished': item and item['done'], 'board': board}) def tasks_parity(aup, pure_run, work, seeds, steps=400): @@ -507,6 +544,23 @@ def tasks_parity(aup, pure_run, work, seeds, steps=400): return compared, failed +def conditionals(aup, pure_run, work): + """Only the chosen branch runs (10 / x is never computed at x = 0), else-if + chains pick the first true arm, and conditionals nest inside arithmetic.""" + source = work / 'conditionals.aup' + source.write_text('state x = 0\ninput k = key KeyK\non k.down { x = x + 1 }\n' + 'view grade = if x > 2 { "high" } else if x > 0 { "some" } else { "none" }\n' + 'view safe = if x > 0 { 10 / x } else { 0 }\n' + 'view nested = 1 + (if x == 1 { 2 * (if x > 0 { 3 } else { 4 }) } else { 5 })\n') + compile_module(aup, source, work / 'conditionals.pmod') + lines = run(pure_run, work / 'conditionals.pmod', stdin='show\n' + 'down k\n' * 3).stdout.splitlines() + expected = [('none', 0, 6), ('some', 10, 7), ('some', 5, 6), ('high', 10 / 3, 6)] + for line, (grade, safe, nested) in zip(lines, expected): + views = json.loads(line)['views'] + assert views == {'grade': grade, 'safe': safe, 'nested': nested}, (views, grade, safe, nested) + return len(expected) + + def text_parity(aup, pure_run, work, seeds, steps=400): module = work / 'notes.pmod' compile_module(aup, ROOT / 'examples/notes.aup', module) @@ -617,6 +671,7 @@ def main(): report['text_model_snapshots'] = text_parity(aup, pure_run, work, range(args.text_seeds)) report['list_model_snapshots'], report['list_model_failed_events'] = \ tasks_parity(aup, pure_run, work, range(args.text_seeds)) + report['conditional_snapshots'] = conditionals(aup, pure_run, work) report['editor_parity_operations'], report['editor_parity_outcomes'] = \ editor_parity(aup, args.pure.resolve(), work, range(args.editor_seeds)) print(json.dumps(report)) |
