diff --git a/lisa-utils/src/main/scala/lisa/utils/Parser.scala b/lisa-utils/src/main/scala/lisa/utils/Parser.scala
index cf4f171388571685878751b631c8cf589d56edd1..e9cb3d679a72ad398dca76fdf3bdeb600e5dceb4 100644
--- a/lisa-utils/src/main/scala/lisa/utils/Parser.scala
+++ b/lisa-utils/src/main/scala/lisa/utils/Parser.scala
@@ -1,10 +1,11 @@
 package lisa.utils
 
 import lisa.kernel.fol.FOL
-import lisa.kernel.fol.FOL._
+import lisa.kernel.fol.FOL.*
 import lisa.kernel.fol.FOL.equality
 import lisa.kernel.proof.SequentCalculus.*
 import scallion.*
+import scallion.util.Unfolds.unfoldRight
 import silex.*
 
 object Parser {
@@ -29,6 +30,12 @@ object Parser {
     case SequentParser.UnexpectedEnd(_) => throw ParserException(s"Unexpected end of input")
   }
 
+  def printSequent(s: Sequent): String = SequentParser.printSequent(s).get
+
+  def printFormula(f: Formula): String = SequentParser.printFormula(f).get
+
+  def printTerm(t: Term): String = SequentParser.printTerm(t).get
+
   private[Parser] sealed abstract class FormulaToken
   case object ForallToken extends FormulaToken
   case object ExistsOneToken extends FormulaToken
@@ -85,6 +92,27 @@ object Parser {
       val source = Source.fromIterator(it, NoPositioner)
       lexer(source).filter(_ != SpaceToken)
     }
+
+    // TODO: add spaces as needed
+    def unapply(tokens: Iterator[Token]): String = tokens.map {
+      case ForallToken => Forall.id
+      case ExistsOneToken => ExistsOne.id
+      case ExistsToken => Exists.id
+      case DotToken => "."
+      case AndToken => And.id
+      case OrToken => Or.id
+      case ImpliesToken => Implies.id
+      case IffToken => Iff.id
+      case NegationToken => Neg.id
+      case EqualityToken => equality.id
+      case ConstantToken(id) => id
+      case SchematicToken(id) => "?" + id
+      case ParenthesisToken(isOpen) => if (isOpen) "(" else ")"
+      case CommaToken => ","
+      case SemicolonToken => ";"
+      case SequentToken => "⊢"
+      case SpaceToken => " "
+    }.mkString
   }
 
   private[Parser] object SequentParser extends Parsers {
@@ -146,25 +174,49 @@ object Parser {
     ///////////////////////////////////////////////////////////////////
 
     //////////////////////// LABELS ///////////////////////////////////
-    val toplevelConnector: Syntax[Implies.type | Iff.type] = accept(TopLevelConnectorKind) {
-      case ImpliesToken => Implies
-      case IffToken => Iff
-    }
+    val toplevelConnector: Syntax[Implies.type | Iff.type] = accept(TopLevelConnectorKind)(
+      {
+        case ImpliesToken => Implies
+        case IffToken => Iff
+      },
+      {
+        case Implies => Seq(ImpliesToken)
+        case Iff => Seq(IffToken)
+      }
+    )
 
-    val negation: Syntax[Neg.type] = accept(NegationKind) { case NegationToken => Neg }
+    val negation: Syntax[Neg.type] = accept(NegationKind)({ case NegationToken => Neg }, { case Neg => Seq(NegationToken) })
     ///////////////////////////////////////////////////////////////////
 
     //////////////////////// TERMS ////////////////////////////////////
     lazy val args: Syntax[Seq[Term]] = recursive(open.skip ~ repsep(term, comma) ~ closed.skip)
 
-    lazy val term: Syntax[Term] = recursive((elem(FunctionOrPredicateKind) ~ opt(args)).map {
-      case ConstantToken(id) ~ maybeArgs =>
-        val args = maybeArgs.getOrElse(Seq())
-        FunctionTerm(ConstantFunctionLabel(id, args.length), args)
-      case SchematicToken(id) ~ Some(args) => FunctionTerm(SchematicFunctionLabel(id, args.length), args)
-      case SchematicToken(id) ~ None => VariableTerm(VariableLabel(id))
-      case _ => throw UnreachableException
-    })
+    def invertTerm(t: Term): Token ~ Option[Seq[Term]] = t match {
+      case VariableTerm(label) => SchematicToken(label.id) ~ None
+      case FunctionTerm(label, args) =>
+        val optArgs = args match {
+          case Seq() => None
+          case _ => Some(args)
+        }
+        label match {
+          case ConstantFunctionLabel(id, _) => ConstantToken(id) ~ optArgs
+          case SchematicFunctionLabel(id, _) => SchematicToken(id) ~ optArgs
+        }
+    }
+
+    lazy val term: Syntax[Term] = recursive(
+      (elem(FunctionOrPredicateKind) ~ opt(args)).map(
+        {
+          case ConstantToken(id) ~ maybeArgs =>
+            val args = maybeArgs.getOrElse(Seq())
+            FunctionTerm(ConstantFunctionLabel(id, args.length), args)
+          case SchematicToken(id) ~ Some(args) => FunctionTerm(SchematicFunctionLabel(id, args.length), args)
+          case SchematicToken(id) ~ None => VariableTerm(VariableLabel(id))
+          case _ => throw UnreachableException
+        },
+        t => Seq(invertTerm(t))
+      )
+    )
     ///////////////////////////////////////////////////////////////////
 
     //////////////////////// FORMULAS /////////////////////////////////
@@ -182,87 +234,164 @@ object Parser {
       case _ => throw UnreachableException
     }
 
-    val predicate: Syntax[PredicateFormula] = (elem(FunctionOrPredicateKind) ~ opt(args) ~ opt(eq.skip ~ elem(FunctionOrPredicateKind) ~ opt(args))).map {
-      // predicate application
-      case ConstantToken(id) ~ maybeArgs ~ None =>
-        val args = maybeArgs.getOrElse(Seq())
-        PredicateFormula(ConstantPredicateLabel(id, args.size), args)
-      case SchematicToken(id) ~ Some(args) ~ None => PredicateFormula(SchematicNPredicateLabel(id, args.size), args)
-      case SchematicToken(id) ~ None ~ None => PredicateFormula(VariableFormulaLabel(id), Seq())
+    val predicate: Syntax[PredicateFormula] = (elem(FunctionOrPredicateKind) ~ opt(args) ~ opt(eq.skip ~ elem(FunctionOrPredicateKind) ~ opt(args))).map(
+      {
+        // predicate application
+        case ConstantToken(id) ~ maybeArgs ~ None =>
+          val args = maybeArgs.getOrElse(Seq())
+          PredicateFormula(ConstantPredicateLabel(id, args.size), args)
+        case SchematicToken(id) ~ Some(args) ~ None => PredicateFormula(SchematicNPredicateLabel(id, args.size), args)
+        case SchematicToken(id) ~ None ~ None =>
+          PredicateFormula(VariableFormulaLabel(id), Seq())
 
-      // equality of two function applications
-      case fun1 ~ args1 ~ Some(fun2 ~ args2) =>
-        PredicateFormula(FOL.equality, Seq(createFunctionTerm(fun1, args1.getOrElse(Seq())), createFunctionTerm(fun2, args2.getOrElse(Seq()))))
+        // equality of two function applications
+        case fun1 ~ args1 ~ Some(fun2 ~ args2) =>
+          PredicateFormula(FOL.equality, Seq(createFunctionTerm(fun1, args1.getOrElse(Seq())), createFunctionTerm(fun2, args2.getOrElse(Seq()))))
 
-      case _ => throw UnreachableException
-    }
+        case _ => throw UnreachableException
+      },
+      { case PredicateFormula(label, args) =>
+        label match {
+          case FOL.equality =>
+            args match {
+              case Seq(first, second) => Seq(invertTerm(first) ~ Some(invertTerm(second)))
+              case _ => Seq()
+            }
+          case other =>
+            val predicateApp = other match {
+              case ConstantPredicateLabel(id, 0) => ConstantToken(id) ~ None
+              case ConstantPredicateLabel(id, _) => ConstantToken(id) ~ Some(args)
+              case VariableFormulaLabel(id) => SchematicToken(id) ~ None
+              case SchematicNPredicateLabel(id, _) => SchematicToken(id) ~ Some(args)
+            }
+            Seq(predicateApp ~ None)
+        }
+      }
+    )
 
     val negated: Syntax[ConnectorFormula] = recursive {
-      (negation ~ subformula).map { case n ~ f =>
-        ConnectorFormula(n, Seq(f))
-      }
+      (negation ~ subformula).map(
+        { case n ~ f =>
+          ConnectorFormula(n, Seq(f))
+        },
+        {
+          case ConnectorFormula(Neg, Seq(f)) => Seq(Neg ~ f)
+          case _ => throw UnreachableException
+        }
+      )
     }
 
-    // 'and' has higher priority than 'or'
-    val connectorFormula: Syntax[Formula] = operators(subformula)(
-      elem(AndKind) map {
+    val and: Syntax[ConnectorLabel] = elem(AndKind).map[ConnectorLabel](
+      {
         case AndToken => And
         case _ => throw UnreachableException
-      } is LeftAssociative,
-      elem(OrKind) map {
+      },
+      {
+        case And => Seq(AndToken)
+        case _ => throw UnreachableException
+      }
+    )
+
+    val or: Syntax[ConnectorLabel] = elem(OrKind).map[ConnectorLabel](
+      {
         case OrToken => Or
         case _ => throw UnreachableException
-      } is LeftAssociative
-    )(
-      { case (l, conn, r) =>
-        ConnectorFormula(conn, Seq(l, r))
       },
+      {
+        case Or => Seq(OrToken)
+        case _ => throw UnreachableException
+      }
+    )
+
+    // 'and' has higher priority than 'or'
+    val connectorFormula: Syntax[Formula] = operators(subformula)(
+      and is LeftAssociative,
+      or is LeftAssociative
+    )(
+      (l, conn, r) => ConnectorFormula(conn, Seq(l, r)),
       { case ConnectorFormula(conn, Seq(l, r)) =>
         (l, conn, r)
       }
     )
 
-    // consume binders and return a function that constructs a BinderFormula given the inner formula
-    val binder: Syntax[(BinderLabel, VariableLabel)] = (accept(BinderKind) {
-      case ExistsToken => Exists
-      case ExistsOneToken => ExistsOne
-      case ForallToken => Forall
-    } ~ accept(FunctionOrPredicateKind) {
-      case ConstantToken(id) => VariableLabel(id)
-      case SchematicToken(id) => VariableLabel(id)
-    } ~ elem(DotKind).unit(DotToken).skip) map { case b ~ v =>
-      (b, v)
-    }
+    val binderLabel: Syntax[BinderLabel] = accept(BinderKind)(
+      {
+        case ExistsToken => Exists
+        case ExistsOneToken => ExistsOne
+        case ForallToken => Forall
+      },
+      {
+        case Exists => Seq(ExistsToken)
+        case ExistsOne => Seq(ExistsOneToken)
+        case Forall => Seq(ForallToken)
+      }
+    )
+
+    val boundVariable: Syntax[VariableLabel] = accept(FunctionOrPredicateKind)(
+      {
+        case ConstantToken(id) => VariableLabel(id)
+        case SchematicToken(id) => VariableLabel(id)
+      },
+      { case VariableLabel(id) =>
+        Seq(SchematicToken(id))
+      }
+    )
+
+    val binder: Syntax[BinderLabel ~ VariableLabel] = binderLabel ~ boundVariable ~ elem(DotKind).unit(DotToken).skip
+
+    val iffImpliesFormula: Syntax[Formula] = (connectorFormula ~ opt(toplevelConnector ~ connectorFormula)).map[Formula](
+      {
+        case left ~ Some(c ~ right) => ConnectorFormula(c, Seq(left, right))
+        case f ~ None => f
+      },
+      {
+        case ConnectorFormula(c @ (Iff | Implies), Seq(left, right)) => Seq(left ~ Some(c ~ right))
+        case f => Seq(f ~ None)
+      }
+    )
 
     lazy val formula: Syntax[Formula] = recursive {
-      (many(binder) ~ connectorFormula ~ opt(toplevelConnector ~ connectorFormula)) map { case binders ~ f ~ rest =>
-        val inner = rest match {
-          case Some(conn ~ f2) => ConnectorFormula(conn, Seq(f, f2))
-          case None => f
+      prefixes(binder, iffImpliesFormula)(
+        { case (label ~ variable, f) => BinderFormula(label, variable, f) },
+        { case BinderFormula(label, variable, f) =>
+          (label ~ variable, f)
         }
-        binders.foldRight(inner) { case ((binder, v), f) =>
-          BinderFormula(binder, v, f)
-        }
-      }
+      )
     }
     ///////////////////////////////////////////////////////////////////
-    val sequent: Syntax[Sequent] = repsep(formula, semicolon) ~ opt(sequentSymbol.skip ~ repsep(formula, semicolon)) map {
-      case left ~ Some(right) => Sequent(left.toSet, right.toSet)
-      case right ~ None => Sequent(Set(), right.toSet)
-    }
+
+    val sequent: Syntax[Sequent] = (repsep(formula, semicolon) ~ opt(sequentSymbol.skip ~ repsep(formula, semicolon))).map[Sequent](
+      {
+        case left ~ Some(right) => Sequent(left.toSet, right.toSet)
+        case right ~ None => Sequent(Set(), right.toSet)
+      },
+      {
+        case Sequent(Seq(), right) => Seq(right.toSeq ~ None)
+        case Sequent(left, right) => Seq(left.toSeq ~ Some(right.toSeq))
+      }
+    )
 
     val parser: Parser[Sequent] = Parser(sequent)
+    val printer: PrettyPrinter[Sequent] = PrettyPrinter(sequent)
 
-    val formulaParser: SequentParser.Parser[FOL.Formula] = Parser(formula)
+    val formulaParser: SequentParser.Parser[Formula] = Parser(formula)
+    val formulaPrinter: SequentParser.PrettyPrinter[Formula] = PrettyPrinter(formula)
 
-    val termParser: SequentParser.Parser[FOL.Term] = Parser(term)
+    val termParser: SequentParser.Parser[Term] = Parser(term)
+    val termPrinter: SequentParser.PrettyPrinter[Term] = PrettyPrinter(term)
 
     def apply(it: Iterator[Token]): ParseResult[Sequent] = parseSequent(it)
 
+    def unapply(s: Sequent): Option[String] = printSequent(s)
+
     def parseSequent(it: Iterator[Token]): ParseResult[Sequent] = parser(it)
+    def printSequent(s: Sequent): Option[String] = printer(s).map(SequentLexer.unapply)
 
     def parseFormula(it: Iterator[Token]): ParseResult[Formula] = formulaParser(it)
+    def printFormula(f: Formula): Option[String] = formulaPrinter(f).map(SequentLexer.unapply)
 
     def parseTerm(it: Iterator[Token]): ParseResult[Term] = termParser(it)
+
+    def printTerm(t: Term): Option[String] = termPrinter(t).map(SequentLexer.unapply)
   }
 }