git @ Cat's Eye Technologies Maxixe / master src / maxixe / checker.py
master

Tree @master (Download .tar.gz)

checker.py @masterraw · history · blame

from maxixe.ast import Block


class ProofStructureError(ValueError):
    pass


class ReasoningError(ValueError):
    pass


class Checker(object):
    def __init__(self, proof):
        self.proof = proof
        self.current_block = None
        self.local_atoms = set()
        self.used_atoms = set()
        self.current_step = None

    def check(self):
        if not self.proof.goal.is_ground():
            raise ProofStructureError("goal is not ground")

        self.check_block(self.proof.block)

        if self.current_step.term != self.proof.goal:
            raise ProofStructureError("proof does not reach goal")

    def check_block(self, block):
        prev_block, prev_local_atoms = self.current_block, self.local_atoms
        self.current_block, self.local_atoms = block, set()

        block_rule = self.proof.get_block_rule(self.current_block.name)
        if len(block_rule.cases) != len(block.cases):
            raise ProofStructureError("block must have same number of cases as block rule")
        last_term = None
        for (case_num, (block_rule_case, case)) in enumerate(zip(block_rule.cases, block.cases)):
            self.check_case(case_num + 1, block_rule_case, case)
            if last_term is None:
                last_term = case.steps[-1].term
            elif last_term != case.steps[-1].term:
                raise ProofStructureError("cases do not finish with same term")

        self.current_block, self.local_atoms = prev_block, prev_local_atoms

    def check_case(self, case_num, block_rule_case, case):
        if block_rule_case.initial is not None and case.steps[0].by.name != block_rule_case.initial.var.name:
            raise ProofStructureError("initial step of case %s of %s must use rule %s" %
                (case_num, self.current_block.name, block_rule_case.initial.var.name)
            )
        if block_rule_case.final is not None and case.steps[-1].by.name != block_rule_case.final.var.name:
            raise ProofStructureError("final step of case %s of %s must use rule %s" %
                (case_num, self.current_block.name, block_rule_case.final.var.name)
            )

        for step in case.steps:
            if isinstance(step, Block):
                self.check_block(step)
            else:
                self.check_step(step)

        final_atoms = set()
        case.steps[-1].term.collect_atoms(final_atoms)
        for atom in self.local_atoms:
            if atom in final_atoms:
                raise ProofStructureError("Local atom '%s' cannot be used in final step of case" % atom)

    def check_step(self, step):
        block = self.current_block
        self.current_step = step
        rule = self.proof.get_rule(step.by.name)

        if len(rule.hypotheses) != len(step.with_):
            self.step_error("Number of arguments provided (%s) does not match number of hypotheses (%s)" %
                (len(step.with_), len(rule.hypotheses))
            )
        unifier = {}
        with_terms = []
        for (hypothesis, with_) in zip(rule.hypotheses, step.with_):
            if hypothesis.has_attribute('atom'):
                if not with_.is_atom():
                    self.step_error("argument '%s' to hypothesis '%s' is not an atom" % (with_, hypothesis))
                hypothesis.term.match(with_, unifier)
                with_term = with_
                if hypothesis.has_attribute('local'):
                    self.local_atoms.add(with_term)
            elif hypothesis.has_attribute('term'):
                hypothesis.term.match(with_, unifier)
                with_term = with_
                if hypothesis.has_attribute('nonlocal'):
                    atoms = set()
                    with_term.collect_atoms(atoms)
                    for atom in self.local_atoms:
                        if atom in atoms:
                            self.step_error("argument '%s' to hypothesis '%s' must not contain local atom '%s'" % (
                                with_, hypothesis, atom
                            ))
            else:
                with_step, from_block = self.proof.find_step_and_block(with_.name)
                if from_block.level > block.level:
                    if not from_block.has_as_last_step(with_step):
                        self.step_error('%s is a non-final step in an inner block' % with_.name)
                try:
                    hypothesis.term.match(with_step.term, unifier)
                except ValueError as e:
                    unifier_str = ", ".join(["%s -> %s" % (k, v) for k, v in unifier.iteritems()])
                    self.step_error(
                        "could not match '%s' with '%s' with unifier '%s': %s" % (
                            hypothesis.term, with_step.term, unifier_str, e
                        ), class_=ReasoningError
                    )
                with_term = with_step.term
            with_terms.append(with_term)
            if hypothesis.has_attribute('unique'):
                if with_term in self.used_atoms:
                    self.step_error("'%s' has already been used as an atom in this proof" % (step.by.name, with_term))

        instance = rule.conclusion.subst(unifier)
        if not instance.is_ground():
            self.step_error("Not all variables replaced during rule instantiation")

        instance = instance.resolve_substs(unifier)
        
        if instance != step.term:
            self.step_error("%s does not follow from %s with %s - it would be %s." % (
                step.term, step.by.name, ' ; '.join([str(t) for t in with_terms]), instance
            ), class_=ReasoningError)

        step.term.collect_atoms(self.used_atoms)

    def step_error(self, message, class_=ProofStructureError):
        message = "In %s, %s" % (self.current_step.var.name, message)
        raise class_(message)