aboutsummaryrefslogtreecommitdiffstats
path: root/tests
diff options
context:
space:
mode:
authoreunhhu <[email protected]>2026-09-30 16:32:17 +0100
committereunhhu <[email protected]>2026-09-30 16:32:17 +0100
commit24d95e19b4eec44c5f8c9f551ebf4e8fc465d5f5 (patch)
tree5314f4e52dab77fbd0f40d4c76922d75bc3babf9 /tests
parentb8e3faaf1a3766591d31e6c5411c9e4fcc59a7a4 (diff)
downloadaup-24d95e19b4eec44c5f8c9f551ebf4e8fc465d5f5.tar.gz
aup-24d95e19b4eec44c5f8c9f551ebf4e8fc465d5f5.zip
Give aup an i64: exact integers, and literals typed by what they meet
state, record, message, output and argument fields, lets, views, keys and slices can be i64 (Pure 016 stage 2). An integer literal becomes an i64 where the other side of an operation, a comparison or an assignment is one; a literal-only side follows the other, found by compiling it on trial and rolling back. + - * and negation fail the event with overflow, / and % floor, and i64(x) and number(n) are the only way between the two, so a mix is a diagnostic that says so. The check adds 18 i64 diagnostics, i64 accepted forms (with a 30-deep chain that must stay linear), an event-by-event program past 2^53 and at the edges, and i64 values in the generated programs.
Diffstat (limited to 'tests')
-rw-r--r--tests/check.py162
1 files changed, 140 insertions, 22 deletions
diff --git a/tests/check.py b/tests/check.py
index 3100e64..df670ee 100644
--- a/tests/check.py
+++ b/tests/check.py
@@ -91,7 +91,7 @@ DIAGNOSTICS = [
# v0b: messages, keyed documents and types
('state d: bytes by key\ninput m = message\non m {\n reply d[key]\n}\n', '4:9', 'd[key] needs the handler of a message by key'),
('input m = message by key { a: u32 }\non m {\n reply m.a\n}\n', '3:9', 'reply takes bytes or text, not a number'),
- ('input m = message {\n a: float\n}\n', '2:6', 'field types are u32, number, bool, bytes, text and list of a record'),
+ ('input m = message {\n a: float\n}\n', '2:6', 'field types are u32, i64, number, bool, bytes, text and list of a record'),
('input m = message by key\non m.down {\n}\n', '2:4', "'m' is a message; write on m { }"),
('input k = key Space\non k {\n}\n', '2:4', "'k' is a key input; write on k.down or on k.up"),
('state d: bytes by key\ninput m = message by key { a: bytes }\non m {\n d[1].replace(0 .. 0, m.a)\n}\n', '4:5',
@@ -129,8 +129,8 @@ DIAGNOSTICS = [
'6:12', "a cursor of 'a' cannot point into 'b'"),
('state a: text\nstate at: cursor in a\ninput k = key KeyK\non k.down {\n at = 3\n}\n', '5:8',
'expected a cursor, not a number'),
- ('state a: text\nstate at: cursor in a\nview v = at\n', '3:10', 'a view is a number, a bool or text, not a cursor'),
- ('state a: text\nstate at: cursor in a\nview v = "{at}"\n', '3:12', 'a string shows numbers, bools and text, not a cursor'),
+ ('state a: text\nstate at: cursor in a\nview v = at\n', '3:10', 'a view is a number, a bool, an i64 or text, not a cursor'),
+ ('state a: text\nstate at: cursor in a\nview v = "{at}"\n', '3:12', 'a string shows numbers, bools, i64s and text, not a cursor'),
('state x = 1\nstate t: text = "{x}"\n', '2:17', 'a text state starts from a plain string'),
('view v = "a\\qb"\n', '1:12', 'unknown escape'),
('view v = "\\u{D800}"\n', '1:11', 'Unicode scalar'),
@@ -145,7 +145,7 @@ DIAGNOSTICS = [
# records, lists and indices
('record T {\n a = 0\n a = 1\n}\n', '3:3', "duplicate field 'a'"),
('record T {\n}\n', '1:8', 'a record needs at least one field'),
- ('record T {\n a: number\n}\n', '2:6', 'a field is a number (field = 0), a bool (field = false) or text (field: text)'),
+ ('record T {\n a: number\n}\n', '2:6', 'a field is a number (field = 0), a bool (field = false), text (field: text) or an i64 (field: i64)'),
('record T {\n a = 0\n}\nstate T = 0\n', '4:7', "'T' is already declared as a record"),
('state xs: list of Missing\n', '1:7', "a list holds a record; 'Missing' is not one"),
('state x = 0\nstate at: index of x\n', '2:7', "an index points into a list state; 'x' is not one"),
@@ -156,7 +156,7 @@ DIAGNOSTICS = [
(RECORD + 'state xs: list of T\nstate at: index of xs\nview v = xs[at]\n', '6:10', 'read a field of the item'),
(RECORD + 'state xs: list of T\nview v = xs\n', '5:10', "'xs' is a list; read xs.count"),
(RECORD + 'state xs: list of T\nview v = xs.size\n', '5:10', "a list has count, first and last, not 'size'"),
- (RECORD + 'state xs: list of T\nstate at: index of xs\nview v = at\n', '6:10', 'a view is a number, a bool or text, not an index'),
+ (RECORD + 'state xs: list of T\nstate at: index of xs\nview v = at\n', '6:10', 'a view is a number, a bool, an i64 or text, not an index'),
(RECORD + 'state xs: list of T\nstate at: index of xs\n' + ON_K + ' at = 3\n}\n', '8:8', 'expected an index, not a number'),
(RECORD + 'state xs: list of T\n' + ON_K + ' xs = 1\n}\n', '7:3', 'a list changes by append, insert, remove'),
(RECORD + 'record U {\n a = 0\n}\nstate xs: list of T\n' + ON_K + ' xs.append(U { a: 1 })\n}\n', '10:13',
@@ -191,7 +191,7 @@ DIAGNOSTICS = [
(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, a bool or text, not an index'),
+ (LIST + 'view t = each x in xs {\n c = x\n}\n', '7:7', 'a column is a number, a bool, an i64 or text, not an index'),
('view each = 1\n', '1:6', "'each' is reserved"),
# fns
('fn f(x) = x\nview v = f(1, 2)\n', '2:10', 'f expects 1 argument'),
@@ -207,13 +207,13 @@ DIAGNOSTICS = [
(LIST + 'view v = count(x in xs where count(y in xs) > 0)\n', '6:30', 'a count, sum, max or min cannot hold another'),
(LIST + 'view v = count(x in at)\n', '6:10', "'at' is not a list"),
('record T {\n t: text\n}\nstate xs: list of T\nview v = sum(x.t for x in xs)\n', '5:14',
- 'sum, max, min and distinct take numbers, not text'),
+ 'sum, max, min and distinct take numbers or i64s, not text'),
(LIST + 'fn f(n) = count(x in xs where x.a == n)\nview v = f(1)\n', '6:38',
"'n' is a parameter outside this count, sum, max or min"),
(LIST + 'view v = sum(dt for x in xs)\n', '6:14', 'dt is not available in count, sum, max or min'),
(LIST + 'view v = sum(x.a for x xs)\n', '6:24', "expected 'in' and a list, as in sum(x in tasks)"),
(LIST + 'view w = each r in xs where r {\n n = r.a\n}\n', '6:29', 'expected a bool, not an index'),
- (LIST + 'view v = first(x in xs)\n', '6:10', 'a view is a number, a bool or text, not an index'),
+ (LIST + 'view v = first(x in xs)\n', '6:10', 'a view is a number, a bool, an i64 or text, not an index'),
(LIST + 'view w = each r in xs where count(y in xs) > 0 {\n n = r.a\n}\n', '6:29',
"a table's where cannot hold a count, sum, max or min"),
# Keyed lists: a key field of the record, placed by add, found by key, never changed.
@@ -276,7 +276,7 @@ DIAGNOSTICS = [
(ARGS + 'view v(k: u32, k: u32) = k\n', '6:16', "duplicate argument 'k'"),
(ARGS + 'view v(n: u32) = n\n', '6:8', "'n' is already declared as a state"),
(ARGS + 'view t(r: u32) = each r in xs {\n a = r.a\n}\n', '6:8', "'r' names the table's row"),
- (ARGS + 'view v(k: list) = 1\n', '6:11', 'an argument is a u32, number, bool, bytes or text'),
+ (ARGS + 'view v(k: list) = 1\n', '6:11', 'an argument is a u32, i64, number, bool, bytes or text'),
(ARGS + 'view v() = 1\n', '6:6', "'v()' takes no argument: write view v = ..."),
(ARGS + 'view averylongviewnamethatistoolong(k: u32) = k\n', '6:6',
'a view with arguments has a name of at most 26 characters'),
@@ -284,7 +284,7 @@ DIAGNOSTICS = [
# A let holds a number or an index for the rest of its block, in its handler.
(LETS + 'on k.down {\n let n = 1\n}\n', '10:7', "'n' is already declared as a state"),
(LETS + 'on k.down {\n let x = 1\n let x = 2\n}\n', '11:7', "'x' is already a let here"),
- (LETS + 'on k.down {\n let x = t\n}\n', '10:11', 'a let holds a number, a bool or an index, not text'),
+ (LETS + 'on k.down {\n let x = t\n}\n', '10:11', 'a let holds a number, a bool, an i64 or an index, not text'),
(LETS + 'on k.down {\n let x = 1\n x = xs.first\n}\n', '11:7', "'x' holds a number"),
(LETS + 'on k.down {\n let i = xs.first\n i = ys.first\n}\n', '11:7', "'i' holds an index of its list"),
(LETS + 'on k.down {\n if n > 0 {\n let x = 1\n }\n n = x\n}\n', '13:7', "unknown state 'x'"),
@@ -292,8 +292,8 @@ DIAGNOSTICS = [
(LETS + 'view v = x\non k.down {\n let x = 1\n}\n', '9:10', "unknown state 'x'"),
(LETS + 'on k.down {\n let let = 1\n}\n', '10:7', "'let' is reserved"),
# A slice takes a list keyed by a number.
- (LETS + 'view v = count(x in xs[0 .. 3])\n', '9:10', "'xs' is not keyed by a number: a slice takes one"),
- (LETS + 'on k.down {\n for x in xs[0 .. 1] {\n n = 1\n }\n}\n', '10:12', "'xs' is not keyed by a number: a slice takes one"),
+ (LETS + 'view v = count(x in xs[0 .. 3])\n', '9:10', "'xs' is not keyed by a number or an i64: a slice takes one"),
+ (LETS + 'on k.down {\n for x in xs[0 .. 1] {\n n = 1\n }\n}\n', '10:12', "'xs' is not keyed by a number or an i64: a slice takes one"),
# A bool is not a number: conditions take bools, and a number says how to become one.
('state n = 0\non frame {\n if n {\n n = 1\n }\n}\n', '3:6', 'if takes a bool, not a number: compare it, as in n != 0'),
('view v = if 1 { 2 } else { 3 }\n', '1:13', 'if takes a bool, not a number'),
@@ -310,14 +310,50 @@ DIAGNOSTICS = [
('state lit = true\nstate n = 0\non frame {\n n = lit\n}\n', '4:7', 'expected a number, not a bool'),
('state lit = true\non frame {\n lit = 1\n}\n', '3:9', 'expected a bool, not a number'),
('record T {\n a = false\n}\nstate xs: list of T by a\n', '4:24', "a list is keyed by a number or text; 'a' is a bool"),
- ('record T {\n a = false\n}\nstate xs: list of T\nview v = sum(x.a for x in xs)\n', '5:', 'sum, max, min and distinct take numbers, not a bool'),
+ ('record T {\n a = false\n}\nstate xs: list of T\nview v = sum(x.a for x in xs)\n', '5:', 'sum, max, min and distinct take numbers or i64s, not a bool'),
('record T {\n a = false\n}\nstate xs: list of T\n' + ON_K + ' xs.append(T { a: 1 })\n}\n', '7:20', 'expected a bool, not a number'),
('input m = message {\n ok: bool\n}\nstate n = 0\non m {\n n = m.ok\n}\n', '6:7', 'expected a number, not a bool'),
('state lit = true\noutput o = message {\n n: number\n}\n' + ON_K + ' send o { n: lit }\n}\n', '7:15', 'expected a number, not a bool'),
('state true = 1\n', '1:7', "'true' is reserved"),
+ # An i64 is exact and whole, and does not mix with numbers: a literal takes the type the other side has.
+ ('state n: i64 = 1.5\n', '1:16', "expected a whole number for an i64 state"),
+ ('state n: i64 = 9223372036854775808\n', '1:16', "the number is past an i64's range"),
+ ('state n: i64\nstate x = 0\nview v = n + x\n', '3:14', 'expected an i64, not a number: convert it, as in i64(x)'),
+ ('state n: i64\nstate x = 0\nview v = x + n\n', '3:14', 'an i64 and a number do not mix: convert one, as in i64(x) or number(n)'),
+ ('state n: i64\non frame {\n n = 2.5\n}\n', '3:7', 'an i64 is a whole number; this one is not'),
+ ('state n: i64\nstate x = 0\non frame {\n n = x\n}\n', '4:7', 'expected an i64, not a number: convert it, as in i64(x)'),
+ ('state n: i64\nstate x = 0\non frame {\n x = n\n}\n', '4:7', 'expected a number, not an i64'),
+ ('state n: i64\nview v = clamp(n, 0, 1)\n', '2:16', 'expected a number, not an i64'),
+ ('state n: i64\nview v = floor(n)\n', '2:16', 'expected a number, not an i64'),
+ ('state n: i64\nview v = i64(n)\n', '2:14', 'expected a number, not an i64'),
+ ('state x = 0\nview v = number(x)\n', '2:17', 'expected an i64, not a number: convert it, as in i64(x)'),
+ ('record T {\n id: i64 = 1.5\n}\n', '2:13', "expected a whole number for an i64 field's default"),
+ ('record T {\n id: i64\n}\nstate xs: list of T by id\nview v = xs[1.5].id\n', '5:13',
+ "'xs' is keyed by id, an i64: expected an i64 or an index, not a number"),
+ ('state n: i64\nview v = if true { n } else { 1.5 }\n', '2:31', 'the branches of if give an i64 and a number'),
+ ('state n: i64\nview v = n < true\n', '2:14', 'expected an i64, not a bool'),
+ ('view v = 9223372036854775808 + i64(1)\n', '1:10', "the number is past an i64's range"),
+ ('input m = message {\n id: i64\n}\nstate x = 0\non m {\n x = m.id\n}\n', '6:7', 'expected a number, not an i64'),
+ ('record T {\n a: i64\n}\nstate xs: list of T\nview v = sum(x.a for x in xs) + 1.5\n', '5:33',
+ 'an i64 is a whole number; this one is not'),
]
ACCEPTED = [
+ # Literals beside numbers and i64s, nested deep: each chain is typed in one probe (a probe per level would take 2^30).
+ 'state x = 0\nstate y: i64\nview v = ' + '1 + (' * 30 + 'x' + ')' * 30 + '\nview w = ' + '1 + (' * 30 + 'y' + ')' * 30 + '\n',
+ # i64s: states, fields, keys, slices, messages, outputs, arguments, lets, arithmetic, conditionals, folds, text.
+ 'record Order {\n id: i64\n total: i64 = -1\n note: text\n}\nstate orders: list of Order by id\n'
+ 'state next: i64 = 9007199254740993\nstate sum: i64\nstate seen = 0\ninput put = message {\n id: i64\n total: i64\n}\n'
+ 'output told = message {\n id: i64\n big: bool\n}\ninput go = key KeyG\n'
+ 'on put {\n orders.add(Order { id: put.id, total: put.total * 2 + 1, note: "{put.id}" })\n'
+ ' send told { id: put.id, big: put.total > 1000 }\n}\n'
+ 'on go.down {\n let step: = 0\n}\n'.replace(' let step: = 0\n', ' let step = next / 3 % 7 - 1\n next = next + step\n'
+ ' sum = sum + max(-step, 0) + min(1, next)\n seen = number(step) + seen\n'
+ ' for o in orders[0 .. next] where o.total != 0 {\n o.total = -o.total\n }\n if orders.has(5) { orders[5].total = 9 }\n')
+ + 'view count = count(o in orders[-5 .. 5])\nview most = max(o.total for o in orders)\nview kinds = distinct(o.total for o in orders)\n'
+ 'view pick = if seen > 0 { next } else if seen < 0 { 0 } else { -1 }\nview line = "{next}/{sum}"\n'
+ 'view from(low: i64) = sum(o.total for o in orders where o.id >= low)\nview back = i64(2.0) + i64(seen)\n'
+ 'view board = each o in orders where o.id > 0 {\n id = o.id\n total = o.total\n}\n',
# Bools: states, fields, message and output fields, arguments, lets, views, columns, interpolation, has.
'state lit = false\nstate n = 0\nrecord T {\n done = false\n title: text\n}\nstate ts: list of T\n'
'record K {\n name: text\n flag = true\n}\nstate ks: list of K by name\ninput flip = key Space\n'
@@ -508,9 +544,11 @@ KEYS = ['Space', 'Enter', 'ArrowLeft', 'ArrowRight', 'KeyA', 'KeyQ', 'Digit0', '
def random_program(rng):
- """A random well-formed program of numbers and bools: every one must compile and verify."""
- states = [(f's{i}', rng.random() < 0.3) for i in range(rng.randint(1, 6))]
- numbers, flags = [name for name, flag in states if not flag], [name for name, flag in states if flag]
+ """A random well-formed program of numbers, bools and i64s: every one must compile and verify."""
+ states = [(f's{i}', rng.choice(['number', 'number', 'bool', 'i64'])) for i in range(rng.randint(1, 6))]
+ numbers = [name for name, kind in states if kind == 'number']
+ flags = [name for name, kind in states if kind == 'bool']
+ integers = [name for name, kind in states if kind == 'i64']
inputs = [f'i{i}' for i in range(rng.randint(0, 4))]
keys = rng.sample(KEYS, len(inputs))
@@ -520,26 +558,50 @@ def random_program(rng):
return rng.choice([str(rng.choice([0, 1, 2, 0.5, 1e-3, 1e300, 7.25]))] + numbers + (['dt'] if handler else []))
if choice < 0.35:
return '-' + number(depth - 1, handler)
- if choice < 0.5:
+ if choice < 0.45:
name, count = rng.choice([('min', 2), ('max', 2), ('clamp', 3)])
return f'{name}(' + ', '.join(number(depth - 1, handler) for _ in range(count)) + ')'
+ if choice < 0.5:
+ return f'number({integer(depth - 1, handler)})'
if choice < 0.6:
return f'(if {truth(depth - 1, handler)} {{ {number(depth - 1, handler)} }} else {{ {number(depth - 1, handler)} }})'
op = rng.choice(['+', '-', '*', '/'])
return f'({number(depth - 1, handler)} {op} {number(depth - 1, handler)})'
+ def integer(depth, handler):
+ choice = rng.random()
+ if depth <= 0 or choice < 0.25:
+ literal = str(rng.choice([0, 1, 7, -3, 2**31, 2**53 + 1, 2**62, -2**62, 2**63 - 1]))
+ return rng.choice([f'i64({literal})' if not integers or rng.random() < 0.5 else literal] + integers)
+ if choice < 0.35:
+ return f'i64({number(depth - 1, handler)})'
+ if choice < 0.45:
+ return f'{rng.choice(["min", "max"])}({integer(depth - 1, handler)}, {integer(depth - 1, handler)})'
+ if choice < 0.55:
+ return f'(if {truth(depth - 1, handler)} {{ {integer(depth - 1, handler)} }} else {{ {rng.choice(["0", "-1"])} }})'
+ if choice < 0.6:
+ return '-' + integer(depth - 1, handler)
+ op = rng.choice(['+', '-', '*', '/', '%'])
+ right = str(rng.choice([1, 2, 10, -3])) if rng.random() < 0.3 else integer(depth - 1, handler)
+ return f'({integer(depth - 1, handler)} {op} {right})'
+
def truth(depth, handler):
choice = rng.random()
if depth <= 0 or choice < 0.2:
return rng.choice(['true', 'false'] + flags)
if choice < 0.35:
return '!' + truth(depth - 1, handler)
- if choice < 0.7:
+ if choice < 0.55:
op = rng.choice(['<', '<=', '>', '>=', '==', '!='])
return f'({number(depth - 1, handler)} {op} {number(depth - 1, handler)})'
+ if choice < 0.7:
+ op = rng.choice(['<', '<=', '>', '>=', '==', '!='])
+ return f'({integer(depth - 1, handler)} {op} {integer(depth - 1, handler)})'
op = rng.choice(['&&', '||', '==', '!='])
return f'({truth(depth - 1, handler)} {op} {truth(depth - 1, handler)})'
+ make = {'number': number, 'bool': truth, 'i64': integer}
+
def body(depth, indent):
lines = []
for _ in range(rng.randint(0, 3)):
@@ -551,13 +613,14 @@ def random_program(rng):
lines += body(depth - 1, indent + ' ')
lines.append(f'{indent}}}')
else:
- name, flag = rng.choice(states)
- lines.append(f'{indent}{name} = {truth(3, True) if flag else number(3, True)}')
+ name, kind = rng.choice(states)
+ lines.append(f'{indent}{name} = {make[kind](3, True)}')
return lines
- lines = [f'state {name} = {rng.choice(["true", "false"] if flag else ["0", "-1", "3.5", "+2"])}' for name, flag in states]
+ starts = {'number': ['0', '-1', '3.5', '+2'], 'bool': ['true', 'false'], 'i64': ['0', '-9', '9007199254740993']}
+ lines = [f'state {name}{": i64" if kind == "i64" else ""} = {rng.choice(starts[kind])}' for name, kind in states]
lines += [f'input {name} = key {key}' for name, key in zip(inputs, keys)]
- lines += [f'view v{i} = {truth(3, False) if rng.random() < 0.3 else number(3, False)}' for i in range(rng.randint(0, 3))]
+ lines += [f'view v{i} = {rng.choice(list(make.values()))(3, False)}' for i in range(rng.randint(0, 3))]
events = ['frame'] + [f'{name}.{edge}' for name in inputs for edge in ('down', 'up')]
for _ in range(rng.randint(0, 5)):
lines.append(f'on {rng.choice(events)} {{')
@@ -1348,6 +1411,60 @@ def bools(aup, pure_run, work):
return len(shown)
+I64_SOURCE = """record Row {
+ id: i64
+ n = 0
+}
+state rows: list of Row by id
+state next: i64 = 9007199254740990
+state n = 0
+input add = key KeyA
+input grow = key KeyG
+input half = key KeyH
+input far = key KeyF
+on add.down {
+ rows.add(Row { id: next, n: n })
+ next = next + 1
+ n = n + 1
+}
+on grow.down { next = next * 1024 }
+on half.down { next = -next / 2 }
+on far.down { n = number(next) }
+view total = sum(r.id for r in rows)
+view line = "{next} {i64(-7) / 2} {i64(-7) % 2} {i64(7) % -2} {-7 / 2}"
+view least = min(r.id for r in rows)
+"""
+
+
+def integers(aup, pure_run, work):
+ """i64s are exact past 2^53 in every output (states, items, views, text),
+ arithmetic past their range fails as overflow and changes nothing,
+ division and remainders floor, a conversion to a number fails where
+ the value does not fit, and a module replaced keeps what fits."""
+ (work / 'integers.aup').write_text(I64_SOURCE)
+ (work / 'integers2.aup').write_text('record Row {\n id = 0\n n: i64\n}\nstate rows: list of Row\nstate next = 0\nstate n: i64\n')
+ compile_module(aup, work / 'integers.aup', work / 'integers.pmod')
+ compile_module(aup, work / 'integers2.aup', work / 'integers2.pmod')
+ commands = ['show', 'down add', 'down add', 'down add', 'down add', 'down far', 'down grow', 'down half',
+ f'replace {work / "integers2.pmod"}']
+ shown = [json.loads(line) for line in run(pure_run, work / 'integers.pmod', stdin='\n'.join(commands) + '\n',
+ check=False).stdout.splitlines()]
+ start = 2**53 - 2 # four rows: the last past 2^53
+ rows = [{'id': start + k, 'n': k} for k in range(4)]
+ assert strict(shown[0]['states'], {'rows': [], 'next': start, 'n': 0}), shown[0]
+ assert shown[0]['views']['line'] == f'{start} -4 1 -1 -3.5', shown[0] # i64s floored; literals alone are numbers
+ assert strict(shown[4]['states'], {'rows': rows, 'next': start + 4, 'n': 4}), shown[4]
+ assert strict(shown[4]['views'], {'total': 4 * start + 6, 'line': f'{start + 4} -4 1 -1 -3.5', 'least': start}), shown[4]
+ assert shown[5]['fault'] == 'limit' and shown[5]['states']['n'] == 4, shown[5] # 2^53 + 2 is no number
+ assert shown[6]['fault'] == 'overflow' and shown[6]['states']['next'] == start + 4, shown[6]
+ assert shown[7]['states']['next'] == -(start + 4) // 2, shown[7] # floored, as Python's //
+ # Replaced: next, within 2^53 now, a number; n an i64; ids past 2^53 no number (the default), the rest kept.
+ final = shown[8]
+ assert strict(final['states'], {'rows': [{'id': start + k if k < 3 else 0, 'n': k} for k in range(4)],
+ 'next': -(start + 4) // 2, 'n': 4}), final
+ return len(shown)
+
+
def text_parity(aup, pure_run, work, seeds, steps=400):
module = work / 'notes.pmod'
compile_module(aup, ROOT / 'examples/notes.aup', module)
@@ -1500,6 +1617,7 @@ def main():
tasks_parity(aup, pure_run, work, range(args.text_seeds))
report['conditional_snapshots'] = conditionals(aup, pure_run, work)
report['bool_snapshots'] = bools(aup, pure_run, work)
+ report['i64_snapshots'] = integers(aup, pure_run, work)
report['time_snapshots'] = timing(aup, pure_run, work, range(args.seeds))
report['watched_builds'] = watching(aup, pure_run, work)
report['fold_snapshots'] = folds(aup, pure_run, work, range(args.seeds if hasattr(args, 'seeds') else 8))