#![allow(non_snake_case)] use std::collections::BTreeMap; use dhall_core::context::Context; use dhall_core::*; use dhall_generator as dhall; use crate::expr::*; type InputSubExpr = SubExpr>; type OutputSubExpr = SubExpr; impl<'a> Typed<'a> { pub fn normalize(self) -> Normalized<'a> { Normalized(normalize(self.0), self.1, self.2) } /// Pretends this expression is normalized. Use with care. #[allow(dead_code)] pub fn skip_normalize(self) -> Normalized<'a> { Normalized( self.0.unroll().squash_embed(|e| e.0.clone()), self.1, self.2, ) } } fn apply_builtin(b: Builtin, args: &[WHNF]) -> WhatNext { let args: Vec<_> = args .iter() .map(|val| val.clone().normalize_to_expr().unroll()) .collect(); use dhall_core::Builtin::*; use dhall_core::ExprF::*; use WhatNext::*; let ret = match (b, args.as_slice()) { (OptionalNone, [t]) => rc(EmptyOptionalLit(t.roll())), (NaturalIsZero, [NaturalLit(n)]) => rc(BoolLit(*n == 0)), (NaturalEven, [NaturalLit(n)]) => rc(BoolLit(*n % 2 == 0)), (NaturalOdd, [NaturalLit(n)]) => rc(BoolLit(*n % 2 != 0)), (NaturalToInteger, [NaturalLit(n)]) => rc(IntegerLit(*n as isize)), (NaturalShow, [NaturalLit(n)]) => rc(TextLit(n.to_string().into())), (ListLength, [_, EmptyListLit(_)]) => rc(NaturalLit(0)), (ListLength, [_, NEListLit(ys)]) => rc(NaturalLit(ys.len())), (ListHead, [_, EmptyListLit(t)]) => rc(EmptyOptionalLit(t.clone())), (ListHead, [_, NEListLit(ys)]) => { rc(NEOptionalLit(ys.first().unwrap().clone())) } (ListLast, [_, EmptyListLit(t)]) => rc(EmptyOptionalLit(t.clone())), (ListLast, [_, NEListLit(ys)]) => { rc(NEOptionalLit(ys.last().unwrap().clone())) } (ListReverse, [_, EmptyListLit(t)]) => rc(EmptyListLit(t.clone())), (ListReverse, [_, NEListLit(ys)]) => { let ys = ys.iter().rev().cloned().collect(); rc(NEListLit(ys)) } (ListIndexed, [_, EmptyListLit(t)]) => { dhall::subexpr!([] : List ({ index : Natural, value : t })) } (ListIndexed, [_, NEListLit(xs)]) => { let xs = xs .iter() .cloned() .enumerate() .map(|(i, e)| { let i = rc(NaturalLit(i)); dhall::subexpr!({ index = i, value = e }) }) .collect(); rc(NEListLit(xs)) } (ListBuild, [a0, g]) => { 'ret: { // if let App(f2, args2) = g { // if let (Builtin(ListFold), [_, x, rest_inner..]) = // (f2.as_ref(), args2.as_slice()) // { // // fold/build fusion // break 'ret ( // rc(App(x.clone(), rest_inner.to_vec())), // rest, // ); // } // }; let a0 = a0.roll(); let a1 = shift0(1, &"x".into(), &a0); let g = g.roll(); break 'ret dhall::subexpr!( g (List a0) (λ(x : a0) -> λ(xs : List a1) -> [ x ] # xs) ([] : List a0) ); } } (OptionalBuild, [a0, g]) => { 'ret: { // if let App(f2, args2) = g { // if let (Builtin(OptionalFold), [_, x, rest_inner..]) = // (f2.as_ref(), args2.as_slice()) // { // // fold/build fusion // break 'ret ( // rc(App(x.clone(), rest_inner.to_vec())), // rest, // ); // } // }; let a0 = a0.roll(); let g = g.roll(); break 'ret dhall::subexpr!( g (Optional a0) (λ(x: a0) -> Some x) (None a0) ); } } (ListFold, [_, EmptyListLit(_), _, _, nil]) => nil.roll(), (ListFold, [_, NEListLit(xs), _, cons, nil]) => { xs.iter().rev().fold(nil.roll(), |acc, x| { let x = x.clone(); let acc = acc.clone(); let cons = cons.roll(); dhall::subexpr!(cons x acc) }) } // // fold/build fusion // (ListFold, [_, App(box Builtin(ListBuild), [_, x, rest..]), rest..]) => { // normalize_whnf(&App(bx(x.clone()), rest.to_vec())) // } (OptionalFold, [_, NEOptionalLit(x), _, just, _]) => { let x = x.clone(); let just = just.roll(); dhall::subexpr!(just x) } (OptionalFold, [_, EmptyOptionalLit(_), _, _, nothing]) => { nothing.roll() } // // fold/build fusion // (OptionalFold, [_, App(box Builtin(OptionalBuild), [_, x, rest..]), rest..]) => { // normalize_whnf(&App(bx(x.clone()), rest.to_vec())) // } (NaturalBuild, [g]) => { // 'ret: { // if let App(f2, args2) = g { // if let (Builtin(NaturalFold), [x, rest_inner..]) = // (f2.as_ref(), args2.as_slice()) // { // // fold/build fusion // break 'ret ( // rc(App(x.clone(), rest_inner.to_vec())), // rest, // ); // } // }; // } let g = g.roll(); dhall::subexpr!(g Natural (λ(x : Natural) -> x + 1) 0) } (NaturalFold, [NaturalLit(0), _, _, zero]) => zero.roll(), (NaturalFold, [NaturalLit(n), t, succ, zero]) => { let fold = rc(Builtin(NaturalFold)); let n = rc(NaturalLit(n - 1)); let t = t.roll(); let succ = succ.roll(); let zero = zero.roll(); dhall::subexpr!(succ (fold n t succ zero)) } // (NaturalFold, Some(App(f2, args2)), _) => { // match (f2.as_ref(), args2.as_slice()) { // // fold/build fusion // (Builtin(NaturalBuild), [x, rest..]) => { // rc(App(x.clone(), rest.to_vec())) // } // _ => return rc(App(f, args)), // } // } _ => return DoneAsIs, }; Continue(ret.unroll()) } // #[derive(Debug, Clone)] // struct ContextualizedSubExpr { // ctx: NormalizationContext, // expr: SubExpr>, // } // impl ContextualizedSubExpr { // fn normalize_whnf(self) -> WHNF { // normalize_whnf(&self.ctx, self.expr) // } // fn normalize_expr(self) -> OutputSubExpr { // normalize_whnf(&self.ctx, self.expr).into_expr(&self.ctx) // } // } /// A semantic value. This is partially redundant with `dhall_core::Expr`, on purpose. `Expr` should /// be limited to syntactic expressions: either written by the user or meant to be printed. /// The rule is the following: we must _not_ construct values of type `Expr` while normalizing or /// typechecking, but only construct `WHNF`s. /// /// WHNFs usually store subexpressions unnormalized, to enable lazy normalization. They approximate /// what's called Weak Head Normal-Form (WHNF). This means that the expression is normalized as /// little as possible, but just enough to know the first constructor of the normal form. This is /// identical to full normalization for simple types like integers, but for e.g. a record literal /// this means knowing just the field names. /// /// Each variant captures the relevant contexts when it is constructed. Conceptually each /// subexpression has its own context, but in practice some contexts can be shared when sensible, to /// avoid unnecessary allocations. #[derive(Debug, Clone)] enum WHNF { Closure(Closure), BoolLit(bool), NaturalLit(Natural), EmptyOptionalLit(Now), NEOptionalLit(Now), EmptyListLit(Now), NEListLit(Vec), RecordLit(BTreeMap), UnionType(NormalizationContext, BTreeMap>), UnionLit( Label, Now, (NormalizationContext, BTreeMap>), ), TextLit(Vec>), Expr(OutputSubExpr), } impl WHNF { /// Convert the value back to a (normalized) syntactic expression fn normalize_to_expr(self) -> OutputSubExpr { match self { WHNF::Closure(c) => c.normalize_to_expr(), WHNF::BoolLit(b) => rc(ExprF::BoolLit(b)), WHNF::NaturalLit(n) => rc(ExprF::NaturalLit(n)), WHNF::EmptyOptionalLit(n) => { rc(ExprF::EmptyOptionalLit(n.normalize().normalize_to_expr())) } WHNF::NEOptionalLit(n) => { rc(ExprF::NEOptionalLit(n.normalize().normalize_to_expr())) } WHNF::EmptyListLit(n) => { rc(ExprF::EmptyListLit(n.normalize().normalize_to_expr())) } WHNF::NEListLit(elts) => rc(ExprF::NEListLit( elts.into_iter() .map(|n| n.normalize().normalize_to_expr()) .collect(), )), WHNF::RecordLit(kvs) => rc(ExprF::RecordLit( kvs.into_iter() .map(|(k, v)| (k, v.normalize().normalize_to_expr())) .collect(), )), WHNF::UnionType(ctx, kts) => rc(ExprF::UnionType( kts.into_iter() .map(|(k, v)| { ( k, v.map(|v| { normalize_whnf(&ctx, v).normalize_to_expr() }), ) }) .collect(), )), WHNF::UnionLit(l, v, (kts_ctx, kts)) => rc(ExprF::UnionLit( l, v.normalize().normalize_to_expr(), kts.into_iter() .map(|(k, v)| { ( k, v.map(|v| { normalize_whnf(&kts_ctx, v).normalize_to_expr() }), ) }) .collect(), )), WHNF::TextLit(elts) => { fn normalize_textlit( elts: Vec>, ) -> InterpolatedText { elts.into_iter() .flat_map(|contents| { use InterpolatedTextContents::{Expr, Text}; let new_interpolated = match contents { Expr(n) => match n.normalize() { WHNF::TextLit(elts2) => { normalize_textlit(elts2) } e => InterpolatedText::from(( String::new(), vec![( e.normalize_to_expr(), String::new(), )], )), }, Text(s) => InterpolatedText::from(s), }; new_interpolated.into_iter() }) .collect() } rc(ExprF::TextLit(normalize_textlit(elts))) } WHNF::Expr(e) => e, } } fn shift0(self, delta: isize, label: &Label) -> Self { match self { WHNF::Closure(c) => WHNF::Closure(c.shift0(delta, label)), WHNF::Expr(e) => WHNF::Expr(shift0(delta, label, &e)), WHNF::BoolLit(b) => WHNF::BoolLit(b), WHNF::NaturalLit(n) => WHNF::NaturalLit(n), WHNF::EmptyOptionalLit(n) => { WHNF::EmptyOptionalLit(n.shift0(delta, label)) } WHNF::NEOptionalLit(n) => { WHNF::NEOptionalLit(n.shift0(delta, label)) } WHNF::EmptyListLit(n) => WHNF::EmptyListLit(n.shift0(delta, label)), WHNF::NEListLit(elts) => WHNF::NEListLit( elts.into_iter().map(|n| n.shift0(delta, label)).collect(), ), WHNF::RecordLit(kvs) => WHNF::RecordLit( kvs.into_iter() .map(|(k, v)| (k, v.shift0(delta, label))) .collect(), ), WHNF::UnionType(ctx, kts) => WHNF::UnionType( ctx, kts.into_iter() .map(|(k, v)| (k, v.map(|v| shift0(delta, label, &v)))) .collect(), ), WHNF::UnionLit(l, v, (kts_ctx, kts)) => WHNF::UnionLit( l, v.shift0(delta, label), ( kts_ctx, kts.into_iter() .map(|(k, v)| (k, v.map(|v| shift0(delta, label, &v)))) .collect(), ), ), WHNF::TextLit(elts) => WHNF::TextLit( elts.into_iter() .map(|contents| { use InterpolatedTextContents::{Expr, Text}; match contents { Expr(n) => Expr(n.shift0(delta, label)), Text(s) => Text(s), } }) .collect(), ), } } } /// Normalize-on-write smart container. Contains either a (partially) normalized value or a /// non-normalized value alongside a normalization context. /// The name is a pun on std::borrow::Cow. #[derive(Debug, Clone)] enum Now { Normalized(Box), Unnormalized(NormalizationContext, InputSubExpr), } impl Now { fn new(ctx: NormalizationContext, e: InputSubExpr) -> Now { Now::Unnormalized(ctx, e) } fn from_whnf(v: WHNF) -> Now { Now::Normalized(Box::new(v)) } fn normalize(self) -> WHNF { match self { Now::Normalized(v) => *v, Now::Unnormalized(ctx, e) => normalize_whnf(&ctx, e), } } fn shift0(self, delta: isize, label: &Label) -> Self { match self { Now::Normalized(v) => { Now::Normalized(Box::new(v.shift0(delta, label))) } Now::Unnormalized(ctx, e) => { Now::Unnormalized(ctx, shift0(delta, label, &e)) } } } } #[derive(Debug, Clone)] enum Closure { Lam(Label, Now, (NormalizationContext, InputSubExpr)), AppliedBuiltin(NormalizationContext, Builtin, Vec), UnionConstructor( NormalizationContext, Label, BTreeMap>, ), } impl Closure { /// Apply the closure to a value fn app(self, val: WHNF) -> WHNF { match self { Closure::Lam(x, _, (ctx, e)) => { let ctx2 = ctx.insert(&x, val); normalize_whnf(&ctx2, e) } Closure::AppliedBuiltin(ctx, b, mut args) => { args.push(val); match apply_builtin(b, &args).into_value(&ctx) { Some(v) => v, None => { WHNF::Closure(Closure::AppliedBuiltin(ctx, b, args)) } } } Closure::UnionConstructor(ctx, l, kts) => { WHNF::UnionLit(l, Now::from_whnf(val), (ctx, kts)) } } } /// Convert the closure back to a (normalized) syntactic expression fn normalize_to_expr(self) -> OutputSubExpr { match self { Closure::Lam(x, t, (ctx, e)) => { let ctx2 = ctx.skip(&x); rc(ExprF::Lam( x.clone(), t.normalize().normalize_to_expr(), normalize_whnf(&ctx2, e).normalize_to_expr(), )) } Closure::AppliedBuiltin(_ctx, b, args) => { if args.is_empty() { rc(ExprF::Builtin(b)) } else { args.into_iter() .map(|e| e.normalize_to_expr()) .fold(rc(ExprF::Builtin(b)), |acc, e| { rc(ExprF::App(acc, e)) }) } } Closure::UnionConstructor(ctx, l, kts) => { let kts = kts .into_iter() .map(|(k, v)| { ( k, v.map(|v| { normalize_whnf(&ctx, v).normalize_to_expr() }), ) }) .collect(); rc(ExprF::Field(rc(ExprF::UnionType(kts)), l)) } } } fn shift0(self, delta: isize, label: &Label) -> Self { match self { Closure::Lam(x, t, (ctx, e)) => Closure::Lam( x, t.shift0(delta, label), (ctx, shift(delta, &V(label.clone(), 1), &e)), ), Closure::AppliedBuiltin(ctx, b, args) => Closure::AppliedBuiltin( ctx, b, args.into_iter().map(|e| e.shift0(delta, label)).collect(), ), Closure::UnionConstructor(ctx, l, kts) => { let kts = kts .into_iter() .map(|(k, v)| (k, v.map(|v| shift0(delta, label, &v)))) .collect(); Closure::UnionConstructor(ctx, l, kts) } } } } #[derive(Debug, Clone)] enum EnvItem { Expr(WHNF), Skip(usize), } #[derive(Debug, Clone)] struct NormalizationContext(Context); impl NormalizationContext { fn new() -> NormalizationContext { NormalizationContext(Context::new()) } fn insert(&self, x: &Label, e: WHNF) -> NormalizationContext { NormalizationContext(self.0.insert(x.clone(), EnvItem::Expr(e))) } fn skip(&self, x: &Label) -> NormalizationContext { NormalizationContext( self.0 .map(|l, e| { use EnvItem::*; match e { Expr(e) => Expr(e.clone().shift0(1, x)), Skip(n) if l == x => Skip(*n + 1), Skip(n) => Skip(*n), } }) .insert(x.clone(), EnvItem::Skip(0)), ) } fn lookup(&self, var: &V