summaryrefslogtreecommitdiff
path: root/dhall_syntax
diff options
context:
space:
mode:
authorNadrieril2019-05-04 12:38:36 +0200
committerNadrieril2019-05-04 12:38:36 +0200
commit45be2ff1f5bb3d6e0faa098402adf985b3d5e7ca (patch)
treea7b15b0bf4b5b211eb42a559bc3cfde9a14b7749 /dhall_syntax
parentca4e2b39c838cde6da835470699579e8eddc1535 (diff)
Rename dhall_core to dhall_syntax
Diffstat (limited to 'dhall_syntax')
-rw-r--r--dhall_syntax/Cargo.toml16
-rw-r--r--dhall_syntax/src/context.rs80
-rw-r--r--dhall_syntax/src/core.rs563
-rw-r--r--dhall_syntax/src/import.rs64
-rw-r--r--dhall_syntax/src/label.rs34
-rw-r--r--dhall_syntax/src/lib.rs28
-rw-r--r--dhall_syntax/src/parser.rs984
-rw-r--r--dhall_syntax/src/printer.rs498
-rw-r--r--dhall_syntax/src/text.rs116
-rw-r--r--dhall_syntax/src/visitor.rs667
10 files changed, 3050 insertions, 0 deletions
diff --git a/dhall_syntax/Cargo.toml b/dhall_syntax/Cargo.toml
new file mode 100644
index 0000000..3e32930
--- /dev/null
+++ b/dhall_syntax/Cargo.toml
@@ -0,0 +1,16 @@
+[package]
+name = "dhall_syntax"
+version = "0.1.0"
+authors = ["NanoTech <nanotech@nanotechcorp.net>", "Nadrieril <nadrieril@users.noreply.github.com>"]
+license = "BSD-2-Clause"
+edition = "2018"
+
+[lib]
+doctest = false
+
+[dependencies]
+itertools = "0.8.0"
+percent-encoding = "1.0.1"
+pest = "2.1"
+dhall_generated_parser = { path = "../dhall_generated_parser" }
+improved_slice_patterns = { version = "2.0.0", path = "../improved_slice_patterns" }
diff --git a/dhall_syntax/src/context.rs b/dhall_syntax/src/context.rs
new file mode 100644
index 0000000..55bfff5
--- /dev/null
+++ b/dhall_syntax/src/context.rs
@@ -0,0 +1,80 @@
+use std::cmp::Eq;
+use std::collections::HashMap;
+use std::hash::Hash;
+
+/// A `(Context a)` associates `Text` labels with values of type `a`
+///
+/// The `Context` is used for type-checking when `(a = Expr X)`
+///
+/// * You create a `Context` using `empty` and `insert`
+/// * You transform a `Context` using `fmap`
+/// * You consume a `Context` using `lookup` and `toList`
+///
+/// The difference between a `Context` and a `Map` is that a `Context` lets you
+/// have multiple ordered occurrences of the same key and you can query for the
+/// `n`th occurrence of a given key.
+///
+#[derive(Debug, Clone)]
+pub struct Context<K: Eq + Hash, T>(HashMap<K, Vec<T>>);
+
+impl<K: Hash + Eq + Clone, T> Context<K, T> {
+ /// An empty context with no key-value pairs
+ pub fn new() -> Self {
+ Context(HashMap::new())
+ }
+
+ /// Look up a key by name and index
+ ///
+ /// ```c
+ /// lookup _ _ empty = Nothing
+ /// lookup k 0 (insert k v c) = Just v
+ /// lookup k n (insert k v c) = lookup k (n - 1) c -- 1 <= n
+ /// lookup k n (insert j v c) = lookup k n c -- k /= j
+ /// ```
+ pub fn lookup<'a>(&'a self, k: &K, n: usize) -> Option<&'a T> {
+ self.0.get(k).and_then(|v| {
+ if n < v.len() {
+ v.get(v.len() - 1 - n)
+ } else {
+ None
+ }
+ })
+ }
+
+ pub fn map<U, F: Fn(&K, &T) -> U>(&self, f: F) -> Context<K, U> {
+ Context(
+ self.0
+ .iter()
+ .map(|(k, vs)| {
+ ((*k).clone(), vs.iter().map(|v| f(k, v)).collect())
+ })
+ .collect(),
+ )
+ }
+
+ pub fn lookup_all<'a>(&'a self, k: &K) -> impl Iterator<Item = &T> {
+ self.0.get(k).into_iter().flat_map(|v| v.iter())
+ }
+
+ pub fn iter<'a>(&'a self) -> impl Iterator<Item = (&K, &T)> {
+ self.0
+ .iter()
+ .flat_map(|(k, vs)| vs.iter().map(move |v| (k, v)))
+ }
+
+ pub fn iter_keys<'a>(&'a self) -> impl Iterator<Item = (&K, &Vec<T>)> {
+ self.0.iter()
+ }
+}
+
+impl<K: Hash + Eq + Clone, T: Clone> Context<K, T> {
+ /// Add a key-value pair to the `Context`
+ pub fn insert(&self, k: K, v: T) -> Self {
+ let mut ctx = (*self).clone();
+ {
+ let m = ctx.0.entry(k).or_insert_with(Vec::new);
+ m.push(v);
+ }
+ ctx
+ }
+}
diff --git a/dhall_syntax/src/core.rs b/dhall_syntax/src/core.rs
new file mode 100644
index 0000000..3db07dd
--- /dev/null
+++ b/dhall_syntax/src/core.rs
@@ -0,0 +1,563 @@
+#![allow(non_snake_case)]
+use std::collections::BTreeMap;
+use std::collections::HashMap;
+use std::rc::Rc;
+
+use crate::context::Context;
+use crate::visitor;
+use crate::*;
+
+pub type Integer = isize;
+pub type Natural = usize;
+pub type Double = NaiveDouble;
+
+/// An empty type
+#[derive(Debug, Copy, Clone, PartialEq, Eq)]
+pub enum X {}
+
+pub fn trivial_result<T>(x: Result<T, X>) -> T {
+ match x {
+ Ok(x) => x,
+ Err(e) => match e {},
+ }
+}
+
+/// Double with bitwise equality
+#[derive(Debug, Copy, Clone)]
+pub struct NaiveDouble(f64);
+
+impl PartialEq for NaiveDouble {
+ fn eq(&self, other: &Self) -> bool {
+ self.0.to_bits() == other.0.to_bits()
+ }
+}
+
+impl Eq for NaiveDouble {}
+
+impl From<f64> for NaiveDouble {
+ fn from(x: f64) -> Self {
+ NaiveDouble(x)
+ }
+}
+
+impl From<NaiveDouble> for f64 {
+ fn from(x: NaiveDouble) -> f64 {
+ x.0
+ }
+}
+
+/// Constants for a pure type system
+#[derive(Debug, Copy, Clone, PartialEq, Eq)]
+pub enum Const {
+ Type,
+ Kind,
+ Sort,
+}
+
+/// Bound variable
+///
+/// The `Label` field is the variable's name (i.e. \"`x`\").
+/// The `Int` field is a DeBruijn index.
+/// See dhall-lang/standard/semantics.md for details
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub struct V<Label>(pub Label, pub usize);
+
+// This is only for the specific `Label` type, not generic
+impl From<Label> for V<Label> {
+ fn from(x: Label) -> V<Label> {
+ V(x, 0)
+ }
+}
+impl<'a> From<&'a Label> for V<Label> {
+ fn from(x: &'a Label) -> V<Label> {
+ V(x.clone(), 0)
+ }
+}
+
+// Definition order must match precedence order for
+// pretty-printing to work correctly
+#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
+pub enum BinOp {
+ /// x ? y
+ ImportAlt,
+ /// x || y`
+ BoolOr,
+ /// x + y`
+ NaturalPlus,
+ /// x ++ y`
+ TextAppend,
+ /// x # y
+ ListAppend,
+ /// x && y`
+ BoolAnd,
+ /// x ∧ y`
+ Combine,
+ /// x // y
+ Prefer,
+ /// x //\\ y
+ CombineTypes,
+ /// x * y`
+ NaturalTimes,
+ /// x == y`
+ BoolEQ,
+ /// x != y`
+ BoolNE,
+}
+
+/// Built-ins
+#[derive(Debug, Copy, Clone, PartialEq, Eq)]
+pub enum Builtin {
+ Bool,
+ Natural,
+ Integer,
+ Double,
+ Text,
+ List,
+ Optional,
+ OptionalNone,
+ NaturalBuild,
+ NaturalFold,
+ NaturalIsZero,
+ NaturalEven,
+ NaturalOdd,
+ NaturalToInteger,
+ NaturalShow,
+ IntegerToDouble,
+ IntegerShow,
+ DoubleShow,
+ ListBuild,
+ ListFold,
+ ListLength,
+ ListHead,
+ ListLast,
+ ListIndexed,
+ ListReverse,
+ OptionalFold,
+ OptionalBuild,
+ TextShow,
+}
+
+pub type ParsedExpr = SubExpr<X, Import>;
+pub type ResolvedExpr = SubExpr<X, X>;
+pub type DhallExpr = ResolvedExpr;
+
+#[derive(Debug, PartialEq, Eq)]
+pub struct SubExpr<Note, Embed>(pub Rc<Expr<Note, Embed>>);
+
+pub type Expr<Note, Embed> = ExprF<SubExpr<Note, Embed>, Label, Note, Embed>;
+
+/// Syntax tree for expressions
+// Having the recursion out of the enum definition enables writing
+// much more generic code and improves pattern-matching behind
+// smart pointers.
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub enum ExprF<SubExpr, Label, Note, Embed> {
+ Const(Const),
+ /// `x`
+ /// `x@n`
+ Var(V<Label>),
+ /// `λ(x : A) -> b`
+ Lam(Label, SubExpr, SubExpr),
+ /// `A -> B`
+ /// `∀(x : A) -> B`
+ Pi(Label, SubExpr, SubExpr),
+ /// `f a`
+ App(SubExpr, SubExpr),
+ /// `let x = r in e`
+ /// `let x : t = r in e`
+ Let(Label, Option<SubExpr>, SubExpr, SubExpr),
+ /// `x : t`
+ Annot(SubExpr, SubExpr),
+ /// Built-in values
+ Builtin(Builtin),
+ // Binary operations
+ BinOp(BinOp, SubExpr, SubExpr),
+ /// `True`
+ BoolLit(bool),
+ /// `if x then y else z`
+ BoolIf(SubExpr, SubExpr, SubExpr),
+ /// `1`
+ NaturalLit(Natural),
+ /// `+2`
+ IntegerLit(Integer),
+ /// `3.24`
+ DoubleLit(Double),
+ /// `"Some ${interpolated} text"`
+ TextLit(InterpolatedText<SubExpr>),
+ /// `[] : List t`
+ EmptyListLit(SubExpr),
+ /// `[x, y, z]`
+ NEListLit(Vec<SubExpr>),
+ /// Deprecated Optional literal form
+ /// `[] : Optional a`
+ /// `[x] : Optional a`
+ OldOptionalLit(Option<SubExpr>, SubExpr),
+ /// `Some e`
+ SomeLit(SubExpr),
+ /// `{ k1 : t1, k2 : t1 }`
+ RecordType(BTreeMap<Label, SubExpr>),
+ /// `{ k1 = v1, k2 = v2 }`
+ RecordLit(BTreeMap<Label, SubExpr>),
+ /// `< k1 : t1, k2 >`
+ UnionType(BTreeMap<Label, Option<SubExpr>>),
+ /// `< k1 = t1, k2 : t2, k3 >`
+ UnionLit(Label, SubExpr, BTreeMap<Label, Option<SubExpr>>),
+ /// `merge x y : t`
+ Merge(SubExpr, SubExpr, Option<SubExpr>),
+ /// `e.x`
+ Field(SubExpr, Label),
+ /// `e.{ x, y, z }`
+ Projection(SubExpr, Vec<Label>),
+ /// Annotation on the AST. Unused for now but could hold e.g. file location information
+ Note(Note, SubExpr),
+ /// Embeds an import or the result of resolving the import
+ Embed(Embed),
+}
+
+impl<SE, L, N, E> ExprF<SE, L, N, E> {
+ pub(crate) fn visit<'a, V, Return>(&'a self, v: V) -> Return
+ where
+ V: visitor::GenericVisitor<&'a ExprF<SE, L, N, E>, Return>,
+ {
+ v.visit(self)
+ }
+
+ fn traverse_ref_with_special_handling_of_binders<'a, SE2, L2, N2, E2, Err>(
+ &'a self,
+ visit_subexpr: impl FnMut(&'a SE) -> Result<SE2, Err>,
+ visit_under_binder: impl FnOnce(&'a L, &'a SE) -> Result<SE2, Err>,
+ visit_note: impl FnOnce(&'a N) -> Result<N2, Err>,
+ visit_embed: impl FnOnce(&'a E) -> Result<E2, Err>,
+ visit_label: impl FnMut(&'a L) -> Result<L2, Err>,
+ ) -> Result<ExprF<SE2, L2, N2, E2>, Err>
+ where
+ L: Ord,
+ L2: Ord,
+ {
+ self.visit(visitor::TraverseRefWithBindersVisitor {
+ visit_subexpr,
+ visit_under_binder,
+ visit_note,
+ visit_embed,
+ visit_label,
+ })
+ }
+
+ fn traverse_ref<'a, SE2, L2, N2, E2, Err>(
+ &'a self,
+ visit_subexpr: impl FnMut(&'a SE) -> Result<SE2, Err>,
+ visit_note: impl FnOnce(&'a N) -> Result<N2, Err>,
+ visit_embed: impl FnOnce(&'a E) -> Result<E2, Err>,
+ visit_label: impl FnMut(&'a L) -> Result<L2, Err>,
+ ) -> Result<ExprF<SE2, L2, N2, E2>, Err>
+ where
+ L: Ord,
+ L2: Ord,
+ {
+ self.visit(visitor::TraverseRefVisitor {
+ visit_subexpr,
+ visit_note,
+ visit_embed,
+ visit_label,
+ })
+ }
+
+ pub fn map_ref_with_special_handling_of_binders<'a, SE2, L2, N2, E2>(
+ &'a self,
+ mut map_subexpr: impl FnMut(&'a SE) -> SE2,
+ mut map_under_binder: impl FnMut(&'a L, &'a SE) -> SE2,
+ map_note: impl FnOnce(&'a N) -> N2,
+ map_embed: impl FnOnce(&'a E) -> E2,
+ mut map_label: impl FnMut(&'a L) -> L2,
+ ) -> ExprF<SE2, L2, N2, E2>
+ where
+ L: Ord,
+ L2: Ord,
+ {
+ trivial_result(self.traverse_ref_with_special_handling_of_binders(
+ |x| Ok(map_subexpr(x)),
+ |l, x| Ok(map_under_binder(l, x)),
+ |x| Ok(map_note(x)),
+ |x| Ok(map_embed(x)),
+ |x| Ok(map_label(x)),
+ ))
+ }
+
+ pub fn map_ref<'a, SE2, L2, N2, E2>(
+ &'a self,
+ mut map_subexpr: impl FnMut(&'a SE) -> SE2,
+ map_note: impl FnOnce(&'a N) -> N2,
+ map_embed: impl FnOnce(&'a E) -> E2,
+ mut map_label: impl FnMut(&'a L) -> L2,
+ ) -> ExprF<SE2, L2, N2, E2>
+ where
+ L: Ord,
+ L2: Ord,
+ {
+ trivial_result(self.traverse_ref(
+ |x| Ok(map_subexpr(x)),
+ |x| Ok(map_note(x)),
+ |x| Ok(map_embed(x)),
+ |x| Ok(map_label(x)),
+ ))
+ }
+
+ pub fn traverse_ref_simple<'a, SE2, Err>(
+ &'a self,
+ visit_subexpr: impl FnMut(&'a SE) -> Result<SE2, Err>,
+ ) -> Result<ExprF<SE2, L, N, E>, Err>
+ where
+ L: Ord + Clone,
+ N: Clone,
+ E: Clone,
+ {
+ self.traverse_ref(
+ visit_subexpr,
+ |x| Ok(N::clone(x)),
+ |x| Ok(E::clone(x)),
+ |x| Ok(L::clone(x)),
+ )
+ }
+
+ pub fn map_ref_simple<'a, SE2>(
+ &'a self,
+ map_subexpr: impl Fn(&'a SE) -> SE2,
+ ) -> ExprF<SE2, L, N, E>
+ where
+ L: Ord + Clone,
+ N: Clone,
+ E: Clone,
+ {
+ self.map_ref(map_subexpr, N::clone, E::clone, L::clone)
+ }
+}
+
+impl<N, E> Expr<N, E> {
+ fn traverse_embed<E2, Err>(
+ &self,
+ visit_embed: impl FnMut(&E) -> Result<E2, Err>,
+ ) -> Result<Expr<N, E2>, Err>
+ where
+ N: Clone,
+ {
+ self.visit(&mut visitor::TraverseEmbedVisitor(visit_embed))
+ }
+
+ fn map_embed<E2>(&self, mut map_embed: impl FnMut(&E) -> E2) -> Expr<N, E2>
+ where
+ N: Clone,
+ {
+ trivial_result(self.traverse_embed(|x| Ok(map_embed(x))))
+ }
+
+ pub fn roll(&self) -> SubExpr<N, E>
+ where
+ N: Clone,
+ E: Clone,
+ {
+ rc(ExprF::clone(self))
+ }
+
+ pub fn squash_embed<E2>(
+ &self,
+ f: impl FnMut(&E) -> SubExpr<N, E2>,
+ ) -> SubExpr<N, E2>
+ where
+ N: Clone,
+ {
+ trivial_result(self.visit(&mut visitor::SquashEmbedVisitor(f)))
+ }
+}
+
+impl<E: Clone> Expr<X, E> {
+ pub fn note_absurd<N>(&self) -> Expr<N, E> {
+ self.visit(&mut visitor::NoteAbsurdVisitor)
+ }
+}
+
+impl<N: Clone> Expr<N, X> {
+ pub fn embed_absurd<E>(&self) -> Expr<N, E> {
+ self.visit(&mut visitor::EmbedAbsurdVisitor)
+ }
+}
+
+impl<N, E> SubExpr<N, E> {
+ pub fn as_ref(&self) -> &Expr<N, E> {
+ self.0.as_ref()
+ }
+
+ pub fn traverse_embed<E2, Err>(
+ &self,
+ visit_embed: impl FnMut(&E) -> Result<E2, Err>,
+ ) -> Result<SubExpr<N, E2>, Err>
+ where
+ N: Clone,
+ {
+ Ok(rc(self.as_ref().traverse_embed(visit_embed)?))
+ }
+
+ pub fn map_embed<E2>(
+ &self,
+ map_embed: impl FnMut(&E) -> E2,
+ ) -> SubExpr<N, E2>
+ where
+ N: Clone,
+ {
+ rc(self.as_ref().map_embed(map_embed))
+ }
+
+ pub fn map_subexprs_with_special_handling_of_binders<'a>(
+ &'a self,
+ map_expr: impl FnMut(&'a Self) -> Self,
+ map_under_binder: impl FnMut(&'a Label, &'a Self) -> Self,
+ ) -> Self {
+ match self.as_ref() {
+ ExprF::Embed(_) => SubExpr::clone(self),
+ // Recursive call
+ // TODO: don't discard the note !
+ ExprF::Note(_, e) => e
+ .map_subexprs_with_special_handling_of_binders(
+ map_expr,
+ map_under_binder,
+ ),
+ // Call ExprF::map_ref
+ e => rc(e.map_ref_with_special_handling_of_binders(
+ map_expr,
+ map_under_binder,
+ |_| unreachable!(),
+ |_| unreachable!(),
+ Label::clone,
+ )),
+ }
+ }
+
+ pub fn unroll(&self) -> Expr<N, E>
+ where
+ N: Clone,
+ E: Clone,
+ {
+ ExprF::clone(self.as_ref())
+ }
+
+ pub fn unnote(&self) -> SubExpr<X, E>
+ where
+ E: Clone,
+ {
+ rc(self.as_ref().visit(&mut visitor::UnNoteVisitor))
+ }
+
+ /// `shift` is used by both normalization and type-checking to avoid variable
+ /// capture by shifting variable indices
+ /// See https://github.com/dhall-lang/dhall-lang/blob/master/standard/semantics.md#shift
+ /// for details
+ pub fn shift(&self, delta: isize, var: &V<Label>) -> Self {
+ match self.as_ref() {
+ ExprF::Var(v) => rc(ExprF::Var(v.shift(delta, var))),
+ _ => self.map_subexprs_with_special_handling_of_binders(
+ |e| e.shift(delta, var),
+ |x: &Label, e| e.shift(delta, &var.shift(1, &x.into())),
+ ),
+ }
+ }
+
+ pub fn subst_shift(&self, var: &V<Label>, val: &Self) -> Self {
+ match self.as_ref() {
+ ExprF::Var(v) if v == var => val.clone(),
+ ExprF::Var(v) => rc(ExprF::Var(v.shift(-1, var))),
+ _ => self.map_subexprs_with_special_handling_of_binders(
+ |e| e.subst_shift(var, val),
+ |x: &Label, e| {
+ e.subst_shift(
+ &var.shift(1, &x.into()),
+ &val.shift(1, &x.into()),
+ )
+ },
+ ),
+ }
+ }
+}
+
+impl<N: Clone> SubExpr<N, X> {
+ pub fn embed_absurd<T>(&self) -> SubExpr<N, T> {
+ rc(self.as_ref().embed_absurd())
+ }
+}
+
+impl<E: Clone> SubExpr<X, E> {
+ pub fn note_absurd<N>(&self) -> SubExpr<N, E> {
+ rc(self.as_ref().note_absurd())
+ }
+}
+
+impl<N, E> Clone for SubExpr<N, E> {
+ fn clone(&self) -> Self {
+ SubExpr(Rc::clone(&self.0))
+ }
+}
+
+// Should probably rename this
+pub fn rc<N, E>(x: Expr<N, E>) -> SubExpr<N, E> {
+ SubExpr(Rc::new(x))
+}
+
+/// Add an isize to an usize
+/// Panics on over/underflow
+fn add_ui(u: usize, i: isize) -> usize {
+ if i < 0 {
+ u.checked_sub(i.checked_neg().unwrap() as usize).unwrap()
+ } else {
+ u.checked_add(i as usize).unwrap()
+ }
+}
+
+impl<Label: PartialEq + Clone> V<Label> {
+ pub fn shift(&self, delta: isize, var: &V<Label>) -> Self {
+ let V(x, n) = var;
+ let V(y, m) = self;
+ if x == y && n <= m {
+ V(y.clone(), add_ui(*m, delta))
+ } else {
+ V(y.clone(), *m)
+ }
+ }
+}
+
+/// `shift` is used by both normalization and type-checking to avoid variable
+/// capture by shifting variable indices
+/// See https://github.com/dhall-lang/dhall-lang/blob/master/standard/semantics.md#shift
+/// for details
+///
+pub fn shift<N, E>(
+ delta: isize,
+ var: &V<Label>,
+ in_expr: &SubExpr<N, E>,
+) -> SubExpr<N, E> {
+ in_expr.shift(delta, var)
+}
+
+pub fn shift0_multiple<N, E>(
+ deltas: &HashMap<Label, isize>,
+ in_expr: &SubExpr<N, E>,
+) -> SubExpr<N, E> {
+ shift0_multiple_inner(&Context::new(), deltas, in_expr)
+}
+
+fn shift0_multiple_inner<N, E>(
+ ctx: &Context<Label, ()>,
+ deltas: &HashMap<Label, isize>,
+ in_expr: &SubExpr<N, E>,
+) -> SubExpr<N, E> {
+ use crate::ExprF::*;
+ match in_expr.as_ref() {
+ Var(V(y, m)) if ctx.lookup(y, *m).is_none() => {
+ let delta = deltas.get(y).unwrap_or(&0);
+ rc(Var(V(y.clone(), add_ui(*m, *delta))))
+ }
+ _ => in_expr.map_subexprs_with_special_handling_of_binders(
+ |e| shift0_multiple_inner(ctx, deltas, e),
+ |x: &Label, e| {
+ shift0_multiple_inner(&ctx.insert(x.clone(), ()), deltas, e)
+ },
+ ),
+ }
+}
diff --git a/dhall_syntax/src/import.rs b/dhall_syntax/src/import.rs
new file mode 100644
index 0000000..00f293c
--- /dev/null
+++ b/dhall_syntax/src/import.rs
@@ -0,0 +1,64 @@
+use std::path::PathBuf;
+
+/// The beginning of a file path which anchors subsequent path components
+#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
+pub enum FilePrefix {
+ /// Absolute path
+ Absolute,
+ /// Path relative to .
+ Here,
+ /// Path relative to ..
+ Parent,
+ /// Path relative to ~
+ Home,
+}
+
+/// The location of import (i.e. local vs. remote vs. environment)
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub enum ImportLocation {
+ Local(FilePrefix, PathBuf),
+ Remote(URL),
+ Env(String),
+ Missing,
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct URL {
+ pub scheme: Scheme,
+ pub authority: String,
+ pub path: PathBuf,
+ pub query: Option<String>,
+ pub headers: Option<Box<ImportHashed>>,
+}
+
+#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
+pub enum Scheme {
+ HTTP,
+ HTTPS,
+}
+
+/// How to interpret the import's contents (i.e. as Dhall code or raw text)
+#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
+pub enum ImportMode {
+ Code,
+ RawText,
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct Hash {
+ pub protocol: String,
+ pub hash: String,
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct ImportHashed {
+ pub location: ImportLocation,
+ pub hash: Option<Hash>,
+}
+
+/// Reference to an external resource
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct Import {
+ pub mode: ImportMode,
+ pub location_hashed: ImportHashed,
+}
diff --git a/dhall_syntax/src/label.rs b/dhall_syntax/src/label.rs
new file mode 100644
index 0000000..43c3f53
--- /dev/null
+++ b/dhall_syntax/src/label.rs
@@ -0,0 +1,34 @@
+use std::rc::Rc;
+
+// The type for labels throughout the AST
+// It owns the data because otherwise lifetimes would make recursive imports impossible
+#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
+pub struct Label(Rc<str>);
+
+impl From<String> for Label {
+ fn from(s: String) -> Self {
+ let s: &str = &s;
+ Label(s.into())
+ }
+}
+
+impl<'a> From<&'a str> for Label {
+ fn from(s: &'a str) -> Self {
+ Label(Rc::from(s))
+ }
+}
+
+impl From<&Label> for String {
+ fn from(x: &Label) -> String {
+ x.0.as_ref().to_owned()
+ }
+}
+
+impl Label {
+ pub fn from_str(s: &str) -> Label {
+ Label(s.into())
+ }
+ pub fn as_ref(&self) -> &str {
+ self.0.as_ref()
+ }
+}
diff --git a/dhall_syntax/src/lib.rs b/dhall_syntax/src/lib.rs
new file mode 100644
index 0000000..3db8222
--- /dev/null
+++ b/dhall_syntax/src/lib.rs
@@ -0,0 +1,28 @@
+#![feature(trace_macros)]
+#![feature(slice_patterns)]
+#![allow(
+ clippy::many_single_char_names,
+ clippy::should_implement_trait,
+ clippy::new_without_default,
+ clippy::type_complexity
+)]
+
+//! This crate contains the core AST-handling primitives for the [dhall-rust][dhall-rust] crate.
+//! This is highly unstable and breaks regularly; use at your own risk.
+//!
+//! [dhall-rust]: https://github.com/Nadrieril/dhall-rust
+
+mod core;
+pub use crate::core::*;
+mod import;
+pub use crate::import::*;
+mod label;
+pub use crate::label::*;
+mod text;
+pub use crate::text::*;
+mod printer;
+pub use crate::printer::*;
+mod parser;
+pub use crate::parser::*;
+pub mod context;
+pub mod visitor;
diff --git a/dhall_syntax/src/parser.rs b/dhall_syntax/src/parser.rs
new file mode 100644
index 0000000..12383d4
--- /dev/null
+++ b/dhall_syntax/src/parser.rs
@@ -0,0 +1,984 @@
+use itertools::Itertools;
+use pest::iterators::Pair;
+use pest::Parser;
+pub use pest::Span;
+use std::borrow::Cow;
+use std::collections::BTreeMap;
+use std::path::PathBuf;
+
+use dhall_generated_parser::{DhallParser, Rule};
+
+use crate::*;
+
+// This file consumes the parse tree generated by pest and turns it into
+// our own AST. All those custom macros should eventually moved into
+// their own crate because they are quite general and useful. For now they
+// are here and hopefully you can figure out how they work.
+
+use crate::ExprF::*;
+
+type ParsedExpr<'a> = Expr<Span<'a>, Import>;
+type ParsedSubExpr<'a> = SubExpr<Span<'a>, Import>;
+type ParsedText<'a> = InterpolatedText<SubExpr<Span<'a>, Import>>;
+type ParsedTextContents<'a> =
+ InterpolatedTextContents<SubExpr<Span<'a>, Import>>;
+
+pub type ParseError = pest::error::Error<Rule>;
+
+pub type ParseResult<T> = Result<T, ParseError>;
+
+fn rc(x: ParsedExpr<'_>) -> ParsedSubExpr<'_> {
+ crate::rc(x)
+}
+
+fn spanned<'a>(_span: Span<'a>, x: ParsedExpr<'a>) -> ParsedExpr<'a> {
+ x
+ // This breaks equality testing; I need to fix that first
+ // Note(span, rc(x))
+}
+
+#[derive(Debug)]
+enum Either<A, B> {
+ Left(A),
+ Right(B),
+}
+
+impl crate::Builtin {
+ pub fn parse(s: &str) -> Option<Self> {
+ use crate::Builtin::*;
+ match s {
+ "Bool" => Some(Bool),
+ "Natural" => Some(Natural),
+ "Integer" => Some(Integer),
+ "Double" => Some(Double),
+ "Text" => Some(Text),
+ "List" => Some(List),
+ "Optional" => Some(Optional),
+ "None" => Some(OptionalNone),
+ "Natural/build" => Some(NaturalBuild),
+ "Natural/fold" => Some(NaturalFold),
+ "Natural/isZero" => Some(NaturalIsZero),
+ "Natural/even" => Some(NaturalEven),
+ "Natural/odd" => Some(NaturalOdd),
+ "Natural/toInteger" => Some(NaturalToInteger),
+ "Natural/show" => Some(NaturalShow),
+ "Integer/toDouble" => Some(IntegerToDouble),
+ "Integer/show" => Some(IntegerShow),
+ "Double/show" => Some(DoubleShow),
+ "List/build" => Some(ListBuild),
+ "List/fold" => Some(ListFold),
+ "List/length" => Some(ListLength),
+ "List/head" => Some(ListHead),
+ "List/last" => Some(ListLast),
+ "List/indexed" => Some(ListIndexed),
+ "List/reverse" => Some(ListReverse),
+ "Optional/fold" => Some(OptionalFold),
+ "Optional/build" => Some(OptionalBuild),
+ "Text/show" => Some(TextShow),
+ _ => None,
+ }
+ }
+}
+
+pub fn custom_parse_error(pair: &Pair<Rule>, msg: String) -> ParseError {
+ let msg =
+ format!("{} while matching on:\n{}", msg, debug_pair(pair.clone()));
+ let e = pest::error::ErrorVariant::CustomError { message: msg };
+ pest::error::Error::new_from_span(e, pair.as_span())
+}
+
+fn debug_pair(pair: Pair<Rule>) -> String {
+ use std::fmt::Write;
+ let mut s = String::new();
+ fn aux(s: &mut String, indent: usize, prefix: String, pair: Pair<Rule>) {
+ let indent_str = "| ".repeat(indent);
+ let rule = pair.as_rule();
+ let contents = pair.as_str();
+ let mut inner = pair.into_inner();
+ let mut first = true;
+ while let Some(p) = inner.next() {
+ if first {
+ first = false;
+ let last = inner.peek().is_none();
+ if last && p.as_str() == contents {
+ let prefix = format!("{}{:?} > ", prefix, rule);
+ aux(s, indent, prefix, p);
+ continue;
+ } else {
+ writeln!(
+ s,
+ r#"{}{}{:?}: "{}""#,
+ indent_str, prefix, rule, contents
+ )
+ .unwrap();
+ }
+ }
+ aux(s, indent + 1, "".into(), p);
+ }
+ if first {
+ writeln!(
+ s,
+ r#"{}{}{:?}: "{}""#,
+ indent_str, prefix, rule, contents
+ )
+ .unwrap();
+ }
+ }
+ aux(&mut s, 0, "".into(), pair);
+ s
+}
+
+macro_rules! make_parser {
+ (@pattern, rule, $name:ident) => (Rule::$name);
+ (@pattern, token_rule, $name:ident) => (Rule::$name);
+ (@pattern, rule_group, $name:ident) => (_);
+ (@filter, rule) => (true);
+ (@filter, token_rule) => (true);
+ (@filter, rule_group) => (false);
+
+ (@body,
+ $pair:expr,
+ $children:expr,
+ rule!( $name:ident<$o:ty>; $($args:tt)* )
+ ) => (
+ make_parser!(@body,
+ $pair,
+ $children,
+ rule!( $name<$o> as $name; $($args)* )
+ )
+ );
+ (@body,
+ $pair:expr,
+ $children:expr,
+ rule!(
+ $name:ident<$o:ty>
+ as $group:ident;
+ captured_str!($x:pat) => $body:expr
+ )
+ ) => ({
+ let $x = $pair.as_str();
+ let res: $o = $body;
+ Ok(ParsedValue::$group(res))
+ });
+ (@body,
+ $pair:expr,
+ $children:expr,
+ rule!(
+ $name:ident<$o:ty>
+ as $group:ident;
+ children!( $( [$($args:tt)*] => $body:expr ),* $(,)* )
+ )
+ ) => ({
+ #[allow(unused_imports)]
+ use ParsedValue::*;
+ #[allow(unreachable_code)]
+ let res: $o = improved_slice_patterns::match_vec!($children;
+ $( [$($args)*] => $body, )*
+ [x..] => Err(
+ format!("Unexpected children: {:?}", x.collect::<Vec<_>>())
+ )?,
+ ).map_err(|_| -> String { unreachable!() })?;
+ Ok(ParsedValue::$group(res))
+ });
+ (@body,
+ $pair:expr,
+ $children:expr,
+ rule!(
+ $name:ident<$o:ty>
+ as $group:ident;
+ $span:ident;
+ $($args:tt)*
+ )
+ ) => ({
+ let $span = $pair.as_span();
+ make_parser!(@body,
+ $pair,
+ $children,
+ rule!(
+ $name<$o>
+ as $group;
+ $($args)*
+ )
+ )
+ });
+ (@body,
+ $pair:expr,
+ $children:expr,
+ token_rule!($name:ident<$o:ty>)
+ ) => ({
+ Ok(ParsedValue::$name(()))
+ });
+ (@body, $pair:expr, $children:expr, rule_group!( $name:ident<$o:ty> )) => (
+ unreachable!()
+ );
+
+ ($( $submac:ident!( $name:ident<$o:ty> $($args:tt)* ); )*) => (
+ #[allow(non_camel_case_types, dead_code, clippy::large_enum_variant)]
+ #[derive(Debug)]
+ enum ParsedValue<'a> {
+ $( $name($o), )*
+ }
+
+ fn parse_any<'a>(pair: Pair<'a, Rule>, children: Vec<ParsedValue<'a>>)
+ -> Result<ParsedValue<'a>, String> {
+ match pair.as_rule() {
+ $(
+ make_parser!(@pattern, $submac, $name)
+ if make_parser!(@filter, $submac)
+ => make_parser!(@body, pair, children,
+ $submac!( $name<$o> $($args)* ))
+ ,
+ )*
+ r => Err(format!("Unexpected {:?}", r)),
+ }
+ }
+ );
+}
+
+// Non-recursive implementation to avoid stack overflows
+fn do_parse<'a>(initial_pair: Pair<'a, Rule>) -> ParseResult<ParsedValue<'a>> {
+ enum StackFrame<'a> {
+ Unprocessed(Pair<'a, Rule>),
+ Processed(Pair<'a, Rule>, usize),
+ }
+ use StackFrame::*;
+ let mut pairs_stack: Vec<StackFrame> =
+ vec![Unprocessed(initial_pair.clone())];
+ let mut values_stack: Vec<ParsedValue> = vec![];
+ while let Some(p) = pairs_stack.pop() {
+ match p {
+ Unprocessed(mut pair) => loop {
+ let mut pairs: Vec<_> = pair.clone().into_inner().collect();
+ let n_children = pairs.len();
+ if n_children == 1 && can_be_shortcutted(pair.as_rule()) {
+ pair = pairs.pop().unwrap();
+ continue;
+ } else {
+ pairs_stack.push(Processed(pair, n_children));
+ pairs_stack
+ .extend(pairs.into_iter().map(StackFrame::Unprocessed));
+ break;
+ }
+ },
+ Processed(pair, n) => {
+ let mut children: Vec<_> =
+ values_stack.split_off(values_stack.len() - n);
+ children.reverse();
+ let val = match parse_any(pair.clone(), children) {
+ Ok(v) => v,
+ Err(msg) => Err(custom_parse_error(&pair, msg))?,
+ };
+ values_stack.push(val);
+ }
+ }
+ }
+ Ok(values_stack.pop().unwrap())
+}
+
+// List of rules that can be shortcutted if they have a single child
+fn can_be_shortcutted(rule: Rule) -> bool {
+ use Rule::*;
+ match rule {
+ expression
+ | import_alt_expression
+ | or_expression
+ | plus_expression
+ | text_append_expression
+ | list_append_expression
+ | and_expression
+ | combine_expression
+ | prefer_expression
+ | combine_types_expression
+ | times_expression
+ | equal_expression
+ | not_equal_expression
+ | application_expression
+ | first_application_expression
+ | selector_expression
+ | annotated_expression => true,
+ _ => false,
+ }
+}
+
+make_parser! {
+ token_rule!(EOI<()>);
+
+ rule!(simple_label<Label>;
+ captured_str!(s) => Label::from(s.trim().to_owned())
+ );
+ rule!(quoted_label<Label>;
+ captured_str!(s) => Label::from(s.trim().to_owned())
+ );
+ rule!(label<Label>; children!(
+ [simple_label(l)] => l,
+ [quoted_label(l)] => l,
+ ));
+
+ rule!(double_quote_literal<ParsedText<'a>>; children!(
+ [double_quote_chunk(chunks)..] => {
+ chunks.collect()
+ }
+ ));
+
+ rule!(double_quote_chunk<ParsedTextContents<'a>>; children!(
+ [interpolation(e)] => {
+ InterpolatedTextContents::Expr(rc(e))
+ },
+ [double_quote_escaped(s)] => {
+ InterpolatedTextContents::Text(s)
+ },
+ [double_quote_char(s)] => {
+ InterpolatedTextContents::Text(s.to_owned())
+ },
+ ));
+ rule!(double_quote_escaped<String>;
+ captured_str!(s) => {
+ match s {
+ "\"" => "\"".to_owned(),
+ "$" => "$".to_owned(),
+ "\\" => "\\".to_owned(),
+ "/" => "/".to_owned(),
+ "b" => "\u{0008}".to_owned(),
+ "f" => "\u{000C}".to_owned(),
+ "n" => "\n".to_owned(),
+ "r" => "\r".to_owned(),
+ "t" => "\t".to_owned(),
+ _ => {
+ // "uXXXX"
+ use std::convert::TryFrom;
+ let c = u16::from_str_radix(&s[1..5], 16).unwrap();
+ let c = char::try_from(u32::from(c)).unwrap();
+ std::iter::once(c).collect()
+ }
+ }
+ }
+ );
+ rule!(double_quote_char<&'a str>;
+ captured_str!(s) => s
+ );
+
+ rule!(single_quote_literal<ParsedText<'a>>; children!(
+ [single_quote_continue(lines)] => {
+ let space = InterpolatedTextContents::Text(" ".to_owned());
+ let newline = InterpolatedTextContents::Text("\n".to_owned());
+ let min_indent = lines
+ .iter()
+ .map(|l| {
+ l.iter().rev().take_while(|c| **c == space).count()
+ })
+ .min()
+ .unwrap();
+
+ lines
+ .into_iter()
+ .rev()
+ .map(|mut l| { l.split_off(l.len() - min_indent); l })
+ .intersperse(vec![newline])
+ .flat_map(|x| x.into_iter().rev())
+ .collect::<ParsedText>()
+ }
+ ));
+ rule!(single_quote_char<&'a str>;
+ captured_str!(s) => s
+ );
+ rule!(escaped_quote_pair<&'a str>;
+ captured_str!(_) => "''"
+ );
+ rule!(escaped_interpolation<&'a str>;
+ captured_str!(_) => "${"
+ );
+ rule!(interpolation<ParsedExpr<'a>>; children!(
+ [expression(e)] => e
+ ));
+
+ rule!(single_quote_continue<Vec<Vec<ParsedTextContents<'a>>>>; children!(
+ [interpolation(c), single_quote_continue(lines)] => {
+ let c = InterpolatedTextContents::Expr(rc(c));
+ let mut lines = lines;
+ lines.last_mut().unwrap().push(c);
+ lines
+ },
+ [escaped_quote_pair(c), single_quote_continue(lines)] => {
+ let c = InterpolatedTextContents::Text(c.to_owned());
+ let mut lines = lines;
+ lines.last_mut().unwrap().push(c);
+ lines
+ },
+ [escaped_interpolation(c), single_quote_continue(lines)] => {
+ let c = InterpolatedTextContents::Text(c.to_owned());
+ let mut lines = lines;
+ lines.last_mut().unwrap().push(c);
+ lines
+ },
+ [single_quote_char("\n"), single_quote_continue(lines)] => {
+ let mut lines = lines;
+ lines.push(vec![]);
+ lines
+ },
+ [single_quote_char(c), single_quote_continue(lines)] => {
+ let c = InterpolatedTextContents::Text(c.to_owned());
+ let mut lines = lines;
+ lines.last_mut().unwrap().push(c);
+ lines
+ },
+ [] => {
+ vec![vec![]]
+ },
+ ));
+
+ rule!(builtin<ParsedExpr<'a>>; span;
+ captured_str!(s) => {
+ spanned(span, match crate::Builtin::parse(s) {
+ Some(b) => Builtin(b),
+ None => match s {
+ "True" => BoolLit(true),
+ "False" => BoolLit(false),
+ "Type" => Const(crate::Const::Type),
+ "Kind" => Const(crate::Const::Kind),
+ "Sort" => Const(crate::Const::Sort),
+ _ => Err(
+ format!("Unrecognized builtin: '{}'", s)
+ )?,
+ }
+ })
+ }
+ );
+
+ token_rule!(NaN<()>);
+ token_rule!(minus_infinity_literal<()>);
+ token_rule!(plus_infinity_literal<()>);
+
+ rule!(numeric_double_literal<core::Double>;
+ captured_str!(s) => {
+ let s = s.trim();
+ match s.parse::<f64>() {
+ Ok(x) if x.is_infinite() =>
+ Err(format!("Overflow while parsing double literal '{}'", s))?,
+ Ok(x) => NaiveDouble::from(x),
+ Err(e) => Err(format!("{}", e))?,
+ }
+ }
+ );
+
+ rule!(double_literal<core::Double>; children!(
+ [numeric_double_literal(n)] => n,
+ [minus_infinity_literal(n)] => std::f64::NEG_INFINITY.into(),
+ [plus_infinity_literal(n)] => std::f64::INFINITY.into(),
+ [NaN(n)] => std::f64::NAN.into(),
+ ));
+
+ rule!(natural_literal<core::Natural>;
+ captured_str!(s) => {
+ s.trim()
+ .parse()
+ .map_err(|e| format!("{}", e))?
+ }
+ );
+
+ rule!(integer_literal<core::Integer>;
+ captured_str!(s) => {
+ s.trim()
+ .parse()
+ .map_err(|e| format!("{}", e))?
+ }
+ );
+
+ rule!(identifier<ParsedExpr<'a>> as expression; span; children!(
+ [variable(v)] => {
+ spanned(span, Var(v))
+ },
+ [builtin(e)] => e,
+ ));
+
+ rule!(variable<V<Label>>; children!(
+ [label(l), natural_literal(idx)] => {
+ V(l, idx)
+ },
+ [label(l)] => {
+ V(l, 0)
+ },
+ ));
+
+ rule!(unquoted_path_component<&'a str>; captured_str!(s) => s);
+ rule!(quoted_path_component<&'a str>; captured_str!(s) => s);
+ rule!(path_component<String>; children!(
+ [unquoted_path_component(s)] => {
+ percent_encoding::percent_decode(s.as_bytes())
+ .decode_utf8_lossy()
+ .into_owned()
+ },
+ [quoted_path_component(s)] => s.to_string(),
+ ));
+ rule!(path<PathBuf>; children!(
+ [path_component(components)..] => {
+ components.collect()
+ }
+ ));
+
+ rule_group!(local<(FilePrefix, PathBuf)>);
+
+ rule!(parent_path<(FilePrefix, PathBuf)> as local; children!(
+ [path(p)] => (FilePrefix::Parent, p)
+ ));
+ rule!(here_path<(FilePrefix, PathBuf)> as local; children!(
+ [path(p)] => (FilePrefix::Here, p)
+ ));
+ rule!(home_path<(FilePrefix, PathBuf)> as local; children!(
+ [path(p)] => (FilePrefix::Home, p)
+ ));
+ rule!(absolute_path<(FilePrefix, PathBuf)> as local; children!(
+ [path(p)] => (FilePrefix::Absolute, p)
+ ));
+
+ rule!(scheme<Scheme>; captured_str!(s) => match s {
+ "http" => Scheme::HTTP,
+ "https" => Scheme::HTTPS,
+ _ => unreachable!(),
+ });
+
+ rule!(http_raw<URL>; children!(
+ [scheme(sch), authority(auth), path(p)] => URL {
+ scheme: sch,
+ authority: auth,
+ path: p,
+ query: None,
+ headers: None,
+ },
+ [scheme(sch), authority(auth), path(p), query(q)] => URL {
+ scheme: sch,
+ authority: auth,
+ path: p,
+ query: Some(q),
+ headers: None,
+ },
+ ));
+
+ rule!(authority<String>; captured_str!(s) => s.to_owned());
+
+ rule!(query<String>; captured_str!(s) => s.to_owned());
+
+ rule!(http<URL>; children!(
+ [http_raw(url)] => url,
+ [http_raw(url), import_hashed(ih)] =>
+ URL { headers: Some(Box::new(ih)), ..url },
+ ));
+
+ rule!(env<String>; children!(
+ [bash_environment_variable(s)] => s,
+ [posix_environment_variable(s)] => s,
+ ));
+ rule!(bash_environment_variable<String>; captured_str!(s) => s.to_owned());
+ rule!(posix_environment_variable<String>; children!(
+ [posix_environment_variable_character(chars)..] => {
+ chars.collect()
+ },
+ ));
+ rule!(posix_environment_variable_character<Cow<'a, str>>;
+ captured_str!(s) => {
+ match s {
+ "\\\"" => Cow::Owned("\"".to_owned()),
+ "\\\\" => Cow::Owned("\\".to_owned()),
+ "\\a" => Cow::Owned("\u{0007}".to_owned()),
+ "\\b" => Cow::Owned("\u{0008}".to_owned()),
+ "\\f" => Cow::Owned("\u{000C}".to_owned()),
+ "\\n" => Cow::Owned("\n".to_owned()),
+ "\\r" => Cow::Owned("\r".to_owned()),
+ "\\t" => Cow::Owned("\t".to_owned()),
+ "\\v" => Cow::Owned("\u{000B}".to_owned()),
+ _ => Cow::Borrowed(s)
+ }
+ }
+ );
+
+ token_rule!(missing<()>);
+
+ rule!(import_type<ImportLocation>; children!(
+ [missing(_)] => {
+ ImportLocation::Missing
+ },
+ [env(e)] => {
+ ImportLocation::Env(e)
+ },
+ [http(url)] => {
+ ImportLocation::Remote(url)
+ },
+ [local((prefix, p))] => {
+ ImportLocation::Local(prefix, p)
+ },
+ ));
+
+ rule!(hash<Hash>; captured_str!(s) =>
+ Hash {
+ protocol: s.trim()[..6].to_owned(),
+ hash: s.trim()[7..].to_owned(),
+ }
+ );
+
+ rule!(import_hashed<ImportHashed>; children!(
+ [import_type(location)] =>
+ ImportHashed { location, hash: None },
+ [import_type(location), hash(h)] =>
+ ImportHashed { location, hash: Some(h) },
+ ));
+
+ token_rule!(Text<()>);
+
+ rule!(import<ParsedExpr<'a>> as expression; span; children!(
+ [import_hashed(location_hashed)] => {
+ spanned(span, Embed(Import {
+ mode: ImportMode::Code,
+ location_hashed
+ }))
+ },
+ [import_hashed(location_hashed), Text(_)] => {
+ spanned(span, Embed(Import {
+ mode: ImportMode::RawText,
+ location_hashed
+ }))
+ },
+ ));
+
+ token_rule!(lambda<()>);
+ token_rule!(forall<()>);
+ token_rule!(arrow<()>);
+ token_rule!(merge<()>);
+ token_rule!(if_<()>);
+ token_rule!(in_<()>);
+
+ rule!(expression<ParsedExpr<'a>> as expression; span; children!(
+ [lambda(()), label(l), expression(typ),
+ arrow(()), expression(body)] => {
+ spanned(span, Lam(l, rc(typ), rc(body)))
+ },
+ [if_(()), expression(cond), expression(left), expression(right)] => {
+ spanned(span, BoolIf(rc(cond), rc(left), rc(right)))
+ },
+ [let_binding(bindings).., in_(()), expression(final_expr)] => {
+ bindings.rev().fold(
+ final_expr,
+ |acc, x| Let(x.0, x.1, x.2, rc(acc))
+ )
+ },
+ [forall(()), label(l), expression(typ),
+ arrow(()), expression(body)] => {
+ spanned(span, Pi(l, rc(typ), rc(body)))
+ },
+ [expression(typ), arrow(()), expression(body)] => {
+ spanned(span, Pi("_".into(), rc(typ), rc(body)))
+ },
+ [merge(()), expression(x), expression(y), expression(z)] => {
+ spanned(span, Merge(rc(x), rc(y), Some(rc(z))))
+ },
+ [expression(e)] => e,
+ ));
+
+ rule!(let_binding<(Label, Option<ParsedSubExpr<'a>>, ParsedSubExpr<'a>)>;
+ children!(
+ [label(name), expression(annot), expression(expr)] =>
+ (name, Some(rc(annot)), rc(expr)),
+ [label(name), expression(expr)] =>
+ (name, None, rc(expr)),
+ ));
+
+ token_rule!(List<()>);
+ token_rule!(Optional<()>);
+
+ rule!(empty_collection<ParsedExpr<'a>> as expression; span; children!(
+ [List(_), expression(t)] => {
+ spanned(span, EmptyListLit(rc(t)))
+ },
+ [Optional(_), expression(t)] => {
+ spanned(span, OldOptionalLit(None, rc(t)))
+ },
+ ));
+
+ rule!(non_empty_optional<ParsedExpr<'a>> as expression; span; children!(
+ [expression(x), Optional(_), expression(t)] => {
+ spanned(span, OldOptionalLit(Some(rc(x)), rc(t)))
+ }
+ ));
+
+ rule!(import_alt_expression<ParsedExpr<'a>> as expression; children!(
+ [expression(e)] => e,
+ [expression(first), expression(rest)..] => {
+ let o = crate::BinOp::ImportAlt;
+ rest.fold(first, |acc, e| BinOp(o, rc(acc), rc(e)))
+ },
+ ));
+ rule!(or_expression<ParsedExpr<'a>> as expression; children!(
+ [expression(e)] => e,
+ [expression(first), expression(rest)..] => {
+ let o = crate::BinOp::BoolOr;
+ rest.fold(first, |acc, e| BinOp(o, rc(acc), rc(e)))
+ },
+ ));
+ rule!(plus_expression<ParsedExpr<'a>> as expression; children!(
+ [expression(e)] => e,
+ [expression(first), expression(rest)..] => {
+ let o = crate::BinOp::NaturalPlus;
+ rest.fold(first, |acc, e| BinOp(o, rc(acc), rc(e)))
+ },
+ ));
+ rule!(text_append_expression<ParsedExpr<'a>> as expression; children!(
+ [expression(e)] => e,
+ [expression(first), expression(rest)..] => {
+ let o = crate::BinOp::TextAppend;
+ rest.fold(first, |acc, e| BinOp(o, rc(acc), rc(e)))
+ },
+ ));
+ rule!(list_append_expression<ParsedExpr<'a>> as expression; children!(
+ [expression(e)] => e,
+ [expression(first), expression(rest)..] => {
+ let o = crate::BinOp::ListAppend;
+ rest.fold(first, |acc, e| BinOp(o, rc(acc), rc(e)))
+ },
+ ));
+ rule!(and_expression<ParsedExpr<'a>> as expression; children!(
+ [expression(e)] => e,
+ [expression(first), expression(rest)..] => {
+ let o = crate::BinOp::BoolAnd;
+ rest.fold(first, |acc, e| BinOp(o, rc(acc), rc(e)))
+ },
+ ));
+ rule!(combine_expression<ParsedExpr<'a>> as expression; children!(
+ [expression(e)] => e,
+ [expression(first), expression(rest)..] => {
+ let o = crate::BinOp::Combine;
+ rest.fold(first, |acc, e| BinOp(o, rc(acc), rc(e)))
+ },
+ ));
+ rule!(prefer_expression<ParsedExpr<'a>> as expression; children!(
+ [expression(e)] => e,
+ [expression(first), expression(rest)..] => {
+ let o = crate::BinOp::Prefer;
+ rest.fold(first, |acc, e| BinOp(o, rc(acc), rc(e)))
+ },
+ ));
+ rule!(combine_types_expression<ParsedExpr<'a>> as expression; children!(
+ [expression(e)] => e,
+ [expression(first), expression(rest)..] => {
+ let o = crate::BinOp::CombineTypes;
+ rest.fold(first, |acc, e| BinOp(o, rc(acc), rc(e)))
+ },
+ ));
+ rule!(times_expression<ParsedExpr<'a>> as expression; children!(
+ [expression(e)] => e,
+ [expression(first), expression(rest)..] => {
+ let o = crate::BinOp::NaturalTimes;
+ rest.fold(first, |acc, e| BinOp(o, rc(acc), rc(e)))
+ },
+ ));
+ rule!(equal_expression<ParsedExpr<'a>> as expression; children!(
+ [expression(e)] => e,
+ [expression(first), expression(rest)..] => {
+ let o = crate::BinOp::BoolEQ;
+ rest.fold(first, |acc, e| BinOp(o, rc(acc), rc(e)))
+ },
+ ));
+ rule!(not_equal_expression<ParsedExpr<'a>> as expression; children!(
+ [expression(e)] => e,
+ [expression(first), expression(rest)..] => {
+ let o = crate::BinOp::BoolNE;
+ rest.fold(first, |acc, e| BinOp(o, rc(acc), rc(e)))
+ },
+ ));
+
+ rule!(annotated_expression<ParsedExpr<'a>> as expression; span; children!(
+ [expression(e)] => e,
+ [expression(e), expression(annot)] => {
+ spanned(span, Annot(rc(e), rc(annot)))
+ },
+ ));
+
+ token_rule!(Some_<()>);
+
+ rule!(application_expression<ParsedExpr<'a>> as expression; children!(
+ [expression(e)] => e,
+ [expression(first), expression(rest)..] => {
+ rest.fold(first, |acc, e| App(rc(acc), rc(e)))
+ },
+ ));
+
+ rule!(first_application_expression<ParsedExpr<'a>> as expression; span;
+ children!(
+ [expression(e)] => e,
+ [Some_(()), expression(e)] => {
+ spanned(span, SomeLit(rc(e)))
+ },
+ [merge(()), expression(x), expression(y)] => {
+ spanned(span, Merge(rc(x), rc(y), None))
+ },
+ ));
+
+ rule!(selector_expression<ParsedExpr<'a>> as expression; children!(
+ [expression(e)] => e,
+ [expression(first), selector(rest)..] => {
+ rest.fold(first, |acc, e| match e {
+ Either::Left(l) => Field(rc(acc), l),
+ Either::Right(ls) => Projection(rc(acc), ls),
+ })
+ }
+ ));
+
+ rule!(selector<Either<Label, Vec<Label>>>; children!(
+ [label(l)] => Either::Left(l),
+ [labels(ls)] => Either::Right(ls),
+ ));
+
+ rule!(labels<Vec<Label>>; children!(
+ [label(ls)..] => ls.collect(),
+ ));
+
+ rule!(primitive_expression<ParsedExpr<'a>> as expression; span; children!(
+ [double_literal(n)] => spanned(span, DoubleLit(n)),
+ [natural_literal(n)] => spanned(span, NaturalLit(n)),
+ [integer_literal(n)] => spanned(span, IntegerLit(n)),
+ [double_quote_literal(s)] => spanned(span, TextLit(s)),
+ [single_quote_literal(s)] => spanned(span, TextLit(s)),
+ [expression(e)] => e,
+ ));
+
+ rule!(empty_record_literal<ParsedExpr<'a>> as expression; span;
+ captured_str!(_) => spanned(span, RecordLit(BTreeMap::new()))
+ );
+
+ rule!(empty_record_type<ParsedExpr<'a>> as expression; span;
+ captured_str!(_) => spanned(span, RecordType(BTreeMap::new()))
+ );
+
+ rule!(non_empty_record_type_or_literal<ParsedExpr<'a>> as expression; span;
+ children!(
+ [label(first_label), non_empty_record_type(rest)] => {
+ let (first_expr, mut map) = rest;
+ map.insert(first_label, rc(first_expr));
+ spanned(span, RecordType(map))
+ },
+ [label(first_label), non_empty_record_literal(rest)] => {
+ let (first_expr, mut map) = rest;
+ map.insert(first_label, rc(first_expr));
+ spanned(span, RecordLit(map))
+ },
+ ));
+
+ rule!(non_empty_record_type
+ <(ParsedExpr<'a>, BTreeMap<Label, ParsedSubExpr<'a>>)>; children!(
+ [expression(expr), record_type_entry(entries)..] => {
+ (expr, entries.collect())
+ }
+ ));
+
+ rule!(record_type_entry<(Label, ParsedSubExpr<'a>)>; children!(
+ [label(name), expression(expr)] => (name, rc(expr))
+ ));
+
+ rule!(non_empty_record_literal
+ <(ParsedExpr<'a>, BTreeMap<Label, ParsedSubExpr<'a>>)>; children!(
+ [expression(expr), record_literal_entry(entries)..] => {
+ (expr, entries.collect())
+ }
+ ));
+
+ rule!(record_literal_entry<(Label, ParsedSubExpr<'a>)>; children!(
+ [label(name), expression(expr)] => (name, rc(expr))
+ ));
+
+ rule!(union_type_or_literal<ParsedExpr<'a>> as expression; span; children!(
+ [empty_union_type(_)] => {
+ spanned(span, UnionType(BTreeMap::new()))
+ },
+ [non_empty_union_type_or_literal((Some((l, e)), entries))] => {
+ spanned(span, UnionLit(l, e, entries))
+ },
+ [non_empty_union_type_or_literal((None, entries))] => {
+ spanned(span, UnionType(entries))
+ },
+ ));
+
+ token_rule!(empty_union_type<()>);
+
+ rule!(non_empty_union_type_or_literal
+ <(Option<(Label, ParsedSubExpr<'a>)>,
+ BTreeMap<Label, Option<ParsedSubExpr<'a>>>)>;
+ children!(
+ [label(l), union_literal_variant_value((e, entries))] => {
+ (Some((l, e)), entries)
+ },
+ [label(l), union_type_or_literal_variant_type((e, rest))] => {
+ let (x, mut entries) = rest;
+ entries.insert(l, e);
+ (x, entries)
+ },
+ ));
+
+ rule!(union_literal_variant_value
+ <(ParsedSubExpr<'a>, BTreeMap<Label, Option<ParsedSubExpr<'a>>>)>;
+ children!(
+ [expression(e), union_type_entry(entries)..] => {
+ (rc(e), entries.collect())
+ },
+ ));
+
+ rule!(union_type_entry<(Label, Option<ParsedSubExpr<'a>>)>; children!(
+ [label(name), expression(expr)] => (name, Some(rc(expr))),
+ [label(name)] => (name, None),
+ ));
+
+ // TODO: unary union variants
+ rule!(union_type_or_literal_variant_type
+ <(Option<ParsedSubExpr<'a>>,
+ (Option<(Label, ParsedSubExpr<'a>)>,
+ BTreeMap<Label, Option<ParsedSubExpr<'a>>>))>;
+ children!(
+ [expression(e), non_empty_union_type_or_literal(rest)] => {
+ (Some(rc(e)), rest)
+ },
+ [expression(e)] => {
+ (Some(rc(e)), (None, BTreeMap::new()))
+ },
+ [non_empty_union_type_or_literal(rest)] => {
+ (None, rest)
+ },
+ [] => {
+ (None, (None, BTreeMap::new()))
+ },
+ ));
+
+ rule!(non_empty_list_literal<ParsedExpr<'a>> as expression; span;
+ children!(
+ [expression(items)..] => spanned(
+ span,
+ NEListLit(items.map(rc).collect())
+ )
+ ));
+
+ rule!(final_expression<ParsedExpr<'a>> as expression; children!(
+ [expression(e), EOI(_eoi)] => e
+ ));
+}
+
+pub fn parse_expr<'a>(s: &'a str) -> ParseResult<ParsedSubExpr<'a>> {
+ let mut pairs = DhallParser::parse(Rule::final_expression, s)?;
+ let expr = do_parse(pairs.next().unwrap())?;
+ assert_eq!(pairs.next(), None);
+ match expr {
+ ParsedValue::expression(e) => Ok(rc(e)),
+ _ => unreachable!(),
+ }
+ // Ok(rc(BoolLit(false)))
+}
+
+#[test]
+fn test_parse() {
+ // let expr = r#"{ x = "foo", y = 4 }.x"#;
+ // let expr = r#"(1 + 2) * 3"#;
+ let expr = r#"(1) + 3 * 5"#;
+ println!("{:?}", parse_expr(expr));
+ match parse_expr(expr) {
+ Err(e) => {
+ println!("{:?}", e);
+ println!("{}", e);
+ }
+ ok => println!("{:?}", ok),
+ };
+ // assert!(false);
+}
diff --git a/dhall_syntax/src/printer.rs b/dhall_syntax/src/printer.rs
new file mode 100644
index 0000000..704000a
--- /dev/null
+++ b/dhall_syntax/src/printer.rs
@@ -0,0 +1,498 @@
+use crate::*;
+use itertools::Itertools;
+use std::fmt::{self, Display};
+
+/// Generic instance that delegates to subexpressions
+impl<SE: Display + Clone, N, E: Display> Display for ExprF<SE, Label, N, E> {
+ fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
+ use crate::ExprF::*;
+ match self {
+ Lam(a, b, c) => {
+ write!(f, "λ({} : {}) → {}", a, b, c)?;
+ }
+ BoolIf(a, b, c) => {
+ write!(f, "if {} then {} else {}", a, b, c)?;
+ }
+ Pi(a, b, c) if &String::from(a) == "_" => {
+ write!(f, "{} → {}", b, c)?;
+ }
+ Pi(a, b, c) => {
+ write!(f, "∀({} : {}) → {}", a, b, c)?;
+ }
+ Let(a, b, c, d) => {
+ write!(f, "let {}", a)?;
+ if let Some(b) = b {
+ write!(f, " : {}", b)?;
+ }
+ write!(f, " = {} in {}", c, d)?;
+ }
+ EmptyListLit(t) => {
+ write!(f, "[] : List {}", t)?;
+ }
+ NEListLit(es) => {
+ fmt_list("[", ", ", "]", es, f, Display::fmt)?;
+ }
+ OldOptionalLit(None, t) => {
+ write!(f, "[] : Optional {}", t)?;
+ }
+ OldOptionalLit(Some(x), t) => {
+ write!(f, "[{}] : Optional {}", x, t)?;
+ }
+ SomeLit(e) => {
+ write!(f, "Some {}", e)?;
+ }
+ Merge(a, b, c) => {
+ write!(f, "merge {} {}", a, b)?;
+ if let Some(c) = c {
+ write!(f, " : {}", c)?;
+ }
+ }
+ Annot(a, b) => {
+ write!(f, "{} : {}", a, b)?;
+ }
+ ExprF::BinOp(op, a, b) => {
+ write!(f, "{} {} {}", a, op, b)?;
+ }
+ ExprF::App(a, b) => {
+ write!(f, "{} {}", a, b)?;
+ }
+ Field(a, b) => {
+ write!(f, "{}.{}", a, b)?;
+ }
+ Projection(e, ls) => {
+ write!(f, "{}.", e)?;
+ fmt_list("{ ", ", ", " }", ls, f, Display::fmt)?;
+ }
+ Var(a) => a.fmt(f)?,
+ Const(k) => k.fmt(f)?,
+ Builtin(v) => v.fmt(f)?,
+ BoolLit(true) => f.write_str("True")?,
+ BoolLit(false) => f.write_str("False")?,
+ NaturalLit(a) => a.fmt(f)?,
+ IntegerLit(a) if *a >= 0 => {
+ f.write_str("+")?;
+ a.fmt(f)?;
+ }
+ IntegerLit(a) => a.fmt(f)?,
+ DoubleLit(a) => a.fmt(f)?,
+ TextLit(a) => a.fmt(f)?,
+ RecordType(a) if a.is_empty() => f.write_str("{}")?,
+ RecordType(a) => fmt_list("{ ", ", ", " }", a, f, |(k, t), f| {
+ write!(f, "{} : {}", k, t)
+ })?,
+ RecordLit(a) if a.is_empty() => f.write_str("{=}")?,
+ RecordLit(a) => fmt_list("{ ", ", ", " }", a, f, |(k, v), f| {
+ write!(f, "{} = {}", k, v)
+ })?,
+ UnionType(a) => fmt_list("< ", " | ", " >", a, f, |(k, v), f| {
+ write!(f, "{}", k)?;
+ if let Some(v) = v {
+ write!(f, ": {}", v)?;
+ }
+ Ok(())
+ })?,
+ UnionLit(a, b, c) => {
+ write!(f, "< {} = {}", a, b)?;
+ for (k, v) in c {
+ write!(f, " | {}", k)?;
+ if let Some(v) = v {
+ write!(f, ": {}", v)?;
+ }
+ }
+ f.write_str(" >")?
+ }
+ Embed(a) => a.fmt(f)?,
+ Note(_, b) => b.fmt(f)?,
+ }
+ Ok(())
+ }
+}
+
+// There is a one-to-one correspondence between the formatter and the grammar. Each phase is
+// named after a corresponding grammar group, and the structure of the formatter reflects
+// the relationship between the corresponding grammar rules. This leads to the nice property
+// of automatically getting all the parentheses and precedences right.
+#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq)]
+enum PrintPhase {
+ Base,
+ Operator,
+ BinOp(core::BinOp),
+ App,
+ Import,
+ Primitive,
+}
+
+// Wraps an Expr with a phase, so that phase selsction can be done
+// separate from the actual printing
+#[derive(Clone)]
+struct PhasedExpr<'a, S, A>(&'a SubExpr<S, A>, PrintPhase);
+
+impl<'a, S: Clone, A: Display + Clone> Display for PhasedExpr<'a, S, A> {
+ fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
+ self.0.as_ref().fmt_phase(f, self.1)
+ }
+}
+
+impl<'a, S: Clone, A: Display + Clone> PhasedExpr<'a, S, A> {
+ fn phase(self, phase: PrintPhase) -> PhasedExpr<'a, S, A> {
+ PhasedExpr(self.0, phase)
+ }
+}
+
+impl<S: Clone, A: Display + Clone> Expr<S, A> {
+ fn fmt_phase(
+ &self,
+ f: &mut fmt::Formatter,
+ mut phase: PrintPhase,
+ ) -> Result<(), fmt::Error> {
+ use crate::ExprF::*;
+ use PrintPhase::*;
+
+ let needs_paren = match self {
+ Lam(_, _, _)
+ | BoolIf(_, _, _)
+ | Pi(_, _, _)
+ | Let(_, _, _, _)
+ | EmptyListLit(_)
+ | NEListLit(_)
+ | OldOptionalLit(_, _)
+ | SomeLit(_)
+ | Merge(_, _, _)
+ | Annot(_, _)
+ if phase > Base =>
+ {
+ true
+ }
+ // Precedence is magically handled by the ordering of BinOps.
+ ExprF::BinOp(op, _, _) if phase > PrintPhase::BinOp(*op) => true,
+ ExprF::App(_, _) if phase > PrintPhase::App => true,
+ Field(_, _) | Projection(_, _) if phase > Import => true,
+ _ => false,
+ };
+
+ if needs_paren {
+ phase = Base;
+ }
+
+ // Annotate subexpressions with the appropriate phase, defaulting to Base
+ let phased_self = match self.map_ref_simple(|e| PhasedExpr(e, Base)) {
+ Pi(a, b, c) => {
+ if &String::from(&a) == "_" {
+ Pi(a, b.phase(Operator), c)
+ } else {
+ Pi(a, b, c)
+ }
+ }
+ Merge(a, b, c) => Merge(
+ a.phase(Import),
+ b.phase(Import),
+ c.map(|x| x.phase(PrintPhase::App)),
+ ),
+ Annot(a, b) => Annot(a.phase(Operator), b),
+ ExprF::BinOp(op, a, b) => ExprF::BinOp(
+ op,
+ a.phase(PrintPhase::BinOp(op)),
+ b.phase(PrintPhase::BinOp(op)),
+ ),
+ EmptyListLit(t) => EmptyListLit(t.phase(Import)),
+ OldOptionalLit(x, t) => OldOptionalLit(x, t.phase(Import)),
+ SomeLit(e) => SomeLit(e.phase(Import)),
+ ExprF::App(f, a) => ExprF::App(f.phase(Import), a.phase(Import)),
+ Field(a, b) => Field(a.phase(Primitive), b),
+ Projection(e, ls) => Projection(e.phase(Primitive), ls),
+ Note(n, b) => Note(n, b.phase(phase)),
+ e => e,
+ };
+
+ if needs_paren {
+ f.write_str("(")?;
+ }
+
+ // Uses the ExprF<PhasedExpr<_>, _, _, _> instance
+ phased_self.fmt(f)?;
+
+ if needs_paren {
+ f.write_str(")")?;
+ }
+ Ok(())
+ }
+}
+
+impl<S: Clone, A: Display + Clone> Display for SubExpr<S, A> {
+ fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
+ self.as_ref().fmt_phase(f, PrintPhase::Base)
+ }
+}
+
+fn fmt_list<T, I, F>(
+ open: &str,
+ sep: &str,
+ close: &str,
+ it: I,
+ f: &mut fmt::Formatter,
+ func: F,
+) -> Result<(), fmt::Error>
+where
+ I: IntoIterator<Item = T>,
+ F: Fn(T, &mut fmt::Formatter) -> Result<(), fmt::Error>,
+{
+ f.write_str(open)?;
+ for (i, x) in it.into_iter().enumerate() {
+ if i > 0 {
+ f.write_str(sep)?;
+ }
+ func(x, f)?;
+ }
+ f.write_str(close)
+}
+
+impl<SubExpr: Display + Clone> Display for InterpolatedText<SubExpr> {
+ fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
+ f.write_str("\"")?;
+ for x in self.iter() {
+ match x {
+ InterpolatedTextContents::Text(a) => {
+ for c in a.chars() {
+ match c {
+ '\\' => f.write_str("\\\\"),
+ '"' => f.write_str("\\\""),
+ '$' => f.write_str("\\$"),
+ '\u{0008}' => f.write_str("\\b"),
+ '\u{000C}' => f.write_str("\\f"),
+ '\n' => f.write_str("\\n"),
+ '\r' => f.write_str("\\r"),
+ '\t' => f.write_str("\\t"),
+ c => write!(f, "{}", c),
+ }?;
+ }
+ }
+ InterpolatedTextContents::Expr(e) => {
+ f.write_str("${ ")?;
+ e.fmt(f)?;
+ f.write_str(" }")?;
+ }
+ }
+ }
+ f.write_str("\"")?;
+ Ok(())
+ }
+}
+
+impl Display for Const {
+ fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
+ <Self as fmt::Debug>::fmt(self, f)
+ }
+}
+
+impl Display for BinOp {
+ fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
+ use crate::BinOp::*;
+ f.write_str(match self {
+ BoolOr => "||",
+ TextAppend => "++",
+ NaturalPlus => "+",
+ BoolAnd => "&&",
+ Combine => "/\\",
+ NaturalTimes => "*",
+ BoolEQ => "==",
+ BoolNE => "!=",
+ CombineTypes => "//\\\\",
+ ImportAlt => "?",
+ Prefer => "//",
+ ListAppend => "#",
+ })
+ }
+}
+
+impl Display for NaiveDouble {
+ fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
+ let v = f64::from(*self);
+ if v == std::f64::INFINITY {
+ f.write_str("Infinity")
+ } else if v == std::f64::NEG_INFINITY {
+ f.write_str("-Infinity")
+ } else if v.is_nan() {
+ f.write_str("NaN")
+ } else {
+ let s = format!("{}", v);
+ if s.contains('e') || s.contains('.') {
+ f.write_str(&s)
+ } else {
+ write!(f, "{}.0", s)
+ }
+ }
+ }
+}
+
+impl Display for Label {
+ fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
+ // TODO: distinguish between reserved and nonreserved locations for quoting builtins
+ let s = String::from(self);
+ let is_reserved = match s.as_str() {
+ "let" | "in" | "if" | "then" | "else" | "Type" | "Kind"
+ | "Sort" | "True" | "False" => true,
+ _ => crate::Builtin::parse(&s).is_some(),
+ };
+ if !is_reserved && s.chars().all(|c| c.is_ascii_alphanumeric()) {
+ write!(f, "{}", s)
+ } else {
+ write!(f, "`{}`", s)
+ }
+ }
+}
+
+impl Display for Hash {
+ fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
+ write!(f, "{}:{}", self.protocol, self.hash)
+ }
+}
+impl Display for ImportHashed {
+ fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
+ use std::path::PathBuf;
+ use FilePrefix::*;
+ use ImportLocation::*;
+ let quoted_path_component = |s: &str| -> String {
+ if s.chars().all(|c| c.is_ascii_alphanumeric()) {
+ s.to_owned()
+ } else {
+ format!("\"{}\"", s)
+ }
+ };
+ let fmt_path = |f: &mut fmt::Formatter, p: &PathBuf| {
+ let res: String = p
+ .iter()
+ .map(|c| quoted_path_component(c.to_string_lossy().as_ref()))
+ .join("/");
+ f.write_str(&res)
+ };
+
+ match &self.location {
+ Local(prefix, path) => {
+ let prefix = match prefix {
+ Here => ".",
+ Parent => "..",
+ Home => "~",
+ Absolute => "",
+ };
+ write!(f, "{}/", prefix)?;
+ fmt_path(f, path)?;
+ }
+ Remote(url) => {
+ write!(f, "{}://{}/", url.scheme, url.authority,)?;
+ fmt_path(f, &url.path)?;
+ if let Some(q) = &url.query {
+ write!(f, "?{}", q)?
+ }
+ if let Some(h) = &url.headers {
+ write!(f, " using ({})", h)?
+ }
+ }
+ Env(s) => {
+ write!(f, "env:")?;
+ if s.chars().all(|c| c.is_ascii_alphanumeric()) {
+ write!(f, "{}", s)?;
+ } else {
+ write!(f, "\"")?;
+ for c in s.chars() {
+ match c {
+ '"' => f.write_str("\\\"")?,
+ '\\' => f.write_str("\\\\")?,
+ '\u{0007}' => f.write_str("\\a")?,
+ '\u{0008}' => f.write_str("\\b")?,
+ '\u{000C}' => f.write_str("\\f")?,
+ '\n' => f.write_str("\\n")?,
+ '\r' => f.write_str("\\r")?,
+ '\t' => f.write_str("\\t")?,
+ '\u{000B}' => f.write_str("\\v")?,
+ _ => write!(f, "{}", c)?,
+ }
+ }
+ write!(f, "\"")?;
+ }
+ }
+ Missing => {
+ write!(f, "missing")?;
+ }
+ }
+ if let Some(hash) = &self.hash {
+ write!(f, " ")?;
+ hash.fmt(f)?;
+ }
+ Ok(())
+ }
+}
+
+impl Display for Import {
+ fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
+ self.location_hashed.fmt(f)?;
+ use ImportMode::*;
+ match self.mode {
+ Code => {}
+ RawText => write!(f, " as Text")?,
+ }
+ Ok(())
+ }
+}
+
+impl Display for Builtin {
+ fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
+ use crate::Builtin::*;
+ f.write_str(match *self {
+ Bool => "Bool",
+ Natural => "Natural",
+ Integer => "Integer",
+ Double => "Double",
+ Text => "Text",
+ List => "List",
+ Optional => "Optional",
+ OptionalNone => "None",
+ NaturalBuild => "Natural/build",
+ NaturalFold => "Natural/fold",
+ NaturalIsZero => "Natural/isZero",
+ NaturalEven => "Natural/even",
+ NaturalOdd => "Natural/odd",
+ NaturalToInteger => "Natural/toInteger",
+ NaturalShow => "Natural/show",
+ IntegerToDouble => "Integer/toDouble",
+ IntegerShow => "Integer/show",
+ DoubleShow => "Double/show",
+ ListBuild => "List/build",
+ ListFold => "List/fold",
+ ListLength => "List/length",
+ ListHead => "List/head",
+ ListLast => "List/last",
+ ListIndexed => "List/indexed",
+ ListReverse => "List/reverse",
+ OptionalFold => "Optional/fold",
+ OptionalBuild => "Optional/build",
+ TextShow => "Text/show",
+ })
+ }
+}
+
+impl Display for Scheme {
+ fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
+ use crate::Scheme::*;
+ f.write_str(match *self {
+ HTTP => "http",
+ HTTPS => "https",
+ })
+ }
+}
+
+impl<Label: Display> Display for V<Label> {
+ fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
+ let V(x, n) = self;
+ x.fmt(f)?;
+ if *n != 0 {
+ write!(f, "@{}", n)?;
+ }
+ Ok(())
+ }
+}
+
+impl Display for X {
+ fn fmt(&self, _: &mut fmt::Formatter) -> Result<(), fmt::Error> {
+ match *self {}
+ }
+}
diff --git a/dhall_syntax/src/text.rs b/dhall_syntax/src/text.rs
new file mode 100644
index 0000000..83643d9
--- /dev/null
+++ b/dhall_syntax/src/text.rs
@@ -0,0 +1,116 @@
+use std::iter::FromIterator;
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub struct InterpolatedText<SubExpr> {
+ head: String,
+ tail: Vec<(SubExpr, String)>,
+}
+
+impl<SubExpr> From<(String, Vec<(SubExpr, String)>)>
+ for InterpolatedText<SubExpr>
+{
+ fn from(x: (String, Vec<(SubExpr, String)>)) -> Self {
+ InterpolatedText {
+ head: x.0,
+ tail: x.1,
+ }
+ }
+}
+
+impl<SubExpr> From<String> for InterpolatedText<SubExpr> {
+ fn from(s: String) -> Self {
+ InterpolatedText {
+ head: s,
+ tail: vec![],
+ }
+ }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub enum InterpolatedTextContents<SubExpr> {
+ Text(String),
+ Expr(SubExpr),
+}
+
+impl<SubExpr> InterpolatedTextContents<SubExpr> {
+ pub fn is_empty(&self) -> bool {
+ use InterpolatedTextContents::{Expr, Text};
+ match self {
+ Expr(_) => false,
+ Text(s) => s.is_empty(),
+ }
+ }
+}
+
+impl<SubExpr> InterpolatedText<SubExpr> {
+ pub fn traverse_ref<'a, SubExpr2, E, F>(
+ &'a self,
+ mut f: F,
+ ) -> Result<InterpolatedText<SubExpr2>, E>
+ where
+ F: FnMut(&'a SubExpr) -> Result<SubExpr2, E>,
+ {
+ Ok(InterpolatedText {
+ head: self.head.clone(),
+ tail: self
+ .tail
+ .iter()
+ .map(|(e, s)| Ok((f(e)?, s.clone())))
+ .collect::<Result<_, _>>()?,
+ })
+ }
+
+ pub fn iter<'a>(
+ &'a self,
+ ) -> impl Iterator<Item = InterpolatedTextContents<SubExpr>> + 'a
+ where
+ SubExpr: Clone,
+ {
+ use std::iter::once;
+ use InterpolatedTextContents::{Expr, Text};
+ once(Text(self.head.clone()))
+ .chain(self.tail.iter().flat_map(|(e, s)| {
+ once(Expr(SubExpr::clone(e))).chain(once(Text(s.clone())))
+ }))
+ .filter(|c| !c.is_empty())
+ }
+
+ pub fn into_iter(
+ self,
+ ) -> impl Iterator<Item = InterpolatedTextContents<SubExpr>> {
+ use std::iter::once;
+ use InterpolatedTextContents::{Expr, Text};
+ once(Text(self.head))
+ .chain(
+ self.tail
+ .into_iter()
+ .flat_map(|(e, s)| once(Expr(e)).chain(once(Text(s)))),
+ )
+ .filter(|c| !c.is_empty())
+ }
+}
+
+impl<SubExpr> FromIterator<InterpolatedTextContents<SubExpr>>
+ for InterpolatedText<SubExpr>
+{
+ fn from_iter<T>(iter: T) -> Self
+ where
+ T: IntoIterator<Item = InterpolatedTextContents<SubExpr>>,
+ {
+ let mut res = InterpolatedText {
+ head: String::new(),
+ tail: Vec::new(),
+ };
+ let mut crnt_str = &mut res.head;
+ for x in iter.into_iter() {
+ match x {
+ InterpolatedTextContents::Text(s) => crnt_str.push_str(&s),
+ InterpolatedTextContents::Expr(e) => {
+ res.tail.push((e, String::new()));
+ crnt_str = &mut res.tail.last_mut().unwrap().1;
+ }
+ }
+ }
+ res
+ }
+}
diff --git a/dhall_syntax/src/visitor.rs b/dhall_syntax/src/visitor.rs
new file mode 100644
index 0000000..caaefce
--- /dev/null
+++ b/dhall_syntax/src/visitor.rs
@@ -0,0 +1,667 @@
+use std::collections::BTreeMap;
+
+use crate::*;
+
+/// A way too generic Visitor trait.
+pub trait GenericVisitor<Input, Return>: Sized {
+ fn visit(self, input: Input) -> Return;
+}
+
+/// A visitor trait that can be used to traverse `ExprF`s. We need this pattern
+/// so that Rust lets us have as much mutability as we can.
+/// For example, `traverse_embed` cannot be made using only `traverse_ref`, because
+/// `traverse_ref` takes a `FnMut` so we would need to pass multiple mutable
+/// reverences to this argument to `traverse_ref`. But Rust's ownership system
+/// is all about preventing exactly this ! So we have to be more clever.
+/// The visitor pattern allows us to have only one mutable thing the whole
+/// time: the visitor itself. The visitor can then carry around multiple closures
+/// or just one, and Rust is ok with either. See for example TraverseRefVisitor
+/// and TraverseEmbedVisitor.
+/// This is very generic. For a more legible trait, see ExprFInFallibleVisitor
+pub trait ExprFVeryGenericVisitor<'a, Ret, SE1, L1, N1, E1>: Sized {
+ type Error;
+ type SE2;
+ type L2;
+ type N2;
+ type E2;
+
+ fn visit_subexpr(
+ &mut self,
+ subexpr: &'a SE1,
+ ) -> Result<Self::SE2, Self::Error>;
+ fn visit_label(&mut self, label: &'a L1) -> Result<Self::L2, Self::Error>;
+
+ fn visit_binder(
+ self,
+ label: &'a L1,
+ subexpr: &'a SE1,
+ ) -> Result<(Self::L2, Self::SE2), Self::Error>;
+
+ fn visit_embed_squash(self, embed: &'a E1) -> Result<Ret, Self::Error>;
+
+ fn visit_note_squash(
+ self,
+ note: &'a N1,
+ subexpr: &'a SE1,
+ ) -> Result<Ret, Self::Error>;
+
+ // Called with the result of the map, in the non-embed/note case.
+ // Useful to change the result type, and/or avoid some loss of info
+ fn visit_resulting_exprf(
+ result: ExprF<Self::SE2, Self::L2, Self::N2, Self::E2>,
+ ) -> Result<Ret, Self::Error>;
+}
+
+impl<'a, T, Ret, SE1, L1, N1, E1>
+ GenericVisitor<&'a ExprF<SE1, L1, N1, E1>, Result<Ret, T::Error>> for T
+where
+ L1: Ord,
+ T::L2: Ord,
+ T: ExprFVeryGenericVisitor<'a, Ret, SE1, L1, N1, E1>,
+{
+ fn visit(self, input: &'a ExprF<SE1, L1, N1, E1>) -> Result<Ret, T::Error> {
+ fn vec<'a, T, U, Err, F: FnMut(&'a T) -> Result<U, Err>>(
+ x: &'a [T],
+ f: F,
+ ) -> Result<Vec<U>, Err> {
+ x.iter().map(f).collect()
+ }
+ fn opt<'a, T, U, Err, F: FnOnce(&'a T) -> Result<U, Err>>(
+ x: &'a Option<T>,
+ f: F,
+ ) -> Result<Option<U>, Err> {
+ Ok(match x {
+ Some(x) => Some(f(x)?),
+ None => None,
+ })
+ }
+ fn btmap<'a, V, Ret, SE, L, N, E>(
+ x: &'a BTreeMap<L, SE>,
+ mut v: V,
+ ) -> Result<BTreeMap<V::L2, V::SE2>, V::Error>
+ where
+ L: Ord,
+ V::L2: Ord,
+ V: ExprFVeryGenericVisitor<'a, Ret, SE, L, N, E>,
+ {
+ x.iter()
+ .map(|(k, x)| Ok((v.visit_label(k)?, v.visit_subexpr(x)?)))
+ .collect()
+ }
+ fn btoptmap<'a, V, Ret, SE, L, N, E>(
+ x: &'a BTreeMap<L, Option<SE>>,
+ mut v: V,
+ ) -> Result<BTreeMap<V::L2, Option<V::SE2>>, V::Error>
+ where
+ L: Ord,
+ V::L2: Ord,
+ V: ExprFVeryGenericVisitor<'a, Ret, SE, L, N, E>,
+ {
+ x.iter()
+ .map(|(k, x)| {
+ Ok((
+ v.visit_label(k)?,
+ match x {
+ Some(x) => Some(v.visit_subexpr(x)?),
+ None => None,
+ },
+ ))
+ })
+ .collect()
+ }
+
+ let mut v = self;
+ use crate::ExprF::*;
+ T::visit_resulting_exprf(match input {
+ Var(V(l, n)) => Var(V(v.visit_label(l)?, *n)),
+ Lam(l, t, e) => {
+ let t = v.visit_subexpr(t)?;
+ let (l, e) = v.visit_binder(l, e)?;
+ Lam(l, t, e)
+ }
+ Pi(l, t, e) => {
+ let t = v.visit_subexpr(t)?;
+ let (l, e) = v.visit_binder(l, e)?;
+ Pi(l, t, e)
+ }
+ Let(l, t, a, e) => {
+ let t = opt(t, &mut |e| v.visit_subexpr(e))?;
+ let a = v.visit_subexpr(a)?;
+ let (l, e) = v.visit_binder(l, e)?;
+ Let(l, t, a, e)
+ }
+ App(f, a) => App(v.visit_subexpr(f)?, v.visit_subexpr(a)?),
+ Annot(x, t) => Annot(v.visit_subexpr(x)?, v.visit_subexpr(t)?),
+ Const(k) => Const(*k),
+ Builtin(v) => Builtin(*v),
+ BoolLit(b) => BoolLit(*b),
+ NaturalLit(n) => NaturalLit(*n),
+ IntegerLit(n) => IntegerLit(*n),
+ DoubleLit(n) => DoubleLit(*n),
+ TextLit(t) => TextLit(t.traverse_ref(|e| v.visit_subexpr(e))?),
+ BinOp(o, x, y) => {
+ BinOp(*o, v.visit_subexpr(x)?, v.visit_subexpr(y)?)
+ }
+ BoolIf(b, t, f) => BoolIf(
+ v.visit_subexpr(b)?,
+ v.visit_subexpr(t)?,
+ v.visit_subexpr(f)?,
+ ),
+ EmptyListLit(t) => EmptyListLit(v.visit_subexpr(t)?),
+ NEListLit(es) => NEListLit(vec(es, |e| v.visit_subexpr(e))?),
+ OldOptionalLit(x, t) => OldOptionalLit(
+ opt(x, |e| v.visit_subexpr(e))?,
+ v.visit_subexpr(t)?,
+ ),
+ SomeLit(e) => SomeLit(v.visit_subexpr(e)?),
+ RecordType(kts) => RecordType(btmap(kts, v)?),
+ RecordLit(kvs) => RecordLit(btmap(kvs, v)?),
+ UnionType(kts) => UnionType(btoptmap(kts, v)?),
+ UnionLit(k, x, kts) => UnionLit(
+ v.visit_label(k)?,
+ v.visit_subexpr(x)?,
+ btoptmap(kts, v)?,
+ ),
+ Merge(x, y, t) => Merge(
+ v.visit_subexpr(x)?,
+ v.visit_subexpr(y)?,
+ opt(t, |e| v.visit_subexpr(e))?,
+ ),
+ Field(e, l) => Field(v.visit_subexpr(e)?, v.visit_label(l)?),
+ Projection(e, ls) => {
+ Projection(v.visit_subexpr(e)?, vec(ls, |l| v.visit_label(l))?)
+ }
+ Note(n, e) => return v.visit_note_squash(n, e),
+ Embed(a) => return v.visit_embed_squash(a),
+ })
+ }
+}
+
+/// Like ExprFVeryGenericVisitor, but sets the return
+/// type to ExprF<_>
+pub trait ExprFFallibleVisitor<'a, SE1, SE2, L1, L2, N1, N2, E1, E2>:
+ Sized
+{
+ type Error;
+
+ fn visit_subexpr(&mut self, subexpr: &'a SE1) -> Result<SE2, Self::Error>;
+ fn visit_label(&mut self, label: &'a L1) -> Result<L2, Self::Error>;
+ fn visit_note(self, note: &'a N1) -> Result<N2, Self::Error>;
+ fn visit_embed(self, embed: &'a E1) -> Result<E2, Self::Error>;
+
+ fn visit_subexpr_under_binder(
+ mut self,
+ _label: &'a L1,
+ subexpr: &'a SE1,
+ ) -> Result<SE2, Self::Error> {
+ self.visit_subexpr(subexpr)
+ }
+
+ fn visit_binder(
+ mut self,
+ label: &'a L1,
+ subexpr: &'a SE1,
+ ) -> Result<(L2, SE2), Self::Error> {
+ Ok((
+ self.visit_label(label)?,
+ self.visit_subexpr_under_binder(label, subexpr)?,
+ ))
+ }
+
+ fn visit_embed_squash(
+ self,
+ embed: &'a E1,
+ ) -> Result<ExprF<SE2, L2, N2, E2>, Self::Error> {
+ Ok(ExprF::Embed(self.visit_embed(embed)?))
+ }
+
+ fn visit_note_squash(
+ mut self,
+ note: &'a N1,
+ subexpr: &'a SE1,
+ ) -> Result<ExprF<SE2, L2, N2, E2>, Self::Error> {
+ let subexpr = self.visit_subexpr(subexpr)?;
+ let note = self.visit_note(note)?;
+ Ok(ExprF::Note(note, subexpr))
+ }
+}
+
+impl<'a, T, SE1, SE2, L1, L2, N1, N2, E1, E2>
+ ExprFVeryGenericVisitor<'a, ExprF<SE2, L2, N2, E2>, SE1, L1, N1, E1> for T
+where
+ T: ExprFFallibleVisitor<'a, SE1, SE2, L1, L2, N1, N2, E1, E2>,
+{
+ type Error = T::Error;
+ type SE2 = SE2;
+ type L2 = L2;
+ type N2 = N2;
+ type E2 = E2;
+
+ fn visit_subexpr(
+ &mut self,
+ subexpr: &'a SE1,
+ ) -> Result<Self::SE2, Self::Error> {
+ self.visit_subexpr(subexpr)
+ }
+
+ fn visit_label(&mut self, label: &'a L1) -> Result<Self::L2, Self::Error> {
+ self.visit_label(label)
+ }
+
+ fn visit_binder(
+ self,
+ label: &'a L1,
+ subexpr: &'a SE1,
+ ) -> Result<(Self::L2, Self::SE2), Self::Error> {
+ self.visit_binder(label, subexpr)
+ }
+
+ fn visit_embed_squash(
+ self,
+ embed: &'a E1,
+ ) -> Result<ExprF<SE2, L2, N2, E2>, Self::Error> {
+ self.visit_embed_squash(embed)
+ }
+
+ fn visit_note_squash(
+ self,
+ note: &'a N1,
+ subexpr: &'a SE1,
+ ) -> Result<ExprF<SE2, L2, N2, E2>, Self::Error> {
+ self.visit_note_squash(note, subexpr)
+ }
+
+ // Called with the result of the map, in the non-embed/note case.
+ // Useful to change the result type, and/or avoid some loss of info
+ fn visit_resulting_exprf(
+ result: ExprF<Self::SE2, Self::L2, Self::N2, Self::E2>,
+ ) -> Result<ExprF<SE2, L2, N2, E2>, Self::Error> {
+ Ok(result)
+ }
+}
+
+/// Like ExprFFallibleVisitor, but without the error handling.
+pub trait ExprFInFallibleVisitor<'a, SE1, SE2, L1, L2, N1, N2, E1, E2>:
+ Sized
+{
+ fn visit_subexpr(&mut self, subexpr: &'a SE1) -> SE2;
+ fn visit_label(&mut self, label: &'a L1) -> L2;
+ fn visit_note(self, note: &'a N1) -> N2;
+ fn visit_embed(self, embed: &'a E1) -> E2;
+
+ fn visit_subexpr_under_binder(
+ mut self,
+ _label: &'a L1,
+ subexpr: &'a SE1,
+ ) -> SE2 {
+ self.visit_subexpr(subexpr)
+ }
+
+ fn visit_binder(mut self, label: &'a L1, subexpr: &'a SE1) -> (L2, SE2) {
+ (
+ self.visit_label(label),
+ self.visit_subexpr_under_binder(label, subexpr),
+ )
+ }
+
+ fn visit_embed_squash(self, embed: &'a E1) -> ExprF<SE2, L2, N2, E2> {
+ ExprF::Embed(self.visit_embed(embed))
+ }
+
+ fn visit_note_squash(
+ mut self,
+ note: &'a N1,
+ subexpr: &'a SE1,
+ ) -> ExprF<SE2, L2, N2, E2> {
+ let subexpr = self.visit_subexpr(subexpr);
+ let note = self.visit_note(note);
+ ExprF::Note(note, subexpr)
+ }
+}
+
+struct InfallibleWrapper<T>(T);
+
+impl<'a, T, SE1, SE2, L1, L2, N1, N2, E1, E2>
+ ExprFFallibleVisitor<'a, SE1, SE2, L1, L2, N1, N2, E1, E2>
+ for InfallibleWrapper<T>
+where
+ T: ExprFInFallibleVisitor<'a, SE1, SE2, L1, L2, N1, N2, E1, E2>,
+{
+ type Error = X;
+
+ fn visit_subexpr(&mut self, subexpr: &'a SE1) -> Result<SE2, Self::Error> {
+ Ok(self.0.visit_subexpr(subexpr))
+ }
+ fn visit_label(&mut self, label: &'a L1) -> Result<L2, Self::Error> {
+ Ok(self.0.visit_label(label))
+ }
+ fn visit_note(self, note: &'a N1) -> Result<N2, Self::Error> {
+ Ok(self.0.visit_note(note))
+ }
+ fn visit_embed(self, embed: &'a E1) -> Result<E2, Self::Error> {
+ Ok(self.0.visit_embed(embed))
+ }
+
+ fn visit_binder(
+ self,
+ label: &'a L1,
+ subexpr: &'a SE1,
+ ) -> Result<(L2, SE2), Self::Error> {
+ Ok(self.0.visit_binder(label, subexpr))
+ }
+
+ fn visit_embed_squash(
+ self,
+ embed: &'a E1,
+ ) -> Result<ExprF<SE2, L2, N2, E2>, Self::Error> {
+ Ok(self.0.visit_embed_squash(embed))
+ }
+
+ fn visit_note_squash(
+ self,
+ note: &'a N1,
+ subexpr: &'a SE1,
+ ) -> Result<ExprF<SE2, L2, N2, E2>, Self::Error> {
+ Ok(self.0.visit_note_squash(note, subexpr))
+ }
+}
+
+impl<'a, T, SE1, SE2, L1, L2, N1, N2, E1, E2>
+ GenericVisitor<&'a ExprF<SE1, L1, N1, E1>, ExprF<SE2, L2, N2, E2>> for T
+where
+ L1: Ord,
+ L2: Ord,
+ T: ExprFInFallibleVisitor<'a, SE1, SE2, L1, L2, N1, N2, E1, E2>,
+{
+ fn visit(
+ self,
+ input: &'a ExprF<SE1, L1, N1, E1>,
+ ) -> ExprF<SE2, L2, N2, E2> {
+ trivial_result(InfallibleWrapper(self).visit(input))
+ }
+}
+
+pub struct TraverseRefWithBindersVisitor<F1, F2, F3, F4, F5> {
+ pub visit_subexpr: F1,
+ pub visit_under_binder: F2,
+ pub visit_note: F3,
+ pub visit_embed: F4,
+ pub visit_label: F5,
+}
+
+impl<'a, SE, L, N, E, SE2, L2, N2, E2, Err, F1, F2, F3, F4, F5>
+ ExprFFallibleVisitor<'a, SE, SE2, L, L2, N, N2, E, E2>
+ for TraverseRefWithBindersVisitor<F1, F2, F3, F4, F5>
+where
+ SE: 'a,
+ L: 'a,
+ N: 'a,
+ E: 'a,
+ L: Ord,
+ L2: Ord,
+ F1: FnMut(&'a SE) -> Result<SE2, Err>,
+ F2: FnOnce(&'a L, &'a SE) -> Result<SE2, Err>,
+ F3: FnOnce(&'a N) -> Result<N2, Err>,
+ F4: FnOnce(&'a E) -> Result<E2, Err>,
+ F5: FnMut(&'a L) -> Result<L2, Err>,
+{
+ type Error = Err;
+
+ fn visit_subexpr(&mut self, subexpr: &'a SE) -> Result<SE2, Self::Error> {
+ (self.visit_subexpr)(subexpr)
+ }
+ fn visit_subexpr_under_binder(
+ self,
+ label: &'a L,
+ subexpr: &'a SE,
+ ) -> Result<SE2, Self::Error> {
+ (self.visit_under_binder)(label, subexpr)
+ }
+ fn visit_note(self, note: &'a N) -> Result<N2, Self::Error> {
+ (self.visit_note)(note)
+ }
+ fn visit_embed(self, embed: &'a E) -> Result<E2, Self::Error> {
+ (self.visit_embed)(embed)
+ }
+ fn visit_label(&mut self, label: &'a L) -> Result<L2, Self::Error> {
+ (self.visit_label)(label)
+ }
+}
+
+pub struct TraverseRefVisitor<F1, F2, F3, F4> {
+ pub visit_subexpr: F1,
+ pub visit_note: F2,
+ pub visit_embed: F3,
+ pub visit_label: F4,
+}
+
+impl<'a, SE, L, N, E, SE2, L2, N2, E2, Err, F1, F2, F3, F4>
+ ExprFFallibleVisitor<'a, SE, SE2, L, L2, N, N2, E, E2>
+ for TraverseRefVisitor<F1, F2, F3, F4>
+where
+ SE: 'a,
+ L: 'a,
+ N: 'a,
+ E: 'a,
+ L: Ord,
+ L2: Ord,
+ F1: FnMut(&'a SE) -> Result<SE2, Err>,
+ F2: FnOnce(&'a N) -> Result<N2, Err>,
+ F3: FnOnce(&'a E) -> Result<E2, Err>,
+ F4: FnMut(&'a L) -> Result<L2, Err>,
+{
+ type Error = Err;
+
+ fn visit_subexpr(&mut self, subexpr: &'a SE) -> Result<SE2, Self::Error> {
+ (self.visit_subexpr)(subexpr)
+ }
+ fn visit_note(self, note: &'a N) -> Result<N2, Self::Error> {
+ (self.visit_note)(note)
+ }
+ fn visit_embed(self, embed: &'a E) -> Result<E2, Self::Error> {
+ (self.visit_embed)(embed)
+ }
+ fn visit_label(&mut self, label: &'a L) -> Result<L2, Self::Error> {
+ (self.visit_label)(label)
+ }
+}
+
+pub struct TraverseEmbedVisitor<F1>(pub F1);
+
+impl<'a, 'b, N, E, E2, Err, F1>
+ ExprFFallibleVisitor<
+ 'a,
+ SubExpr<N, E>,
+ SubExpr<N, E2>,
+ Label,
+ Label,
+ N,
+ N,
+ E,
+ E2,
+ > for &'b mut TraverseEmbedVisitor<F1>
+where
+ N: Clone + 'a,
+ F1: FnMut(&E) -> Result<E2, Err>,
+{
+ type Error = Err;
+
+ fn visit_subexpr(
+ &mut self,
+ subexpr: &'a SubExpr<N, E>,
+ ) -> Result<SubExpr<N, E2>, Self::Error> {
+ Ok(rc(subexpr.as_ref().visit(&mut **self)?))
+ }
+ fn visit_note(self, note: &'a N) -> Result<N, Self::Error> {
+ Ok(N::clone(note))
+ }
+ fn visit_embed(self, embed: &'a E) -> Result<E2, Self::Error> {
+ (self.0)(embed)
+ }
+ fn visit_label(&mut self, label: &'a Label) -> Result<Label, Self::Error> {
+ Ok(Label::clone(label))
+ }
+}
+
+pub struct SquashEmbedVisitor<F1>(pub F1);
+
+impl<'a, 'b, N, E1, E2, F1>
+ ExprFVeryGenericVisitor<'a, SubExpr<N, E2>, SubExpr<N, E1>, Label, N, E1>
+ for &'b mut SquashEmbedVisitor<F1>
+where
+ N: Clone + 'a,
+ F1: FnMut(&E1) -> SubExpr<N, E2>,
+{
+ type Error = X;
+ type SE2 = SubExpr<N, E2>;
+ type L2 = Label;
+ type N2 = N;
+ type E2 = E2;
+
+ fn visit_subexpr(
+ &mut self,
+ subexpr: &'a SubExpr<N, E1>,
+ ) -> Result<Self::SE2, Self::Error> {
+ Ok(subexpr.as_ref().visit(&mut **self)?)
+ }
+
+ fn visit_label(
+ &mut self,
+ label: &'a Label,
+ ) -> Result<Self::L2, Self::Error> {
+ Ok(Label::clone(label))
+ }
+
+ fn visit_binder(
+ mut self,
+ label: &'a Label,
+ subexpr: &'a SubExpr<N, E1>,
+ ) -> Result<(Self::L2, Self::SE2), Self::Error> {
+ Ok((self.visit_label(label)?, self.visit_subexpr(subexpr)?))
+ }
+
+ fn visit_embed_squash(
+ self,
+ embed: &'a E1,
+ ) -> Result<SubExpr<N, E2>, Self::Error> {
+ Ok((self.0)(embed))
+ }
+
+ fn visit_note_squash(
+ mut self,
+ note: &'a N,
+ subexpr: &'a SubExpr<N, E1>,
+ ) -> Result<SubExpr<N, E2>, Self::Error> {
+ let subexpr = self.visit_subexpr(subexpr)?;
+ let note = N::clone(note);
+ Ok(rc(ExprF::Note(note, subexpr)))
+ }
+
+ // Called with the result of the map, in the non-embed/note case.
+ // Useful to change the result type, and/or avoid some loss of info
+ fn visit_resulting_exprf(
+ result: ExprF<Self::SE2, Self::L2, Self::N2, Self::E2>,
+ ) -> Result<SubExpr<N, E2>, Self::Error> {
+ Ok(rc(result))
+ }
+}
+
+pub struct UnNoteVisitor;
+
+impl<'a, 'b, N, E>
+ ExprFInFallibleVisitor<
+ 'a,
+ SubExpr<N, E>,
+ SubExpr<X, E>,
+ Label,
+ Label,
+ N,
+ X,
+ E,
+ E,
+ > for &'b mut UnNoteVisitor
+where
+ E: Clone + 'a,
+{
+ fn visit_subexpr(&mut self, subexpr: &'a SubExpr<N, E>) -> SubExpr<X, E> {
+ rc(subexpr.as_ref().visit(&mut **self))
+ }
+ fn visit_note(self, _: &'a N) -> X {
+ unreachable!()
+ }
+ fn visit_note_squash(
+ self,
+ _: &'a N,
+ subexpr: &'a SubExpr<N, E>,
+ ) -> Expr<X, E> {
+ subexpr.as_ref().visit(self)
+ }
+ fn visit_embed(self, embed: &'a E) -> E {
+ E::clone(embed)
+ }
+ fn visit_label(&mut self, label: &'a Label) -> Label {
+ Label::clone(label)
+ }
+}
+
+pub struct NoteAbsurdVisitor;
+
+impl<'a, 'b, N, E>
+ ExprFInFallibleVisitor<
+ 'a,
+ SubExpr<X, E>,
+ SubExpr<N, E>,
+ Label,
+ Label,
+ X,
+ N,
+ E,
+ E,
+ > for &'b mut NoteAbsurdVisitor
+where
+ E: Clone + 'a,
+{
+ fn visit_subexpr(&mut self, subexpr: &'a SubExpr<X, E>) -> SubExpr<N, E> {
+ rc(subexpr.as_ref().visit(&mut **self))
+ }
+ fn visit_note(self, note: &'a X) -> N {
+ match *note {}
+ }
+ fn visit_embed(self, embed: &'a E) -> E {
+ E::clone(embed)
+ }
+ fn visit_label(&mut self, label: &'a Label) -> Label {
+ Label::clone(label)
+ }
+}
+
+pub struct EmbedAbsurdVisitor;
+
+impl<'a, 'b, N, E>
+ ExprFInFallibleVisitor<
+ 'a,
+ SubExpr<N, X>,
+ SubExpr<N, E>,
+ Label,
+ Label,
+ N,
+ N,
+ X,
+ E,
+ > for &'b mut EmbedAbsurdVisitor
+where
+ N: Clone + 'a,
+{
+ fn visit_subexpr(&mut self, subexpr: &'a SubExpr<N, X>) -> SubExpr<N, E> {
+ rc(subexpr.as_ref().visit(&mut **self))
+ }
+ fn visit_note(self, note: &'a N) -> N {
+ N::clone(note)
+ }
+ fn visit_embed(self, embed: &'a X) -> E {
+ match *embed {}
+ }
+ fn visit_label(&mut self, label: &'a Label) -> Label {
+ Label::clone(label)
+ }
+}