From 423fdeebe9247b16744fae4b50df415bbd08be04 Mon Sep 17 00:00:00 2001 From: Nadrieril Date: Mon, 6 May 2019 22:09:55 +0200 Subject: Reorganize dhall into a phase structure --- dhall/src/phase/binary.rs | 363 ++++++++ dhall/src/phase/mod.rs | 389 +++++++++ dhall/src/phase/normalize.rs | 1888 ++++++++++++++++++++++++++++++++++++++++++ dhall/src/phase/parse.rs | 38 + dhall/src/phase/resolve.rs | 139 ++++ dhall/src/phase/typecheck.rs | 1276 ++++++++++++++++++++++++++++ 6 files changed, 4093 insertions(+) create mode 100644 dhall/src/phase/binary.rs create mode 100644 dhall/src/phase/mod.rs create mode 100644 dhall/src/phase/normalize.rs create mode 100644 dhall/src/phase/parse.rs create mode 100644 dhall/src/phase/resolve.rs create mode 100644 dhall/src/phase/typecheck.rs (limited to 'dhall/src/phase') diff --git a/dhall/src/phase/binary.rs b/dhall/src/phase/binary.rs new file mode 100644 index 0000000..9c31d4c --- /dev/null +++ b/dhall/src/phase/binary.rs @@ -0,0 +1,363 @@ +use dhall_syntax::*; +use itertools::*; +use serde_cbor::value::value as cbor; + +type ParsedExpr = SubExpr; + +#[derive(Debug)] +pub enum DecodeError { + CBORError(serde_cbor::error::Error), + WrongFormatError(String), +} + +pub fn decode(data: &[u8]) -> Result { + match serde_cbor::de::from_slice(data) { + Ok(v) => cbor_value_to_dhall(&v), + Err(e) => Err(DecodeError::CBORError(e)), + } +} + +fn cbor_value_to_dhall(data: &cbor::Value) -> Result { + use cbor::Value::*; + use dhall_syntax::{BinOp, Builtin, Const}; + use ExprF::*; + Ok(rc(match data { + String(s) => match Builtin::parse(s) { + Some(b) => ExprF::Builtin(b), + None => match s.as_str() { + "True" => BoolLit(true), + "False" => BoolLit(false), + "Type" => Const(Const::Type), + "Kind" => Const(Const::Kind), + "Sort" => Const(Const::Sort), + _ => Err(DecodeError::WrongFormatError("builtin".to_owned()))?, + }, + }, + U64(n) => Var(V(Label::from("_"), *n as usize)), + F64(x) => DoubleLit((*x).into()), + Bool(b) => BoolLit(*b), + Array(vec) => match vec.as_slice() { + [String(l), U64(n)] => { + let l = Label::from(l.as_str()); + Var(V(l, *n as usize)) + } + [U64(0), f, args..] => { + let mut f = cbor_value_to_dhall(&f)?; + for a in args { + let a = cbor_value_to_dhall(&a)?; + f = rc(App(f, a)) + } + return Ok(f); + } + [U64(1), x, y] => { + let x = cbor_value_to_dhall(&x)?; + let y = cbor_value_to_dhall(&y)?; + Lam(Label::from("_"), x, y) + } + [U64(1), String(l), x, y] => { + let x = cbor_value_to_dhall(&x)?; + let y = cbor_value_to_dhall(&y)?; + let l = Label::from(l.as_str()); + Lam(l, x, y) + } + [U64(2), x, y] => { + let x = cbor_value_to_dhall(&x)?; + let y = cbor_value_to_dhall(&y)?; + Pi(Label::from("_"), x, y) + } + [U64(2), String(l), x, y] => { + let x = cbor_value_to_dhall(&x)?; + let y = cbor_value_to_dhall(&y)?; + let l = Label::from(l.as_str()); + Pi(l, x, y) + } + [U64(3), U64(n), x, y] => { + let x = cbor_value_to_dhall(&x)?; + let y = cbor_value_to_dhall(&y)?; + use BinOp::*; + let op = match n { + 0 => BoolOr, + 1 => BoolAnd, + 2 => BoolEQ, + 3 => BoolNE, + 4 => NaturalPlus, + 5 => NaturalTimes, + 6 => TextAppend, + 7 => ListAppend, + 8 => Combine, + 9 => Prefer, + 10 => CombineTypes, + 11 => ImportAlt, + _ => { + Err(DecodeError::WrongFormatError("binop".to_owned()))? + } + }; + BinOp(op, x, y) + } + [U64(4), t] => { + let t = cbor_value_to_dhall(&t)?; + EmptyListLit(t) + } + [U64(4), Null, rest..] => { + let rest = rest + .iter() + .map(cbor_value_to_dhall) + .collect::, _>>()?; + NEListLit(rest) + } + [U64(5), t] => { + let t = cbor_value_to_dhall(&t)?; + OldOptionalLit(None, t) + } + [U64(5), Null, x] => { + let x = cbor_value_to_dhall(&x)?; + SomeLit(x) + } + [U64(5), t, x] => { + let x = cbor_value_to_dhall(&x)?; + let t = cbor_value_to_dhall(&t)?; + OldOptionalLit(Some(x), t) + } + [U64(6), x, y] => { + let x = cbor_value_to_dhall(&x)?; + let y = cbor_value_to_dhall(&y)?; + Merge(x, y, None) + } + [U64(6), x, y, z] => { + let x = cbor_value_to_dhall(&x)?; + let y = cbor_value_to_dhall(&y)?; + let z = cbor_value_to_dhall(&z)?; + Merge(x, y, Some(z)) + } + [U64(7), Object(map)] => { + let map = cbor_map_to_dhall_map(map)?; + RecordType(map) + } + [U64(8), Object(map)] => { + let map = cbor_map_to_dhall_map(map)?; + RecordLit(map) + } + [U64(9), x, String(l)] => { + let x = cbor_value_to_dhall(&x)?; + let l = Label::from(l.as_str()); + Field(x, l) + } + [U64(11), Object(map)] => { + let map = cbor_map_to_dhall_opt_map(map)?; + UnionType(map) + } + [U64(12), String(l), x, Object(map)] => { + let map = cbor_map_to_dhall_opt_map(map)?; + let x = cbor_value_to_dhall(&x)?; + let l = Label::from(l.as_str()); + UnionLit(l, x, map) + } + [U64(14), x, y, z] => { + let x = cbor_value_to_dhall(&x)?; + let y = cbor_value_to_dhall(&y)?; + let z = cbor_value_to_dhall(&z)?; + BoolIf(x, y, z) + } + [U64(15), U64(x)] => NaturalLit(*x as Natural), + [U64(16), U64(x)] => IntegerLit(*x as Integer), + [U64(16), I64(x)] => IntegerLit(*x as Integer), + [U64(18), String(first), rest..] => { + TextLit(InterpolatedText::from(( + first.clone(), + rest.iter() + .tuples() + .map(|(x, y)| { + let x = cbor_value_to_dhall(&x)?; + let y = match y { + String(s) => s.clone(), + _ => Err(DecodeError::WrongFormatError( + "text".to_owned(), + ))?, + }; + Ok((x, y)) + }) + .collect::>()?, + ))) + } + [U64(24), hash, U64(mode), U64(scheme), rest..] => { + let mode = match mode { + 1 => ImportMode::RawText, + _ => ImportMode::Code, + }; + let hash = match hash { + Null => None, + Array(vec) => match vec.as_slice() { + [String(protocol), String(hash)] => Some(Hash { + protocol: protocol.clone(), + hash: hash.clone(), + }), + _ => Err(DecodeError::WrongFormatError( + "import/hash".to_owned(), + ))?, + }, + _ => Err(DecodeError::WrongFormatError( + "import/hash".to_owned(), + ))?, + }; + let mut rest = rest.iter(); + let location = match scheme { + 0 | 1 => { + let scheme = match scheme { + 0 => Scheme::HTTP, + _ => Scheme::HTTPS, + }; + let headers = match rest.next() { + Some(Null) => None, + Some(x) => { + match cbor_value_to_dhall(&x)?.as_ref() { + Embed(import) => Some(Box::new( + import.location_hashed.clone(), + )), + _ => Err(DecodeError::WrongFormatError( + "import/remote/headers".to_owned(), + ))?, + } + } + _ => Err(DecodeError::WrongFormatError( + "import/remote/headers".to_owned(), + ))?, + }; + let authority = match rest.next() { + Some(String(s)) => s.to_owned(), + _ => Err(DecodeError::WrongFormatError( + "import/remote/authority".to_owned(), + ))?, + }; + let query = match rest.next_back() { + Some(Null) => None, + Some(String(s)) => Some(s.to_owned()), + _ => Err(DecodeError::WrongFormatError( + "import/remote/query".to_owned(), + ))?, + }; + let path = rest + .map(|s| { + s.as_string().ok_or_else(|| { + DecodeError::WrongFormatError( + "import/remote/path".to_owned(), + ) + }) + }) + .collect::>()?; + ImportLocation::Remote(URL { + scheme, + authority, + path, + query, + headers, + }) + } + 2 | 3 | 4 | 5 => { + let prefix = match scheme { + 2 => FilePrefix::Absolute, + 3 => FilePrefix::Here, + 4 => FilePrefix::Parent, + 5 => FilePrefix::Home, + _ => Err(DecodeError::WrongFormatError( + "import/local/prefix".to_owned(), + ))?, + }; + let path = rest + .map(|s| { + s.as_string().ok_or_else(|| { + DecodeError::WrongFormatError( + "import/local/path".to_owned(), + ) + }) + }) + .collect::>()?; + ImportLocation::Local(prefix, path) + } + 6 => { + let env = match rest.next() { + Some(String(s)) => s.to_owned(), + _ => Err(DecodeError::WrongFormatError( + "import/env".to_owned(), + ))?, + }; + ImportLocation::Env(env) + } + 7 => ImportLocation::Missing, + _ => Err(DecodeError::WrongFormatError( + "import/type".to_owned(), + ))?, + }; + Embed(Import { + mode, + location_hashed: ImportHashed { hash, location }, + }) + } + [U64(25), bindings..] => { + let mut tuples = bindings.iter().tuples(); + let bindings = (&mut tuples) + .map(|(x, t, v)| { + let x = x.as_string().ok_or_else(|| { + DecodeError::WrongFormatError( + "let/label".to_owned(), + ) + })?; + let x = Label::from(x.as_str()); + let t = match t { + Null => None, + t => Some(cbor_value_to_dhall(&t)?), + }; + let v = cbor_value_to_dhall(&v)?; + Ok((x, t, v)) + }) + .collect::, _>>()?; + let expr = tuples.into_buffer().next().ok_or_else(|| { + DecodeError::WrongFormatError("let/expr".to_owned()) + })?; + let expr = cbor_value_to_dhall(expr)?; + return Ok(bindings + .into_iter() + .rev() + .fold(expr, |acc, (x, t, v)| rc(Let(x, t, v, acc)))); + } + [U64(26), x, y] => { + let x = cbor_value_to_dhall(&x)?; + let y = cbor_value_to_dhall(&y)?; + Annot(x, y) + } + _ => Err(DecodeError::WrongFormatError(format!("{:?}", data)))?, + }, + _ => Err(DecodeError::WrongFormatError(format!("{:?}", data)))?, + })) +} + +fn cbor_map_to_dhall_map( + map: &std::collections::BTreeMap, +) -> Result, DecodeError> { + map.iter() + .map(|(k, v)| -> Result<(_, _), _> { + let k = k.as_string().ok_or_else(|| { + DecodeError::WrongFormatError("map/key".to_owned()) + })?; + let v = cbor_value_to_dhall(v)?; + Ok((Label::from(k.as_ref()), v)) + }) + .collect::>() +} + +fn cbor_map_to_dhall_opt_map( + map: &std::collections::BTreeMap, +) -> Result>, DecodeError> +{ + map.iter() + .map(|(k, v)| -> Result<(_, _), _> { + let k = k.as_string().ok_or_else(|| { + DecodeError::WrongFormatError("map/key".to_owned()) + })?; + let v = match v { + cbor::Value::Null => None, + _ => Some(cbor_value_to_dhall(v)?), + }; + Ok((Label::from(k.as_ref()), v)) + }) + .collect::>() +} diff --git a/dhall/src/phase/mod.rs b/dhall/src/phase/mod.rs new file mode 100644 index 0000000..0b6eca6 --- /dev/null +++ b/dhall/src/phase/mod.rs @@ -0,0 +1,389 @@ +use std::borrow::Cow; +use std::fmt::Display; +use std::path::Path; + +use dhall_syntax::{Const, Import, Span, SubExpr, X}; + +use crate::error::Error; + +use normalize::{AlphaVar, Thunk, Value}; +use resolve::{ImportError, ImportRoot}; +use typecheck::{ + const_to_typed, type_of_const, TypeError, TypeMessage, TypecheckContext, +}; + +pub(crate) mod binary; +pub(crate) mod normalize; +pub(crate) mod parse; +pub(crate) mod resolve; +pub(crate) mod typecheck; + +pub(crate) type ParsedSubExpr = SubExpr; +pub(crate) type ResolvedSubExpr = SubExpr; +pub(crate) type NormalizedSubExpr = SubExpr; + +#[derive(Debug, Clone)] +pub(crate) struct Parsed(pub(crate) ParsedSubExpr, pub(crate) ImportRoot); + +/// An expression where all imports have been resolved +#[derive(Debug, Clone)] +pub(crate) struct Resolved(pub(crate) ResolvedSubExpr); + +/// A typed expression +#[derive(Debug, Clone)] +pub(crate) enum Typed { + // The `Sort` higher-kinded type; it doesn't have a type + Sort, + // Any other value, along with (optionally) its type + Value(Thunk, Option), +} + +/// A normalized expression. +/// +/// Invariant: the contained Typed expression must be in normal form, +#[derive(Debug, Clone)] +pub(crate) struct Normalized(pub(crate) Typed); + +/// A Dhall expression representing a simple type. +/// +/// This captures what is usually simply called a "type", like +/// `Bool`, `{ x: Integer }` or `Natural -> Text`. +/// +/// For a more general notion of "type", see [Type]. +#[derive(Debug, Clone)] +pub struct SimpleType(pub(crate) NormalizedSubExpr); + +/// A Dhall expression representing a (possibly higher-kinded) type. +/// +/// This includes [SimpleType]s but also higher-kinded expressions like +/// `Type`, `Kind` and `{ x: Type }`. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Type(pub(crate) TypeInternal); + +#[derive(Debug, Clone)] +pub(crate) enum TypeInternal { + Const(Const), + /// This must not contain a Const value. + Typed(Box), +} + +impl Parsed { + pub fn parse_file(f: &Path) -> Result { + parse::parse_file(f) + } + + pub fn parse_str(s: &str) -> Result { + parse::parse_str(s) + } + + #[allow(dead_code)] + pub fn parse_binary_file(f: &Path) -> Result { + parse::parse_binary_file(f) + } + + pub fn resolve(self) -> Result { + resolve::resolve(self) + } + + #[allow(dead_code)] + pub fn skip_resolve(self) -> Result { + resolve::skip_resolve_expr(self) + } +} + +impl Resolved { + pub fn typecheck(self) -> Result { + typecheck::typecheck(self) + } + pub fn typecheck_with(self, ty: &Type) -> Result { + typecheck::typecheck_with(self, ty) + } + /// Pretends this expression has been typechecked. Use with care. + #[allow(dead_code)] + pub fn skip_typecheck(self) -> Typed { + typecheck::skip_typecheck(self) + } +} + +impl Typed { + /// Reduce an expression to its normal form, performing beta reduction + /// + /// `normalize` does not type-check the expression. You may want to type-check + /// expressions before normalizing them since normalization can convert an + /// ill-typed expression into a well-typed expression. + /// + /// However, `normalize` will not fail if the expression is ill-typed and will + /// leave ill-typed sub-expressions unevaluated. + pub fn normalize(self) -> Normalized { + match &self { + Typed::Sort => {} + Typed::Value(thunk, _) => { + thunk.normalize_nf(); + } + } + Normalized(self) + } + + pub(crate) fn from_thunk_and_type(th: Thunk, t: Type) -> Self { + Typed::Value(th, Some(t)) + } + pub(crate) fn from_thunk_untyped(th: Thunk) -> Self { + Typed::Value(th, None) + } + + // TODO: Avoid cloning if possible + pub(crate) fn to_value(&self) -> Value { + match self { + Typed::Value(th, _) => th.to_value(), + Typed::Sort => Value::Const(Const::Sort), + } + } + pub(crate) fn to_expr(&self) -> NormalizedSubExpr { + self.to_value().normalize_to_expr() + } + pub(crate) fn to_expr_alpha(&self) -> NormalizedSubExpr { + self.to_value().normalize_to_expr_maybe_alpha(true) + } + pub(crate) fn to_thunk(&self) -> Thunk { + match self { + Typed::Value(th, _) => th.clone(), + Typed::Sort => Thunk::from_value(Value::Const(Const::Sort)), + } + } + pub(crate) fn to_type(&self) -> Type { + match self { + Typed::Sort => Type(TypeInternal::Const(Const::Sort)), + Typed::Value(th, _) => match &*th.as_value() { + Value::Const(c) => Type(TypeInternal::Const(*c)), + _ => Type(TypeInternal::Typed(Box::new(self.clone()))), + }, + } + } + + pub(crate) fn get_type(&self) -> Result, TypeError> { + match self { + Typed::Value(_, Some(t)) => Ok(Cow::Borrowed(t)), + Typed::Value(_, None) => Err(TypeError::new( + &TypecheckContext::new(), + TypeMessage::Untyped, + )), + Typed::Sort => { + Err(TypeError::new(&TypecheckContext::new(), TypeMessage::Sort)) + } + } + } + + pub(crate) fn const_sort() -> Self { + Typed::Sort + } + + pub(crate) fn shift(&self, delta: isize, var: &AlphaVar) -> Self { + match self { + Typed::Value(th, t) => Typed::Value( + th.shift(delta, var), + t.as_ref().map(|x| x.shift(delta, var)), + ), + Typed::Sort => Typed::Sort, + } + } + + pub(crate) fn subst_shift(&self, var: &AlphaVar, val: &Typed) -> Self { + match self { + Typed::Value(th, t) => Typed::Value( + th.subst_shift(var, val), + t.as_ref().map(|x| x.subst_shift(var, val)), + ), + Typed::Sort => Typed::Sort, + } + } +} + +impl Type { + pub(crate) fn to_normalized(&self) -> Normalized { + self.0.to_normalized() + } + pub(crate) fn to_expr(&self) -> NormalizedSubExpr { + self.0.to_expr() + } + pub(crate) fn to_value(&self) -> Value { + self.0.to_value() + } + pub(crate) fn as_const(&self) -> Option { + self.0.as_const() + } + pub(crate) fn internal_whnf(&self) -> Option { + self.0.whnf() + } + pub(crate) fn get_type(&self) -> Result, TypeError> { + self.0.get_type() + } + + pub(crate) fn const_sort() -> Self { + Type(TypeInternal::Const(Const::Sort)) + } + pub(crate) fn const_kind() -> Self { + Type(TypeInternal::Const(Const::Kind)) + } + pub(crate) fn const_type() -> Self { + Type(TypeInternal::Const(Const::Type)) + } + + pub(crate) fn shift(&self, delta: isize, var: &AlphaVar) -> Self { + Type(self.0.shift(delta, var)) + } + pub(crate) fn subst_shift(&self, var: &AlphaVar, val: &Typed) -> Self { + Type(self.0.subst_shift(var, val)) + } +} + +impl TypeInternal { + pub(crate) fn to_typed(&self) -> Typed { + match self { + TypeInternal::Typed(e) => e.as_ref().clone(), + TypeInternal::Const(c) => const_to_typed(*c), + } + } + pub(crate) fn to_normalized(&self) -> Normalized { + self.to_typed().normalize() + } + pub(crate) fn to_expr(&self) -> NormalizedSubExpr { + self.to_normalized().to_expr() + } + pub(crate) fn to_value(&self) -> Value { + self.to_typed().to_value() + } + pub(crate) fn get_type(&self) -> Result, TypeError> { + Ok(match self { + TypeInternal::Typed(e) => e.get_type()?, + TypeInternal::Const(c) => Cow::Owned(type_of_const(*c)?), + }) + } + pub(crate) fn as_const(&self) -> Option { + match self { + TypeInternal::Const(c) => Some(*c), + _ => None, + } + } + pub(crate) fn whnf(&self) -> Option { + match self { + TypeInternal::Typed(e) => Some(e.to_value()), + _ => None, + } + } + pub(crate) fn shift(&self, delta: isize, var: &AlphaVar) -> Self { + use TypeInternal::*; + match self { + Typed(e) => Typed(Box::new(e.shift(delta, var))), + Const(c) => Const(*c), + } + } + pub(crate) fn subst_shift(&self, var: &AlphaVar, val: &Typed) -> Self { + use TypeInternal::*; + match self { + Typed(e) => Typed(Box::new(e.subst_shift(var, val))), + Const(c) => Const(*c), + } + } +} + +impl Normalized { + pub(crate) fn from_thunk_and_type(th: Thunk, t: Type) -> Self { + Normalized(Typed::from_thunk_and_type(th, t)) + } + + pub(crate) fn to_expr(&self) -> NormalizedSubExpr { + self.0.to_expr() + } + #[allow(dead_code)] + pub(crate) fn to_expr_alpha(&self) -> NormalizedSubExpr { + self.0.to_expr_alpha() + } + pub(crate) fn to_value(&self) -> Value { + self.0.to_value() + } + pub(crate) fn to_thunk(&self) -> Thunk { + self.0.to_thunk() + } + pub(crate) fn to_type(self) -> Type { + self.0.to_type() + } + pub(crate) fn shift(&self, delta: isize, var: &AlphaVar) -> Self { + Normalized(self.0.shift(delta, var)) + } +} + +macro_rules! derive_traits_for_wrapper_struct { + ($ty:ident) => { + impl std::cmp::PartialEq for $ty { + fn eq(&self, other: &Self) -> bool { + self.0 == other.0 + } + } + + impl std::cmp::Eq for $ty {} + + impl std::fmt::Display for $ty { + fn fmt( + &self, + f: &mut std::fmt::Formatter, + ) -> Result<(), std::fmt::Error> { + self.0.fmt(f) + } + } + }; +} + +derive_traits_for_wrapper_struct!(Parsed); +derive_traits_for_wrapper_struct!(Resolved); +derive_traits_for_wrapper_struct!(Normalized); +derive_traits_for_wrapper_struct!(SimpleType); + +impl Eq for Typed {} +impl PartialEq for Typed { + fn eq(&self, other: &Self) -> bool { + self.to_value() == other.to_value() + } +} + +impl Eq for TypeInternal {} +impl PartialEq for TypeInternal { + fn eq(&self, other: &Self) -> bool { + self.to_normalized() == other.to_normalized() + } +} + +impl Display for Typed { + fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> { + self.to_expr().fmt(f) + } +} + +impl Display for Type { + fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> { + self.to_normalized().fmt(f) + } +} + +// Exposed for the macros +#[doc(hidden)] +impl From for NormalizedSubExpr { + fn from(x: SimpleType) -> NormalizedSubExpr { + x.0 + } +} + +// Exposed for the macros +#[doc(hidden)] +impl From for SimpleType { + fn from(x: NormalizedSubExpr) -> SimpleType { + SimpleType(x) + } +} + +// Exposed for the macros +#[doc(hidden)] +impl From for Typed { + fn from(x: Normalized) -> Typed { + x.0 + } +} diff --git a/dhall/src/phase/normalize.rs b/dhall/src/phase/normalize.rs new file mode 100644 index 0000000..2340bbd --- /dev/null +++ b/dhall/src/phase/normalize.rs @@ -0,0 +1,1888 @@ +#![allow(non_snake_case)] +use std::collections::BTreeMap; +use std::rc::Rc; + +use dhall_proc_macros as dhall; +use dhall_syntax::context::Context; +use dhall_syntax::{ + rc, BinOp, Builtin, Const, ExprF, Integer, InterpolatedTextContents, Label, + Natural, V, X, +}; + +use crate::phase::{NormalizedSubExpr, ResolvedSubExpr, Type, Typed}; + +type InputSubExpr = ResolvedSubExpr; +type OutputSubExpr = NormalizedSubExpr; + +/// Stores a pair of variables: a normal one and if relevant one +/// that corresponds to the alpha-normalized version of the first one. +#[derive(Debug, Clone, Eq)] +pub(crate) struct AlphaVar { + normal: V