tricu

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

parser_control.py (4762B)


      1 #!/usr/bin/env python3
      2 """Opt-in selected parser kernel checks; full replies and lazy handler probes.
      3 
      4 Driver is an explicitly compiled higher-order module, not a replacement parser.
      5 Reference/candidate code is passed as an export after driver construction.
      6 """
      7 import argparse
      8 import json
      9 from pathlib import Path
     10 from http_headers import LEAF, TRUE, number, pair, tree_list, expect, sha
     11 from source_order import Runner
     12 
     13 
     14 def diagnostic(code):
     15     return pair(number(code), pair(LEAF, pair(LEAF, pair(LEAF, LEAF))))
     16 
     17 
     18 def reply(row):
     19     success, value, rest, consumed, committed = row
     20     return pair(TRUE if success else LEAF, pair(value, pair(rest, pair(TRUE if consumed else LEAF, TRUE if committed else LEAF))))
     21 
     22 
     23 def parse(mode, data):
     24     if mode in (0, 5):
     25         prefix = b'\2' + number(1)
     26         if data.startswith(prefix):
     27             return True, number(1), data[len(prefix):], True, mode == 5
     28         return False, diagnostic(7), data, False, False
     29     if mode == 1:
     30         return True, number(9), data, False, False
     31     if mode == 2:
     32         return True, LEAF, data, False, True
     33     return False, diagnostic(7), data, mode == 3, mode == 4
     34 
     35 
     36 def many(mode, data):
     37     values=[];consumed=committed=False
     38     while True:
     39         ok,value,rest,c,k=parse(mode,data)
     40         if not ok:
     41             return reply((False,value,rest,consumed or c,committed or k)) if c or k else reply((True,tree_list(values),data,consumed,committed))
     42         if not c:
     43             return reply((False,diagnostic(3),data,consumed,committed or k))
     44         values.append(value);data=rest;consumed=True;committed |= k
     45 
     46 
     47 def cases():
     48     inputs=[tree_list([number(x) for x in xs]) for xs in [[],[1],[2],[1,1,2],[1]*32+[2]]]
     49     for mode in range(6):
     50         for index,data in enumerate(inputs):
     51             yield f'many-{mode}-{index}', 'parserMany','many',[number(mode),data],many(mode,data)
     52     for mode in ['sequenced','lexed']:
     53         for first in range(6):
     54             for second in range(6):
     55                 for index,data in enumerate(inputs[:3]):
     56                     a=parse(first,data)
     57                     answer=a
     58                     if a[0]:
     59                         b=parse(second,a[1] if mode=='lexed' else a[2])
     60                         answer=(*b[:3],a[3] or b[3],a[4] or b[4])
     61                     yield f'{mode}-{first}-{second}-{index}', 'parserWithLexer' if mode=='lexed' else 'parserBind',mode,[number(first),number(second),data],reply(answer)
     62     for index,status in enumerate([LEAF,TRUE,pair(LEAF,LEAF),b'\1\1\0']):
     63         yield f'ignored-fields-{index}','parserMatchReply','ignoredFields',[status],number(11 if status==LEAF else 22)
     64     yield 'unselected-failure','parserMatchReply','unselectedFailure',[TRUE],number(22)
     65     yield 'unselected-success','parserMatchReply','unselectedSuccess',[LEAF],number(11)
     66 
     67 
     68 def main():
     69     p=argparse.ArgumentParser(description=__doc__)
     70     p.add_argument('tricu',type=Path)
     71     for name in ['subject','reference','driver','directory']:
     72         p.add_argument('--'+name,type=Path,required=True)
     73     a=p.parse_args();a.directory.mkdir(parents=True)
     74     r=Runner(a.tricu,a.directory);pins={}
     75     report=dict(complete=False,cases=[],artifacts={},runtime_sha256=sha(a.tricu.read_bytes()),harness_sha256=sha(Path(__file__).read_bytes()))
     76     for name in ['subject','reference','driver']:
     77         path=getattr(a,name);pins[name]=r.imported(path,name)
     78         report['artifacts'][name]=sha(path.read_bytes())
     79     try:
     80         for label,export,driver,values,expected in cases():
     81             args=[]
     82             for index,value in enumerate(values):
     83                 path=a.directory/(label+f'-{index}.tree');path.write_bytes(value)
     84                 args+=['--arg-tree-file',path]
     85             for side in ['reference','subject']:
     86                 actual=r.invoke(label+'-'+side,pins['driver'],driver,['--arg-export',pins[side]+':'+export,*args])
     87                 expect(actual,expected,label+' '+side+' independent oracle')
     88             report['cases'].append(dict(label=label,result_sha256=sha(actual)))
     89         # Same-arity wrong subject must disagree on consumption/repetition.
     90         path=a.directory/'control.tree';path.write_bytes(tree_list([number(1),number(1)]))
     91         mode=a.directory/'mode.tree';mode.write_bytes(number(0))
     92         wrong=r.invoke('wrong',pins['driver'],'many',['--arg-export',pins['subject']+':parserOptional','--arg-tree-file',mode,'--arg-tree-file',path])
     93         assert wrong != many(0,path.read_bytes())
     94         report.update(complete=True,wrong_subject_rejected=True)
     95     finally:
     96         (a.directory/'report.json').write_text(json.dumps(report,indent=2)+'\n')
     97     print(f'PASS: {len(report["cases"])} parser reply/laziness cases + wrong-subject control')
     98 
     99 
    100 if __name__=='__main__':
    101     main()