# mmverify.py -- Proof verifier for the Metamath language # Copyright (C) 2002 Raph Levien raph (at) acm (dot) org # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License # This program has been tested with python 2.2.1. It does not run on 1.5.2. # Before using this program, any compressed proofs must be expanded with the # Metamath program, e.g. for the set.mm database: # $ ./metamath 'read set.mm' 'save proof *' 'write s set.mm' exit > /dev/null # To run the program, type # $ python mmverify.py < set.mm > set.log # and set.log will have the verification results. # (nm 27-Jun-2005) mmverify.py requires that a $f hypothesis must not occur # after a $e hypothesis in the same scope, even though this is allowed by # the Metamath spec. This is not a serious limitation since it can be # met by rearranging the hypothesis order. # (rl 2-Oct-2006) removed extraneous line found by Jason Orendorff import string verbosity = 1 class toks: def __init__(self, lines): self.lines = lines self.tokbuf = [] def read(self): while self.tokbuf == []: line = self.lines.readline() if line == '': return None line = line.replace('$(', ' $( ') line = line.replace('$)', ' $) ') self.tokbuf = line.split() self.tokbuf.reverse() return self.tokbuf.pop() class Frame: def __init__(self): self.c = [] self.v = [] self.d = [] self.f = [] self.e = [] class FrameStack: def __init__(self): self.stack = [] def push(self): frame = Frame() self.stack.append(frame) def pop(self): self.stack.pop() def add_c(self, tok): frame = self.stack[-1] if tok in frame.c: raise 'const already defined in scope' if tok in frame.v: raise 'const already defined as var in scope' frame.c.append(tok) def add_v(self, tok): frame = self.stack[-1] if tok in frame.v: raise 'var already defined in scope' if tok in frame.c: raise 'var already defined as const in scope' frame.v.append(tok) def add_f(self, var, kind): if not self.lookup_v(var): raise ('var in $f not defined: ' + var) if not self.lookup_c(kind): raise ('const in $f not defined' + kind) frame = self.stack[-1] for (v, k) in frame.f: if v == var: raise 'var in $f already defined in scope' frame.f.append((var, kind)) def add_e(self, stat): frame = self.stack[-1] frame.e.append(stat) def add_d(self, stat): frame = self.stack[-1] for i in range(len(stat)): for j in range(i + 1, len(stat)): x, y = stat[i], stat[j] if x > y: x, y = y, x if x != y and not (x, y) in frame.d: frame.d.append((x, y)) def lookup_c(self, tok): for i in range(len(self.stack) - 1, -1, -1): if tok in self.stack[i].c: return 1 def lookup_v(self, tok): for i in range(len(self.stack) - 1, -1, -1): if tok in self.stack[i].v: return 1 def lookup_f(self, var): for i in range(len(self.stack) - 1, -1, -1): frame = self.stack[i] for (v, k) in frame.f: if v == var: return k def lookup_d(self, x, y): # return 1 if disjoint, None if not if x > y: x, y = y, x for i in range(len(self.stack) - 1, -1, -1): frame = self.stack[i] if (x, y) in frame.d: return 1 def make_assertion(self, stat): mand_vars = [] hyps = [] for fr in self.stack: hyps.extend(fr.e) frame = self.stack[-1] visible = hyps[:] visible.append(stat) for hyp in visible: for tok in hyp: if self.lookup_v(tok) and tok not in mand_vars: mand_vars.append(tok) dm = [] for i in range(len(self.stack)): fr = self.stack[i] for (x, y) in fr.d: if x in mand_vars and y in mand_vars and not (x, y) in dm: dm.append((x, y)) mand_hyps = [] for i in range(len(self.stack) - 1, -1, -1): fr = self.stack[i] for j in range(len(fr.f) - 1, -1, -1): (v, k) = fr.f[j] if v in mand_vars: mand_hyps.append((k, v)) mand_vars.remove(v) mand_hyps.reverse() if verbosity >= 18: print 'ma:', (dm, mand_hyps, hyps, stat) return (dm, mand_hyps, hyps, stat) class MM: def __init__(self): self.fs = FrameStack() self.labels = {} def readc(self, toks): while 1: tok = toks.read() if tok == None: return None if tok == '$(': while 1: tok = toks.read() if tok == '$)': break else: return tok def readstat(self, toks): # read out to $. token; return list stat = [] while 1: tok = self.readc(toks) if tok == None: raise 'EOF before $.' elif tok == '$.': break stat.append(tok) return stat def read(self, toks): self.fs.push() label = None while 1: tok = self.readc(toks) if tok == None or tok == '$}': break elif tok == '$c': for tok in self.readstat(toks): self.fs.add_c(tok) elif tok == '$v': for tok in self.readstat(toks): self.fs.add_v(tok) elif tok == '$f': stat = self.readstat(toks) if len(stat) != 2: raise '$f must have be length 2' if verbosity >= 15: print label, '$f', stat[0], stat[1], '$.' self.fs.add_f(stat[1], stat[0]) if not label: raise '$f must have label' self.labels[label] = ('$f', stat[0], stat[1]) label = None elif tok == '$a': stat = self.readstat(toks) if not label: raise '$a must have label' self.labels[label] = ('$a', self.fs.make_assertion(stat)) label = None elif tok == '$e': stat = self.readstat(toks) self.fs.add_e(stat) if not label: raise '$e must have label' self.labels[label] = ('$e', stat) label = None elif tok == '$p': stat = self.readstat(toks) proof = None try: i = stat.index('$=') proof = stat[i + 1:] stat = stat[:i] except ValueError: raise '$p must contain proof after $=' if not label: raise '$p must have label' if verbosity >= 1: print 'verifying', label self.verify(stat, proof) self.labels[label] = ('$p', self.fs.make_assertion(stat)) label = None elif tok == '$d': stat = self.readstat(toks) self.fs.add_d(stat) elif tok == '${': self.read(toks) elif tok[0] != '$': label = tok else: print 'tok:', tok self.fs.pop() def apply_subst(self, stat, subst): result = [] for tok in stat: if subst.has_key(tok): result.extend(subst[tok]) else: result.append(tok) if verbosity >= 20: print 'apply_subst', (stat, subst), '=', result return result def find_vars(self, stat): vars = [] for x in stat: if not x in vars and self.fs.lookup_v(x): vars.append(x) return vars def verify(self, stat, proof): stack = [] for label in proof: step = self.labels[label] if verbosity >= 10: print label, ':', step if step[0] == '$f': stack.append([step[1], step[2]]) elif step[0] in ('$a', '$p'): (distinct, mand_var, hyp, result) = step[1] if verbosity >= 12: print (distinct, mand_var, hyp, result) npop = len(mand_var) + len(hyp) sp = len(stack) - npop if sp < 0: raise 'stack underflow' subst = {} for (k, v) in mand_var: entry = stack[sp] if entry[0] != k: print (k, v), entry raise "stack entry doesn't match mandatory var hyp" subst[v] = entry[1:] sp += 1 if verbosity >= 15: print 'subst:', subst for x, y in distinct: if verbosity >= 16: print 'dist', x, y, subst[x], subst[y] x_vars = self.find_vars(subst[x]) y_vars = self.find_vars(subst[y]) if verbosity >= 16: print 'V(x) =', x_vars print 'V(y) =', y_vars for gam in x_vars: for delt in y_vars: if gam == delt: raise "disjoint violation " + gam x, y = gam, delt if x > y: x, y = y, x if not self.fs.lookup_d(x, y): raise "disjoint violation " + x + ", " + y for h in hyp: entry = stack[sp] subst_h = self.apply_subst(h, subst) if entry != subst_h: print 'st:', entry print 'hy:', subst_h raise "stack entry doesn't match hypothesis" sp += 1 del stack[len(stack) - npop:] stack.append(self.apply_subst(result, subst)) elif step[0] == '$e': stack.append(step[1]) if verbosity >= 12: print 'st:', stack if len(stack) != 1: raise 'stack has >1 entry at end' if stack[0] != stat: raise "assertion proved doesn't match" def dump(self): print self.labels import sys mm = MM() mm.read(toks(sys.stdin)) #mm.dump()