tricu

An interpreted language for exploring Tree Calculus
Log | Files | Refs | README | LICENSE

local_control_consumers.py (6860B)


      1 #!/usr/bin/env python3
      2 """Opt-in local-control fragments: full constructed code, values and effect traces.
      3 
      4 Uses authenticated published HTTP/server kernels as unchanged baselines and the
      5 existing independent consumer oracles. Never replaces either fixture cohort.
      6 Cost samples are whole CLI invocations, not live-network benchmarks.
      7 """
      8 import argparse
      9 import json
     10 from pathlib import Path
     11 import time
     12 from bundle_dag import read
     13 from fixture_sources import verify
     14 from http_headers import LEAF, TRUE, byte_tree as bs, number, pair, tree_list, expect, sha
     15 from local_control import decode_text
     16 from source_order import Runner, field
     17 from registry_server import pure_cases, effect_cases
     18 
     19 ROOT = Path(__file__).resolve().parent.parent
     20 SOURCES = {
     21     'ascii': '''main byte = case (Nat.inRange byte 65 90) of
     22   ^true = Nat.add byte 32
     23   _ = byte''',
     24     'hash': '''main hash = (let rec
     25   walk [] 0 = true
     26   walk _ 0 = false
     27   walk [byte | rest] remaining = case (bundleHexByte? byte) of
     28     ^true = walk rest (Nat.subtract remaining 1)
     29     _ = false
     30   walk _ _ = false
     31   in walk hash 64)''',
     32     'serve': '''main root hash = (do bind
     33   reply <- getBundleByHash root hash
     34   case reply of
     35     ((t t), (bytes, _)) = pure (bundleResponse bytes)
     36     (t, (status, message)) = pure (errorResponse status message)
     37     _ = pure (errorResponse 500 "Invalid bundle result"))''',
     38 }
     39 
     40 
     41 def expanded(bundle, name):
     42     """Bounded iterative wire expansion; no evaluation or source frontend."""
     43     nodes, exports = bundle[2:4]
     44     out = bytearray(); pending = [exports[name]]
     45     while pending:
     46         tag, *children = nodes[pending.pop()]
     47         out.append(tag); pending.extend(reversed(children))
     48         if len(out) > 32*1024*1024:
     49             raise ValueError('fragment environment tree exceeds 32 MiB')
     50     return bytes(out)
     51 
     52 
     53 def main():
     54     p = argparse.ArgumentParser(description=__doc__)
     55     p.add_argument('tricu', type=Path)
     56     p.add_argument('--unit', type=Path, required=True)
     57     p.add_argument('--driver', type=Path, required=True)
     58     p.add_argument('--directory', type=Path, required=True)
     59     args = p.parse_args()
     60     root = args.directory.resolve(); root.mkdir(parents=True, exist_ok=False)
     61     fixtures = ROOT/'test/fixtures'; verify(fixtures)
     62     runner = Runner(args.tricu, root)
     63     compiler = read(fixtures/'compiler-library.arboricx')
     64     support = read(fixtures/'support-library.arboricx')
     65     bindings = [pair(bs(name.encode()), expanded(compiler, old)) for name, old in [
     66         ('true', 'base.true'), ('false', 'base.false'), ('Nat.add', 'native_math.add'),
     67         ('Nat.inRange', 'native_math.inRange'), ('Nat.subtract', 'native_math.subtract'),
     68         ('pure', 'io.pure'), ('bind', 'io.bind')]]
     69     bindings += [pair(bs(name.encode()), expanded(support, old)) for name, old in [
     70         ('bundleHexByte?', 'arboricx_server.bundleHexByte?'),
     71         ('getBundleByHash', 'arboricx_server.getBundleByHash'),
     72         ('bundleResponse', 'arboricx_server.bundleResponse'), ('errorResponse', 'http.errorResponse')]]
     73     environment = root/'environment.tree'; environment.write_bytes(tree_list(bindings))
     74     report = dict(complete=False, cases=0, fragments={}, inputs={})
     75     for label, path in [('unit', args.unit), ('driver', args.driver),
     76                         ('compiler-reference', fixtures/'compiler-library.arboricx'),
     77                         ('support-reference', fixtures/'support-library.arboricx')]:
     78         selected = root/(label+'.arboricx'); selected.write_bytes(path.read_bytes())
     79         report['inputs'][label] = sha(selected.read_bytes())
     80     try:
     81         unit = runner.imported(root/'unit.arboricx', 'unit')
     82         driver = runner.imported(root/'driver.arboricx', 'driver')
     83         for label, source in SOURCES.items():
     84             source_path = root/(label+'.source.tree'); source_path.write_bytes(bs(source.encode()))
     85             answer = runner.invoke(label+'-compile', unit, 'compileStableModuleDefinitions',
     86                 ['--arg-tree-file', environment, '--arg-tree-file', source_path])
     87             expect(field(answer, 0), TRUE, label+' compile')
     88             definitions = field(answer, 1, 0)
     89             if decode_text(field(definitions, 0, 0)) != 'main': raise ValueError('unexpected export')
     90             expect(field(definitions, 1), LEAF, label+' no escaped locals')
     91             expect(field(answer, 1, 1), LEAF, label+' compile remainder')
     92             code = field(definitions, 0, 1)
     93             baseline = expanded(support, {'ascii':'http.toLowerAsciiByte',
     94                 'hash':'arboricx_server.bundleHashValid?', 'serve':'arboricx_server.serveBundleHash'}[label])
     95             report['fragments'][label] = dict(source_sha256=sha(source.encode()),
     96                 candidate_wire_bytes=len(code), baseline_wire_bytes=len(baseline))
     97             if label == 'ascii':
     98                 rows = [(number(n),) for n in range(256)]
     99                 expected = tree_list([number(n+32 if 65 <= n <= 90 else n) for n in range(256)])
    100             elif label == 'hash':
    101                 cases = [c for c in pure_cases() if c.export == 'bundleHashValid?']
    102                 rows = [c.arguments for c in cases]; expected = tree_list([c.expected for c in cases])
    103             else:
    104                 cases = [c for c in effect_cases() if c.export == 'serveBundleHash']
    105             for variant, tree in [('baseline', baseline), ('candidate', code)]:
    106                 code_path = root/(label+'-'+variant+'.tree'); code_path.write_bytes(tree)
    107                 if label != 'serve':
    108                     rows_path = root/(label+'-rows.tree'); rows_path.write_bytes(tree_list([tree_list(r) for r in rows]))
    109                     actual = runner.invoke(label+'-'+variant, driver, 'values',
    110                         ['--arg-tree-file', code_path, '--arg-tree-file', rows_path])
    111                     expect(actual, expected, label+' '+variant+' independent values')
    112                 else:
    113                     for i, case in enumerate(cases):
    114                         paths = []
    115                         for j, argument in enumerate(case.arguments):
    116                             path = root/f'serve-{i}-{j}.tree'; path.write_bytes(argument)
    117                             paths.extend(['--arg-tree-file', path])
    118                         actual = runner.invoke('serve-'+variant+'-'+str(i), driver, 'effects',
    119                             ['--arg-tree-file', code_path, *paths])
    120                         expect(actual, case.expected, case.label+' '+variant+' independent full trace')
    121             report['cases'] += len(cases) if label == 'serve' else len(rows)
    122         report['complete'] = True
    123     finally:
    124         report['seconds'] = time.monotonic()-runner.started
    125         (root/'report.json').write_text(json.dumps(report, indent=2)+'\n')
    126     print(f'PASS: {report["cases"]} consumer cases against candidate and baseline')
    127 
    128 
    129 if __name__ == '__main__': main()