-rwxr-xr-x 12578 holtrace-20250629/ot2holtrace raw
#!/usr/bin/env pypy3 import sys sys.setrecursionlimit(1000000) import os # ===== inferences import type import term from trace import AXIOM,REFL,ASSUME,BETA,TRANS,MK_COMB,EQ_MP,DEDUCT_ANTISYM_RULE,ABS,INST,INST_TYPE,NEW_DEFINITION,TYPE_DEFINITION,NAME def BETA_CONV(t): if not term.is_eval(t): reject('BETA_CONV: non-eval') f,x = term.eval_funpoint(t) if not term.is_mapsto(f): reject('BETA_CONV: non-mapsto') v,y = term.mapsto_varout(f) if x == v: return BETA(t) return INST(BETA(term.eval(f,v)),((v,x),)) def SYM(LR): L,R = term.eq_split(LR[1]) eq = term.eval_fun(term.eval_fun(LR[1])) # LR is: L = R; i.e., eq(L)(R) LL = REFL(L) # L = L eqeq = REFL(eq) # eq = eq eqLR = MK_COMB(eqeq,LR) # eq(L) = eq(R) LLRL = MK_COMB(eqLR,LL) # eq(L)(L) = eq(R)(L) return EQ_MP(LLRL,LL) # eq(R)(L); i.e., R = L def PROVE_HYP(th,TH): if any(term.alphaequivalent(th[1],h) for h in TH[0]): return EQ_MP(DEDUCT_ANTISYM_RULE(th,TH),th) return TH # prove h|-c # caller provides th alpha-equivalent to h|-c def ALPHA_THM(th,h,c): if th[1] != c: th = EQ_MP(TRANS(REFL(th[1]),REFL(c)),th) for t in h: if t in th[0]: continue tt = ASSUME(t) th = EQ_MP(DEDUCT_ANTISYM_RULE(tt,th),tt) return th # ===== pretty-printing watch = False def format_type(x): if type.is_var(x): return type.var_name(x) app = type.app_name(x) args = type.app_args(x) args = ','.join(map(format_type,args)) return f'{app}({args})' def format_term(t): if term.is_const(t): return term.const_name(t) if term.is_var(t): return term.var_name(t) if term.is_eval(t): f,x = term.eval_funpoint(t) return f'{format_term(f)}({format_term(x)})' v,b = term.mapsto_varout(t) return f'{format_term(v)}|->{format_term(b)}' def format_thm(th): h,c = th result = ','.join(format_term(t) for t in h) result += '|-' result += format_term(c) return result def format_list(x): return '['+','.join(format_obj(y) for y in x)+']' def format_obj(x): if x[0] == 'num': return f'{x[1]}' if x[0] == 'name': return f'"{x[1]}"' if x[0] == 'const': return f'const("{x[1]}")' if x[0] == 'typeop': return f'typeop("{x[1]}")' if x[0] == 'type': return format_type(x[1]) if x[0] == 'term': return format_term(x[1]) if x[0] == 'var': return format_term(x[1]) if x[0] == 'thm': return format_thm(x[1]) if x[0] == 'list': return format_list(x[1]) raise Exception('format_obj') # ===== opentheory state ot_version = 5 S = [] # stack D = {} # dictionary: int->obj T = set() # theorems def pop_obj(): return S.pop() def push_obj(x): S.append(x) def obj2num(x): assert x[0] == 'num'; return x[1] def obj2name(x): assert x[0] == 'name'; return x[1] def obj2const(x): assert x[0] == 'const'; return x[1] def obj2typeop(x): assert x[0] == 'typeop'; return x[1] def obj2type(x): assert x[0] == 'type'; return x[1] def obj2term(x): assert x[0] == 'term'; return x[1] def obj2var(x): assert x[0] == 'var'; return x[1] def obj2thm(x): assert x[0] == 'thm'; return x[1] def obj2list(x): assert x[0] == 'list'; return x[1] def pop_num(): return obj2num(pop_obj()) def pop_name(): return obj2name(pop_obj()) def pop_const(): return obj2const(pop_obj()) def pop_typeop(): return obj2typeop(pop_obj()) def pop_type(): return obj2type(pop_obj()) def pop_term(): return obj2term(pop_obj()) def pop_var(): return obj2var(pop_obj()) def pop_thm(): return obj2thm(pop_obj()) def pop_list(): return obj2list(pop_obj()) def push_num(x): push_obj(('num',x)) def push_name(x): push_obj(('name',x)) def push_const(x): push_obj(('const',x)) def push_typeop(x): push_obj(('typeop',x)) def push_type(x): push_obj(('type',x)) def push_term(x): push_obj(('term',x)) def push_var(x): push_obj(('var',x)) def push_thm(x): push_obj(('thm',x)) def push_list(x): push_obj(('list',x)) # ===== object management def pragma(): pop_obj() def cons(): t = pop_list() h = pop_obj() push_list((h,)+t) def dictadd(): # "def" k = pop_num() x = pop_obj() D[k] = x push_obj(x) def hdTl(): ht = pop_list() push_obj(ht[0]) push_list(ht[1:]) def nil(): result = () push_list(result) def pop(): pop_obj() def ref(): k = pop_num() push_obj(D[k]) def remove(): k = pop_num() push_obj(D[k]) del D[k] # ===== name management def converttermname(n): if n == 'select': n = '@' if n.startswith('Data.Bool.'): n = n[10:] # XXX: remove namespaces more broadly? # XXX: do more conversions to HOL Light syntax? return n def const(): n = converttermname(pop_name()) push_const(n) def typeOp(): n = pop_name() if n == '->': n = type.FUN push_typeop(n) # ===== building types def opType(): x = tuple(map(obj2type,pop_list())) app = pop_typeop() push_type(type.app(app,*x)) def varType(): push_type(type.var(pop_name())) # ===== building terms def absTerm(): b = pop_term() v = pop_var() push_term(term.mapsto(v,b)) def appTerm(): x = pop_term() f = pop_term() push_term(term.eval(f,x)) def constTerm(): ty = pop_type() c = pop_const() push_term(term.const(c,ty)) def var(): ty = pop_type() n = converttermname(pop_name()) push_var(term.var(n,ty)) def varTerm(): v = pop_var() push_term(v) # ===== inferences def assume(): tm = pop_term() push_thm(ASSUME(tm)) def refl(): tm = pop_term() push_thm(REFL(tm)) def betaConv(): tm = pop_term() push_thm(BETA_CONV(tm)) def appThm(): TH = pop_thm() th = pop_thm() push_thm(MK_COMB(th,TH)) def deductAntisym(): TH = pop_thm() th = pop_thm() push_thm(DEDUCT_ANTISYM_RULE(th,TH)) def eqMp(): TH = pop_thm() th = pop_thm() push_thm(EQ_MP(th,TH)) def trans(): TH = pop_thm() th = pop_thm() push_thm(TRANS(th,TH)) def proveHyp(): TH = pop_thm() th = pop_thm() push_thm(PROVE_HYP(th,TH)) def sym(): th = pop_thm() push_thm(SYM(th)) def axiom(): C = pop_term() H = tuple(map(obj2term,pop_list())) result = H,C if result not in T: assert len(H) == 0 result = AXIOM(C) push_thm(result) def absThm(): th = pop_thm() v = pop_var() push_thm(ABS(v,th)) def subst(): th = pop_thm() todo = pop_list() tytodo,vtodo = map(obj2list,todo) tysubst = [(type.var(obj2name(n)),obj2type(ty)) for n,ty in map(obj2list,tytodo)] th = INST_TYPE(th,tysubst) vsubst = [(obj2var(v),obj2term(t)) for v,t in map(obj2list,vtodo)] th = INST(th,vsubst) push_thm(th) # ===== theorem naming thmnumber = 0 def thm(): global thmnumber newC = pop_term() newH = tuple(map(obj2term,pop_list())) th = pop_thm() th = ALPHA_THM(th,newH,newC) T.add(th) thmnumber += 1 NAME(th,f'T{thmnumber}') # ===== definitions constname2def = {} def defineConst(): t = pop_term() name = converttermname(pop_name()) push_const(name) if name in constname2def: result = constname2def[name] assert len(result[0]) == 0 assert result[1] == term.eq(term.const(name,term.ty(t)),t) else: c = term.var(name,term.ty(t)) ct = term.eq(c,t) result = NEW_DEFINITION(ct,name) constname2def[name] = result push_thm(result) def defineConstList(): th = pop_thm() nvlist = pop_list() v2t = {} for h in th[0]: v,t = term.eq_split(h) if v in v2t: raise Exception(f'defineConstList repeated variable {v}') v2t[v] = t # XXX: this is defining constants more specifically than defineConstList does constlist = () vsubst = () n2thm = {} for n,v in map(obj2list,nvlist): n,v = obj2name(n),obj2var(v) if n in constname2def: result = constname2def[n] else: result = NEW_DEFINITION(term.eq(term.var(n,term.ty(v)),v2t[v]),n) constname2def[n] = result assert len(result[0]) == 0 assert result[1] == term.eq(term.const(n,term.ty(v)),v2t[v]) n2thm[n] = result C = term.const(n,term.ty(v2t[v])) constlist += ('const',n), vsubst += (v,C), th = INST(th,vsubst) # th now looks like: C1 = t1, ... |-> vsubst(phi) for n in n2thm: th = PROVE_HYP(n2thm[n],th) assert len(th[0]) == 0 push_list(constlist) push_thm(th) # ===== type definitions typename2def = {} def defineTypeOp(): H,C = pop_thm() # XXX: does sorting change the opentheory semantics? tyvarnames = tuple(sorted(set(map(obj2name,pop_list())))) repname = converttermname(pop_name()) absname = converttermname(pop_name()) abstypename = pop_name() if abstypename in typename2def: absthm,repthm,absthm2,repthm2 = typename2def[abstypename] # XXX: check consistency else: repprop,witness = term.eval_funpoint(C) reptype = term.ty(witness) absthm,repthm = TYPE_DEFINITION(reptype,repprop,(H,C),abstypename,absname,repname,tyvarnames) tyvars = tuple(map(type.var,tyvarnames)) abstype = type.app(abstypename,*tyvars) a = term.var('a',abstype) r = term.var('r',reptype) absthm2 = ABS(a,absthm) repthm2 = SYM(ABS(r,repthm)) typename2def[abstypename] = absthm,repthm,absthm2,repthm2 push_typeop(abstypename) push_const(absname) push_const(repname) if ot_version == 6: push_thm(absthm2) push_thm(repthm2) else: push_thm(absthm) push_thm(repthm) # ===== driver commands = { 'absTerm':absTerm, 'absThm':absThm, 'appTerm':appTerm, 'appThm':appThm, 'assume':assume, 'axiom':axiom, 'betaConv':betaConv, 'cons':cons, 'const':const, 'constTerm':constTerm, 'deductAntisym':deductAntisym, 'def':dictadd, 'defineConst':defineConst, 'defineConstList':defineConstList, 'defineTypeOp':defineTypeOp, 'eqMp':eqMp, 'hdTl':hdTl, 'nil':nil, 'opType':opType, 'pop':pop, 'pragma':pragma, 'proveHyp':proveHyp, 'ref':ref, 'refl':refl, 'remove':remove, 'subst':subst, 'sym':sym, 'thm':thm, 'trans':trans, 'typeOp':typeOp, 'var':var, 'varTerm':varTerm, 'varType':varType, } def unbackslash(s): result = '' afterbackslash = False for c in s: if afterbackslash: result += c afterbackslash = False elif c == '\\': afterbackslash = True else: result += c return result sys.stdout.buffer.write(b'HOLTrace 1\n') dir = sys.argv[1] packages_available = sorted(os.listdir(dir)) packages_started = [] packages_finished = set() stat_bytes = 0 stat_lines = 0 stat_commands = 0 def dopackage(p): global S global D global ot_version global stat_bytes global stat_lines global stat_commands if p in packages_available: pack = '-'.join(p.split('-')[:-1]) package = p else: package = None for q in packages_available: if '-'.join(q.split('-')[:-1]) == p: pack = p package = q # XXX: how is order defined for multiple versions of same package? if package is None: raise Exception(f'package {p} not found') if package in packages_finished: return if package in packages_started: raise Exception(f'loop with package {package}: {packages_started}') packages_started.append(package) sys.stderr.write(f'handling {package}\n') artfile = None pack = '-'.join(package.split('-')[:-1]) with open(f'{dir}/{package}/{pack}.thy') as f: for line in f: line = line.split() if line[:1] == ['package:']: dopackage(line[1]) if line[:1] == ['requires:']: dopackage(line[1]) if line[:1] == ['article:']: artfile = line[1] assert len(artfile) >= 2 and artfile.startswith('"') and artfile.endswith('"') artfile = artfile[1:-1] if artfile is not None: with open(f'{dir}/{package}/{artfile}') as f: ot_version = 5 S = [] D = {} # do not reset T: want to remember theorems from other files firstcommand = True for line in f: stat_bytes += len(line) stat_lines += 1 if line[:1] == '#': continue line = line.strip() if watch: for spos,s in enumerate(S): sys.stderr.write(f'stack {spos} {format_obj(s)}\n') sys.stderr.write(f'processing {line}\n') if line == 'version': assert firstcommand ot_version = pop_num() firstcommand = False elif line in commands: stat_commands += 1 commands[line]() firstcommand = False elif line[:1] == '"': assert line[-1:] == '"' assert len(line) >= 2 push_name(unbackslash(line[1:-1])) else: push_num(int(line)) assert packages_started.pop() == package packages_finished.add(package) for package in sys.argv[2:]: dopackage(package) sys.stderr.write(f'finished bytes {stat_bytes} lines {stat_lines} commands {stat_commands}\n')