All Downloads are FREE. Search and download functionalities are using the official Maven repository.

gapt.formats.ivy.Ivy.scala Maven / Gradle / Ivy

The newest version!
package gapt.formats.ivy

import gapt.formats.lisp._
import gapt.expr._
import gapt.expr.formula.Eq
import gapt.expr.formula.Formula
import gapt.expr.formula.Neg
import gapt.expr.formula.fol.FOLAtom
import gapt.expr.formula.fol.FOLConst
import gapt.expr.formula.fol.FOLExpression
import gapt.expr.formula.fol.FOLFunction
import gapt.expr.formula.fol.FOLTerm
import gapt.expr.formula.fol.FOLVar
import gapt.expr.subst.FOLSubstitution
import gapt.formats.InputFile
import gapt.proofs._
import gapt.utils.Logger

import scala.collection.mutable

/**
 * Implements parsing of ivy format: https://www.cs.unm.edu/~mccune/papers/ivy/ into Ivy's Resolution calculus.
 */

/* Constructor object, takes a filename and tries to parse as a lisp_file  */
object IvyParser {
  val logger = Logger("IvyParser")

  // calls the sexpression parser on the given file and parses it, needs a naming convention
  def apply(fn: InputFile): IvyResolutionProof =
    parse(SExpressionParser.parse(fn))

  def parse(exp: Seq[SExpression]): IvyResolutionProof = {
    require(exp.length >= 1, "An ivy proof must contain at least one proof object, not " + exp.length + "! ")
    if (exp.length > 1) logger.warn("WARNING: Ivy proof contains more than one proof, taking the first one.")
    parse(exp(0))
  }

  // the type synoyms should make the parsing functions more readable
  type ProofId = String
  type Position = List[Int]

  // decompose the proof object to a list and hand it to parse(exp: List[SExpression], found_steps : ProofMap )
  def parse(exp: SExpression): IvyResolutionProof = exp match {
    case LList()       => throw new Exception("Trying to parse an empty proof!")
    case LList(l @ _*) => parse_steps(l) // extract the list of inferences from exp
    case _             => throw new Exception("Parsing error: The proof object is not a list!")
  }

  /* traverses the list of inference sexpressions and returns an IvyResolution proof - this can then be translated to
   * our resolution calculus (i.e. where instantiation is contained in the substitution)
   * note: requires that an if inference a references inference b, then a occurs before b in the list */
  def parse_steps(exp: Seq[SExpression]): IvyResolutionProof = {
    var lastStep: IvyResolutionProof = null
    val found_steps = mutable.Map[ProofId, IvyResolutionProof]()

    exp foreach { step =>
      lastStep = parse_step(step, found_steps)
      found_steps(lastStep.id) = lastStep
    }

    lastStep
  }

  /* parses an inference step and updates the proof map  */
  def parse_step(exp: SExpression, found_steps: ProofId => IvyResolutionProof): IvyResolutionProof =
    exp match {
      /* ================== Atom ========================== */
      case LFun(id, LFun("input"), clause, _*) =>
        InitialClause(id, clause, parse_clause(clause))

      /* ================== Instance ========================== */
      case LFun(id, LFun("instantiate", LSymbol(parent_id), subst_exp), clause, _*) =>
        val parent_proof = found_steps(parent_id)
        val sub: FOLSubstitution = parse_substitution(subst_exp)

        Instantiate(id, clause, sub, parse_clause(clause), parent_proof)

      /* ================== Resolution ========================== */
      case LFun(id, LFun("resolve", LSymbol(parent_id1), LList(position1 @ _*), LSymbol(parent_id2), LList(position2 @ _*)), clause, _*) =>
        val parent_proof1 = found_steps(parent_id1)
        val parent_proof2 = found_steps(parent_id2)
        val fclause = parse_clause(clause)

        val (occ1, _) = get_literal_by_position(parent_proof1.conclusion, position1, parent_proof1.clause_exp)
        val (occ2, _) = get_literal_by_position(parent_proof2.conclusion, position2, parent_proof2.clause_exp)

        Resolution(id, clause, occ1, occ2, fclause, parent_proof1, parent_proof2)

      /* ================== Flip ========================== */
      case LFun(id, LFun("flip", LSymbol(parent_id), LList(position @ _*)), clause, _*) =>
        val parent_proof = found_steps(parent_id)
        val fclause = parse_clause(clause)
        val (occ, _) = get_literal_by_position(parent_proof.conclusion, position, parent_proof.clause_exp)

        parent_proof.conclusion(occ) match {
          case Eq(left, right) =>
            Flip(id, clause, occ, fclause, parent_proof)
        }

      /* ================== Paramodulation ========================== */
      case LFun(id, LFun("paramod", LSymbol(modulant_id), LList(mposition @ _*), LSymbol(parent_id), LList(pposition @ _*)), clause, _*) =>
        val modulant_proof = found_steps(modulant_id)
        val parent_proof = found_steps(parent_id)
        val fclause = parse_clause(clause)
        val (mocc, direction) = get_literal_by_position(modulant_proof.conclusion, mposition, modulant_proof.clause_exp)
        require(direction == List(1) || direction == List(2), "Must indicate if paramod or demod!")
        val orientation = if (direction.head == 1) true else false // true = paramod (left to right), false = demod (right to left)

        require(mocc.isSuc, "Paramodulated literal must be positive!")
        val (pocc, int_position) = get_literal_by_position(parent_proof.conclusion, pposition, parent_proof.clause_exp)

        modulant_proof.conclusion(mocc) match {
          case Eq(left: FOLTerm, right: FOLTerm) =>
            val paraformula = if (orientation)
              replaceTerm_by_in_at(left, right, parent_proof.conclusion(pocc), int_position).asInstanceOf[FOLAtom]
            else
              replaceTerm_by_in_at(right, left, parent_proof.conclusion(pocc), int_position).asInstanceOf[FOLAtom]

            Paramodulation(id, clause, int_position, mocc, pocc, paraformula, orientation, fclause, modulant_proof, parent_proof)

        }

      /* ================== Propositional ========================== */
      case LFun(id, LFun("propositional", LSymbol(parent_id)), clause, _*) => {
        val parent_proof = found_steps(parent_id)
        val fclause = parse_clause(clause)

        Propositional(id, clause, fclause, parent_proof)
      }

      // new symbol
      case LFun(id, LFun("new_symbol", LSymbol(parent_id)), clause, _*) =>
        val parent_proof = found_steps(parent_id)
        val fclause = parse_clause(clause)
        require(fclause.antecedent.isEmpty, "Expecting only positive equations in parsing of new_symbol rule " + id)
        require(fclause.succedent.size == 1, "Expecting exactly one positive equation in parsing of new_symbol rule " + id)

        val Eq(l: FOLTerm, r: FOLConst) = fclause(Suc(0)): @unchecked

        NewSymbol(id, clause, Suc(0), r, l, fclause, parent_proof)

      case _ => throw new Exception("Error parsing inference rule in expression " + exp)
    }

  // extracts a literal from a clause - since the clause seperates positive and negative clauses,
  // we also need the original SEXpression to make sense of the position.
  // paramodulation continues inside the term, so we return the remaining position together with the occurrence
  // the boolean indicates a positive or negative formula

  def get_literal_by_position(c: FOLClause, pos: Seq[SExpression], clauseexp: SExpression): (SequentIndex, List[Int]) = {
    val ipos = parse_position(pos)
    val (iformula, termpos) = parse_clause_frompos(clauseexp, ipos)
    // Remark: we actually return the first occurrence of the formula, not the one at the position indicated as
    //        it should not make a difference. (if f occurs twice in the clause, it might be derived differently
    //        but we usually don't care for that)
    iformula match {
      case a @ FOLAtom(sym, args) =>
        (Suc(c.positive.indexOf(a)), termpos)
      case Neg(a @ FOLAtom(sym, args)) =>
        (Ant(c.negative.indexOf(a)), termpos)
    }
  }

  // term replacement
  // TODO: refactor replacement for lambda expressions
  def replaceTerm_by_in_at(what: FOLTerm, by: FOLTerm, exp: FOLExpression, pos: List[Int]): FOLExpression = pos match {
    case p :: ps =>
      exp match {
        case FOLAtom(sym, args) =>
          require(1 <= p && p <= args.length, "Error in parsing replacement: invalid argument position in atom!")
          val (args1, rterm :: args2) = args.splitAt(p - 1): @unchecked
          FOLAtom(sym, (args1 ++ List(replaceTerm_by_in_at(what, by, rterm, ps).asInstanceOf[FOLTerm]) ++ args2))
        case FOLFunction(sym, args) =>
          require(1 <= p && p <= args.length, "Error in parsing replacement: invalid argument position in function!")
          val (args1, rterm :: args2) = args.splitAt(p - 1): @unchecked
          FOLFunction(sym, (args1 ++ List(replaceTerm_by_in_at(what, by, rterm, ps).asInstanceOf[FOLTerm]) ++ args2))
        case _ => throw new Exception("Error in parsing replacement: unexpected (sub)term " + exp + " )")
      }

    case Nil =>
      if (exp == what) by else throw new Exception("Error in parsing replacement: (sub)term " + exp + " is not the expected term " + what)
  }

  def parse_position(l: Seq[SExpression]): List[Int] =
    l.view.map(s => s.asInstanceOf[LSymbol].name.toInt).toList

  def parse_substitution(exp: SExpression): FOLSubstitution = exp match {
    case LList(list @ _*) =>
      FOLSubstitution(parse_substitution_(list))
    case _ => throw new Exception("Error parsing substitution expression " + exp + " (not a list)")
  }

  // Note:substitution are sometimes given as lists of cons and sometimes as two-element list...
  def parse_substitution_(exp: Seq[SExpression]): List[(FOLVar, FOLTerm)] =
    exp.toList map {
      e =>
        (e: @unchecked) match {
          case LList(vexp, texp @ _*) =>
            val v = parse_term(vexp)
            val t = parse_term(LList(texp: _*))

            v.asInstanceOf[FOLVar] -> t
          case LCons(vexp, texp) =>
            val v = parse_term(vexp)
            val t = parse_term(texp)

            v.asInstanceOf[FOLVar] -> t
        }
    }

  /* parses a clause sexpression to a fclause -- the structure is (or lit1 (or lit2 .... (or litn-1 litn)...)) */
  def parse_clause(exp: SExpression): FOLClause = {
    val clauses = parse_clause_(exp)
    var pos: List[FOLAtom] = Nil
    var neg: List[FOLAtom] = Nil

    for (c <- clauses) {
      c match {
        case Neg(formula) =>
          formula match {
            case a @ FOLAtom(_, _) => neg = a :: neg
            case _                 => throw new Exception("Error parsing clause: negative Literal " + formula + " is not an atom!")
          }
        case a @ FOLAtom(_, _) =>
          pos = a :: pos
        case _ =>
          throw new Exception("Error parsing clause: formula " + c + " is not a literal!")
      }
    }

    // the literals were prepended to the list, so we have to reverse them to get the original order
    Sequent(neg.reverse, pos.reverse)
  }

  // TODO: merge code with parse_clause_
  def parse_clause_frompos(exp: SExpression, pos: List[Int]): (Formula, List[Int]) = exp match {
    case LFun("or", left, right) =>
      pos match {
        case 1 :: rest =>
          left match {
            case LFun("not", LFun(name, args @ _*)) =>
              val npos = if (rest.isEmpty) rest else rest.tail // if we point to a term we have to strip the indicator for neg
              (Neg(parse_atom(name, args)), npos)
            case LFun(name, args @ _*) =>
              (parse_atom(name, args), rest)
            case _ => throw new Exception("Parsing Error: unexpected element " + exp + " in parsing of Ivy proof object.")
          }
        case 2 :: rest =>
          parse_clause_frompos(right, rest)
        case _ => throw new Exception("pos " + pos + " did not point to a literal!")
      }

    case LFun("not", LFun(name, args @ _*)) =>
      val npos = if (pos.isEmpty) pos else pos.tail // if we point to a term we have to strip the indicator for neg
      (Neg(parse_atom(name, args)), npos)

    case LFun(name, args @ _*) =>
      (parse_atom(name, args), pos)

    // the empty clause is denoted by false
    case LSymbol("false") =>
      throw new Exception("Parsing Error: want to extract literal from empty clause!")

    case _ => throw new Exception("Parsing Error: unexpected element " + exp + " in parsing of Ivy proof object.")
  }

  // directly converts a clause as nested or expression into a list with the literals in the same order
  def parse_clause_(exp: SExpression): List[Formula] = exp match {
    case LFun("or", left, right) =>
      val rightclause = parse_clause_(right)

      left match {
        case LFun("not", LFun(name, args @ _*)) =>
          Neg(parse_atom(name, args)) :: rightclause
        case LFun(name, args @ _*) =>
          parse_atom(name, args) :: rightclause
        case _ => throw new Exception("Parsing Error: unexpected element " + exp + " in parsing of Ivy proof object.")
      }

    case LFun("not", LFun(name, args @ _*)) =>
      Neg(parse_atom(name, args)) :: Nil

    case LFun(name, args @ _*) =>
      parse_atom(name, args) :: Nil

    // the empty clause is denoted by false
    case LSymbol("false") =>
      List()

    case _ => throw new Exception("Parsing Error: unexpected element " + exp + " in parsing of Ivy proof object.")
  }

  def parse_atom(name: String, args: Seq[SExpression]) = {
    val argterms = args map parse_term
    if (name == "=") {
      require(args.length == 2, "Error parsing equality: = must be a binary predicate!")
      Eq(argterms(0), argterms(1))
    } else {
      FOLAtom(name, argterms)
    }

  }

  // some names are escaped for ivy, see also  LADR-2009-11A/ladr/ivy.c in the Prover9 source
  val ivy_escape_table = Map[String, String](
    ("zero_for_ivy", "0"),
    ("one_for_ivy", "1"),
    ("quote_for_ivy", "'"),
    ("backslash_for_ivy", "\\"),
    ("at_for_ivy", "@"),
    ("meet_for_ivy", "^")
  )
  def rewrite_name(s: String): String = if (ivy_escape_table contains s) ivy_escape_table(s) else s

  def parse_term(ts: SExpression): FOLTerm = ts match {
    case LSymbol(name) =>
      val rname = rewrite_name(name)
      FOLVar(rname)
    case LFun(name, args @ _*) =>
      val rname = rewrite_name(name)
      FOLFunction(rname, args.map(parse_term(_)))
    case _ =>
      throw new Exception("Parsing Error: Unexpected expression " + ts + " in parsing of a term.")
  }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy