From 6209ddb42b953902828972b2e09f0296a51fe565 Mon Sep 17 00:00:00 2001 From: eunhhu Date: Tue, 13 Oct 2026 06:16:12 +0100 Subject: Formats, joins, slices, lets of bytes and text, while and && and || that decide alone (Pure 027's stage 2a): {v:08x}, {p:.2f} (Python's mini-language, [0][width][.digits][d x X b o f]), a + b of texts or of bytes, b[a .. c] of bytes and of texts (a cut inside a character failing as encoding), byte(n), let s = bytes or text (16 of each a handler, s = s + e appending in place), while c { } as a count to 2^53 that breaks when c is false, && and || computing their right side only where it decides (an AND or OR still where it cannot fail: comparisons, states and fields, a walked item's fields, a number plus a small literal, what the left side computed; every module of the apps and benchmarks byte for byte the same but json's); examples/json.aup, examples/sha256.aup in formats and joined bytes; 14 diagnostics, one accepted form, 144 random formats against Python's format() and an app cutting at commas --- tests/check.py | 105 +++++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 103 insertions(+), 2 deletions(-) (limited to 'tests') diff --git a/tests/check.py b/tests/check.py index 9ddffb6..965b099 100644 --- a/tests/check.py +++ b/tests/check.py @@ -76,10 +76,28 @@ DIAGNOSTICS = [ ('state a = 0\nstate b = 0\non frame {\n a, b = 1\n}\n', '4:3', '2 names take 2 values, not 1'), ('state a = 0\non frame {\n a, a = 1, 2\n}\n', '3:6', "'a' is named twice"), ('state s: text\nstate a = 0\non frame {\n s, a = "x", 1\n}\n', '4:3', "'s' takes its value apart"), - ('state x = 0\non frame {\n let a, b = 1, "s"\n}\n', '3:17', 'a let holds a number, a bool, an i64 or an index, not text'), + ('state t: text\nstate c: cursor in t\non frame {\n let a, b = 1, c\n}\n', '4:17', + 'a let holds a number, a bool, an i64, an index, bytes or text, not a cursor'), ('record R {\n v = 0\n}\ninput m = message { d: bytes }\nstate t: text\non m {\n let xs: list of R\n' ' t = text(bytes(xs))\n}\n', '8:18', 'bytes(...) makes bytes of a list of i64, not of R'), ('state x = 0\nview v = [1, 2]\n', '2:10', "a list's elements begin a let's list"), + # Formats, joins, slices, lets of bytes and text, while (Pure 027's stage 2a). + ('state t: text\nstate n = 0\non frame {\n t = "{n:08q}"\n}\n', '4:11', 'a format is [0][width][.digits][d x X b o f]'), + ('state t: text\nstate n = 0\non frame {\n t = "{n:.2x}"\n}\n', '4:11', 'digits after the point go with f'), + ('state t: text\non frame {\n t = "{t:5}"\n}\n', '3:9', 'a format writes a number, a bool or an i64, not text'), + ('state t: text\non frame {\n t = "{true:x}"\n}\n', '3:9', "a bool's format is a width alone"), + ('state t: text\nstate y: i64\non frame {\n t = "{y:.2f}"\n}\n', '4:9', 'f writes a number'), + ('state t: text\non frame {\n t = "{1:256}"\n}\n', '3:11', "a format's width is at most 255"), + ('state t: text\non frame {\n t = t - "a"\n}\n', '3:9', 'texts and bytes are joined with +'), + ('state t: text\ninput m = message { d: bytes }\non m {\n t = t + m.d\n}\n', '4:11', 'text joins text, not bytes'), + ('state t: text\non frame {\n let s = "a"\n s = 1\n}\n', '4:7', "'s' holds text, not a number"), + ('state t: text\non frame {\n let a, b = "x", "y"\n a, b = b, a\n}\n', '4:3', "'a' holds text: it takes its value apart"), + ('state n = 0\non frame {\n while n {\n n = n + 1\n }\n}\n', '3:9', 'while takes a bool, not a number'), + ('record R {\n v = 0\n}\nstate xs: list of R\nstate n = 0\non frame {\n n = xs[0 .. 2]\n}\n', '7:7', + "a list's slice goes in a for or a fold"), + ('state n = 0\nview v = n[1 .. 2]\n', '2:10', 'only bytes and texts are sliced, not a number'), + ('state t: text\non frame {\n' + ''.join(f' let b{i} = byte({i})\n' for i in range(17)) + '}\n', '19:7', + 'at most 16 lets of bytes are allowed in a handler'), ('state x = 1\nstate x = 2\n', '2:7', "already declared as a state"), ('state a = b\n', '1:11', 'expected a numeric state initializer'), ('view v = dt\n', '1:10', 'dt is only available in handlers'), @@ -307,7 +325,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, an i64 or an index, not text'), + (LETS + 'on k.down {\n let x = t.start\n}\n', '10:11', 'a let holds a number, a bool, an i64, an index, bytes or text, not a cursor'), (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'"), @@ -392,6 +410,12 @@ DIAGNOSTICS = [ ] ACCEPTED = [ + # Formats, joins, slices, lets of bytes and text appended to, while, && and || that decide alone (Pure 027's 2a). + 'state t: text\nstate n = 0\nstate y: i64\ninput m = message { d: bytes }\n' + 'on m {\n let s = "" + "{n:5}{y:08x}{n:.2f}{y:X}{true:6}"\n let b = m.d[0 .. 0] + byte(0x41)\n b = b + m.d\n' + ' let i = 0\n while i < b.bytes && b[i] != 0x2c {\n s = s + text(b[i .. i + 1])\n i = i + 1\n }\n' + ' t = s[0 .. 2] + t\n n = if i > 0 || b[0] == 0 { 1 } else { 0 }\n}\n' + 'view v(d: bytes) = if d.bytes > 0 && d[0] == 1 { 1 } else { 0 }\n', # Bits and bytes (Pure 027's stage 1): hex, bit operations by Python's precedence, a byte of a message's bytes, of an # argument's and of a fn's parameter, lists of i64, number, bool and text with their elements, positions read and # written, a, b = b, a and let a, b = ..., bytes(xs). @@ -1258,6 +1282,82 @@ def bits_and_bytes(aup, pure_run, work, apps=8, per_app=24): return {'expressions': apps * per_app, 'events': checked, 'faults': faults, 'bytes_events': len(wanted)} +def formats_and_texts(aup, pure_run, work, apps=6, per_app=24): + """Pure 027's stage 2a. Random formats ({v:08x}, {x:.3f}, ...) of i64s + and numbers a message gives, against Python's format(): an integer's + form of a number that is not whole fails as limit, -0 is written as 0. + Then an app that cuts a message's bytes at commas into a text joined by + '|', counting with while and reading by position only where && decides + (an empty message reads nothing), against Python.""" + import struct + rng = random.Random(272) + checked = faults = 0 + for app in range(apps): + specs = [] + for _ in range(per_app): + integer = rng.random() < 0.6 + form = rng.choice('dxXbo' if integer else 'dxXbof') + width = rng.choice([0, 0, 1, 5, 8, 20]) + zeros = width > 0 and rng.random() < 0.5 + digits = rng.randrange(0, 11) if form == 'f' else None + spec = ('0' if zeros else '') + (str(width) if width else '') + (f'.{digits}' if digits is not None else '') + form + specs.append((integer, spec)) + source = 'state t: text\n' + ''.join(f'input e{i} = message {{\n v: i64\n x: number\n}}\n' for i in range(per_app)) + source += ''.join(f'on e{i} {{\n t = "{{e{i}.{"v" if integer else "x"}:{spec}}}"\n}}\n' for i, (integer, spec) in enumerate(specs)) + path = work / f'formats{app}.aup' + path.write_text(source) + compile_module(aup, path, work / f'formats{app}.pmod') + given, wanted = '', [] + for i, (integer, spec) in enumerate(specs): + for _ in range(5): + v = rng.choice([rng.randrange(-(1 << 63), 1 << 63), rng.randrange(-1000, 1000), 0]) + x = rng.choice([rng.uniform(-1e6, 1e6), float(rng.randrange(-5000, 5000)), rng.uniform(-1, 1), -0.0, 2.5, 1e20]) + given += f'message e{i} {struct.pack(" 0 { "|" } else { "" }) + text(d[start .. i])\n' + ' fields = fields + 1\n start = i + 1\n }\n i = i + 1\n }\n' + ' t = out\n n = if d.bytes > 0 && d[0] == 0x2c { -fields } else { fields }\n}\n') + path = work / 'cuts.aup' + path.write_text(source) + compile_module(aup, path, work / 'cuts.pmod') + samples = [b'', b',', b'a', b'a,b', b'ab,,cd,', ',x'.encode(), 'é,ü'.encode()] + samples += [bytes(rng.choice(b'ab,') for _ in range(rng.randrange(0, 30))) for _ in range(60)] + given = ''.join(f"message go {d.hex() or '-'}\n" for d in samples) + lines = run(pure_run, work / 'cuts.pmod', stdin=given, check=False).stdout.splitlines() + for d, line in zip(samples, lines): + state = json.loads(line) + fields = d.split(b',') + want_n = -len(fields) if d[:1] == b',' else len(fields) + assert state['fault'] == 'none' and state['states']['t'] == '|'.join(f.decode() for f in fields) and \ + state['states']['n'] == want_n, (d, state) + return {'formats': apps * per_app, 'events': checked, 'faults': faults, 'cuts': len(samples)} + + def computing(aup, pure_run, work, graphs=20): """Computing in one event (Pure 018): examples/routes.aup's searches over random graphs (breadth first, depth first, nearest by weight) and @@ -2064,6 +2164,7 @@ def main(): report['transients'] = transients(aup, pure_run, work) report['computing'] = computing(aup, pure_run, work) report['bits_and_bytes'] = bits_and_bytes(aup, pure_run, work) + report['formats_and_texts'] = formats_and_texts(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)) -- cgit v1.2.3