├── project ├── build.properties └── plugins.sbt ├── .gitignore ├── src ├── main │ └── scala │ │ └── trepplein │ │ ├── unionfind.scala │ │ ├── name.scala │ │ ├── quotient.scala │ │ ├── doc.scala │ │ ├── reduction.scala │ │ ├── level.scala │ │ ├── environment.scala │ │ ├── inductive.scala │ │ ├── main.scala │ │ ├── parser.scala │ │ ├── pretty.scala │ │ ├── typechecker.scala │ │ └── expr.scala └── test │ └── scala │ └── nat.scala ├── README.md ├── .github └── workflows │ └── build.yml └── COPYING /project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version = 1.5.1 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.history 2 | /.idea 3 | target 4 | /.bsp 5 | -------------------------------------------------------------------------------- /project/plugins.sbt: -------------------------------------------------------------------------------- 1 | resolvers += Classpaths.sbtPluginReleases 2 | logLevel := Level.Warn 3 | 4 | resolvers += Resolver.sbtPluginRepo("releases") 5 | addSbtPlugin("org.scalariform" % "sbt-scalariform" % "1.8.3") 6 | addSbtPlugin("com.typesafe.sbt" % "sbt-native-packager" % "1.8.1") 7 | -------------------------------------------------------------------------------- /src/main/scala/trepplein/unionfind.scala: -------------------------------------------------------------------------------- 1 | package trepplein 2 | 3 | class UFNode { 4 | var parent: UFNode = this 5 | var rank = 0 6 | 7 | def find(): UFNode = { 8 | if (parent.parent eq parent) return parent 9 | parent = parent.find() 10 | parent 11 | } 12 | 13 | def union(that: UFNode): Unit = { 14 | val a = this.find() 15 | val b = that.find() 16 | 17 | if (a eq b) return 18 | 19 | if (a.rank < b.rank) { 20 | a.parent = b 21 | if (a.rank == b.rank) b.rank += 1 22 | } else { 23 | b.parent = a 24 | if (a.rank == b.rank) a.rank += 1 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## trepplein: a Lean type-checker 2 | 3 | Lean is an interactive theorem prover based on dependent type theory. For 4 | additional trust, Lean can [export the generated proofs][1] so that they can be 5 | independently verified. Trepplein is a tool that can check these exported proofs. 6 | 7 | [1]: https://github.com/leanprover/lean/blob/master/doc/export_format.md 8 | 9 | Trepplein is written in Scala, and requires [SBT](http://www.scala-sbt.org/) to 10 | build. 11 | ``` 12 | sbt stage 13 | ./target/universal/stage/bin/trepplein .../export.out 14 | ``` 15 | 16 | ### Other checkers 17 | 18 | * [tc](https://github.com/dselsam/tc), a type-checker written in Haskell. 19 | * [leanchecker](https://github.com/leanprover/lean/tree/master/src/checker), a bare-bones version of the Lean kernel. 20 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | on: 2 | pull_request: 3 | push: 4 | branches: 5 | - '*' 6 | tags: 7 | - '*' 8 | 9 | name: ci 10 | 11 | jobs: 12 | build: 13 | name: Build 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v2 17 | 18 | - uses: actions/setup-java@v2 19 | with: 20 | distribution: 'adopt' 21 | java-version: '16' 22 | 23 | - name: install sbt 24 | run: | 25 | pushd .. 26 | sbt_version=1.5.1 27 | wget https://github.com/sbt/sbt/releases/download/v$sbt_version/sbt-$sbt_version.tgz 28 | tar xf sbt-$sbt_version.tgz 29 | echo $PWD/sbt/bin >>$GITHUB_PATH 30 | popd 31 | 32 | - name: build trepplein 33 | run: sbt compile 34 | 35 | - name: create release zip 36 | run: sbt universal:packageBin 37 | 38 | - name: release 39 | uses: softprops/action-gh-release@v1 40 | if: startsWith(github.ref, 'refs/tags/') 41 | with: 42 | files: target/universal/*.zip 43 | env: 44 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 45 | 46 | - name: run unit tests 47 | run: sbt test 48 | -------------------------------------------------------------------------------- /src/test/scala/nat.scala: -------------------------------------------------------------------------------- 1 | package trepplein 2 | 3 | import org.specs2.mutable._ 4 | 5 | class NatTest extends Specification { 6 | val nat = Const("nat", Vector()) 7 | val natZero = Const(Name("nat", "zero"), Vector()) 8 | val natSucc = Const(Name("nat", "succ"), Vector()) 9 | 10 | val natAdd = Const(Name("nat", "add"), Vector()) 11 | def addDef = { 12 | val x = LocalConst(Binding("x", nat, BinderInfo.Default)) 13 | val y = LocalConst(Binding("y", nat, BinderInfo.Default)) 14 | Lam(x, Apps( 15 | Const(Name("nat", "rec"), Vector(1)), 16 | Lam(y, nat), x, Lam(y, natSucc))) 17 | } 18 | 19 | val eqDef = { 20 | val u = Level.Param("u") 21 | val alpha = LocalConst(Binding("A", Sort(u), BinderInfo.Default)) 22 | val x = LocalConst(Binding("x", alpha, BinderInfo.Default)) 23 | val y = LocalConst(Binding("y", alpha, BinderInfo.Default)) 24 | IndMod("eq", Vector(u), Pi(alpha, alpha -->: alpha -->: Sort.Prop), 2, 25 | Vector(Name("eq", "refl") -> Pis(alpha, x)(Apps(Const("eq", Vector(u)), alpha, x, x)))) 26 | } 27 | 28 | def env_ = 29 | Environment.default 30 | .addNow(eqDef) 31 | .addNow(IndMod(nat.name, Vector(), Sort(1), 32 | 0, Vector(natZero.name -> nat, natSucc.name -> (nat -->: nat)))) 33 | .addNow(DefMod(natAdd.name, Vector(), nat -->: nat -->: nat, addDef)) 34 | 35 | def numeral(n: Int): Expr = 36 | if (n == 0) Const(Name("nat", "zero"), Vector()) 37 | else App(Const(Name("nat", "succ"), Vector()), numeral(n - 1)) 38 | 39 | def tc = new TypeChecker(env_) 40 | 41 | "add" in { 42 | val t = Apps(natAdd, numeral(2), numeral(4)) 43 | tc.checkDefEq(tc.infer(t), nat) must beLike { case IsDefEq => ok } 44 | tc.checkDefEq(t, numeral(6)) must beLike { case IsDefEq => ok } 45 | tc.checkDefEq(t, numeral(7)) must beLike { case NotDefEq(_, _) => ok } 46 | } 47 | 48 | "infer zeta" in { 49 | val x = LocalConst(Binding("x", nat, BinderInfo.Default)) 50 | val b = Let(x, numeral(0), Apps(Const("eq.refl", Vector(1)), nat, x)) 51 | b.hasLocals must_== false 52 | val ty = tc.infer(b) 53 | ty.hasLocals must_== false 54 | } 55 | } -------------------------------------------------------------------------------- /src/main/scala/trepplein/name.scala: -------------------------------------------------------------------------------- 1 | package trepplein 2 | 3 | import Name._ 4 | 5 | import scala.annotation.tailrec 6 | import scala.collection.immutable.ArraySeq 7 | import scala.language.implicitConversions 8 | import scala.runtime.ScalaRunTime 9 | 10 | sealed abstract class Name extends Product { 11 | override val hashCode: Int = ScalaRunTime._hashCode(this) 12 | 13 | @tailrec 14 | private def equals(n1: Name, n2: Name): Boolean = 15 | (n1 eq n2) || ((n1, n2) match { 16 | case (Name.Str(p1, l1), Name.Str(p2, l2)) => 17 | if (l1 != l2) return false 18 | equals(p1, p2) 19 | case (Name.Num(p1, l1), Name.Num(p2, l2)) => 20 | if (l1 != l2) return false 21 | equals(p1, p2) 22 | case _ => false 23 | }) 24 | override def equals(that: Any): Boolean = 25 | that match { 26 | case that: Name => equals(this, that) 27 | case _ => false 28 | } 29 | 30 | override def toString: String = { 31 | val buf = new StringBuilder 32 | def write(n: Name): Boolean = 33 | n match { 34 | case Anon => false 35 | case Str(prefix, limb) => 36 | if (write(prefix)) buf += '.' 37 | buf ++= limb 38 | true 39 | case Num(prefix, limb) => 40 | if (write(prefix)) buf += '.' 41 | buf.append(limb) 42 | true 43 | } 44 | write(this) 45 | buf.result() 46 | } 47 | 48 | def isAnon: Boolean = this == Anon 49 | 50 | def dump: String = 51 | this match { 52 | case Anon => "Name.Anon" 53 | case Str(prefix, limb) => s"""Name.Str(${prefix.dump}, "$limb")""" 54 | case Num(prefix, limb) => s"Name.Num(${prefix.dump}, $limb)" 55 | } 56 | } 57 | object Name { 58 | def apply(limbs: String*): Name = 59 | limbs.foldLeft[Name](Anon)(Str) 60 | 61 | def fresh(suggestion: Name, blacklist: Set[Name]): Name = 62 | (suggestion #:: LazyList.from(0).map(i => Name.Num(suggestion, i): Name)). 63 | filterNot(blacklist).head 64 | 65 | case object Anon extends Name 66 | final case class Str(prefix: Name, limb: String) extends Name 67 | final case class Num(prefix: Name, limb: Long) extends Name 68 | 69 | implicit def ofString(s: String): Name = 70 | Name(ArraySeq.unsafeWrapArray(s.split("\\.")): _*) 71 | } 72 | -------------------------------------------------------------------------------- /src/main/scala/trepplein/quotient.scala: -------------------------------------------------------------------------------- 1 | package trepplein 2 | 3 | object quotient { 4 | val univParams = Vector(Level.Param(Name("u"))) 5 | val A = LocalConst(Binding(Name("A"), Sort(univParams(0)), BinderInfo.Implicit)) 6 | val R = LocalConst(Binding(Name("R"), A -->: A -->: Sort.Prop, BinderInfo.Default)) 7 | 8 | val quot = Declaration(Name("quot"), univParams, Pis(A, R)(Sort(univParams(0))), builtin = true) 9 | 10 | val quotMk = Declaration(Name.Str(quot.name, "mk"), univParams, 11 | Pis(A, R)(A -->: Apps(Const(quot.name, univParams), A, R)), builtin = true) 12 | 13 | val liftUnivParams = univParams :+ Level.Param(Name("v")) 14 | val B = LocalConst(Binding(Name("B"), Sort(liftUnivParams(1)), BinderInfo.Implicit)) 15 | val f = LocalConst(Binding(Name("f"), A -->: B, BinderInfo.Default)) 16 | val Seq(a, b) = Seq("a", "b").map(n => LocalConst(Binding(Name(n), A, BinderInfo.Default))) 17 | 18 | val quotLift = Declaration(Name.Str(quot.name, "lift"), liftUnivParams, 19 | Pis(A, R, B, f)( 20 | Pis(a, b)(Apps(R, a, b) -->: Apps(Const(Name("eq"), Vector(liftUnivParams(1))), B, App(f, a), App(f, b))) -->: 21 | Apps(Const(quot.name, Vector(liftUnivParams(0))), A, R) -->: B), 22 | builtin = true) 23 | 24 | val B2 = LocalConst(Binding(Name("B"), Apps(Const(quot.name, Vector(univParams(0))), A, R) -->: Sort.Prop, BinderInfo.Implicit)) 25 | val q = LocalConst(Binding(Name("q"), Apps(Const(quot.name, Vector(univParams(0))), A, R), BinderInfo.Default)) 26 | val quotInd = Declaration(Name.Str(quot.name, "ind"), univParams, Pis(A, R, B2)( 27 | Pi(a, Apps(B2, Apps(Const(quotMk.name, Vector(univParams(0))), A, R, a))) -->: Pi(q, Apps(B2, q))), builtin = true) 28 | 29 | val h = LocalConst(Binding(Name("h"), Apps(Const(Name("eq"), Vector(liftUnivParams(1))), B, App(f, a), App(f, b)), BinderInfo.Default)) 30 | val quotRed = ReductionRule( 31 | Vector(A, R, B, f, a, h), 32 | Apps(Const(quotLift.name, univParams), A, R, B, f, h, Apps(Const(quotMk.name, Vector(univParams(0))), A, R, a)), 33 | Apps(f, a), 34 | List()) 35 | } 36 | 37 | case object QuotMod extends Modification { 38 | import quotient._ 39 | def name: Name = quot.name 40 | def compile(env: PreEnvironment) = new CompiledModification { 41 | val decls = Seq(quot, quotMk, quotInd, quotLift) 42 | val rules = Seq(quotRed) 43 | override def check(): Unit = 44 | decls.foldLeft(env)((env, decl) => env.addNow(decl)) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/main/scala/trepplein/doc.scala: -------------------------------------------------------------------------------- 1 | package trepplein 2 | import Doc._ 3 | 4 | import scala.language.implicitConversions 5 | 6 | sealed trait Doc { 7 | def <>(that: Doc): Doc = Concat(this, that) 8 | def <+>(that: Doc): Doc = this <> " " <> that 9 | def (that: Doc): Doc = this <> line <> that 10 | def nest(i: Int): Doc = Nest(i, this) 11 | def group: Doc = Group(this) 12 | 13 | private val flatSize: Int = this match { 14 | case Concat(a, b) => a.flatSize + b.flatSize 15 | case Nest(_, d) => d.flatSize 16 | case Text(t) => t.length 17 | case Line(orElse) => orElse.length 18 | case Group(a) => a.flatSize 19 | } 20 | private val containsLine: Boolean = this match { 21 | case Line(_) => true 22 | case Concat(a, b) => a.containsLine || b.containsLine 23 | case Nest(_, d) => d.containsLine 24 | case Text(_) => false 25 | case Group(a) => a.containsLine 26 | } 27 | private val distToFirstLine: Int = this match { 28 | case Line(_) => 0 29 | case Concat(a, b) => a.distToLine(b.distToFirstLine) 30 | case Nest(_, d) => d.distToFirstLine 31 | case Text(t) => t.length 32 | case Group(a) => a.distToFirstLine 33 | } 34 | private def distToLine(afterwards: Int): Int = 35 | if (containsLine) distToFirstLine else distToFirstLine + afterwards 36 | 37 | def render(lineWidth: Int): String = { 38 | val out = new StringBuilder 39 | var endOfLine = out.size + lineWidth 40 | def go(d: Doc, nest: Int, flatMode: Boolean, distToNextLine: Int): Unit = 41 | d match { 42 | case Concat(a, b) => 43 | go(a, nest, flatMode, b.distToLine(distToNextLine)) 44 | go(b, nest, flatMode, distToNextLine) 45 | case Nest(i, a) => 46 | go(a, nest + i, flatMode, distToNextLine) 47 | case Text(t) => 48 | out ++= t 49 | case Line(_) if !flatMode => 50 | out += '\n' 51 | endOfLine = out.size + lineWidth 52 | for (_ <- 0 until nest) out += ' ' 53 | case Line(orElse) if flatMode => 54 | out ++= orElse 55 | case Group(a) => 56 | go(a, nest, flatMode || out.size + a.flatSize + distToNextLine <= endOfLine, distToNextLine) 57 | } 58 | go(this, nest = 0, flatMode = false, distToNextLine = 0) 59 | out.result() 60 | } 61 | 62 | } 63 | 64 | object Doc { 65 | private case class Concat(a: Doc, b: Doc) extends Doc 66 | private case class Nest(i: Int, d: Doc) extends Doc 67 | private case class Text(t: String) extends Doc 68 | private case class Line(orElse: String) extends Doc 69 | private case class Group(a: Doc) extends Doc 70 | 71 | def line: Doc = Line(" ") 72 | def zeroWidthLine: Doc = Line("") 73 | implicit def text(t: String): Doc = Text(t) 74 | 75 | def sep(docs: Iterable[Doc], by: Doc): Doc = 76 | docs.reduceLeftOption(_ <> by <> _).getOrElse(Text("")) 77 | 78 | def spread(cols: Iterable[Doc]): Doc = sep(cols, Text(" ")) 79 | def spread(cols: Doc*): Doc = spread(cols) 80 | def stack(lines: Iterable[Doc]): Doc = sep(lines, line) 81 | def stack(cols: Doc*): Doc = stack(cols) 82 | 83 | def wordwrap(ds: Iterable[Doc]): Doc = 84 | ds.view.zipWithIndex. 85 | map { case (d, i) => if (i == 0) d else (line <> d).group }. 86 | reduceLeftOption(_ <> _).getOrElse("") 87 | } -------------------------------------------------------------------------------- /src/main/scala/trepplein/reduction.scala: -------------------------------------------------------------------------------- 1 | package trepplein 2 | 3 | import trepplein.Level.Param 4 | 5 | import scala.collection.mutable 6 | import scala.runtime.ScalaRunTime 7 | 8 | trait ReductionRuleCache { 9 | def instantiation(rr: ReductionRule, subst: Map[Param, Level], v: => Expr): Expr 10 | } 11 | 12 | private object NoReductionRuleCache extends ReductionRuleCache { 13 | override def instantiation(rr: ReductionRule, subst: Map[Param, Level], v: => Expr): Expr = v 14 | } 15 | 16 | final case class ReductionRule(ctx: Vector[Binding], lhs: Expr, rhs: Expr, defEqConstraints: List[(Expr, Expr)]) { 17 | require(!lhs.hasLocals) 18 | require(!rhs.hasLocals) 19 | 20 | val varBound: Int = lhs.varBound 21 | require(rhs.varBound <= varBound) 22 | 23 | val Apps(Const(lhsConst, _), lhsArgs) = lhs 24 | val lhsArgsSize: Int = lhsArgs.size 25 | 26 | val major: List[Int] = for ((a, i) <- lhsArgs.zipWithIndex if !a.isInstanceOf[Var]) yield i 27 | 28 | private def applyCore(e: Expr)(implicit cache: ReductionRuleCache = NoReductionRuleCache): Option[(Expr, List[(Expr, Expr)])] = { 29 | val subst = if (varBound == 0) null else new Array[Expr](varBound) 30 | val univSubst = mutable.Map[Level.Param, Level]() 31 | 32 | def go(a: Expr, b: Expr): Boolean = 33 | (a, b) match { 34 | case (App(a1, a2), App(b1, b2)) => go(a1, b1) && go(a2, b2) 35 | case (Const(an, als), Const(bn, bls)) if an == bn => 36 | als.lazyZip(bls).foreach { (al, bl) => univSubst(al.asInstanceOf[Level.Param]) = bl } 37 | true 38 | case (Var(i), _) => 39 | subst(i) = b; true 40 | case (_, _) => false 41 | } 42 | 43 | if (!go(lhs, e)) return None 44 | 45 | val univSubstMap = univSubst.toMap 46 | if (varBound == 0) { 47 | Some(cache.instantiation(this, univSubstMap, rhs.instantiate(univSubstMap)) -> defEqConstraints) 48 | } else { 49 | val substVect = subst.toVector 50 | Some(cache.instantiation(this, univSubstMap, rhs.instantiate(univSubstMap)).instantiate(0, substVect) -> 51 | defEqConstraints.map { case (i, j) => i.instantiate(0, substVect) -> j.instantiate(0, substVect) }) 52 | } 53 | } 54 | 55 | def apply(hd: Const, as: List[Expr])(implicit cache: ReductionRuleCache = NoReductionRuleCache): Option[(Expr, List[(Expr, Expr)])] = 56 | if (as.length < lhsArgsSize) None else { 57 | val (as1, as2) = as.splitAt(lhsArgsSize) 58 | applyCore(Apps(hd, as1)) match { 59 | case Some((red, cs)) => Some(Apps(red, as2) -> cs) 60 | case None => None 61 | } 62 | } 63 | 64 | def apply(e: Expr): Option[(Expr, List[(Expr, Expr)])] = e match { 65 | case Apps(hd: Const, as) => apply(hd, as) 66 | case _ => None 67 | } 68 | 69 | override val hashCode: Int = ScalaRunTime._hashCode(this) 70 | } 71 | object ReductionRule { 72 | def apply(lcs: Vector[LocalConst], lhs: Expr, rhs: Expr, defEqConstraints: List[(Expr, Expr)])(implicit dummy: DummyImplicit): ReductionRule = 73 | ReductionRule(lcs.map(_.of), lhs.abstr(0, lcs), rhs.abstr(0, lcs), 74 | defEqConstraints.map { case (a, b) => a.abstr(0, lcs) -> b.abstr(0, lcs) }) 75 | } 76 | 77 | final class ReductionMap private (keyMap: Map[Name, (Vector[ReductionRule], Set[Int])]) { 78 | def +(reductionRule: ReductionRule): ReductionMap = { 79 | val (rs, ms) = keyMap(reductionRule.lhsConst) 80 | new ReductionMap(keyMap.updated(reductionRule.lhsConst, (rs :+ reductionRule) -> (ms ++ reductionRule.major))) 81 | } 82 | 83 | def ++(rs: Iterable[ReductionRule]): ReductionMap = rs.foldLeft(this)((t, r) => t + r) 84 | 85 | def major(k: Name): Set[Int] = keyMap(k)._2 86 | 87 | def rules: Vector[ReductionRule] = Vector() ++ keyMap.values.flatMap(_._1) 88 | def get(k: Name): Vector[ReductionRule] = keyMap(k)._1 89 | 90 | def apply(e: Expr)(implicit cache: ReductionRuleCache = NoReductionRuleCache): Option[(Expr, List[(Expr, Expr)])] = 91 | e match { 92 | case Apps(hd @ Const(c, _), as) => 93 | val it = keyMap(c)._1.iterator 94 | while (it.hasNext) { 95 | val res = it.next().apply(hd, as) 96 | if (res.isDefined) return res 97 | } 98 | None 99 | case _ => None 100 | } 101 | } 102 | object ReductionMap { 103 | def apply() = new ReductionMap(keyMap = Map().withDefaultValue(Vector() -> Set())) 104 | } -------------------------------------------------------------------------------- /src/main/scala/trepplein/level.scala: -------------------------------------------------------------------------------- 1 | package trepplein 2 | 3 | import trepplein.Level.{ IMax, Max, _ } 4 | 5 | import scala.annotation.tailrec 6 | import scala.language.implicitConversions 7 | 8 | sealed abstract class Level { 9 | def dump: String = 10 | this match { 11 | case Zero => "Level.Zero" 12 | case Succ(level) => s"Level.Succ(${level.dump})" 13 | case Max(a, b) => s"Level.Max(${a.dump}, ${b.dump})" 14 | case IMax(a, b) => s"Level.IMax(${a.dump}, ${b.dump})" 15 | case Param(param) => s"Level.Param(${param.dump})" 16 | } 17 | 18 | def instantiate(subst: Map[Param, Level]): Level = 19 | this match { 20 | case Zero => Zero 21 | case Succ(level) => Succ(level.instantiate(subst)) 22 | case Max(a, b) => Max(a.instantiate(subst), b.instantiate(subst)) 23 | case IMax(a, b) => IMax(a.instantiate(subst), b.instantiate(subst)) 24 | case p: Param => subst.getOrElse(p, p) 25 | } 26 | 27 | def univParams: Set[Param] = 28 | this match { 29 | case Zero => Set() 30 | case Succ(level) => level.univParams 31 | case Max(a, b) => a.univParams union b.univParams 32 | case IMax(a, b) => a.univParams union b.univParams 33 | case param: Param => Set(param) 34 | } 35 | 36 | def simplify: Level = 37 | this match { 38 | case Zero | Param(_) => this 39 | case Succ(level) => Succ(level.simplify) 40 | case Max(l1, l2) => Max.combining(l1.simplify, l2.simplify) 41 | case IMax(a, b) => 42 | b.simplify match { 43 | case b_ @ Succ(_) => Max.combining(a.simplify, b_) 44 | case Zero => Zero 45 | case b_ => IMax(a.simplify, b_) 46 | } 47 | } 48 | 49 | def <==(that: Level): Boolean = isLeqCore(this.simplify, that.simplify, 0) 50 | 51 | def ===(that: Level): Boolean = { 52 | val a = this.simplify 53 | val b = that.simplify 54 | isLeqCore(a, b, 0) && isLeqCore(b, a, 0) 55 | } 56 | 57 | def isZero: Boolean = this <== Zero 58 | def isNonZero: Boolean = Succ(Zero) <== this 59 | 60 | def maybeZero: Boolean = !isNonZero 61 | def maybeNonZero: Boolean = !isZero 62 | } 63 | 64 | object Level { 65 | case object Zero extends Level 66 | case class Succ(level: Level) extends Level 67 | case class Max(a: Level, b: Level) extends Level 68 | case class IMax(a: Level, b: Level) extends Level 69 | case class Param(param: Name) extends Level 70 | 71 | object Max { 72 | def combining(l1: Level, l2: Level): Level = 73 | (l1, l2) match { 74 | case (Succ(l1_), Succ(l2_)) => Succ(combining(l1_, l2_)) 75 | case (Zero, _) => l2 76 | case (_, Zero) => l1 77 | case (_, _) => Max(l1, l2) 78 | } 79 | } 80 | 81 | implicit def ofNat(n: Int): Level = Offset(n, Zero) 82 | 83 | object Offset { 84 | def unapply(level: Level): Some[(Int, Level)] = { 85 | @tailrec 86 | def decompose(i: Int, level: Level): (Int, Level) = 87 | level match { 88 | case Succ(l) => decompose(i + 1, l) 89 | case _ => (i, level) 90 | } 91 | Some(decompose(0, level)) 92 | } 93 | 94 | @tailrec 95 | def apply(i: Int, level: Level): Level = 96 | if (i == 0) level else apply(i - 1, Succ(level)) 97 | } 98 | 99 | /** l1 <= l2 + diff */ 100 | private def isLeqCore(l1: Level, l2: Level, diff: Int): Boolean = { 101 | def split(p: Param): Boolean = 102 | Seq(Map(p -> Succ(p)), Map(p -> Zero)).forall(subst => 103 | isLeqCore(l1.instantiate(subst).simplify, l2.instantiate(subst).simplify, diff)) 104 | 105 | (l1, l2) match { 106 | // simplification 107 | case (Zero, _) if diff >= 0 => true 108 | case (_, Zero) if diff < 0 => false 109 | case (Param(i), Param(j)) => i == j && diff >= 0 110 | case (Param(_), Zero) => false 111 | case (Zero, Param(_)) => diff >= 0 112 | case (Succ(l1_), _) => isLeqCore(l1_, l2, diff - 1) 113 | case (_, Succ(l2_)) => isLeqCore(l1, l2_, diff + 1) 114 | 115 | // descend left 116 | case (Max(a, b), _) => isLeqCore(a, l2, diff) && isLeqCore(b, l2, diff) 117 | 118 | // descend right 119 | case (Param(_) | Zero, Max(a, b)) => isLeqCore(l1, a, diff) || isLeqCore(l1, b, diff) 120 | 121 | // imax 122 | case (IMax(a1, b1), IMax(a2, b2)) if a1 == a2 && b1 == b2 => true 123 | case (IMax(_, p @ Param(_)), _) => split(p) 124 | case (_, IMax(_, p @ Param(_))) => split(p) 125 | case (IMax(a, IMax(b, c)), _) => isLeqCore(Max(IMax(a, c), IMax(b, c)), l2, diff) 126 | case (IMax(a, Max(b, c)), _) => isLeqCore(Max(IMax(a, b), IMax(a, c)).simplify, l2, diff) 127 | case (_, IMax(a, IMax(b, c))) => isLeqCore(l1, Max(IMax(a, c), IMax(b, c)), diff) 128 | case (_, IMax(a, Max(b, c))) => isLeqCore(l1, Max(IMax(a, b), IMax(a, c)).simplify, diff) 129 | } 130 | } 131 | } 132 | 133 | -------------------------------------------------------------------------------- /src/main/scala/trepplein/environment.scala: -------------------------------------------------------------------------------- 1 | package trepplein 2 | 3 | import scala.concurrent.{ ExecutionContext, Future } 4 | import scala.language.implicitConversions 5 | import scala.util.Try 6 | 7 | final case class Declaration(name: Name, univParams: Vector[Level.Param], ty: Expr, 8 | height: Int = 0, builtin: Boolean = false) { 9 | def check(env: PreEnvironment): Unit = check(env, new TypeChecker(env)) 10 | def check(env: PreEnvironment, tc: TypeChecker): Unit = { 11 | require(!env.declarations.contains(name)) 12 | require(ty.univParams.subsetOf(univParams.toSet)) 13 | require(!ty.hasVars) 14 | require(!ty.hasLocals) 15 | tc.inferUniverseOfType(ty) 16 | } 17 | } 18 | 19 | trait CompiledModification { 20 | def check(): Unit 21 | def decls: Seq[Declaration] 22 | def rules: Seq[ReductionRule] 23 | } 24 | 25 | trait Modification { 26 | def name: Name 27 | def compile(env: PreEnvironment): CompiledModification 28 | } 29 | object Modification { 30 | implicit def ofAxiom(axiom: Declaration): Modification = AxiomMod(axiom.name, axiom.univParams, axiom.ty) 31 | } 32 | final case class AxiomMod(name: Name, univParams: Vector[Level.Param], ty: Expr) extends Modification { 33 | def compile(env: PreEnvironment): CompiledModification = new CompiledModification { 34 | val decl = Declaration(name, univParams, ty) 35 | def check(): Unit = decl.check(env) 36 | def decls: Seq[Declaration] = Seq(decl) 37 | def rules: Seq[ReductionRule] = Seq() 38 | } 39 | } 40 | final case class DefMod(name: Name, univParams: Vector[Level.Param], ty: Expr, value: Expr) extends Modification { 41 | def compile(env: PreEnvironment): CompiledModification = new CompiledModification { 42 | val height: Int = 43 | value.constants.view. 44 | flatMap(env.get). 45 | map(_.height). 46 | fold(0)(math.max) + 1 47 | 48 | val decl = Declaration(name, univParams, ty, height = height) 49 | val rule = ReductionRule(Vector[Binding](), Const(name, univParams), value, List()) 50 | 51 | def check(): Unit = { 52 | val tc = new TypeChecker(env) 53 | decl.check(env, tc) 54 | require(!value.hasVars) 55 | require(!value.hasLocals) 56 | tc.checkType(value, ty) 57 | } 58 | def decls: Seq[Declaration] = Seq(decl) 59 | def rules: Seq[ReductionRule] = Seq(rule) 60 | } 61 | } 62 | 63 | case class EnvironmentUpdateError(mod: Modification, msg: String) { 64 | override def toString = s"${mod.name}: $msg" 65 | } 66 | 67 | sealed class PreEnvironment protected ( 68 | val declarations: Map[Name, Declaration], 69 | val reductions: ReductionMap, 70 | val proofObligations: List[Future[Option[EnvironmentUpdateError]]]) { 71 | 72 | def get(name: Name): Option[Declaration] = 73 | declarations.get(name) 74 | def apply(name: Name): Declaration = 75 | declarations(name) 76 | 77 | def value(name: Name): Option[Expr] = 78 | reductions.get(name).find(_.lhs.isInstanceOf[Const]).map(_.rhs) 79 | 80 | def isAxiom(name: Name): Boolean = 81 | !this(name).builtin && value(name).isEmpty 82 | 83 | private def addDeclsFor(mod: CompiledModification): Map[Name, Declaration] = 84 | declarations ++ mod.decls.view.map(d => d.name -> d) 85 | 86 | def addWithFuture(mod: Modification)(implicit executionContext: ExecutionContext): (Future[Option[EnvironmentUpdateError]], PreEnvironment) = { 87 | val compiled = mod.compile(this) 88 | val checkingTask = Future { 89 | Try(compiled.check()).failed.toOption. 90 | map(t => EnvironmentUpdateError(mod, t.getMessage)) 91 | } 92 | checkingTask -> new PreEnvironment(addDeclsFor(compiled), reductions ++ compiled.rules, checkingTask :: proofObligations) 93 | } 94 | 95 | def addNow(mod: Modification): PreEnvironment = { 96 | val compiled = mod.compile(this) 97 | compiled.check() 98 | new PreEnvironment(addDeclsFor(compiled), reductions ++ compiled.rules, proofObligations) 99 | } 100 | 101 | def add(mod: Modification)(implicit executionContext: ExecutionContext): PreEnvironment = 102 | addWithFuture(mod)._2 103 | 104 | def force(implicit executionContext: ExecutionContext): Future[Either[Seq[EnvironmentUpdateError], Environment]] = 105 | Environment.force(this) 106 | } 107 | 108 | final class Environment private (declarations: Map[Name, Declaration], reductionMap: ReductionMap) 109 | extends PreEnvironment(declarations, reductionMap, Nil) 110 | object Environment { 111 | def force(preEnvironment: PreEnvironment)(implicit executionContext: ExecutionContext): Future[Either[Seq[EnvironmentUpdateError], Environment]] = 112 | Future.sequence(preEnvironment.proofObligations).map(_.flatten).map { 113 | case Nil => Right(new Environment(preEnvironment.declarations, preEnvironment.reductions)) 114 | case exs => Left(exs) 115 | } 116 | 117 | def default = new Environment(Map(), ReductionMap()) 118 | } -------------------------------------------------------------------------------- /src/main/scala/trepplein/inductive.scala: -------------------------------------------------------------------------------- 1 | package trepplein 2 | 3 | import trepplein.Level.Param 4 | 5 | final case class CompiledIndMod(indMod: IndMod, env: PreEnvironment) extends CompiledModification { 6 | import indMod._ 7 | val tc = new TypeChecker(env.addNow(decl)) 8 | import tc.NormalizedPis 9 | 10 | def name: Name = indMod.name 11 | def univParams: Vector[Param] = indMod.univParams 12 | val indTy = Const(name, univParams) 13 | 14 | val ((params, indices), level) = ty match { 15 | case NormalizedPis(doms, Sort(lvl)) => 16 | (doms.splitAt(numParams), lvl) 17 | } 18 | val indTyWParams = Apps(indTy, params) 19 | 20 | case class CompiledIntro(name: Name, ty: Expr) { 21 | val NormalizedPis(arguments, Apps(introType, introTyArgs)) = NormalizedPis.instantiate(ty, params) 22 | val introTyIndices: List[Expr] = introTyArgs.drop(numParams) 23 | 24 | type ArgInfo = Either[Expr, (List[LocalConst], List[Expr])] 25 | 26 | val argInfos: List[ArgInfo] = arguments.map { 27 | case LocalConst(Binding(_, NormalizedPis(eps, Apps(recArgIndTy @ Const(indMod.name, _), recArgs)), _), _) => 28 | require(recArgs.size >= numParams) 29 | tc.requireDefEq(Apps(recArgIndTy, recArgs.take(numParams)), indTyWParams) 30 | Right((eps, recArgs.drop(numParams))) 31 | case nonRecArg => Left(nonRecArg) 32 | } 33 | 34 | lazy val ihs: List[LocalConst] = arguments.lazyZip(argInfos).collect { 35 | case (recArg, Right((eps, recIndices))) => 36 | LocalConst(Binding("ih", Pis(eps)(mkMotiveApp(recIndices, Apps(recArg, eps))), BinderInfo.Default)) 37 | }.toList 38 | 39 | lazy val minorPremise = LocalConst(Binding("h", Pis(arguments ++ ihs)(mkMotiveApp( 40 | introTyIndices, 41 | Apps(Const(name, univParams), params ++ arguments))), BinderInfo.Default)) 42 | 43 | lazy val redRule: ReductionRule = { 44 | val recCalls = arguments.zip(argInfos).collect { 45 | case (recArg, Right((eps, recArgIndices))) => 46 | Lams(eps)(Apps(Const(elimDecl.name, elimLevelParams), params ++ Seq(motive) ++ minorPremises ++ recArgIndices :+ Apps(recArg, eps))) 47 | } 48 | ReductionRule( 49 | Vector() ++ params ++ Seq(motive) ++ minorPremises ++ indices ++ arguments, 50 | Apps(Const(elimDecl.name, elimLevelParams), params ++ Seq(motive) ++ minorPremises ++ indices 51 | :+ Apps(Const(name, univParams), params ++ arguments)), 52 | Apps(minorPremise, arguments ++ recCalls), 53 | List()) 54 | } 55 | 56 | def check(): Unit = { 57 | require(introTyArgs.size >= numParams) 58 | tc.requireDefEq(Apps(introType, introTyArgs.take(numParams)), Apps(indTy, params)) 59 | 60 | val tc0 = new TypeChecker(env) 61 | arguments.zip(argInfos).foreach { 62 | case (_, Left(nonRecArg)) => 63 | tc0.inferUniverseOfType(tc0.infer(nonRecArg)) 64 | case (_, Right((eps, _))) => 65 | for (e <- eps) tc0.inferUniverseOfType(tc0.infer(e)) 66 | } 67 | 68 | if (level.maybeNonZero) for (arg <- arguments) { 69 | val argLevel = tc.inferUniverseOfType(tc.infer(arg)) 70 | require(argLevel <== level) 71 | } 72 | } 73 | } 74 | 75 | val compiledIntros: Vector[CompiledIntro] = intros.map(CompiledIntro.tupled) 76 | 77 | val elimIntoProp: Boolean = level.maybeZero && 78 | (intros.size > 1 || compiledIntros.exists { intro => 79 | intro.arguments.exists { arg => !tc.isProof(arg) && !intro.introTyArgs.contains(arg) } 80 | }) 81 | val elimLevel: Level = 82 | if (elimIntoProp) Level.Zero 83 | else Level.Param(Name.fresh("l", univParams.map(_.param).toSet)) 84 | val extraElimLevelParams: Vector[Param] = 85 | Vector(elimLevel).collect { case p: Level.Param => p } 86 | 87 | val useDepElim: Boolean = level.maybeNonZero 88 | val motiveType: Expr = 89 | if (useDepElim) 90 | Pis(indices :+ LocalConst(Binding("c", Apps(indTy, params ++ indices), BinderInfo.Default)))(Sort(elimLevel)) 91 | else 92 | Pis(indices)(Sort(elimLevel)) 93 | val motive = LocalConst(Binding("C", motiveType, BinderInfo.Implicit)) 94 | def mkMotiveApp(indices: Seq[Expr], e: Expr): Expr = 95 | if (useDepElim) App(Apps(motive, indices), e) else Apps(motive, indices) 96 | 97 | val minorPremises: Vector[LocalConst] = compiledIntros.map { _.minorPremise } 98 | val majorPremise = LocalConst(Binding("x", Apps(indTy, params ++ indices), BinderInfo.Default)) 99 | val elimType: Expr = Pis(params ++ Seq(motive) ++ minorPremises ++ indices :+ majorPremise)(mkMotiveApp(indices, majorPremise)) 100 | val elimLevelParams: Vector[Param] = extraElimLevelParams ++ univParams 101 | val elimDecl = Declaration(Name.Str(name, "rec"), elimLevelParams, elimType, builtin = true) 102 | 103 | val kIntroRule: Option[ReductionRule] = 104 | compiledIntros match { 105 | case Vector(intro) if intro.arguments.isEmpty => 106 | Some(ReductionRule( 107 | Vector() ++ params ++ Seq(motive) ++ minorPremises ++ indices ++ Seq(majorPremise), 108 | Apps(Const(elimDecl.name, elimLevelParams), params ++ Seq(motive) ++ minorPremises ++ indices 109 | ++ Seq(majorPremise)), 110 | minorPremises(0), 111 | (intro.introTyArgs zip (params ++ indices)).filter { case (a, b) => a != b })) 112 | case _ => None 113 | } 114 | 115 | val introDecls: Vector[Declaration] = 116 | for (i <- compiledIntros) 117 | yield Declaration(i.name, univParams, i.ty, builtin = true) 118 | 119 | val decls: Vector[Declaration] = Declaration(name, univParams, ty, builtin = true) +: introDecls :+ elimDecl 120 | val rules: Vector[ReductionRule] = 121 | if (kIntroRule.isDefined) 122 | kIntroRule.toVector 123 | else 124 | compiledIntros.map(_.redRule) 125 | 126 | def check(): Unit = { 127 | val withType = env.addNow(decl) 128 | val withIntros = introDecls.foldLeft(withType) { (env, i) => i.check(withType); env.addNow(i) } 129 | withIntros.addNow(elimDecl) 130 | 131 | for (i <- compiledIntros) i.check() 132 | } 133 | } 134 | 135 | final case class IndMod(name: Name, univParams: Vector[Level.Param], ty: Expr, 136 | numParams: Int, intros: Vector[(Name, Expr)]) extends Modification { 137 | val decl = Declaration(name, univParams, ty, builtin = true) 138 | 139 | def compile(env: PreEnvironment) = CompiledIndMod(this, env) 140 | } 141 | -------------------------------------------------------------------------------- /src/main/scala/trepplein/main.scala: -------------------------------------------------------------------------------- 1 | package trepplein 2 | 3 | import scala.collection.mutable 4 | import scala.concurrent.Await 5 | import scala.concurrent.duration.Duration 6 | import scala.concurrent.ExecutionContext.Implicits.global 7 | 8 | class LibraryPrinter(env: PreEnvironment, notations: Map[Name, Notation], 9 | out: String => Unit, 10 | prettyOptions: PrettyOptions, 11 | lineWidth: Int = 80, 12 | printReductions: Boolean = false, 13 | printDependencies: Boolean = true) { 14 | private val declsPrinted = mutable.Map[Name, Unit]() 15 | def printDecl(name: Name): Unit = declsPrinted.getOrElseUpdate(name, { 16 | val tc = new TypeChecker(env, unsafeUnchecked = true) 17 | val pp = new PrettyPrinter(typeChecker = Some(tc), notations = notations, options = prettyOptions) 18 | 19 | val decl = env(name) 20 | if (printDependencies) { 21 | decl.ty.constants.foreach(printDecl) 22 | if (!prettyOptions.hideProofs || !tc.isProposition(decl.ty)) 23 | env.value(name).foreach(_.constants.foreach(printDecl)) 24 | } 25 | 26 | var doc = pp.pp(decl, env) 27 | 28 | val reds = env.reductions.get(name) 29 | if (printReductions && reds.nonEmpty) { 30 | doc = doc <> "/-" Doc.stack(reds.map { 31 | case ReductionRule(ctx, lhs, rhs, eqs) => 32 | def mkEq(a: Expr, b: Expr): Expr = Apps(Const("eq", Vector(1)), Sort.Prop, a, b) 33 | val term1 = eqs.map((mkEq _).tupled).foldRight(mkEq(lhs, rhs))(_ -->: _) 34 | val term2 = ctx.foldRight(term1)(Lam(_, _)) 35 | pp.parseBinders(term2) { (ctx_, t) => 36 | Doc.text("reduction") <+> pp.nest(Doc.wordwrap(pp.telescope(ctx_) :+ Doc.text(":"))) 37 | pp.pp(t).parens(0).group <> Doc.line 38 | } 39 | }) <> "-/" <> Doc.line 40 | } 41 | 42 | out((doc <> Doc.line).render(lineWidth)) 43 | }) 44 | 45 | private val axiomsChecked = mutable.Map[Name, Unit]() 46 | def checkAxioms(name: Name): Unit = axiomsChecked.getOrElseUpdate(name, env(name) match { 47 | case Declaration(_, _, ty, _, _) => 48 | ty.constants.foreach(checkAxioms) 49 | env.value(name).view.flatMap(_.constants).foreach(checkAxioms) 50 | if (env.isAxiom(name)) printDecl(name) 51 | // TODO: inductive, quotient 52 | }) 53 | 54 | def handleArg(name: Name): Unit = { 55 | checkAxioms(name) 56 | printDecl(name) 57 | } 58 | 59 | val preludeHeader: String = 60 | """prelude 61 | |set_option eqn_compiler.lemmas false 62 | |set_option inductive.no_confusion false 63 | |set_option inductive.rec_on false 64 | |set_option inductive.brec_on false 65 | |set_option inductive.cases_on false 66 | |noncomputable theory 67 | | 68 | |""".stripMargin 69 | } 70 | 71 | case class MainOpts( 72 | inputFile: String = "", 73 | 74 | parallel: Boolean = true, 75 | 76 | printAllDecls: Boolean = false, 77 | printDecls: Seq[Name] = Seq(), 78 | printDependencies: Boolean = false, 79 | printReductions: Boolean = false, 80 | validLean: Boolean = false, 81 | 82 | showImplicits: Boolean = false, 83 | useNotation: Boolean = true, 84 | hideProofs: Boolean = true, 85 | hideProofTerms: Boolean = false) { 86 | def prettyOpts = PrettyOptions( 87 | showImplicits = showImplicits, 88 | hideProofs = hideProofs, hideProofTerms = hideProofTerms, 89 | showNotation = useNotation) 90 | } 91 | object MainOpts { 92 | val parser = new scopt.OptionParser[MainOpts]("trepplein") { 93 | head("trepplein", "1.1") 94 | override def showUsageOnError = Some(true) 95 | 96 | opt[Unit]('s', "sequential").action((_, c) => c.copy(parallel = false)) 97 | .text("type-check declarations on one thread only") 98 | 99 | opt[Unit]('a', "print-all-decls").action((_, c) => c.copy(printAllDecls = true)) 100 | .text("print all checked declarations") 101 | opt[String]('p', "print-decl").unbounded().valueName("decl.name") 102 | .action((x, c) => c.copy(printDecls = c.printDecls :+ Name.ofString(x))) 103 | .text("print specified declarations") 104 | opt[Unit]('d', "print-dependencies") 105 | .action((_, c) => c.copy(printDependencies = true)) 106 | .text("print dependencies of specified declarations as well") 107 | opt[Unit]('r', "print-reductions") 108 | .action((_, c) => c.copy(printReductions = true)) 109 | .text("print reduction rules for specified declarations as well") 110 | opt[Unit]("valid-lean") 111 | .action((_, c) => c.copy(validLean = true, printDependencies = true, useNotation = false)) 112 | .text("try to produce output that can be parsed again") 113 | 114 | opt[Boolean]("show-implicits").action((x, c) => c.copy(showImplicits = x)) 115 | .text("show implicit arguments").valueName("yes/no") 116 | opt[Boolean]("use-notation").action((x, c) => c.copy(useNotation = x)) 117 | .text("use notation for infix/prefix/postfix operators").valueName("yes/no") 118 | opt[Boolean]("hide-proofs").action((x, c) => c.copy(hideProofs = x)) 119 | .text("hide proofs of lemmas").valueName("yes/no") 120 | opt[Boolean]("hide-proof-terms").action((x, c) => c.copy(hideProofTerms = x)) 121 | .text("hide all proof terms").valueName("yes/no") 122 | 123 | help("help").text("prints this usage text") 124 | 125 | arg[String]("").required().action((x, c) => 126 | c.copy(inputFile = x)).text("exported file to check") 127 | } 128 | } 129 | 130 | object main { 131 | def main(args: Array[String]): Unit = 132 | MainOpts.parser.parse(args, MainOpts()) match { 133 | case Some(opts) => 134 | val exportedCommands = TextExportParser.parseFile(opts.inputFile) 135 | 136 | val modifications = exportedCommands.collect { case ExportedModification(mod) => mod } 137 | val env0 = Environment.default 138 | val preEnv = 139 | if (opts.parallel) modifications.foldLeft[PreEnvironment](env0)(_.add(_)) 140 | else modifications.foldLeft[PreEnvironment](env0)(_.addNow(_)) 141 | 142 | val notations = Map() ++ exportedCommands. 143 | collect { case ExportedNotation(not) => not.fn -> not }. 144 | reverse // the beautiful unicode notation is exported first 145 | 146 | val printer = new LibraryPrinter(preEnv, notations, print, opts.prettyOpts, 147 | printReductions = opts.printReductions, 148 | printDependencies = opts.printDependencies || opts.printAllDecls) 149 | val declsToPrint = if (opts.printAllDecls) preEnv.declarations.keys else opts.printDecls 150 | if (opts.validLean) print(printer.preludeHeader) 151 | declsToPrint.foreach(printer.handleArg) 152 | 153 | Await.result(preEnv.force, Duration.Inf) match { 154 | case Left(exs) => 155 | for (ex <- exs) println(ex) 156 | sys.exit(1) 157 | case Right(env) => 158 | println(s"-- successfully checked ${env.declarations.size} declarations") 159 | } 160 | case _ => sys.exit(1) 161 | } 162 | } -------------------------------------------------------------------------------- /src/main/scala/trepplein/parser.scala: -------------------------------------------------------------------------------- 1 | package trepplein 2 | 3 | import java.io.{ FileInputStream, InputStream } 4 | import java.nio.charset.Charset 5 | 6 | import scala.annotation.tailrec 7 | import scala.collection.mutable 8 | 9 | sealed trait ExportFileCommand 10 | case class ExportedModification(modification: Modification) extends ExportFileCommand 11 | case class ExportedNotation(notation: Notation) extends ExportFileCommand 12 | 13 | private class TextExportParser { 14 | val name: mutable.ArrayBuffer[Name] = mutable.ArrayBuffer[Name]() 15 | val level: mutable.ArrayBuffer[Level] = mutable.ArrayBuffer[Level]() 16 | val expr: mutable.ArrayBuffer[Expr] = mutable.ArrayBuffer[Expr]() 17 | 18 | name += Name.Anon 19 | level += Level.Zero 20 | 21 | @tailrec final def write[T](b: mutable.ArrayBuffer[T], i: Int, t: T, default: T): Unit = 22 | b.size match { 23 | case `i` => b += t 24 | case s if s < i => 25 | b += default 26 | write(b, i, t, default) 27 | case s if s > i => 28 | b(i) = t 29 | } 30 | } 31 | 32 | private final class LinesParser(textExportParser: TextExportParser, bytes: Array[Byte], end: Int) { 33 | import textExportParser._ 34 | 35 | var index = 0 36 | def hasNext(): Boolean = index < end 37 | def cur(): Char = bytes(index).toChar 38 | def next(): Char = { 39 | if (!hasNext()) throw new IndexOutOfBoundsException 40 | val c = cur() 41 | index += 1 42 | c 43 | } 44 | 45 | def consume(c: Char): Unit = if (next() != c) throw new IllegalArgumentException(s"expected $c, got ${cur()}") 46 | def consume(s: String): Unit = s.foreach(consume) 47 | 48 | def lines(): Vector[ExportFileCommand] = { 49 | val out = Vector.newBuilder[ExportFileCommand] 50 | while (hasNext()) { line().foreach(out += _); consume('\n') } 51 | out.result() 52 | } 53 | 54 | def line(): Option[ExportFileCommand] = 55 | next() match { 56 | case c if '0' <= c && c <= '9' => 57 | val n = long(c - '0').toInt 58 | consume(' '); consume('#') 59 | next() match { 60 | case 'N' => write(name, n, nameDef(), Name.Anon) 61 | case 'U' => write(level, n, levelDef(), Level.Zero) 62 | case 'E' => write(expr, n, exprDef(), Sort.Prop) 63 | } 64 | None 65 | case '#' => 66 | next() match { 67 | case 'A' => 68 | consume('X') 69 | val n = spc(nameRef()) 70 | val t = spc(exprRef()) 71 | val ups = univParams() 72 | Some(ExportedModification(AxiomMod(n, ups, t))) 73 | case 'D' => 74 | consume('E'); consume('F') 75 | val n = spc(nameRef()) 76 | val t = spc(exprRef()) 77 | val v = spc(exprRef()) 78 | val ups = univParams() 79 | Some(ExportedModification(DefMod(n, ups, t, v))) 80 | case 'Q' => 81 | consume("UOT") 82 | Some(ExportedModification(QuotMod)) 83 | case 'I' => 84 | consume('N') 85 | next() match { 86 | case 'F' => 87 | consume("IX ") 88 | Some(ExportedNotation(Infix(nameRef(), spc(num()), spc(rest())))) 89 | case 'D' => 90 | val numParams = spc(num()) 91 | val n = spc(nameRef()) 92 | val t = spc(exprRef()) 93 | val numIntros = spc(num()) 94 | val rest = restOf(num()) 95 | val (intros, ps) = rest.splitAt(2 * numIntros) 96 | Some(ExportedModification(IndMod( 97 | n, ps.view.map(name).map(Level.Param).toVector, t, 98 | numParams, intros.grouped(2).map { case Seq(in, it) => (name(in), expr(it)) }.toVector))) 99 | } 100 | case 'P' => 101 | next() match { 102 | case 'R' => 103 | consume("EFIX ") 104 | Some(ExportedNotation(Prefix(nameRef(), spc(num()), spc(rest())))) 105 | case 'O' => 106 | consume("STFIX ") 107 | Some(ExportedNotation(Postfix(nameRef(), spc(num()), spc(rest())))) 108 | } 109 | } 110 | } 111 | 112 | def num(): Int = long().toInt 113 | def long(): Long = 114 | next() match { case c if '0' <= c && c <= '9' => long(c - '0') } 115 | def long(acc: Long): Long = 116 | cur() match { 117 | case c if '0' <= c && c <= '9' => 118 | next() 119 | long(10 * acc + (c - '0')) 120 | case _ => acc 121 | } 122 | 123 | def rest(): String = { 124 | val start = index 125 | def nextNL(): Int = if (cur() == '\n') index else { next(); nextNL() } 126 | new String(bytes, start, math.max(nextNL() - start, 0), LinesParser.UTF8) 127 | } 128 | 129 | def nameRef(): Name = name(num()) 130 | def nameDef(): Name = 131 | next() match { 132 | case 'S' => Name.Str(spc(nameRef()), spc(rest())) 133 | case 'I' => Name.Num(spc(nameRef()), spc(long())) 134 | } 135 | 136 | def levelRef(): Level = level(num()) 137 | def levelDef(): Level = 138 | next() match { 139 | case 'S' => Level.Succ(spc(levelRef())) 140 | case 'M' => Level.Max(spc(levelRef()), spc(levelRef())) 141 | case 'I' => Level.IMax(c('M', spc(levelRef())), spc(levelRef())) 142 | case 'P' => Level.Param(spc(nameRef())) 143 | } 144 | 145 | @inline def restOf[T](p: => T): Vector[T] = { 146 | val out = Vector.newBuilder[T] 147 | while (cur() == ' ') { next(); out += p } 148 | out.result() 149 | } 150 | 151 | def binderInfo(): BinderInfo = { 152 | consume('#'); consume('B') 153 | next() match { 154 | case 'D' => BinderInfo.Default 155 | case 'I' => BinderInfo.Implicit 156 | case 'C' => BinderInfo.InstImplicit 157 | case 'S' => BinderInfo.StrictImplicit 158 | } 159 | } 160 | 161 | @inline def c[T](c: Char, p: => T): T = { consume(c); p } 162 | @inline def spc[T](p: => T): T = c(' ', p) 163 | 164 | def exprRef(): Expr = expr(num()) 165 | def exprDef(): Expr = 166 | next() match { 167 | case 'V' => 168 | Var(spc(num())) 169 | case 'S' => 170 | Sort(spc(levelRef())) 171 | case 'C' => 172 | Const(spc(nameRef()), restOf(levelRef())) 173 | case 'A' => 174 | App(spc(exprRef()), spc(exprRef())) 175 | case 'L' => 176 | val b = spc(binderInfo()) 177 | val n = spc(nameRef()) 178 | val d = spc(exprRef()) 179 | val e = spc(exprRef()) 180 | Lam(Binding(n, d, b), e) 181 | case 'P' => 182 | val b = spc(binderInfo()) 183 | val n = spc(nameRef()) 184 | val d = spc(exprRef()) 185 | val e = spc(exprRef()) 186 | Pi(Binding(n, d, b), e) 187 | case 'Z' => 188 | val n = spc(nameRef()) 189 | val t = spc(exprRef()) 190 | val v = spc(exprRef()) 191 | val e = spc(exprRef()) 192 | Let(Binding(n, t, BinderInfo.Default), v, e) 193 | } 194 | 195 | def univParams(): Vector[Level.Param] = 196 | restOf(Level.Param(nameRef())) 197 | } 198 | object LinesParser { 199 | val UTF8: Charset = Charset.forName("UTF-8") 200 | } 201 | 202 | object TextExportParser { 203 | @tailrec private def reverseIndexOf(chunk: Array[Byte], needle: Byte, from: Int): Int = 204 | if (chunk(from) == needle) from 205 | else if (from == 0) -1 206 | else reverseIndexOf(chunk, needle, from - 1) 207 | 208 | def parseStream(in: InputStream): LazyList[ExportFileCommand] = { 209 | def bufSize = 8 << 10 210 | case class Chunk(bytes: Array[Byte], endIndex: Int) 211 | def readChunksCore(buf: Array[Byte], begin: Int): LazyList[Chunk] = { 212 | val len = in.read(buf, begin, buf.length - begin) 213 | if (len <= 0) LazyList.empty else { 214 | val nl = reverseIndexOf(buf, '\n', begin + len - 1) 215 | if (nl == -1) { 216 | // no newline found in the whole chunk, 217 | // this should only happen at the end but let's try again to make sure 218 | Chunk(buf, len) #:: readChunks() 219 | } else { 220 | val nextBuf = new Array[Byte](bufSize) 221 | val reuse = (begin + len) - (nl + 1) 222 | System.arraycopy(buf, nl + 1, nextBuf, 0, reuse) 223 | Chunk(buf, nl + 1) #:: readChunksCore(nextBuf, reuse) 224 | } 225 | } 226 | } 227 | def readChunks(): LazyList[Chunk] = readChunksCore(new Array[Byte](bufSize), 0) 228 | val parser = new TextExportParser 229 | readChunks().flatMap(chunk => new LinesParser(parser, chunk.bytes, chunk.endIndex).lines()) 230 | } 231 | 232 | def parseFile(fn: String): LazyList[ExportFileCommand] = parseStream(new FileInputStream(fn)) 233 | } 234 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | Apache License 2.0 (Apache) 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. 13 | 14 | "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. 15 | 16 | "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. 17 | 18 | "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. 19 | 20 | "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. 21 | 22 | "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). 23 | 24 | "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. 25 | 26 | "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." 27 | 28 | "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 29 | 30 | 2. Grant of Copyright License. 31 | 32 | Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 33 | 34 | 3. Grant of Patent License. 35 | 36 | Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 37 | 38 | 4. Redistribution. 39 | 40 | You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: 41 | 42 | 1. You must give any other recipients of the Work or Derivative Works a copy of this License; and 43 | 44 | 2. You must cause any modified files to carry prominent notices stating that You changed the files; and 45 | 46 | 3. You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and 47 | 48 | 4. If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. 49 | 50 | You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 51 | 52 | 5. Submission of Contributions. 53 | 54 | Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 55 | 56 | 6. Trademarks. 57 | 58 | This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 59 | 60 | 7. Disclaimer of Warranty. 61 | 62 | Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 63 | 64 | 8. Limitation of Liability. 65 | 66 | In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 67 | 68 | 9. Accepting Warranty or Additional Liability. 69 | 70 | While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. 71 | 72 | -------------------------------------------------------------------------------- /src/main/scala/trepplein/pretty.scala: -------------------------------------------------------------------------------- 1 | package trepplein 2 | 3 | import trepplein.BinderInfo._ 4 | import trepplein.Level._ 5 | import Doc._ 6 | 7 | import scala.collection.mutable 8 | 9 | case class PrettyOptions( 10 | showImplicits: Boolean = true, 11 | hideProofs: Boolean = false, 12 | hideProofTerms: Boolean = false, 13 | showNotation: Boolean = true, 14 | nestDepth: Int = 2) 15 | 16 | sealed trait Notation { 17 | def fn: Name 18 | def prio: Int 19 | def op: String 20 | } 21 | case class Infix(fn: Name, prio: Int, op: String) extends Notation 22 | case class Prefix(fn: Name, prio: Int, op: String) extends Notation 23 | case class Postfix(fn: Name, prio: Int, op: String) extends Notation 24 | 25 | class PrettyPrinter( 26 | typeChecker: Option[TypeChecker] = None, 27 | notations: Map[Name, Notation] = Map(), 28 | options: PrettyOptions = PrettyOptions()) { 29 | import options._ 30 | 31 | val usedLCs: mutable.Set[Name] = mutable.Set[Name]() 32 | 33 | val MaxPrio = 1024 34 | def nest(doc: Doc): Doc = doc.group.nest(nestDepth) 35 | case class Parenable(prio: Int, doc: Doc) { 36 | def parens(newPrio: Int): Doc = 37 | if (newPrio > prio) "(" <> doc <> ")" else doc 38 | } 39 | 40 | def showImplicits: Boolean = options.showImplicits && typeChecker.nonEmpty 41 | 42 | def pp(n: Name): Doc = n.toString 43 | 44 | def pp(level: Level): Parenable = 45 | level match { 46 | case Offset(n, Zero) => Parenable(MaxPrio, n.toString) 47 | case Offset(n, l) if n > 0 => 48 | Parenable(0, pp(l).parens(1) <> "+" <> n.toString) 49 | case Max(a, b) => Parenable(0, "max" <+> pp(a).parens(1) pp(b).parens(1)) 50 | case IMax(a, b) => Parenable(0, "imax" <+> pp(a).parens(1) pp(b).parens(1)) 51 | case Param(param) => Parenable(MaxPrio, pp(param)) 52 | } 53 | 54 | def mkFreshName(suggestion: Name): Name = { 55 | def alreadyUsed(n: Name): Boolean = 56 | usedLCs(n) || typeChecker.exists(_.env.declarations.contains(n)) 57 | 58 | val sanitizedSuggestion = suggestion.toString. 59 | filter(c => c.isLetterOrDigit || c == '_'). 60 | dropWhile(c => c.isDigit || c == '_') match { 61 | case "" => "a" 62 | case s => s 63 | } 64 | 65 | def findUnused(base: String, idx: Int): Name = { 66 | val n = Name(base + '_' + idx) 67 | if (alreadyUsed(n)) findUnused(base, idx + 1) else n 68 | } 69 | 70 | val fresh: Name = if (alreadyUsed(sanitizedSuggestion)) findUnused(sanitizedSuggestion, 0) 71 | else sanitizedSuggestion 72 | 73 | usedLCs += fresh 74 | fresh 75 | } 76 | 77 | def withFreshLC[T](suggestion: LocalConst)(f: LocalConst => T): T = { 78 | val fresh = mkFreshName(suggestion.of.prettyName) 79 | try f(suggestion.copy(of = suggestion.of.copy(prettyName = fresh))) 80 | finally usedLCs -= fresh 81 | } 82 | 83 | def ppBareBinder(binding: Binding): Doc = 84 | pp(binding.prettyName) <+> ":" pp(binding.ty).parens(1).group 85 | 86 | def isImplicit(fn: Expr): Boolean = 87 | typeChecker match { 88 | case Some(tc) => 89 | try { 90 | tc.whnf(tc.infer(fn)) match { 91 | case Pi(dom, _) => 92 | dom.info != BinderInfo.Default 93 | case _ => false 94 | } 95 | } catch { case _: Throwable => false } 96 | case _ => false 97 | } 98 | 99 | def pp(us: Iterable[Level]): Doc = 100 | ("{" <> wordwrap(us.map(pp).map(_.parens(0))) <> "}").group 101 | 102 | case class ParsedBinder(isPi: Boolean, occursInBody: Boolean, isAnon: Boolean, lc: LocalConst) { 103 | def isImp: Boolean = isPi && info == Default && isAnon && !occursInBody 104 | def isForall: Boolean = isPi && !isImp 105 | def isLambda: Boolean = !isPi 106 | def info: BinderInfo = lc.of.info 107 | def ty: Expr = lc.of.ty 108 | def name: Name = lc.of.prettyName 109 | } 110 | type ParsedBinders = List[ParsedBinder] 111 | def parseBinders[T](e: Expr)(f: (ParsedBinders, Expr) => T): T = { 112 | def decompose(e: Expr, ctx: ParsedBinders): T = 113 | e match { 114 | case Pi(dom, body) => 115 | val lcName = mkFreshName(dom.prettyName) 116 | val lc = LocalConst(dom.copy(prettyName = lcName, ty = dom.ty.instantiate(0, ctx.view.map(_.lc).toVector))) 117 | decompose(body, ParsedBinder(isPi = true, isAnon = dom.prettyName.isAnon, occursInBody = body.hasVar(0), lc = lc) :: ctx) 118 | case Lam(dom, body) => 119 | val lcName = mkFreshName(dom.prettyName) 120 | val lc = LocalConst(dom.copy(prettyName = lcName, ty = dom.ty.instantiate(0, ctx.view.map(_.lc).toVector))) 121 | decompose(body, ParsedBinder(isPi = false, isAnon = dom.prettyName.isAnon, occursInBody = body.hasVar(0), lc = lc) :: ctx) 122 | case _ => 123 | try f(ctx.reverse, e.instantiate(0, ctx.view.map(_.lc).toVector)) 124 | finally usedLCs --= ctx.view.map(_.lc.of.prettyName) 125 | } 126 | decompose(e, Nil) 127 | } 128 | 129 | private def splitListWhile[T](xs: List[T])(pred: T => Boolean): (List[T], List[T]) = 130 | xs match { 131 | case x :: rest if pred(x) => 132 | val (part1, part2) = splitListWhile(rest)(pred) 133 | (x :: part1, part2) 134 | case _ => 135 | (Nil, xs) 136 | } 137 | 138 | def telescope(binders: List[ParsedBinder]): List[Doc] = 139 | binders match { 140 | case Nil => Nil 141 | case b0 :: _ => 142 | val (group, rest) = if (b0.info == InstImplicit) (List(b0), binders.tail) 143 | else splitListWhile(binders)(b => b.info == b0.info && b.ty == b0.ty) 144 | val bare = wordwrap(group.map(b => if (b.isAnon && !b.occursInBody) text("_") else pp(b.name))) <+> 145 | ":" pp(b0.ty).parens(1).group 146 | nest(b0.info match { 147 | // case Default if group.size == 1 => bare 148 | case Default => "(" <> bare <> ")" 149 | case Implicit => "{" <> bare <> "}" 150 | case StrictImplicit => "{{" <> bare <> "}}" 151 | case InstImplicit => "[" <> bare <> "]" 152 | }) :: telescope(rest) 153 | } 154 | 155 | def pp(binders: ParsedBinders, inner: Parenable): Parenable = 156 | binders match { 157 | case Nil => inner 158 | case b :: rest if b.isImp => 159 | Parenable(24, (nest(pp(b.ty).parens(25)) <+> "→" <> line).group <> pp(rest, inner).parens(24)) 160 | case b :: _ if b.isForall => 161 | val (group, rest) = splitListWhile(binders)(_.isForall) 162 | Parenable(0, nest("∀" <+> wordwrap(telescope(group)) <> ",") pp(rest, inner).parens(0)) 163 | case b :: _ if b.isLambda => 164 | val (group, rest) = splitListWhile(binders)(_.isLambda) 165 | Parenable(0, nest("λ" <+> wordwrap(telescope(group)) <> ",") pp(rest, inner).parens(0)) 166 | } 167 | 168 | private def constName(n: Name): Parenable = 169 | Parenable(MaxPrio, if (!showImplicits) pp(n) else "@" <> pp(n)) 170 | 171 | def pp(e: Expr): Parenable = 172 | e match { 173 | case _ if hideProofTerms && typeChecker.exists(_.isProof(e)) => Parenable(MaxPrio, "_") 174 | case Var(idx) => Parenable(MaxPrio, s"#$idx") 175 | case Sort(level) if level.isZero && options.showNotation => Parenable(MaxPrio, "Prop") 176 | case Sort(Succ(level)) => Parenable(MaxPrio, "Type" <+> pp(level).parens(MaxPrio)) 177 | case Sort(level) => Parenable(MaxPrio, "Sort" <+> pp(level).parens(MaxPrio)) 178 | case Const(name, _) if typeChecker.exists(_.env.get(name).nonEmpty) => 179 | constName(name) 180 | case Const(name, levels) => 181 | val univParams: Doc = if (levels.isEmpty) "" else "." <> pp(levels) 182 | Parenable(MaxPrio, "@" <> pp(name) <> univParams) 183 | case LocalConst(of, _) => constName(of.prettyName) 184 | case Lam(_, _) | Pi(_, _) => 185 | parseBinders(e) { (binders, inner) => pp(binders, pp(inner)) } 186 | case Let(domain, value, body) => 187 | withFreshLC(LocalConst(domain)) { lc => 188 | Parenable(0, (nest("let" <+> ppBareBinder(lc.of).group <+> ":=" pp(value).parens(0).group <+> "in") 189 | pp(body.instantiate(lc)).parens(0)).group) 190 | } 191 | case App(_, _) => 192 | def go(e: Expr, as: List[Expr]): (Expr, List[Expr]) = 193 | e match { 194 | case App(hd, _) if !showImplicits && isImplicit(hd) => go(hd, as) 195 | case App(hd, a) => go(hd, a :: as) 196 | case hd => (hd, as) 197 | } 198 | def printDefault(fn: Expr, as: List[Expr]) = 199 | Parenable(MaxPrio - 1, nest(wordwrap(pp(fn).parens(MaxPrio - 1).group :: as.map(pp(_).parens(MaxPrio).group)))) 200 | go(e, Nil) match { 201 | case (fn, Nil) => pp(fn) 202 | case (fn @ Const(n, _), as) if showNotation => 203 | notations.get(n) match { 204 | case Some(Prefix(_, prio, op)) if as.size == 1 => 205 | Parenable(prio - 1, (op <> zeroWidthLine).group <> pp(as(0)).parens(prio)) 206 | case Some(Postfix(_, prio, op)) if as.size == 1 => 207 | Parenable(prio - 1, (pp(as(0)).parens(prio) <> zeroWidthLine <> op).group) 208 | case Some(Infix(_, prio, op)) if as.size == 2 => 209 | Parenable(prio - 1, nest(pp(as(0)).parens(prio) <> op <> zeroWidthLine <> pp(as(1)).parens(prio))) 210 | case _ => 211 | printDefault(fn, as) 212 | } 213 | case (fn, as) => printDefault(fn, as) 214 | } 215 | } 216 | 217 | def parseParams[T](ty: Expr, value: Expr)(f: (List[ParsedBinder], List[ParsedBinder], Expr, Expr) => T): T = 218 | parseBinders(ty) { (binders, ty) => 219 | def go(binders: List[ParsedBinder], value: Expr, reverseParams: List[ParsedBinder]): T = 220 | (binders, value) match { 221 | case (b :: bs, Lam(_, value_)) if b.isForall => go(bs, value_, b :: reverseParams) 222 | case _ => f(reverseParams.reverse, binders, ty, value.instantiate(0, reverseParams.view.map(_.lc).toVector)) 223 | } 224 | go(binders, value, Nil) 225 | } 226 | 227 | def pp(decl: Declaration, env: PreEnvironment): Doc = { 228 | val Declaration(name, univParams, ty, _, builtin) = decl 229 | env.value(name) match { 230 | case Some(value) => 231 | val ups: Doc = if (univParams.isEmpty) "" else " " <> pp(univParams) 232 | val isProp = typeChecker.exists(_.isProposition(ty)) 233 | parseParams(ty, value) { (params, tyBinders, ty, value) => 234 | val cmd = if (isProp) "lemma" else "def" 235 | val ppVal: Doc = if (isProp && hideProofs) "_" else pp(value).parens(0).group 236 | cmd <> ups <+> nest(nest(wordwrap(pp(name) :: telescope(params))) <+> 237 | ":" pp(tyBinders, pp(ty)).parens(0).group <+> ":=") ppVal <> line 238 | } 239 | case None => 240 | val ups: Doc = if (univParams.isEmpty) "" else " " <> pp(univParams) 241 | val doc = parseBinders(ty) { (binders, ty) => 242 | val (params, rest) = splitListWhile(binders)(_.isForall) 243 | "axiom" <> ups <+> nest(nest(wordwrap(pp(name) :: telescope(params))) <+> 244 | ":" pp(rest, pp(ty)).parens(0).group) <> line 245 | } 246 | if (builtin) "/- builtin -/" <+> doc else doc 247 | } 248 | } 249 | } 250 | 251 | object pretty { 252 | def apply(e: Expr): String = new PrettyPrinter().pp(e).doc.group.render(lineWidth = 80) 253 | } -------------------------------------------------------------------------------- /src/main/scala/trepplein/typechecker.scala: -------------------------------------------------------------------------------- 1 | package trepplein 2 | 3 | import scala.annotation.tailrec 4 | import scala.collection.mutable 5 | 6 | sealed trait DefEqRes { 7 | @inline final def &(that: => DefEqRes): DefEqRes = if (this != IsDefEq) this else that 8 | } 9 | case object IsDefEq extends DefEqRes { 10 | def forall(rs: Iterable[DefEqRes]): DefEqRes = 11 | rs.collectFirst { case r: NotDefEq => r }.getOrElse(IsDefEq) 12 | } 13 | final case class NotDefEq(a: Expr, b: Expr) extends DefEqRes 14 | 15 | class TypeChecker(val env: PreEnvironment, val unsafeUnchecked: Boolean = false) { 16 | def shouldCheck: Boolean = !unsafeUnchecked 17 | 18 | object NormalizedPis { 19 | def unapply(e: Expr): Some[(List[LocalConst], Expr)] = 20 | whnf(e) match { 21 | case Pis(lcs1, f) if lcs1.nonEmpty => 22 | val NormalizedPis(lcs2, g) = f 23 | Some((lcs1 ::: lcs2, g)) 24 | case f => Some((Nil, f)) 25 | } 26 | 27 | @tailrec def instantiate(e: Expr, ts: List[Expr], ctx: List[Expr] = Nil): Expr = 28 | (e, ts) match { 29 | case (Pi(_, body), t :: ts_) => 30 | instantiate(body, ts_, t :: ctx) 31 | case (_, _ :: _) => 32 | instantiate(whnf(e).ensuring(_.isInstanceOf[Pi]), ts, ctx) 33 | case (_, Nil) => e.instantiate(0, ctx.toVector) 34 | } 35 | } 36 | 37 | private val levelDefEqCache = mutable.AnyRefMap[(Level, Level), Boolean]() 38 | def isDefEq(a: Level, b: Level): Boolean = 39 | levelDefEqCache.getOrElseUpdate((a, b), a === b) 40 | 41 | def isProp(s: Expr): Boolean = whnf(s) match { 42 | case Sort(l) => l.isZero 43 | case _ => false 44 | } 45 | def isProposition(ty: Expr): Boolean = isProp(infer(ty)) 46 | def isProof(p: Expr): Boolean = isProposition(infer(p)) 47 | 48 | private def isProofIrrelevantEq(e1: Expr, e2: Expr): Boolean = 49 | isProof(e1) && isProof(e2) 50 | 51 | private def reqDefEq(cond: Boolean, e1: Expr, e2: Expr) = 52 | if (cond) IsDefEq else NotDefEq(e1, e2) 53 | 54 | def isDefEq(e1: Expr, e2: Expr): Boolean = checkDefEq(e1, e2) == IsDefEq 55 | 56 | def defHeight(fn: Expr, as: List[Expr]): Int = 57 | fn match { 58 | case Const(n, _) => env.get(n).map(_.height + 1).getOrElse(0) 59 | case _ => 0 60 | } 61 | 62 | private def reduceOneStep(e1: Expr, e2: Expr)(implicit transparency: Transparency): Option[(Expr, Expr)] = { 63 | val Apps(fn1, as1) = e1 64 | val Apps(fn2, as2) = e2 65 | 66 | @inline def red1 = reduceOneStep(fn1, as1).map(_ -> e2) 67 | @inline def red2 = reduceOneStep(fn2, as2).map(e1 -> _) 68 | 69 | if (defHeight(fn1, as1) > defHeight(fn2, as2)) 70 | red1 orElse red2 71 | else 72 | red2 orElse red1 73 | } 74 | 75 | private val lcCache = mutable.AnyRefMap[Expr, List[LocalConst]]().withDefaultValue(Nil) 76 | private def popCachedLC(binding: Binding): LocalConst = 77 | lcCache(binding.ty) match { 78 | case cached :: rest => 79 | lcCache(binding.ty) = rest 80 | cached 81 | case Nil => LocalConst(binding) 82 | } 83 | @inline private def withLC[T](binding: Binding)(f: LocalConst => T): T = { 84 | val lc = popCachedLC(binding) 85 | val result = f(lc) 86 | lcCache(binding.ty) ::= lc 87 | result 88 | } 89 | 90 | private def checkDefEqCore(e1_0: Expr, e2_0: Expr): DefEqRes = { 91 | val transparency = Transparency(rho = false) 92 | val e1 @ Apps(fn1, as1) = whnfCore(e1_0)(transparency) 93 | val e2 @ Apps(fn2, as2) = whnfCore(e2_0)(transparency) 94 | def checkArgs: DefEqRes = 95 | reqDefEq(as1.size == as2.size, e1, e2) & 96 | IsDefEq.forall(as1.lazyZip(as2).view.map { case (a, b) => checkDefEq(a, b) }) 97 | ((fn1, fn2) match { 98 | case (Sort(l1), Sort(l2)) => 99 | return reqDefEq(isDefEq(l1, l2) && as1.isEmpty && as2.isEmpty, e1, e2) 100 | case (Const(c1, ls1), Const(c2, ls2)) if c1 == c2 && ls1.lazyZip(ls2).forall(isDefEq) => 101 | checkArgs 102 | case (LocalConst(_, i1), LocalConst(_, i2)) if i1 == i2 => 103 | checkArgs 104 | case (Lam(dom, b1), Lam(_, b2)) => 105 | require(as1.isEmpty && as2.isEmpty) 106 | return withLC(dom)(lc => checkDefEqCore(b1.instantiate(lc), b2.instantiate(lc))) 107 | case (Lam(dom1, _), _) => 108 | require(as1.isEmpty) 109 | return checkDefEqCore(e1, Lam(dom1, App(e2, Var(0)))) 110 | case (_, Lam(dom2, _)) => 111 | require(as2.isEmpty) 112 | return checkDefEqCore(Lam(dom2, App(e1, Var(0))), e2) 113 | case (Pi(dom1, b1), Pi(dom2, b2)) => 114 | require(as1.isEmpty && as2.isEmpty) 115 | return checkDefEq(dom1.ty, dom2.ty) & withLC(dom1)(lc => checkDefEqCore(b1.instantiate(lc), b2.instantiate(lc))) 116 | case (_, _) => 117 | NotDefEq(e1, e2) 118 | }) match { 119 | case IsDefEq => IsDefEq 120 | case d @ NotDefEq(_, _) => 121 | reduceOneStep(e1, e2)(Transparency.all) match { 122 | case Some((e1_, e2_)) => 123 | checkDefEqCore(e1_, e2_) 124 | case None => d 125 | } 126 | } 127 | } 128 | 129 | private val defEqCache = mutable.AnyRefMap[(Expr, Expr), DefEqRes]() 130 | // requires that e1 and e2 have the same type, or are types 131 | def checkDefEq(e1: Expr, e2: Expr): DefEqRes = 132 | if (e1.eq(e2) || e1 == e2) IsDefEq else defEqCache.getOrElseUpdate((e1, e2), { 133 | if (isProofIrrelevantEq(e1, e2)) IsDefEq else checkDefEqCore(e1, e2) 134 | }) 135 | 136 | case class Transparency(rho: Boolean) { 137 | def canReduceConstants: Boolean = rho 138 | } 139 | object Transparency { 140 | val all = Transparency(rho = true) 141 | } 142 | 143 | def reduceOneStep(e: Expr)(implicit transparency: Transparency): Option[Expr] = 144 | e match { case Apps(fn, as) => reduceOneStep(fn, as) } 145 | private implicit object reductionRuleCache extends ReductionRuleCache { 146 | private val instantiationCache = mutable.AnyRefMap[(ReductionRule, Map[Level.Param, Level]), Expr]() 147 | override def instantiation(rr: ReductionRule, subst: Map[Level.Param, Level], v: => Expr): Expr = 148 | instantiationCache.getOrElseUpdate((rr, subst), v) 149 | } 150 | def reduceOneStep(fn: Expr, as0: List[Expr])(implicit transparency: Transparency): Option[Expr] = 151 | fn match { 152 | case Const(n, _) if transparency.rho => 153 | val major = env.reductions.major(n) 154 | val as = for ((a, i) <- as0.zipWithIndex) 155 | yield if (major(i)) whnf(a) else a 156 | env.reductions(Apps(fn, as)) match { 157 | case Some((result, constraints)) if constraints.forall { case (a, b) => isDefEq(a, b) } => 158 | Some(result) 159 | case _ => None 160 | } 161 | case _ => None 162 | } 163 | 164 | private val whnfCache = mutable.AnyRefMap[Expr, Expr]() 165 | def whnf(e: Expr): Expr = whnfCache.getOrElseUpdate(e, whnfCore(e)(Transparency.all)) 166 | @tailrec final def whnfCore(e: Expr)(implicit transparency: Transparency = Transparency.all): Expr = { 167 | val Apps(fn, as) = e 168 | fn match { 169 | case Sort(l) => Sort(l.simplify) 170 | case Lam(_, _) if as.nonEmpty => 171 | @tailrec def go(fn: Expr, ctx: List[Expr], as: List[Expr]): Expr = 172 | (fn, as) match { 173 | case (Lam(_, fn_), a :: as_) => go(fn_, a :: ctx, as_) 174 | case _ => Apps(fn.instantiate(0, ctx.toVector), as) 175 | } 176 | whnfCore(go(fn, Nil, as)) 177 | case Let(_, value, body) => 178 | whnfCore(Apps(body.instantiate(value), as)) 179 | case _ => 180 | reduceOneStep(fn, as) match { 181 | case Some(e_) => whnfCore(e_) 182 | case None => e 183 | } 184 | } 185 | } 186 | 187 | def stuck(e: Expr): Option[Expr] = whnf(e) match { 188 | case Apps(Const(n, _), as) if env.reductions.get(n).nonEmpty => 189 | val numAs = as.size 190 | env.reductions.major(n).filter(_ < numAs).map(as(_)).flatMap(stuck).headOption 191 | case e_ => Some(e_) 192 | } 193 | 194 | def ppError(e: Expr): Doc = 195 | new PrettyPrinter(Some(this), options = PrettyOptions(showImplicits = false)).pp(e).doc 196 | 197 | def checkType(e: Expr, ty: Expr): Unit = { 198 | val inferredTy = infer(e) 199 | checkDefEq(ty, inferredTy) match { 200 | case IsDefEq => 201 | case NotDefEq(t_, i_) => 202 | throw new IllegalArgumentException(Doc.stack( 203 | Doc.spread("wrong type: ", ppError(e), " : ", ppError(ty)), 204 | Doc.spread("inferred type: ", ppError(inferredTy)), 205 | Doc.spread(ppError(t_), " !=def ", ppError(i_)), 206 | Doc.spread(Seq[Doc]("stuck on: ") ++ Seq(t_, i_).flatMap(stuck).map(ppError))) 207 | .render(80)) 208 | } 209 | } 210 | def requireDefEq(a: Expr, b: Expr): Unit = 211 | checkDefEq(a, b) match { 212 | case IsDefEq => 213 | case NotDefEq(a_, b_) => 214 | throw new IllegalArgumentException(Doc.stack("", ppError(a_), "!=def", ppError(b_)).render(80)) 215 | } 216 | 217 | def inferUniverseOfType(ty: Expr): Level = 218 | whnf(infer(ty)) match { 219 | case Sort(l) => l 220 | case s => throw new IllegalArgumentException(Doc.spread("not a sort: ", ppError(s)).render(80)) 221 | } 222 | 223 | private val inferCache = mutable.AnyRefMap[Expr, Expr]() 224 | def infer(e: Expr): Expr = inferCache.getOrElseUpdate(e, e match { 225 | case Var(_) => 226 | throw new IllegalArgumentException 227 | case Sort(level) => 228 | Sort(Level.Succ(level)) 229 | case Const(name, levels) => 230 | val decl = env(name) 231 | require( 232 | decl.univParams.size == levels.size, 233 | s"incorrect number of universe parameters: $e, expected ${decl.univParams}") 234 | decl.ty.instantiate(decl.univParams.zip(levels).toMap) 235 | case LocalConst(of, _) => 236 | of.ty 237 | case Apps(fn, as) if as.nonEmpty => 238 | @tailrec def go(fnt: Expr, as: List[Expr], ctx: List[Expr]): Expr = 239 | (fnt, as) match { 240 | case (_, Nil) => fnt.instantiate(0, ctx.toVector) 241 | case (Pi(dom, body), a :: as_) => 242 | if (shouldCheck) checkType(a, dom.ty.instantiate(0, ctx.toVector)) 243 | go(body, as_, a :: ctx) 244 | case (_, _ :: _) => 245 | whnf(fnt.instantiate(0, ctx.toVector)) match { 246 | case fnt_ @ Pi(_, _) => go(fnt_, as, Nil) 247 | case _ => 248 | throw new IllegalArgumentException(s"not a function type: $fnt") 249 | } 250 | } 251 | go(infer(fn), as, Nil) 252 | case Lam(_, _) => 253 | def go(e: Expr, ctx: List[LocalConst]): Expr = e match { 254 | case Lam(dom, body) => 255 | val dom_ = dom.copy(ty = dom.ty.instantiate(0, ctx.toVector)) 256 | if (shouldCheck) inferUniverseOfType(dom_.ty) 257 | Pi(dom, withLC(dom_)(lc => go(body, lc :: ctx))) 258 | case _ => 259 | val ctxVec = ctx.toVector 260 | infer(e.instantiate(0, ctxVec)).abstr(0, ctxVec) 261 | } 262 | go(e, Nil) 263 | case Pi(_, _) => 264 | def go(e: Expr, ctx: List[LocalConst]): Level = e match { 265 | case Pi(dom, body) => 266 | val dom_ = dom.copy(ty = dom.ty.instantiate(0, ctx.toVector)) 267 | val domUniv = inferUniverseOfType(dom_.ty) 268 | Level.IMax(domUniv, withLC(dom_)(lc => go(body, lc :: ctx))) 269 | case _ => 270 | val ctxVec = ctx.toVector 271 | inferUniverseOfType(e.instantiate(0, ctxVec)) 272 | } 273 | Sort(go(e, Nil).simplify) 274 | case Let(domain, value, body) => 275 | if (shouldCheck) inferUniverseOfType(domain.ty) 276 | if (shouldCheck) checkType(value, domain.ty) 277 | infer(body.instantiate(value)) 278 | }) 279 | } -------------------------------------------------------------------------------- /src/main/scala/trepplein/expr.scala: -------------------------------------------------------------------------------- 1 | package trepplein 2 | 3 | import java.util.function.Predicate 4 | 5 | import trepplein.Level._ 6 | 7 | import scala.annotation.tailrec 8 | import scala.collection.mutable 9 | 10 | sealed abstract class BinderInfo extends Product { 11 | def dump = s"BinderInfo.$productPrefix" 12 | } 13 | object BinderInfo { 14 | case object Default extends BinderInfo 15 | case object Implicit extends BinderInfo 16 | case object StrictImplicit extends BinderInfo 17 | case object InstImplicit extends BinderInfo 18 | } 19 | 20 | case class Binding(prettyName: Name, ty: Expr, info: BinderInfo) { 21 | def dump(implicit lcs: mutable.Map[LocalConst.Name, String]) = 22 | s"Binding(${prettyName.dump}, ${ty.dump}, ${info.dump})" 23 | 24 | override val hashCode: Int = prettyName.hashCode + 37 * (ty.hashCode + 37 * info.hashCode) 25 | 26 | def equalsCore(that: Binding)(implicit cache: ExprEqCache): Boolean = 27 | this.info == that.info && 28 | this.ty.equalsCore(that.ty) && 29 | this.prettyName == that.prettyName 30 | } 31 | 32 | private class ExprCache extends java.util.IdentityHashMap[Expr, Expr] { 33 | @inline final def getOrElseUpdate(k: Expr)(v: Expr => Expr): Expr = { 34 | val cached = get(k) 35 | if (cached != null) { 36 | cached 37 | } else { 38 | val computed = v(k) 39 | put(k, computed) 40 | computed 41 | } 42 | } 43 | } 44 | private class ExprOffCache extends mutable.ArrayBuffer[ExprCache] { 45 | @inline final def getOrElseUpdate(k: Expr, off: Int)(v: Expr => Expr): Expr = { 46 | while (off >= size) this += new ExprCache 47 | this(off).getOrElseUpdate(k)(v) 48 | } 49 | } 50 | 51 | private class ExprEqCache extends java.util.IdentityHashMap[Expr, UFNode] { 52 | def find(e: Expr): UFNode = { 53 | var n = get(e) 54 | if (n == null) { 55 | n = new UFNode 56 | put(e, n) 57 | } else { 58 | n = n.find() 59 | } 60 | n 61 | } 62 | 63 | @inline final def checkAndThenUnion(a: Expr, b: Expr)(v: (Expr, Expr) => Boolean): Boolean = { 64 | val a_ = find(a) 65 | val b_ = find(b) 66 | if (a_ eq b_) return true 67 | if (v(a, b)) { 68 | a_.union(b_) 69 | true 70 | } else { 71 | false 72 | } 73 | } 74 | } 75 | 76 | private object Breadcrumb 77 | 78 | sealed abstract class Expr(val varBound: Int, val hasLocals: Boolean, override val hashCode: Int) extends Product { 79 | final def hasVar(i: Int): Boolean = 80 | this match { 81 | case _ if varBound <= i => false 82 | case Var(idx) => idx == i 83 | case App(a, b) => a.hasVar(i) || b.hasVar(i) 84 | case Lam(dom, body) => dom.ty.hasVar(i) || body.hasVar(i + 1) 85 | case Pi(dom, body) => dom.ty.hasVar(i) || body.hasVar(i + 1) 86 | case Let(dom, value, body) => dom.ty.hasVar(i) || value.hasVar(i) || body.hasVar(i + 1) 87 | } 88 | 89 | def hasVars: Boolean = varBound > 0 90 | 91 | override def equals(that: Any): Boolean = 92 | that match { 93 | case that: Expr => equals(that) 94 | case _ => false 95 | } 96 | def equals(that: Expr): Boolean = equalsCore(that)(new ExprEqCache) 97 | def equalsCore(that: Expr)(implicit cache: ExprEqCache): Boolean = 98 | (this eq that) || this.hashCode == that.hashCode && 99 | cache.checkAndThenUnion(this, that) { 100 | case (Var(i1), Var(i2)) => i1 == i2 101 | case (Sort(l1), Sort(l2)) => l1 == l2 102 | case (Const(n1, l1), Const(n2, l2)) => n1 == n2 && l1 == l2 103 | case (LocalConst(_, n1), LocalConst(_, n2)) => n1 == n2 104 | case (App(a1, b1), App(a2, b2)) => a1.equalsCore(a2) && b1.equalsCore(b2) 105 | case (Lam(d1, b1), Lam(d2, b2)) => d1.equalsCore(d2) && b1.equalsCore(b2) 106 | case (Pi(d1, b1), Pi(d2, b2)) => d1.equalsCore(d2) && b1.equalsCore(b2) 107 | case (Let(d1, v1, b1), Let(d2, v2, b2)) => d1.equalsCore(d2) && v1.equalsCore(v2) && b1.equalsCore(b2) 108 | case _ => false 109 | } 110 | 111 | def abstr(lc: LocalConst): Expr = abstr(0, Vector(lc)) 112 | def abstr(off: Int, lcs: Vector[LocalConst]): Expr = 113 | abstrCore(off, lcs)(new ExprOffCache) 114 | private def abstrCore(off: Int, lcs: Vector[LocalConst])(implicit cache: ExprOffCache): Expr = 115 | cache.getOrElseUpdate(this, off) { 116 | case _ if !hasLocals => this 117 | case LocalConst(_, name) => 118 | lcs.indexWhere(_.name == name) match { 119 | case -1 => this 120 | case i => Var(i + off) 121 | } 122 | case App(a, b) => 123 | App(a.abstrCore(off, lcs), b.abstrCore(off, lcs)) 124 | case Lam(domain, body) => 125 | Lam(domain.copy(ty = domain.ty.abstrCore(off, lcs)), body.abstrCore(off + 1, lcs)) 126 | case Pi(domain, body) => 127 | Pi(domain.copy(ty = domain.ty.abstrCore(off, lcs)), body.abstrCore(off + 1, lcs)) 128 | case Let(domain, value, body) => 129 | Let(domain.copy(ty = domain.ty.abstrCore(off, lcs)), value.abstrCore(off, lcs), body.abstrCore(off + 1, lcs)) 130 | } 131 | 132 | def instantiate(e: Expr): Expr = instantiate(0, Vector(e)) 133 | def instantiate(off: Int, es: Vector[Expr]): Expr = 134 | if (varBound <= off) this else 135 | instantiateCore(off, es)(new ExprOffCache) 136 | private def instantiateCore(off: Int, es: Vector[Expr])(implicit cache: ExprOffCache): Expr = 137 | cache.getOrElseUpdate(this, off) { 138 | case _ if varBound <= off => this 139 | case Var(idx) => if (off <= idx && idx < off + es.size) es(idx - off) else this 140 | case App(a, b) => App(a.instantiateCore(off, es), b.instantiateCore(off, es)) 141 | case Lam(domain, body) => Lam(domain.copy(ty = domain.ty.instantiateCore(off, es)), body.instantiateCore(off + 1, es)) 142 | case Pi(domain, body) => Pi(domain.copy(ty = domain.ty.instantiateCore(off, es)), body.instantiateCore(off + 1, es)) 143 | case Let(domain, value, body) => Let( 144 | domain.copy(ty = domain.ty.instantiateCore(off, es)), 145 | value.instantiateCore(off, es), body.instantiateCore(off + 1, es)) 146 | } 147 | 148 | def instantiate(subst: Map[Param, Level]): Expr = 149 | if (subst.forall(x => x._1 == x._2)) this else instantiateCore(subst)(new ExprCache) 150 | private def instantiateCore(subst: Map[Param, Level])(implicit cache: ExprCache): Expr = 151 | cache.getOrElseUpdate(this) { 152 | case v: Var => v 153 | case Sort(level) => Sort(level.instantiate(subst)) 154 | case Const(name, levels) => Const(name, levels.map(_.instantiate(subst))) 155 | case LocalConst(of, name) => LocalConst(of.copy(ty = of.ty.instantiateCore(subst)), name) 156 | case App(a, b) => App(a.instantiateCore(subst), b.instantiateCore(subst)) 157 | case Lam(domain, body) => Lam(domain.copy(ty = domain.ty.instantiateCore(subst)), body.instantiateCore(subst)) 158 | case Pi(domain, body) => Pi(domain.copy(ty = domain.ty.instantiateCore(subst)), body.instantiateCore(subst)) 159 | case Let(domain, value, body) => Let( 160 | domain.copy(ty = domain.ty.instantiateCore(subst)), 161 | value.instantiateCore(subst), body.instantiateCore(subst)) 162 | } 163 | 164 | final def foreach_(f: Predicate[Expr]): Unit = 165 | if (f.test(this)) this match { 166 | case App(a, b) => 167 | a.foreach_(f) 168 | b.foreach_(f) 169 | case Lam(domain, body) => 170 | domain.ty.foreach_(f) 171 | body.foreach_(f) 172 | case Pi(domain, body) => 173 | domain.ty.foreach_(f) 174 | body.foreach_(f) 175 | case Let(domain, value, body) => 176 | domain.ty.foreach_(f) 177 | value.foreach_(f) 178 | body.foreach_(f) 179 | case _: Var | _: Const | _: Sort | _: LocalConst => 180 | } 181 | 182 | @inline final def foreachNoDups(f: Expr => Unit): Unit = { 183 | val seen = new java.util.IdentityHashMap[Expr, Breadcrumb.type]() 184 | foreach_ { x => 185 | if (seen.put(x, Breadcrumb) == null) { 186 | f(x) 187 | true 188 | } else { 189 | false 190 | } 191 | } 192 | } 193 | 194 | @inline private def buildSet[T](f: mutable.Set[T] => Unit): Set[T] = { 195 | val set = mutable.Set[T]() 196 | f(set) 197 | set.toSet 198 | } 199 | 200 | def univParams: Set[Param] = 201 | buildSet { ps => 202 | foreachNoDups { 203 | case Sort(level) => ps ++= level.univParams 204 | case Const(_, levels) => ps ++= levels.view.flatMap(_.univParams) 205 | case _ => 206 | } 207 | } 208 | 209 | def constants: Set[Name] = 210 | buildSet { cs => 211 | foreachNoDups { 212 | case Const(name, _) => cs += name 213 | case _ => 214 | } 215 | } 216 | 217 | def -->:(that: Expr): Expr = 218 | Pi(Binding(Name.Anon, that, BinderInfo.Default), this) 219 | 220 | override def toString: String = pretty(this) 221 | 222 | def dump(implicit lcs: mutable.Map[LocalConst.Name, String] = null): String = 223 | this match { 224 | case _ if lcs eq null => 225 | val lcs_ = mutable.Map[LocalConst.Name, String]() 226 | val d = dump(lcs_) 227 | if (lcs_.isEmpty) d else { 228 | val decls = lcs.values.map { n => s"val $n = new LocalConst.Name()\n" }.mkString 229 | s"{$decls$d}" 230 | } 231 | case Var(i) => s"Var($i)" 232 | case Sort(level) => s"Sort(${level.dump})" 233 | case Const(name, levels) => s"Const(${name.dump}, Vector(${levels.map(_.dump).mkString(", ")}))" 234 | case App(a, b) => s"App(${a.dump}, ${b.dump})" 235 | case Lam(dom, body) => s"Lam(${dom.dump}, ${body.dump})" 236 | case Pi(dom, body) => s"Pi(${dom.dump}, ${body.dump})" 237 | case LocalConst(of, name) => 238 | val of1 = of.prettyName.toString.replace('.', '_').filter { _.isLetterOrDigit } 239 | val of2 = if (of1.isEmpty || !of1.head.isLetter) s"n$of1" else of1 240 | val n = lcs.getOrElseUpdate(name, LazyList.from(0).map(i => s"$of2$i").diff(lcs.values.toSeq).head) 241 | s"LocalConst(${of.dump}, $n)" 242 | case Let(dom, value, body) => s"Let(${dom.dump}, ${value.dump}, ${body.dump})" 243 | } 244 | } 245 | case class Var(idx: Int) extends Expr(varBound = idx + 1, hasLocals = false, hashCode = idx) 246 | case class Sort(level: Level) extends Expr(varBound = 0, hasLocals = false, hashCode = level.hashCode) 247 | 248 | case class Const(name: Name, levels: Vector[Level]) 249 | extends Expr(varBound = 0, hasLocals = false, hashCode = 37 * name.hashCode) 250 | case class LocalConst(of: Binding, name: LocalConst.Name = new LocalConst.Name) 251 | extends Expr(varBound = 0, hasLocals = true, hashCode = 4 + name.hashCode) 252 | case class App(a: Expr, b: Expr) 253 | extends Expr( 254 | varBound = math.max(a.varBound, b.varBound), 255 | hasLocals = a.hasLocals || b.hasLocals, 256 | hashCode = a.hashCode + 37 * b.hashCode) 257 | case class Lam(domain: Binding, body: Expr) 258 | extends Expr( 259 | varBound = math.max(domain.ty.varBound, body.varBound - 1), 260 | hasLocals = domain.ty.hasLocals || body.hasLocals, 261 | hashCode = 1 + 37 * domain.hashCode + body.hashCode) 262 | case class Pi(domain: Binding, body: Expr) 263 | extends Expr( 264 | varBound = math.max(domain.ty.varBound, body.varBound - 1), 265 | hasLocals = domain.ty.hasLocals || body.hasLocals, 266 | hashCode = 2 + 37 * domain.hashCode + body.hashCode) 267 | case class Let(domain: Binding, value: Expr, body: Expr) 268 | extends Expr( 269 | varBound = math.max(math.max(domain.ty.varBound, value.varBound), body.varBound - 1), 270 | hasLocals = domain.ty.hasLocals || value.hasLocals || body.hasLocals, 271 | hashCode = 3 + 37 * (domain.hashCode + 37 * value.hashCode) + body.hashCode) 272 | 273 | object Sort { 274 | val Prop = Sort(Level.Zero) 275 | } 276 | 277 | object LocalConst { 278 | final class Name { 279 | override def toString: String = Integer.toHexString(hashCode()).take(4) 280 | } 281 | } 282 | 283 | trait Binder[T] { 284 | def apply(domain: Binding, body: Expr): T 285 | def apply(domain: LocalConst, body: Expr): T = 286 | apply(domain.of, body.abstr(domain)) 287 | 288 | trait GenericUnapply { 289 | def unapply(e: Expr): Option[(Binding, Expr)] 290 | } 291 | val generic: GenericUnapply 292 | } 293 | 294 | trait Binders[T <: Expr] { 295 | protected val Single: Binder[T] 296 | 297 | def apply(domains: Iterable[LocalConst])(body: Expr): Expr = 298 | domains.foldRight(body)(Single.apply) 299 | 300 | def apply(domains: LocalConst*)(body: Expr): Expr = 301 | apply(domains)(body) 302 | 303 | def unapply(e: Expr): Some[(List[LocalConst], Expr)] = 304 | e match { 305 | case Single.generic(dom, expr) => 306 | val lc = LocalConst(dom) 307 | unapply(expr.instantiate(lc)) match { 308 | case Some((lcs, head)) => 309 | Some((lc :: lcs, head)) 310 | } 311 | case _ => Some((Nil, e)) 312 | } 313 | } 314 | 315 | object Let { 316 | def apply(x: LocalConst, v: Expr, b: Expr): Let = 317 | Let(x.of, v, b.abstr(x)) 318 | } 319 | 320 | object Lam extends Binder[Lam] { 321 | val generic: GenericUnapply = { 322 | case e: Lam => Lam.unapply(e) 323 | case _ => None 324 | } 325 | } 326 | object Lams extends Binders[Lam] { 327 | protected val Single = Lam 328 | } 329 | 330 | object Pi extends Binder[Pi] { 331 | val generic: GenericUnapply = { 332 | case e: Pi => Pi.unapply(e) 333 | case _ => None 334 | } 335 | } 336 | object Pis extends Binders[Pi] { 337 | protected val Single = Pi 338 | } 339 | 340 | object Apps { 341 | @tailrec 342 | private def decompose(e: Expr, as: List[Expr] = Nil): (Expr, List[Expr]) = 343 | e match { 344 | case App(f, a) => decompose(f, a :: as) 345 | case _ => (e, as) 346 | } 347 | 348 | def unapply(e: Expr): Some[(Expr, List[Expr])] = 349 | Some(decompose(e)) 350 | 351 | def apply(fn: Expr, as: Iterable[Expr]): Expr = 352 | as.foldLeft(fn)(App) 353 | 354 | def apply(fn: Expr, as: Expr*): Expr = 355 | apply(fn, as) 356 | } 357 | --------------------------------------------------------------------------------