use std::fmt;
use std::fmt::Write as FmtWrite;
use crate::{BinderInfo, ConstantInfo, Expr, Level, Name};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum IndentMode {
Spaces(usize),
Tabs,
}
impl Default for IndentMode {
fn default() -> Self {
IndentMode::Spaces(2)
}
}
impl IndentMode {
pub fn render(&self, depth: usize) -> String {
match self {
IndentMode::Spaces(n) => " ".repeat(n * depth),
IndentMode::Tabs => "\t".repeat(depth),
}
}
}
#[derive(Clone, Debug)]
pub struct PrettyConfig {
pub width: usize,
pub indent: IndentMode,
pub unicode: bool,
pub show_universes: bool,
pub show_implicit: bool,
pub show_binder_kinds: bool,
pub show_bvar_indices: bool,
pub show_proof_bodies: bool,
pub max_depth: usize,
}
impl Default for PrettyConfig {
fn default() -> Self {
Self {
width: 100,
indent: IndentMode::default(),
unicode: true,
show_universes: false,
show_implicit: false,
show_binder_kinds: true,
show_bvar_indices: false,
show_proof_bodies: false,
max_depth: 0,
}
}
}
impl PrettyConfig {
pub fn verbose() -> Self {
Self {
show_universes: true,
show_implicit: true,
show_binder_kinds: true,
show_bvar_indices: true,
show_proof_bodies: true,
..Default::default()
}
}
pub fn ascii() -> Self {
Self {
unicode: false,
..Default::default()
}
}
pub fn compact() -> Self {
Self {
width: 0,
max_depth: 0,
..Default::default()
}
}
pub fn arrow(&self) -> &'static str {
if self.unicode {
"→"
} else {
"->"
}
}
pub fn lambda(&self) -> &'static str {
if self.unicode {
"λ"
} else {
"fun"
}
}
pub fn forall(&self) -> &'static str {
if self.unicode {
"∀"
} else {
"forall"
}
}
pub fn define(&self) -> &'static str {
":="
}
pub fn ellipsis(&self) -> &'static str {
if self.unicode {
"…"
} else {
"..."
}
}
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug)]
pub struct Prec(pub u32);
impl Prec {
pub const BOTTOM: Prec = Prec(0);
pub const BODY: Prec = Prec(5);
pub const ARROW: Prec = Prec(20);
pub const APP: Prec = Prec(100);
pub const ATOM: Prec = Prec(1000);
}
pub struct PrettyPrinter {
pub config: PrettyConfig,
}
impl Default for PrettyPrinter {
fn default() -> Self {
Self::new()
}
}
impl PrettyPrinter {
pub fn new() -> Self {
Self {
config: PrettyConfig::default(),
}
}
pub fn with_config(config: PrettyConfig) -> Self {
Self { config }
}
pub fn with_unicode(mut self, unicode: bool) -> Self {
self.config.unicode = unicode;
self
}
pub fn with_width(mut self, width: usize) -> Self {
self.config.width = width;
self
}
pub fn with_indent(mut self, indent: IndentMode) -> Self {
self.config.indent = indent;
self
}
pub fn with_universes(mut self, show: bool) -> Self {
self.config.show_universes = show;
self
}
pub fn pp_expr(&self, e: &Expr) -> String {
let mut buf = String::new();
self.write_expr(&mut buf, e, Prec::BOTTOM, 0)
.unwrap_or_default();
buf
}
pub fn pp_type(&self, t: &Expr) -> String {
self.pp_expr(t)
}
pub fn pp_decl(&self, decl: &ConstantInfo) -> String {
let mut buf = String::new();
self.write_decl(&mut buf, decl, 0).unwrap_or_default();
buf
}
pub fn pp_level(&self, l: &Level) -> String {
let mut buf = String::new();
self.write_level(&mut buf, l).unwrap_or_default();
buf
}
pub(super) fn write_expr(
&self,
buf: &mut String,
expr: &Expr,
prec: Prec,
depth: usize,
) -> fmt::Result {
if self.config.max_depth > 0 && depth > self.config.max_depth {
return write!(buf, "{}", self.config.ellipsis());
}
match expr {
Expr::Sort(level) => self.write_sort(buf, level),
Expr::BVar(idx) => {
if self.config.show_bvar_indices {
write!(buf, "#{}", idx)
} else {
write!(buf, "_")
}
}
Expr::FVar(id) => write!(buf, "${}", id.0),
Expr::Const(name, levels) => {
self.write_name(buf, name)?;
if self.config.show_universes && !levels.is_empty() {
write!(buf, ".{{")?;
for (i, lv) in levels.iter().enumerate() {
if i > 0 {
write!(buf, ", ")?;
}
self.write_level(buf, lv)?;
}
write!(buf, "}}")?;
}
Ok(())
}
Expr::App(_, _) => self.write_app(buf, expr, prec, depth),
Expr::Lam(bi, name, ty, body) => self.write_lam(buf, *bi, name, ty, body, prec, depth),
Expr::Pi(bi, name, ty, body) => self.write_pi(buf, *bi, name, ty, body, prec, depth),
Expr::Let(name, ty, val, body) => self.write_let(buf, name, ty, val, body, prec, depth),
Expr::Lit(lit) => write!(buf, "{}", lit),
Expr::Proj(name, idx, inner) => {
let needs_paren = prec > Prec::APP;
if needs_paren {
write!(buf, "(")?;
}
self.write_expr(buf, inner, Prec::ATOM, depth)?;
write!(buf, ".{}.{}", name, idx)?;
if needs_paren {
write!(buf, ")")?;
}
Ok(())
}
}
}
fn write_sort(&self, buf: &mut String, level: &Level) -> fmt::Result {
if level.is_zero() {
write!(buf, "Prop")
} else if let Some(n) = level_to_nat(level) {
if n == 1 {
write!(buf, "Type")
} else {
write!(buf, "Type {}", n - 1)
}
} else {
write!(buf, "Sort ")?;
self.write_level(buf, level)
}
}
fn write_level(&self, buf: &mut String, level: &Level) -> fmt::Result {
match level {
Level::Zero => write!(buf, "0"),
Level::Succ(inner) => {
if let Some(n) = level_to_nat(level) {
write!(buf, "{}", n)
} else {
write!(buf, "succ(")?;
self.write_level(buf, inner)?;
write!(buf, ")")
}
}
Level::Max(a, b) => {
write!(buf, "max(")?;
self.write_level(buf, a)?;
write!(buf, ", ")?;
self.write_level(buf, b)?;
write!(buf, ")")
}
Level::IMax(a, b) => {
write!(buf, "imax(")?;
self.write_level(buf, a)?;
write!(buf, ", ")?;
self.write_level(buf, b)?;
write!(buf, ")")
}
Level::Param(name) => self.write_name(buf, name),
Level::MVar(id) => write!(buf, "?u{}", id.0),
}
}
fn write_name(&self, buf: &mut String, name: &Name) -> fmt::Result {
write!(buf, "{}", name)
}
fn write_app(&self, buf: &mut String, expr: &Expr, prec: Prec, depth: usize) -> fmt::Result {
let (head, args) = collect_app(expr);
let needs_paren = prec > Prec::APP;
if needs_paren {
write!(buf, "(")?;
}
self.write_expr(buf, head, Prec::APP, depth)?;
for arg in args {
write!(buf, " ")?;
self.write_expr(buf, arg, Prec(Prec::APP.0 + 1), depth)?;
}
if needs_paren {
write!(buf, ")")?;
}
Ok(())
}
#[allow(clippy::too_many_arguments)]
fn write_lam(
&self,
buf: &mut String,
bi: BinderInfo,
name: &Name,
ty: &Expr,
body: &Expr,
prec: Prec,
depth: usize,
) -> fmt::Result {
let needs_paren = prec > Prec::BODY;
if needs_paren {
write!(buf, "(")?;
}
write!(buf, "{} ", self.config.lambda())?;
self.write_binder(buf, bi, name, ty, depth)?;
let mut cur_body = body;
while let Expr::Lam(bi2, name2, ty2, body2) = cur_body {
write!(buf, " ")?;
self.write_binder(buf, *bi2, name2, ty2, depth)?;
cur_body = body2;
}
write!(buf, ", ")?;
self.write_expr(buf, cur_body, Prec::BOTTOM, depth + 1)?;
if needs_paren {
write!(buf, ")")?;
}
Ok(())
}
#[allow(clippy::too_many_arguments)]
fn write_pi(
&self,
buf: &mut String,
bi: BinderInfo,
name: &Name,
ty: &Expr,
body: &Expr,
prec: Prec,
depth: usize,
) -> fmt::Result {
let needs_paren = prec > Prec::ARROW;
if needs_paren {
write!(buf, "(")?;
}
let is_nondep = name.is_anonymous() || name.to_string() == "_";
if is_nondep {
self.write_expr(buf, ty, Prec(Prec::ARROW.0 + 1), depth)?;
write!(buf, " {} ", self.config.arrow())?;
self.write_expr(buf, body, Prec::ARROW, depth)?;
} else {
write!(buf, "{} ", self.config.forall())?;
self.write_binder(buf, bi, name, ty, depth)?;
let mut cur_body = body;
while let Expr::Pi(bi2, name2, ty2, body2) = cur_body {
let is_dep2 = !name2.is_anonymous() && name2.to_string() != "_";
if is_dep2 {
write!(buf, " ")?;
self.write_binder(buf, *bi2, name2, ty2, depth)?;
cur_body = body2;
} else {
break;
}
}
write!(buf, ", ")?;
self.write_expr(buf, cur_body, Prec::BOTTOM, depth + 1)?;
}
if needs_paren {
write!(buf, ")")?;
}
Ok(())
}
#[allow(clippy::too_many_arguments)]
fn write_let(
&self,
buf: &mut String,
name: &Name,
ty: &Expr,
val: &Expr,
body: &Expr,
_prec: Prec,
depth: usize,
) -> fmt::Result {
let indent = self.config.indent.render(depth);
write!(buf, "let ")?;
self.write_name(buf, name)?;
write!(buf, " : ")?;
self.write_expr(buf, ty, Prec::BOTTOM, depth)?;
write!(buf, " {} ", self.config.define())?;
self.write_expr(buf, val, Prec::BOTTOM, depth)?;
write!(buf, "\n{}", indent)?;
self.write_expr(buf, body, Prec::BOTTOM, depth + 1)
}
fn write_binder(
&self,
buf: &mut String,
bi: BinderInfo,
name: &Name,
ty: &Expr,
depth: usize,
) -> fmt::Result {
if self.config.show_binder_kinds {
match bi {
BinderInfo::Default => {
write!(buf, "(")?;
self.write_name(buf, name)?;
write!(buf, " : ")?;
self.write_expr(buf, ty, Prec::BOTTOM, depth)?;
write!(buf, ")")
}
BinderInfo::Implicit => {
write!(buf, "{{")?;
self.write_name(buf, name)?;
write!(buf, " : ")?;
self.write_expr(buf, ty, Prec::BOTTOM, depth)?;
write!(buf, "}}")
}
BinderInfo::StrictImplicit => {
write!(buf, "{{")?;
self.write_name(buf, name)?;
write!(buf, " : ")?;
self.write_expr(buf, ty, Prec::BOTTOM, depth)?;
write!(buf, "}}")
}
BinderInfo::InstImplicit => {
write!(buf, "[")?;
self.write_name(buf, name)?;
write!(buf, " : ")?;
self.write_expr(buf, ty, Prec::BOTTOM, depth)?;
write!(buf, "]")
}
}
} else {
self.write_name(buf, name)?;
write!(buf, " : ")?;
self.write_expr(buf, ty, Prec::BOTTOM, depth)
}
}
fn write_decl(&self, buf: &mut String, decl: &ConstantInfo, depth: usize) -> fmt::Result {
match decl {
ConstantInfo::Axiom(ax) => {
write!(buf, "axiom ")?;
self.write_name(buf, &ax.common.name)?;
self.write_level_params(buf, &ax.common.level_params)?;
write!(buf, " : ")?;
self.write_expr(buf, &ax.common.ty, Prec::BOTTOM, depth)
}
ConstantInfo::Definition(def) => {
write!(buf, "def ")?;
self.write_name(buf, &def.common.name)?;
self.write_level_params(buf, &def.common.level_params)?;
write!(buf, " : ")?;
self.write_expr(buf, &def.common.ty, Prec::BOTTOM, depth)?;
write!(buf, " {} ", self.config.define())?;
self.write_expr(buf, &def.value, Prec::BOTTOM, depth)
}
ConstantInfo::Theorem(thm) => {
write!(buf, "theorem ")?;
self.write_name(buf, &thm.common.name)?;
self.write_level_params(buf, &thm.common.level_params)?;
write!(buf, " : ")?;
self.write_expr(buf, &thm.common.ty, Prec::BOTTOM, depth)?;
if self.config.show_proof_bodies {
write!(buf, " {} ", self.config.define())?;
self.write_expr(buf, &thm.value, Prec::BOTTOM, depth)?;
} else {
let proof_kw = if self.config.unicode {
" := ⊢ …"
} else {
" := <proof>"
};
write!(buf, "{}", proof_kw)?;
}
Ok(())
}
ConstantInfo::Opaque(op) => {
write!(buf, "opaque ")?;
self.write_name(buf, &op.common.name)?;
self.write_level_params(buf, &op.common.level_params)?;
write!(buf, " : ")?;
self.write_expr(buf, &op.common.ty, Prec::BOTTOM, depth)
}
ConstantInfo::Inductive(ind) => {
write!(buf, "inductive ")?;
self.write_name(buf, &ind.common.name)?;
self.write_level_params(buf, &ind.common.level_params)?;
write!(buf, " : ")?;
self.write_expr(buf, &ind.common.ty, Prec::BOTTOM, depth)?;
write!(buf, " [{} ctors]", ind.ctors.len())
}
ConstantInfo::Constructor(ctor) => {
write!(buf, "constructor ")?;
self.write_name(buf, &ctor.common.name)?;
write!(buf, " : ")?;
self.write_expr(buf, &ctor.common.ty, Prec::BOTTOM, depth)
}
ConstantInfo::Recursor(rec) => {
write!(buf, "recursor ")?;
self.write_name(buf, &rec.common.name)?;
write!(buf, " : ")?;
self.write_expr(buf, &rec.common.ty, Prec::BOTTOM, depth)
}
ConstantInfo::Quotient(quot) => {
write!(buf, "quotient ")?;
self.write_name(buf, ".common.name)?;
write!(buf, " : ")?;
self.write_expr(buf, ".common.ty, Prec::BOTTOM, depth)
}
}
}
fn write_level_params(&self, buf: &mut String, params: &[Name]) -> fmt::Result {
if params.is_empty() {
return Ok(());
}
write!(buf, ".{{")?;
for (i, p) in params.iter().enumerate() {
if i > 0 {
write!(buf, ", ")?;
}
self.write_name(buf, p)?;
}
write!(buf, "}}")
}
}
pub(super) fn collect_app(expr: &Expr) -> (&Expr, Vec<&Expr>) {
let mut args = Vec::new();
let mut e = expr;
while let Expr::App(f, a) = e {
args.push(a.as_ref());
e = f;
}
args.reverse();
(e, args)
}
pub(super) fn level_to_nat(level: &Level) -> Option<u32> {
match level {
Level::Zero => Some(0),
Level::Succ(inner) => level_to_nat(inner).map(|n| n + 1),
_ => None,
}
}