tricu

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

prepare-contracts.py (5005B)


      1 #!/usr/bin/env python3
      2 """Independent Haskell capture for the browser's existing packaged contract environment.
      3 No compiler/library source builds or fixture relabeling. Run after nix build .#:
      4 python3 ext/demo-site/test/prepare-contracts.py result/bin/tricu
      5 """
      6 import importlib.util
      7 import json
      8 from pathlib import Path
      9 import re
     10 import subprocess
     11 import sys
     12 import tempfile
     13 
     14 HERE = Path(__file__).resolve().parent
     15 spec = importlib.util.spec_from_file_location('demo_prepare', HERE.parent / 'prepare.py')
     16 p = importlib.util.module_from_spec(spec)
     17 spec.loader.exec_module(p)
     18 host = Path(sys.argv[1]).resolve()
     19 compiler = p.reference_compiler()
     20 source = (HERE.parent / 'src/contracts.ts').read_bytes()
     21 bindings = {quoted or bare: exported for quoted, bare, exported in
     22             re.findall(r"(?:'([^']+)'|([A-Za-z_]\w*)):\s*'(base\.[^']+)'", source.decode())}
     23 assert len(bindings) == 38
     24 cases = [
     25     dict(source='id =@nat? (x : x)', export='id', arguments=[], failure='not a natural number'),
     26     dict(source='id x =@nat? x', export='id', arguments=[p.of_number(42)], expected=p.of_number(42)),
     27     dict(source='id x =@nat? x', export='id', arguments=['10'], failure='not a natural number'),
     28     dict(source='id x =@nat? x', export='id', arguments=['200'], failure='not a natural number'),
     29     dict(source='id x@nat? =@nat? x', export='id', arguments=[p.of_number(7)], expected=p.of_number(7)),
     30     dict(source='id x@nat? =@nat? x', export='id', arguments=[p.of_string('bad')], failure='not a natural number'),
     31     dict(source='id @nat? =@nat? (x : x)', export='id', arguments=[p.of_number(9)], expected=p.of_number(9)),
     32     dict(source='id @nat? =@nat? (x : x)', export='id', arguments=['10'], failure='not a natural number'),
     33     dict(source='first x@nat? y@nat? =@nat? x', export='first', arguments=['10',p.of_number(2)], failure='not a natural number'),
     34     dict(source='main = check nat? 42', export='main', arguments=[], expected=p.of_number(42)),
     35     dict(source='main = check (listOf nat?) [1 2]', export='main', arguments=[], expected='2'+p.of_number(1)+'2'+p.of_number(2)+'0'),
     36 ]
     37 with tempfile.TemporaryDirectory(prefix='arboricx-contract-capture-') as directory:
     38     temp = Path(directory)
     39     store = temp / 'store'
     40     def run(*args):
     41         reply = subprocess.run([str(host), *map(str, args), '+RTS', '-M8192m', '-RTS'], capture_output=True, timeout=120, cwd=p.ROOT)
     42         if reply.returncode:
     43             raise RuntimeError(reply.stderr.decode() + reply.stdout.decode())
     44         return reply.stdout.decode()
     45     def put(kind, payload):
     46         pin = p.digest(kind.encode() + b'\0' + payload)
     47         path = store / 'objects' / pin[:3] / pin
     48         path.parent.mkdir(parents=True, exist_ok=True)
     49         path.write_bytes(payload)
     50         return pin
     51     imported = run('arboricx','import','--file',compiler,'--store',store,'--indexed')
     52     pin = re.search(r'Created module alias .* -> ([0-9a-f]{64})',imported)[1]
     53     roots = {exported: run('arboricx','run','--module',pin,'--name',exported,'--store',store,'--format','ternary').strip()
     54              for exported in set(bindings.values())}
     55     environment = ''.join('22'+p.of_string(name)+roots[exported] for name,exported in bindings.items())+'0'
     56     env_file = temp / 'environment.tree'
     57     env_file.write_bytes(bytes(map(int,environment)))
     58     for case in cases:
     59         result = run('arboricx','run','--module',pin,'--name','source_unit.compileStableModuleDefinitions',
     60                      '--store',store,'--arg-tree-file',env_file,'--arg',case['source'],'--format','ternary').strip()
     61         success,body=p.pair(result)
     62         assert success=='10',(case['source'],result[:120])
     63         definitions,_=p.pair(body)
     64         programs={}
     65         while definitions!='0':
     66             binding,definitions=p.pair(definitions)
     67             name,tree=p.pair(binding)
     68             programs[p.string(name)]=tree
     69         case['definitions']=programs
     70         tree_pin=put('arboricx.tree-term.v1',bytes(map(int,programs[case['export']])))
     71         manifest=put('arboricx.module-manifest.v1',f'arboricx.module-manifest.v1\nexport\tmain\tarboricx.tree-term.v1\t{tree_pin}\tarboricx.abi.tree.v1\n'.encode())
     72         arguments=[]
     73         for i,tree in enumerate(case['arguments']):
     74             file=temp/f'arg{i}.tree';file.write_bytes(bytes(map(int,tree)));arguments+=['--arg-tree-file',file]
     75         case['result']=run('arboricx','run','--module',manifest,'--name','main','--store',store,'--format','ternary',*arguments).strip()
     76         if 'failure' in case:
     77             case['expected']='20'+'2'+p.of_string(case['failure'])+'0'
     78         assert case['result']==case['expected'],case
     79         print('PASS Haskell contracts',case['source'],flush=True)
     80     payload=dict(compilerBundleSha256=p.digest(compiler.read_bytes()),hostSha256=p.digest(host.read_bytes()),
     81                  contractsSourceSha256=p.digest(source),bindings=bindings,backend='stable',cases=cases)
     82     (HERE/'contracts-conformance.json').write_text(json.dumps(payload,indent=2)+'\n')