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()