summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFintan Halpenny2019-09-02 23:09:26 +0100
committerFintan Halpenny2019-09-02 23:09:26 +0100
commit8553b398a5f97eed240f5360282e911392cab6ff (patch)
tree076d554b7e066cf854aa50f350096ce55e3bd691
parente73f822b6972e8fa2e72b56ff5378b91bea1a5e6 (diff)
parent737abd9be6d35bbce784d9cf249edf7ad14677d6 (diff)
Merge remote-tracking branch 'origin/master' into fintan/canonicalize
-rw-r--r--Cargo.lock20
-rw-r--r--Cargo.toml1
m---------dhall-lang0
-rw-r--r--dhall/Cargo.toml4
-rw-r--r--dhall/build.rs490
-rw-r--r--dhall/src/api/mod.rs159
-rw-r--r--dhall/src/core/context.rs171
-rw-r--r--dhall/src/core/mod.rs8
-rw-r--r--dhall/src/core/thunk.rs312
-rw-r--r--dhall/src/core/value.rs842
-rw-r--r--dhall/src/core/valuef.rs335
-rw-r--r--dhall/src/core/var.rs221
-rw-r--r--dhall/src/error/mod.rs63
-rw-r--r--dhall/src/lib.rs119
-rw-r--r--dhall/src/phase/.resolve.rs.swpbin0 -> 16384 bytes
-rw-r--r--dhall/src/phase/binary.rs148
-rw-r--r--dhall/src/phase/mod.rs235
-rw-r--r--dhall/src/phase/normalize.rs953
-rw-r--r--dhall/src/phase/parse.rs12
-rw-r--r--dhall/src/phase/resolve.rs34
-rw-r--r--dhall/src/phase/typecheck.rs846
-rw-r--r--dhall/src/tests.rs327
-rw-r--r--dhall_generated_parser/build.rs61
-rw-r--r--dhall_generated_parser/src/dhall.pest.visibility16
-rw-r--r--dhall_proc_macros/Cargo.toml1
-rw-r--r--dhall_proc_macros/src/derive.rs15
-rw-r--r--dhall_syntax/Cargo.toml3
-rw-r--r--dhall_syntax/src/core/context.rs2
-rw-r--r--dhall_syntax/src/core/expr.rs291
-rw-r--r--dhall_syntax/src/core/import.rs91
-rw-r--r--dhall_syntax/src/core/text.rs2
-rw-r--r--dhall_syntax/src/core/visitor.rs293
-rw-r--r--dhall_syntax/src/lib.rs3
-rw-r--r--dhall_syntax/src/parser.rs821
-rw-r--r--dhall_syntax/src/printer.rs86
-rw-r--r--serde_dhall/Cargo.toml12
-rw-r--r--serde_dhall/src/lib.rs279
-rw-r--r--serde_dhall/src/serde.rs (renamed from dhall/src/api/serde.rs)52
-rw-r--r--serde_dhall/src/static_type.rs (renamed from dhall/src/api/static_type.rs)26
-rw-r--r--serde_dhall/tests/traits.rs (renamed from dhall/tests/traits.rs)11
-rw-r--r--tests_buffer14
41 files changed, 3474 insertions, 3905 deletions
diff --git a/Cargo.lock b/Cargo.lock
index dca96f7..9bd7adc 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -69,13 +69,13 @@ name = "dhall"
version = "0.1.0"
dependencies = [
"bytecount 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
- "dhall_proc_macros 0.1.0",
"dhall_syntax 0.1.0",
"improved_slice_patterns 2.0.0",
"itertools 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
"pretty_assertions 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.90 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_cbor 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "take_mut 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
"term-painter 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)",
"walkdir 2.2.7 (registry+https://github.com/rust-lang/crates.io-index)",
]
@@ -94,7 +94,6 @@ dependencies = [
name = "dhall_proc_macros"
version = "0.1.0"
dependencies = [
- "dhall_syntax 0.1.0",
"itertools 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
"proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -108,9 +107,8 @@ dependencies = [
"dhall_generated_parser 0.1.0",
"either 1.5.2 (registry+https://github.com/rust-lang/crates.io-index)",
"hex 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
- "improved_slice_patterns 2.0.0",
"itertools 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "percent-encoding 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "percent-encoding 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"pest 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"take_mut 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
@@ -211,7 +209,7 @@ dependencies = [
[[package]]
name = "percent-encoding"
-version = "1.0.1"
+version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
@@ -316,6 +314,16 @@ dependencies = [
]
[[package]]
+name = "serde_dhall"
+version = "0.1.0"
+dependencies = [
+ "dhall 0.1.0",
+ "dhall_proc_macros 0.1.0",
+ "dhall_syntax 0.1.0",
+ "serde 1.0.90 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
name = "sha-1"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -453,7 +461,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
"checksum memchr 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2efc7bc57c883d4a4d6e3246905283d8dae951bb3bd32f49d6ef297f546e1c39"
"checksum nom 4.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2ad2a91a8e869eeb30b9cb3119ae87773a8f4ae617f41b1eb9c154b2905f7bd6"
"checksum output_vt100 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "53cdc5b785b7a58c5aad8216b3dfa114df64b0b06ae6e1501cef91df2fbdf8f9"
-"checksum percent-encoding 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "31010dd2e1ac33d5b46a5b413495239882813e0369f8ed8a5e266f173602f831"
+"checksum percent-encoding 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e"
"checksum pest 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "54f0c72a98d8ab3c99560bfd16df8059cc10e1f9a8e83e6e3b97718dd766e9c3"
"checksum pest_generator 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "63120576c4efd69615b5537d3d052257328a4ca82876771d6944424ccfd9f646"
"checksum pest_meta 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f5a3492a4ed208ffc247adcdcc7ba2a95be3104f58877d0d02f0df39bf3efb5e"
diff --git a/Cargo.toml b/Cargo.toml
index f48e33a..1e40748 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -8,6 +8,7 @@ members = [
"dhall_syntax",
"dhall_proc_macros",
"improved_slice_patterns",
+ "serde_dhall"
]
# # Parser is super slow when not optimized
diff --git a/dhall-lang b/dhall-lang
-Subproject 235d2c0b11a539003d2de6110f8666e93ae1ccd
+Subproject 7639b4ed2fd457bfa77daabfd329fea3b25581c
diff --git a/dhall/Cargo.toml b/dhall/Cargo.toml
index d08627d..16c1c78 100644
--- a/dhall/Cargo.toml
+++ b/dhall/Cargo.toml
@@ -9,12 +9,12 @@ build = "build.rs"
[dependencies]
bytecount = "0.5.1"
itertools = "0.8.0"
+take_mut = "0.2.2"
term-painter = "0.2.3"
-serde = { version = "1.0", features = ["derive"] }
+serde = { version = "1.0" }
serde_cbor = "0.9.0"
improved_slice_patterns = { version = "2.0.0", path = "../improved_slice_patterns" }
dhall_syntax = { path = "../dhall_syntax" }
-dhall_proc_macros = { path = "../dhall_proc_macros" }
[dev-dependencies]
pretty_assertions = "0.6.1"
diff --git a/dhall/build.rs b/dhall/build.rs
index 790ad8e..757eed8 100644
--- a/dhall/build.rs
+++ b/dhall/build.rs
@@ -2,77 +2,99 @@ use std::env;
use std::ffi::OsString;
use std::fs::File;
use std::io::Write;
-use std::path::Path;
+use std::path::{Path, PathBuf};
use walkdir::WalkDir;
+#[derive(Debug, Clone, Copy)]
+enum FileType {
+ Text,
+ Binary,
+}
+
+impl FileType {
+ fn to_ext(self) -> &'static str {
+ match self {
+ FileType::Text => "dhall",
+ FileType::Binary => "dhallb",
+ }
+ }
+}
+
fn dhall_files_in_dir<'a>(
dir: &'a Path,
take_a_suffix: bool,
+ filetype: FileType,
) -> impl Iterator<Item = (String, String)> + 'a {
WalkDir::new(dir)
.into_iter()
.filter_map(|e| e.ok())
.filter_map(move |path| {
- let path = path.path();
- let path = path.strip_prefix(dir).unwrap();
- let ext = path.extension();
- if ext != Some(&OsString::from("dhall"))
- && ext != Some(&OsString::from("dhallb"))
- {
+ let path = path.path().strip_prefix(dir).unwrap();
+ let ext = path.extension()?;
+ if ext != &OsString::from(filetype.to_ext()) {
return None;
}
- let ext = ext.unwrap();
let path = path.to_string_lossy();
let path = &path[..path.len() - 1 - ext.len()];
- let path = if take_a_suffix {
- if &path[path.len() - 1..] != "A" {
- return None;
- } else {
- path[..path.len() - 1].to_owned()
- }
+ let path = if take_a_suffix && &path[path.len() - 1..] != "A" {
+ return None;
+ } else if take_a_suffix {
+ path[..path.len() - 1].to_owned()
} else {
path.to_owned()
};
+ // Transform path into a valie Rust identifier
let name = path.replace("/", "_").replace("-", "_");
Some((name, path))
})
}
+#[derive(Debug, Clone)]
+struct TestFeature<F> {
+ /// Name of the module, used in the output of `cargo test`
+ module_name: &'static str,
+ /// Directory containing the tests files
+ directory: PathBuf,
+ /// Relevant variant of `dhall::tests::Test`
+ variant: &'static str,
+ /// Given a file name, whether to exclude it
+ path_filter: F,
+ /// Type of the input file
+ input_type: FileType,
+ /// Type of the output file, if any
+ output_type: Option<FileType>,
+}
+
fn make_test_module(
w: &mut impl Write, // Where to output the generated code
- mod_name: &str, // Name of the module, used in the output of `cargo test`
- subdir: &str, // Directory containing the tests files
- feature: &str, // Relevant variant of `dhall::tests::Feature`
- mut exclude: impl FnMut(&str) -> bool, // Given a file name, whether to exclude it
+ mut feature: TestFeature<impl FnMut(&str) -> bool>,
) -> std::io::Result<()> {
- let all_tests_dir = Path::new("../dhall-lang/tests/");
- let tests_dir = all_tests_dir.join(subdir);
- writeln!(w, "mod {} {{", mod_name)?;
- for (name, path) in dhall_files_in_dir(&tests_dir.join("success/"), true) {
- if exclude(&("success/".to_owned() + &path)) {
- continue;
- }
- writeln!(
- w,
- r#"make_spec_test!({}, Success, success_{}, "{}/success/{}");"#,
- feature,
- name,
- tests_dir.to_string_lossy(),
- path
- )?;
- }
- for (name, path) in dhall_files_in_dir(&tests_dir.join("failure/"), false) {
- if exclude(&("failure/".to_owned() + &path)) {
+ let tests_dir = feature.directory;
+ writeln!(w, "mod {} {{", feature.module_name)?;
+ let take_a_suffix = feature.output_type.is_some();
+ for (name, path) in
+ dhall_files_in_dir(&tests_dir, take_a_suffix, feature.input_type)
+ {
+ if (feature.path_filter)(&path) {
continue;
}
- writeln!(
- w,
- r#"make_spec_test!({}, Failure, failure_{}, "{}/failure/{}");"#,
- feature,
- name,
- tests_dir.to_string_lossy(),
- path
- )?;
+ let path = tests_dir.join(path);
+ let path = path.to_string_lossy();
+ let test = match feature.output_type {
+ None => {
+ let input_file =
+ format!("\"{}.{}\"", path, feature.input_type.to_ext());
+ format!("{}({})", feature.variant, input_file)
+ }
+ Some(output_type) => {
+ let input_file =
+ format!("\"{}A.{}\"", path, feature.input_type.to_ext());
+ let output_file =
+ format!("\"{}B.{}\"", path, output_type.to_ext());
+ format!("{}({}, {})", feature.variant, input_file, output_file)
+ }
+ };
+ writeln!(w, "make_spec_test!({}, {});", test, name)?;
}
writeln!(w, "}}")?;
Ok(())
@@ -88,187 +110,265 @@ fn main() -> std::io::Result<()> {
let out_dir = env::var("OUT_DIR").unwrap();
let parser_tests_path = Path::new(&out_dir).join("spec_tests.rs");
+ let spec_tests_dir = Path::new("../dhall-lang/tests/");
let mut file = File::create(parser_tests_path)?;
- make_test_module(&mut file, "parse", "parser/", "Parser", |path| {
- // Too slow in debug mode
- path == "success/largeExpression"
- // TODO: Inline headers
- || path == "success/unit/import/inlineUsing"
- || path == "success/unit/import/Headers"
- || path == "success/unit/import/HeadersDoubleHash"
- || path == "success/unit/import/HeadersDoubleHashPrecedence"
- || path == "success/unit/import/HeadersHashPrecedence"
- || path == "success/unit/import/HeadersInteriorHash"
- // TODO: projection by expression
- || path == "success/recordProjectionByExpression"
- || path == "success/RecordProjectionByType"
- || path == "success/unit/RecordProjectionByType"
- || path == "success/unit/RecordProjectionByTypeEmpty"
- || path == "success/unit/RecordProjectFields"
- // TODO: RFC3986 URLs
- || path == "success/unit/import/urls/emptyPath0"
- || path == "success/unit/import/urls/emptyPath1"
- || path == "success/unit/import/urls/emptyPathSegment"
- // TODO: toMap
- || path == "success/toMap"
- })?;
+ make_test_module(
+ &mut file,
+ TestFeature {
+ module_name: "parser_success",
+ directory: spec_tests_dir.join("parser/success/"),
+ variant: "ParserSuccess",
+ path_filter: |path: &str| {
+ false
+ // Too slow in debug mode
+ || path == "largeExpression"
+ // Pretty sure the test is incorrect
+ || path == "unit/import/urls/quotedPathFakeUrlEncode"
+ // TODO: projection by expression
+ || path == "recordProjectionByExpression"
+ || path == "RecordProjectionByType"
+ || path == "unit/RecordProjectionByType"
+ || path == "unit/RecordProjectionByTypeEmpty"
+ || path == "unit/RecordProjectFields"
+ // TODO: RFC3986 URLs
+ || path == "unit/import/urls/emptyPath0"
+ || path == "unit/import/urls/emptyPath1"
+ || path == "unit/import/urls/emptyPathSegment"
+ },
+ input_type: FileType::Text,
+ output_type: Some(FileType::Binary),
+ },
+ )?;
- make_test_module(&mut file, "printer", "parser/", "Printer", |path| {
- // Failure tests are only for the parser
- path.starts_with("failure/")
- // Too slow in debug mode
- || path == "success/largeExpression"
- // TODO: Inline headers
- || path == "success/unit/import/inlineUsing"
- || path == "success/unit/import/Headers"
- // TODO: projection by expression
- || path == "success/recordProjectionByExpression"
- || path == "success/RecordProjectionByType"
- || path == "success/unit/RecordProjectionByType"
- || path == "success/unit/RecordProjectionByTypeEmpty"
- // TODO: RFC3986 URLs
- || path == "success/unit/import/urls/emptyPath0"
- || path == "success/unit/import/urls/emptyPath1"
- || path == "success/unit/import/urls/emptyPathSegment"
- // TODO: toMap
- || path == "success/toMap"
- })?;
+ make_test_module(
+ &mut file,
+ TestFeature {
+ module_name: "parser_failure",
+ directory: spec_tests_dir.join("parser/failure/"),
+ variant: "ParserFailure",
+ path_filter: |_path: &str| false,
+ input_type: FileType::Text,
+ output_type: None,
+ },
+ )?;
make_test_module(
&mut file,
- "binary_encoding",
- "parser/",
- "BinaryEncoding",
- |path| {
- // Failure tests are only for the parser
- path.starts_with("failure/")
- // Too slow in debug mode
- || path == "success/largeExpression"
- // See https://github.com/pyfisch/cbor/issues/109
- || path == "success/double"
- || path == "success/unit/DoubleLitExponentNoDot"
- || path == "success/unit/DoubleLitSecretelyInt"
- // TODO: Inline headers
- || path == "success/unit/import/inlineUsing"
- || path == "success/unit/import/Headers"
- // TODO: projection by expression
- || path == "success/recordProjectionByExpression"
- || path == "success/RecordProjectionByType"
- || path == "success/unit/RecordProjectionByType"
- || path == "success/unit/RecordProjectionByTypeEmpty"
- // TODO: RFC3986 URLs
- || path == "success/unit/import/urls/emptyPath0"
- || path == "success/unit/import/urls/emptyPath1"
- || path == "success/unit/import/urls/emptyPathSegment"
- // TODO: toMap
- || path == "success/toMap"
+ TestFeature {
+ module_name: "printer",
+ directory: spec_tests_dir.join("parser/success/"),
+ variant: "Printer",
+ path_filter: |path: &str| {
+ false
+ // Too slow in debug mode
+ || path == "largeExpression"
+ // TODO: projection by expression
+ || path == "recordProjectionByExpression"
+ || path == "RecordProjectionByType"
+ || path == "unit/RecordProjectionByType"
+ || path == "unit/RecordProjectionByTypeEmpty"
+ // TODO: RFC3986 URLs
+ || path == "unit/import/urls/emptyPath0"
+ || path == "unit/import/urls/emptyPath1"
+ || path == "unit/import/urls/emptyPathSegment"
+ },
+ input_type: FileType::Text,
+ output_type: Some(FileType::Binary),
},
)?;
make_test_module(
&mut file,
- "binary_decoding",
- "binary-decode/",
- "BinaryDecoding",
- |path| {
- false
- // TODO: projection by expression
- || path == "success/unit/RecordProjectFields"
- || path == "success/unit/recordProjectionByExpression"
- // TODO: toMap
- || path == "success/unit/ToMap"
- || path == "success/unit/ToMapAnnotated"
+ TestFeature {
+ module_name: "binary_encoding",
+ directory: spec_tests_dir.join("parser/success/"),
+ variant: "BinaryEncoding",
+ path_filter: |path: &str| {
+ false
+ // Too slow in debug mode
+ || path == "largeExpression"
+ // Pretty sure the test is incorrect
+ || path == "unit/import/urls/quotedPathFakeUrlEncode"
+ // See https://github.com/pyfisch/cbor/issues/109
+ || path == "double"
+ || path == "unit/DoubleLitExponentNoDot"
+ || path == "unit/DoubleLitSecretelyInt"
+ // TODO: projection by expression
+ || path == "recordProjectionByExpression"
+ || path == "RecordProjectionByType"
+ || path == "unit/RecordProjectionByType"
+ || path == "unit/RecordProjectionByTypeEmpty"
+ // TODO: RFC3986 URLs
+ || path == "unit/import/urls/emptyPath0"
+ || path == "unit/import/urls/emptyPath1"
+ || path == "unit/import/urls/emptyPathSegment"
+ },
+ input_type: FileType::Text,
+ output_type: Some(FileType::Binary),
},
)?;
make_test_module(
&mut file,
- "beta_normalize",
- "normalization/",
- "Normalization",
- |path| {
- // We don't support bignums
- path == "success/simple/integerToDouble"
- // Too slow
- || path == "success/remoteSystems"
- // TODO: projection by expression
- || path == "success/unit/RecordProjectionByTypeEmpty"
- || path == "success/unit/RecordProjectionByTypeNonEmpty"
- || path == "success/unit/RecordProjectionByTypeNormalizeProjection"
- // TODO: fix Double/show
- || path == "success/prelude/JSON/number/1"
- // TODO: toMap
- || path == "success/unit/EmptyToMap"
- || path == "success/unit/ToMap"
- || path == "success/unit/ToMapWithType"
- // TODO: Normalize field selection further by inspecting the argument
- || path == "success/simplifications/rightBiasedMergeWithinRecordProjectionWithinFieldSelection0"
- || path == "success/simplifications/rightBiasedMergeWithinRecordProjectionWithinFieldSelection1"
- || path == "success/simplifications/rightBiasedMergeWithinRecursiveRecordMergeWithinFieldselection"
- || path == "success/unit/RecordProjectionByTypeWithinFieldSelection"
- || path == "success/unit/RecordProjectionWithinFieldSelection"
- || path == "success/unit/RecursiveRecordMergeWithinFieldSelection0"
- || path == "success/unit/RecursiveRecordMergeWithinFieldSelection1"
- || path == "success/unit/RecursiveRecordMergeWithinFieldSelection2"
- || path == "success/unit/RecursiveRecordMergeWithinFieldSelection3"
- || path == "success/unit/RightBiasedMergeWithinFieldSelection0"
- || path == "success/unit/RightBiasedMergeWithinFieldSelection1"
- || path == "success/unit/RightBiasedMergeWithinFieldSelection2"
- || path == "success/unit/RightBiasedMergeWithinFieldSelection3"
- || path == "success/unit/RightBiasedMergeEquivalentArguments"
+ TestFeature {
+ module_name: "binary_decoding_success",
+ directory: spec_tests_dir.join("binary-decode/success/"),
+ variant: "BinaryDecodingSuccess",
+ path_filter: |path: &str| {
+ false
+ // TODO: projection by expression
+ || path == "unit/RecordProjectFields"
+ || path == "unit/recordProjectionByExpression"
+ },
+ input_type: FileType::Binary,
+ output_type: Some(FileType::Text),
},
)?;
make_test_module(
&mut file,
- "alpha_normalize",
- "alpha-normalization/",
- "AlphaNormalization",
- |_| false,
+ TestFeature {
+ module_name: "binary_decoding_failure",
+ directory: spec_tests_dir.join("binary-decode/failure/"),
+ variant: "BinaryDecodingFailure",
+ path_filter: |_path: &str| false,
+ input_type: FileType::Binary,
+ output_type: None,
+ },
+ )?;
+
+ make_test_module(
+ &mut file,
+ TestFeature {
+ module_name: "beta_normalize",
+ directory: spec_tests_dir.join("normalization/success/"),
+ variant: "Normalization",
+ path_filter: |path: &str| {
+ // We don't support bignums
+ path == "simple/integerToDouble"
+ // Too slow
+ || path == "remoteSystems"
+ // TODO: projection by expression
+ || path == "unit/RecordProjectionByTypeEmpty"
+ || path == "unit/RecordProjectionByTypeNonEmpty"
+ || path == "unit/RecordProjectionByTypeNormalizeProjection"
+ // TODO: fix Double/show
+ || path == "prelude/JSON/number/1"
+ // TODO: toMap
+ || path == "unit/EmptyToMap"
+ || path == "unit/ToMap"
+ || path == "unit/ToMapWithType"
+ // TODO: Normalize field selection further by inspecting the argument
+ || path == "simplifications/rightBiasedMergeWithinRecordProjectionWithinFieldSelection0"
+ || path == "simplifications/rightBiasedMergeWithinRecordProjectionWithinFieldSelection1"
+ || path == "simplifications/rightBiasedMergeWithinRecursiveRecordMergeWithinFieldselection"
+ || path == "unit/RecordProjectionByTypeWithinFieldSelection"
+ || path == "unit/RecordProjectionWithinFieldSelection"
+ || path == "unit/RecursiveRecordMergeWithinFieldSelection0"
+ || path == "unit/RecursiveRecordMergeWithinFieldSelection1"
+ || path == "unit/RecursiveRecordMergeWithinFieldSelection2"
+ || path == "unit/RecursiveRecordMergeWithinFieldSelection3"
+ || path == "unit/RightBiasedMergeWithinFieldSelection0"
+ || path == "unit/RightBiasedMergeWithinFieldSelection1"
+ || path == "unit/RightBiasedMergeWithinFieldSelection2"
+ || path == "unit/RightBiasedMergeWithinFieldSelection3"
+ || path == "unit/RightBiasedMergeEquivalentArguments"
+ },
+ input_type: FileType::Text,
+ output_type: Some(FileType::Text),
+ },
+ )?;
+
+ make_test_module(
+ &mut file,
+ TestFeature {
+ module_name: "alpha_normalize",
+ directory: spec_tests_dir.join("alpha-normalization/success/"),
+ variant: "AlphaNormalization",
+ path_filter: |path: &str| {
+ // This test doesn't typecheck
+ path == "unit/FunctionNestedBindingXXFree"
+ },
+ input_type: FileType::Text,
+ output_type: Some(FileType::Text),
+ },
+ )?;
+
+ make_test_module(
+ &mut file,
+ TestFeature {
+ module_name: "typecheck_success",
+ directory: spec_tests_dir.join("typecheck/success/"),
+ variant: "TypecheckSuccess",
+ path_filter: |path: &str| {
+ false
+ // Too slow
+ || path == "prelude"
+ },
+ input_type: FileType::Text,
+ output_type: Some(FileType::Text),
+ },
+ )?;
+
+ make_test_module(
+ &mut file,
+ TestFeature {
+ module_name: "typecheck_failure",
+ directory: spec_tests_dir.join("typecheck/failure/"),
+ variant: "TypecheckFailure",
+ path_filter: |path: &str| {
+ false
+ // TODO: Enable imports in typecheck tests
+ || path == "importBoundary"
+ || path == "customHeadersUsingBoundVariable"
+ // TODO: projection by expression
+ || path == "unit/RecordProjectionByTypeFieldTypeMismatch"
+ || path == "unit/RecordProjectionByTypeNotPresent"
+ // TODO: toMap
+ || path == "unit/EmptyToMap"
+ || path == "unit/HeterogenousToMap"
+ || path == "unit/MistypedToMap1"
+ || path == "unit/MistypedToMap2"
+ || path == "unit/MistypedToMap3"
+ || path == "unit/MistypedToMap4"
+ || path == "unit/NonRecordToMap"
+ },
+ input_type: FileType::Text,
+ output_type: None,
+ },
)?;
make_test_module(
&mut file,
- "typecheck",
- "typecheck/",
- "Typecheck",
- |path| {
- false
- // TODO: Enable imports in typecheck tests
- || path == "failure/importBoundary"
- // Too slow
- || path == "success/prelude"
- // TODO: Inline headers
- || path == "failure/customHeadersUsingBoundVariable"
- // TODO: projection by expression
- || path == "failure/unit/RecordProjectionByTypeFieldTypeMismatch"
- || path == "failure/unit/RecordProjectionByTypeNotPresent"
- // TODO: toMap
- || path == "failure/unit/EmptyToMap"
- || path == "failure/unit/HeterogenousToMap"
- || path == "failure/unit/MistypedToMap1"
- || path == "failure/unit/MistypedToMap2"
- || path == "failure/unit/MistypedToMap3"
- || path == "failure/unit/MistypedToMap4"
- || path == "failure/unit/NonRecordToMap"
+ TestFeature {
+ module_name: "type_inference_success",
+ directory: spec_tests_dir.join("type-inference/success/"),
+ variant: "TypeInferenceSuccess",
+ path_filter: |path: &str| {
+ false
+ // TODO: projection by expression
+ || path == "unit/RecordProjectionByType"
+ || path == "unit/RecordProjectionByTypeEmpty"
+ || path == "unit/RecordProjectionByTypeJudgmentalEquality"
+ // TODO: toMap
+ || path == "unit/ToMap"
+ || path == "unit/ToMapAnnotated"
+ },
+ input_type: FileType::Text,
+ output_type: Some(FileType::Text),
},
)?;
make_test_module(
&mut file,
- "type_inference",
- "type-inference/",
- "TypeInference",
- |path| {
- false
- // TODO: projection by expression
- || path == "success/unit/RecordProjectionByType"
- || path == "success/unit/RecordProjectionByTypeEmpty"
- || path == "success/unit/RecordProjectionByTypeJudgmentalEquality"
- // TODO: toMap
- || path == "success/unit/ToMap"
- || path == "success/unit/ToMapAnnotated"
+ TestFeature {
+ module_name: "type_inference_failure",
+ directory: spec_tests_dir.join("type-inference/failure/"),
+ variant: "TypeInferenceFailure",
+ path_filter: |_path: &str| false,
+ input_type: FileType::Text,
+ output_type: None,
},
)?;
diff --git a/dhall/src/api/mod.rs b/dhall/src/api/mod.rs
deleted file mode 100644
index 188b6c0..0000000
--- a/dhall/src/api/mod.rs
+++ /dev/null
@@ -1,159 +0,0 @@
-mod serde;
-pub(crate) mod static_type;
-
-pub use value::Value;
-
-mod value {
- use super::Type;
- use crate::error::Result;
- use crate::phase::{NormalizedSubExpr, Parsed, Typed};
-
- // A Dhall value
- pub struct Value(Typed);
-
- impl Value {
- pub fn from_str(s: &str, ty: Option<&Type>) -> Result<Self> {
- let resolved = Parsed::parse_str(s)?.resolve()?;
- let typed = match ty {
- None => resolved.typecheck()?,
- Some(t) => resolved.typecheck_with(&t.to_type())?,
- };
- Ok(Value(typed))
- }
- pub(crate) fn to_expr(&self) -> NormalizedSubExpr {
- self.0.to_expr()
- }
- pub(crate) fn to_typed(&self) -> Typed {
- self.0.clone()
- }
- }
-}
-
-pub use typ::Type;
-
-mod typ {
- use dhall_syntax::Builtin;
-
- use crate::core::thunk::{Thunk, TypeThunk};
- use crate::core::value::Value;
- use crate::error::Result;
- use crate::phase::{NormalizedSubExpr, Typed};
-
- /// A Dhall expression representing a type.
- ///
- /// This captures what is usually simply called a "type", like
- /// `Bool`, `{ x: Integer }` or `Natural -> Text`.
- #[derive(Debug, Clone, PartialEq, Eq)]
- pub struct Type(Typed);
-
- impl Type {
- pub(crate) fn from_value(v: Value) -> Self {
- Type(Typed::from_value_untyped(v))
- }
- pub(crate) fn make_builtin_type(b: Builtin) -> Self {
- Self::from_value(Value::from_builtin(b))
- }
- pub(crate) fn make_optional_type(t: Type) -> Self {
- Self::from_value(Value::AppliedBuiltin(
- Builtin::Optional,
- vec![t.to_thunk()],
- ))
- }
- pub(crate) fn make_list_type(t: Type) -> Self {
- Self::from_value(Value::AppliedBuiltin(
- Builtin::List,
- vec![t.to_thunk()],
- ))
- }
- #[doc(hidden)]
- pub fn make_record_type(
- kts: impl Iterator<Item = (String, Type)>,
- ) -> Self {
- Self::from_value(Value::RecordType(
- kts.map(|(k, t)| {
- (k.into(), TypeThunk::from_thunk(t.to_thunk()))
- })
- .collect(),
- ))
- }
- #[doc(hidden)]
- pub fn make_union_type(
- kts: impl Iterator<Item = (String, Option<Type>)>,
- ) -> Self {
- Self::from_value(Value::UnionType(
- kts.map(|(k, t)| {
- (k.into(), t.map(|t| TypeThunk::from_thunk(t.to_thunk())))
- })
- .collect(),
- ))
- }
-
- pub(crate) fn to_thunk(&self) -> Thunk {
- self.0.to_thunk()
- }
- #[allow(dead_code)]
- pub(crate) fn to_expr(&self) -> NormalizedSubExpr {
- self.0.to_expr()
- }
- pub(crate) fn to_type(&self) -> crate::phase::Type {
- self.0.to_type()
- }
- }
-
- impl crate::de::Deserialize for Type {
- fn from_dhall(v: &crate::api::Value) -> Result<Self> {
- Ok(Type(v.to_typed()))
- }
- }
-}
-
-/// Deserialization of Dhall expressions into Rust
-pub mod de {
- pub use super::static_type::StaticType;
- pub use super::{Type, Value};
- use crate::error::Result;
- #[doc(hidden)]
- pub use dhall_proc_macros::StaticType;
-
- /// A data structure that can be deserialized from a Dhall expression
- ///
- /// This is automatically implemented for any type that [serde][serde]
- /// can deserialize.
- ///
- /// This trait cannot be implemented manually.
- // TODO: seal trait
- pub trait Deserialize: Sized {
- /// See [dhall::de::from_str][crate::de::from_str]
- fn from_dhall(v: &Value) -> Result<Self>;
- }
-
- /// Deserialize an instance of type T from a string of Dhall text.
- ///
- /// This will recursively resolve all imports in the expression, and
- /// typecheck it before deserialization. Relative imports will be resolved relative to the
- /// provided file. More control over this process is not yet available
- /// but will be in a coming version of this crate.
- ///
- /// If a type is provided, this additionally checks that the provided
- /// expression has that type.
- pub fn from_str<T>(s: &str, ty: Option<&Type>) -> Result<T>
- where
- T: Deserialize,
- {
- T::from_dhall(&Value::from_str(s, ty)?)
- }
-
- /// Deserialize an instance of type T from a string of Dhall text,
- /// additionally checking that it matches the type of T.
- ///
- /// This will recursively resolve all imports in the expression, and
- /// typecheck it before deserialization. Relative imports will be resolved relative to the
- /// provided file. More control over this process is not yet available
- /// but will be in a coming version of this crate.
- pub fn from_str_auto_type<T>(s: &str) -> Result<T>
- where
- T: Deserialize + StaticType,
- {
- from_str(s, Some(&<T as StaticType>::static_type()))
- }
-}
diff --git a/dhall/src/core/context.rs b/dhall/src/core/context.rs
index 62affcf..2bf39c5 100644
--- a/dhall/src/core/context.rs
+++ b/dhall/src/core/context.rs
@@ -3,64 +3,56 @@ use std::rc::Rc;
use dhall_syntax::{Label, V};
-use crate::core::thunk::Thunk;
use crate::core::value::Value;
+use crate::core::valuef::ValueF;
use crate::core::var::{AlphaVar, Shift, Subst};
use crate::error::TypeError;
-use crate::phase::{Type, Typed};
#[derive(Debug, Clone)]
-pub enum CtxItem<T> {
- Kept(AlphaVar, T),
- Replaced(Thunk, T),
+enum CtxItem {
+ Kept(AlphaVar, Value),
+ Replaced(Value),
}
#[derive(Debug, Clone)]
-pub struct Context<T>(Rc<Vec<(Label, CtxItem<T>)>>);
+pub(crate) struct TypecheckContext(Rc<Vec<(Label, CtxItem)>>);
-#[derive(Debug, Clone)]
-pub struct NormalizationContext(Context<()>);
-
-#[derive(Debug, Clone)]
-pub struct TypecheckContext(Context<Type>);
-
-impl<T> Context<T> {
+impl TypecheckContext {
pub fn new() -> Self {
- Context(Rc::new(Vec::new()))
+ TypecheckContext(Rc::new(Vec::new()))
}
- pub fn insert_kept(&self, x: &Label, t: T) -> Self
- where
- T: Shift + Clone,
- {
+ pub fn insert_type(&self, x: &Label, t: Value) -> Self {
let mut vec = self.0.as_ref().clone();
vec.push((x.clone(), CtxItem::Kept(x.into(), t.under_binder(x))));
- Context(Rc::new(vec))
+ TypecheckContext(Rc::new(vec))
}
- pub fn insert_replaced(&self, x: &Label, th: Thunk, t: T) -> Self
- where
- T: Clone,
- {
+ pub fn insert_value(&self, x: &Label, e: Value) -> Result<Self, TypeError> {
let mut vec = self.0.as_ref().clone();
- vec.push((x.clone(), CtxItem::Replaced(th, t)));
- Context(Rc::new(vec))
+ vec.push((x.clone(), CtxItem::Replaced(e)));
+ Ok(TypecheckContext(Rc::new(vec)))
}
- pub fn lookup(&self, var: &V<Label>) -> Result<CtxItem<T>, V<Label>>
- where
- T: Clone + Shift,
- {
+ pub fn lookup(&self, var: &V<Label>) -> Option<Value> {
let mut var = var.clone();
let mut shift_map: HashMap<Label, _> = HashMap::new();
for (l, i) in self.0.iter().rev() {
match var.over_binder(l) {
- None => return Ok(i.under_multiple_binders(&shift_map)),
+ None => {
+ let i = i.under_multiple_binders(&shift_map);
+ return Some(match i {
+ CtxItem::Kept(newvar, t) => {
+ Value::from_valuef_and_type(ValueF::Var(newvar), t)
+ }
+ CtxItem::Replaced(v) => v,
+ });
+ }
Some(newvar) => var = newvar,
};
if let CtxItem::Kept(_, _) = i {
*shift_map.entry(l.clone()).or_insert(0) += 1;
}
}
- // Free variable
- Err(var)
+ // Unbound variable
+ None
}
/// Given a var that makes sense in the current context, map the given function in such a way
/// that the passed variable always makes sense in the context of the passed item.
@@ -69,11 +61,8 @@ impl<T> Context<T> {
fn do_with_var<E>(
&self,
var: &AlphaVar,
- mut f: impl FnMut(&AlphaVar, &CtxItem<T>) -> Result<CtxItem<T>, E>,
- ) -> Result<Self, E>
- where
- T: Clone,
- {
+ mut f: impl FnMut(&AlphaVar, &CtxItem) -> Result<CtxItem, E>,
+ ) -> Result<Self, E> {
let mut vec = Vec::new();
vec.reserve(self.0.len());
let mut var = var.clone();
@@ -91,125 +80,65 @@ impl<T> Context<T> {
vec.push((l.clone(), (*i).clone()));
}
vec.reverse();
- Ok(Context(Rc::new(vec)))
+ Ok(TypecheckContext(Rc::new(vec)))
}
- fn shift(&self, delta: isize, var: &AlphaVar) -> Option<Self>
- where
- T: Clone + Shift,
- {
- Some(self.do_with_var(var, |var, i| Ok(i.shift(delta, &var)?))?)
+ fn shift(&self, delta: isize, var: &AlphaVar) -> Option<Self> {
+ if delta < 0 {
+ Some(self.do_with_var(var, |var, i| Ok(i.shift(delta, &var)?))?)
+ } else {
+ Some(TypecheckContext(Rc::new(
+ self.0
+ .iter()
+ .map(|(l, i)| Ok((l.clone(), i.shift(delta, &var)?)))
+ .collect::<Result<_, _>>()?,
+ )))
+ }
}
- fn subst_shift(&self, var: &AlphaVar, val: &Typed) -> Self
- where
- T: Clone + Subst<Typed>,
- {
+ fn subst_shift(&self, var: &AlphaVar, val: &Value) -> Self {
self.do_with_var(var, |var, i| Ok::<_, !>(i.subst_shift(&var, val)))
.unwrap()
}
}
-impl NormalizationContext {
- pub fn new() -> Self {
- NormalizationContext(Context::new())
- }
- pub fn skip(&self, x: &Label) -> Self {
- NormalizationContext(self.0.insert_kept(x, ()))
- }
- pub fn lookup(&self, var: &V<Label>) -> Value {
- match self.0.lookup(var) {
- Ok(CtxItem::Replaced(t, ())) => t.to_value(),
- Ok(CtxItem::Kept(newvar, ())) => Value::Var(newvar.clone()),
- Err(var) => Value::Var(AlphaVar::from_var(var)),
- }
- }
-}
-
-impl TypecheckContext {
- pub fn new() -> Self {
- TypecheckContext(Context::new())
- }
- pub fn insert_type(&self, x: &Label, t: Type) -> Self {
- TypecheckContext(self.0.insert_kept(x, t))
- }
- pub fn insert_value(&self, x: &Label, e: Typed) -> Result<Self, TypeError> {
- Ok(TypecheckContext(self.0.insert_replaced(
- x,
- e.to_thunk(),
- e.get_type()?.into_owned(),
- )))
- }
- pub fn lookup(&self, var: &V<Label>) -> Option<Typed> {
- match self.0.lookup(var) {
- Ok(CtxItem::Kept(newvar, t)) => Some(Typed::from_thunk_and_type(
- Thunk::from_value(Value::Var(newvar.clone())),
- t.clone(),
- )),
- Ok(CtxItem::Replaced(th, t)) => {
- Some(Typed::from_thunk_and_type(th.clone(), t.clone()))
- }
- Err(_) => None,
- }
- }
-}
-
-impl<T: Shift> Shift for CtxItem<T> {
+impl Shift for CtxItem {
fn shift(&self, delta: isize, var: &AlphaVar) -> Option<Self> {
Some(match self {
CtxItem::Kept(v, t) => {
CtxItem::Kept(v.shift(delta, var)?, t.shift(delta, var)?)
}
- CtxItem::Replaced(e, t) => {
- CtxItem::Replaced(e.shift(delta, var)?, t.shift(delta, var)?)
- }
+ CtxItem::Replaced(e) => CtxItem::Replaced(e.shift(delta, var)?),
})
}
}
-impl<T: Clone + Shift> Shift for Context<T> {
+impl Shift for TypecheckContext {
fn shift(&self, delta: isize, var: &AlphaVar) -> Option<Self> {
self.shift(delta, var)
}
}
-impl Shift for NormalizationContext {
- fn shift(&self, delta: isize, var: &AlphaVar) -> Option<Self> {
- Some(NormalizationContext(self.0.shift(delta, var)?))
- }
-}
-
-impl<T: Subst<Typed>> Subst<Typed> for CtxItem<T> {
- fn subst_shift(&self, var: &AlphaVar, val: &Typed) -> Self {
+impl Subst<Value> for CtxItem {
+ fn subst_shift(&self, var: &AlphaVar, val: &Value) -> Self {
match self {
- CtxItem::Replaced(e, t) => CtxItem::Replaced(
- e.subst_shift(var, val),
- t.subst_shift(var, val),
- ),
+ CtxItem::Replaced(e) => CtxItem::Replaced(e.subst_shift(var, val)),
CtxItem::Kept(v, t) => match v.shift(-1, var) {
- None => {
- CtxItem::Replaced(val.to_thunk(), t.subst_shift(var, val))
- }
+ None => CtxItem::Replaced(val.clone()),
Some(newvar) => CtxItem::Kept(newvar, t.subst_shift(var, val)),
},
}
}
}
-impl<T: Clone + Subst<Typed>> Subst<Typed> for Context<T> {
- fn subst_shift(&self, var: &AlphaVar, val: &Typed) -> Self {
+impl Subst<Value> for TypecheckContext {
+ fn subst_shift(&self, var: &AlphaVar, val: &Value) -> Self {
self.subst_shift(var, val)
}
}
-impl Subst<Typed> for NormalizationContext {
- fn subst_shift(&self, var: &AlphaVar, val: &Typed) -> Self {
- NormalizationContext(self.0.subst_shift(var, val))
- }
-}
-
+/// Don't count contexts when comparing stuff.
+/// This is dirty but needed.
impl PartialEq for TypecheckContext {
fn eq(&self, _: &Self) -> bool {
- // don't count contexts when comparing stuff
- // this is dirty but needed for now
true
}
}
diff --git a/dhall/src/core/mod.rs b/dhall/src/core/mod.rs
index a202e72..08213f7 100644
--- a/dhall/src/core/mod.rs
+++ b/dhall/src/core/mod.rs
@@ -1,4 +1,4 @@
-pub(crate) mod context;
-pub(crate) mod thunk;
-pub(crate) mod value;
-pub(crate) mod var;
+pub mod context;
+pub mod value;
+pub mod valuef;
+pub mod var;
diff --git a/dhall/src/core/thunk.rs b/dhall/src/core/thunk.rs
deleted file mode 100644
index f41579c..0000000
--- a/dhall/src/core/thunk.rs
+++ /dev/null
@@ -1,312 +0,0 @@
-use std::cell::{Ref, RefCell};
-use std::rc::Rc;
-
-use dhall_syntax::{ExprF, X};
-
-use crate::core::context::NormalizationContext;
-use crate::core::value::Value;
-use crate::core::var::{AlphaVar, Shift, Subst};
-use crate::phase::normalize::{
- apply_any, normalize_one_layer, normalize_whnf, InputSubExpr, OutputSubExpr,
-};
-use crate::phase::{Type, Typed};
-
-#[derive(Debug, Clone, Copy)]
-enum Marker {
- /// Weak Head Normal Form, i.e. subexpressions may not be normalized
- WHNF,
- /// Normal form, i.e. completely normalized
- NF,
-}
-use Marker::{NF, WHNF};
-
-#[derive(Debug)]
-enum ThunkInternal {
- /// Non-normalized value alongside a normalization context
- Unnormalized(NormalizationContext, InputSubExpr),
- /// Partially normalized value whose subexpressions have been thunked (this is returned from
- /// typechecking). Note that this is different from `Value::PartialExpr` because there is no
- /// requirement of WHNF here.
- PartialExpr(ExprF<Thunk, X>),
- /// Partially normalized value.
- /// Invariant: if the marker is `NF`, the value must be fully normalized
- Value(Marker, Value),
-}
-
-/// Stores a possibly unevaluated value. Gets (partially) normalized on-demand,
-/// sharing computation automatically.
-/// Uses a RefCell to share computation.
-#[derive(Debug, Clone)]
-pub struct Thunk(Rc<RefCell<ThunkInternal>>);
-
-/// A thunk in type position. Can optionally store a Type from the typechecking phase to preserve
-/// type information through the normalization phase.
-#[derive(Debug, Clone)]
-pub struct TypeThunk(Typed);
-
-impl ThunkInternal {
- fn into_thunk(self) -> Thunk {
- Thunk(Rc::new(RefCell::new(self)))
- }
-
- fn normalize_whnf(&mut self) {
- match self {
- ThunkInternal::Unnormalized(ctx, e) => {
- *self = ThunkInternal::Value(
- WHNF,
- normalize_whnf(ctx.clone(), e.clone()),
- )
- }
- ThunkInternal::PartialExpr(e) => {
- *self =
- ThunkInternal::Value(WHNF, normalize_one_layer(e.clone()))
- }
- // Already at least in WHNF
- ThunkInternal::Value(_, _) => {}
- }
- }
-
- fn normalize_nf(&mut self) {
- match self {
- ThunkInternal::Unnormalized(_, _)
- | ThunkInternal::PartialExpr(_) => {
- self.normalize_whnf();
- self.normalize_nf();
- }
- ThunkInternal::Value(m @ WHNF, v) => {
- v.normalize_mut();
- *m = NF;
- }
- // Already in NF
- ThunkInternal::Value(NF, _) => {}
- }
- }
-
- // Always use normalize_whnf before
- fn as_whnf(&self) -> &Value {
- match self {
- ThunkInternal::Unnormalized(_, _)
- | ThunkInternal::PartialExpr(_) => unreachable!(),
- ThunkInternal::Value(_, v) => v,
- }
- }
-
- // Always use normalize_nf before
- fn as_nf(&self) -> &Value {
- match self {
- ThunkInternal::Unnormalized(_, _)
- | ThunkInternal::PartialExpr(_)
- | ThunkInternal::Value(WHNF, _) => unreachable!(),
- ThunkInternal::Value(NF, v) => v,
- }
- }
-}
-
-impl Thunk {
- pub fn new(ctx: NormalizationContext, e: InputSubExpr) -> Thunk {
- ThunkInternal::Unnormalized(ctx, e).into_thunk()
- }
-
- pub fn from_value(v: Value) -> Thunk {
- ThunkInternal::Value(WHNF, v).into_thunk()
- }
-
- pub fn from_partial_expr(e: ExprF<Thunk, X>) -> Thunk {
- ThunkInternal::PartialExpr(e).into_thunk()
- }
-
- // Normalizes contents to normal form; faster than `normalize_nf` if
- // no one else shares this thunk
- pub fn normalize_mut(&mut self) {
- match Rc::get_mut(&mut self.0) {
- // Mutate directly if sole owner
- Some(refcell) => RefCell::get_mut(refcell).normalize_nf(),
- // Otherwise mutate through the refcell
- None => self.0.borrow_mut().normalize_nf(),
- }
- }
-
- fn do_normalize_whnf(&self) {
- let borrow = self.0.borrow();
- match &*borrow {
- ThunkInternal::Unnormalized(_, _)
- | ThunkInternal::PartialExpr(_) => {
- drop(borrow);
- self.0.borrow_mut().normalize_whnf();
- }
- // Already at least in WHNF
- ThunkInternal::Value(_, _) => {}
- }
- }
-
- fn do_normalize_nf(&self) {
- let borrow = self.0.borrow();
- match &*borrow {
- ThunkInternal::Unnormalized(_, _)
- | ThunkInternal::PartialExpr(_)
- | ThunkInternal::Value(WHNF, _) => {
- drop(borrow);
- self.0.borrow_mut().normalize_nf();
- }
- // Already in NF
- ThunkInternal::Value(NF, _) => {}
- }
- }
-
- // WARNING: avoid normalizing any thunk while holding on to this ref
- // or you could run into BorrowMut panics
- pub fn normalize_nf(&self) -> Ref<Value> {
- self.do_normalize_nf();
- Ref::map(self.0.borrow(), ThunkInternal::as_nf)
- }
-
- // WARNING: avoid normalizing any thunk while holding on to this ref
- // or you could run into BorrowMut panics
- pub fn as_value(&self) -> Ref<Value> {
- self.do_normalize_whnf();
- Ref::map(self.0.borrow(), ThunkInternal::as_whnf)
- }
-
- pub fn to_value(&self) -> Value {
- self.as_value().clone()
- }
-
- pub fn normalize_to_expr_maybe_alpha(&self, alpha: bool) -> OutputSubExpr {
- self.normalize_nf().normalize_to_expr_maybe_alpha(alpha)
- }
-
- pub fn app_val(&self, val: Value) -> Value {
- self.app_thunk(val.into_thunk())
- }
-
- pub fn app_thunk(&self, th: Thunk) -> Value {
- apply_any(self.clone(), th)
- }
-}
-
-impl TypeThunk {
- pub fn from_value(v: Value) -> TypeThunk {
- TypeThunk::from_thunk(Thunk::from_value(v))
- }
-
- pub fn from_thunk(th: Thunk) -> TypeThunk {
- TypeThunk(Typed::from_thunk_untyped(th))
- }
-
- pub fn from_type(t: Type) -> TypeThunk {
- TypeThunk(t)
- }
-
- pub fn normalize_mut(&mut self) {
- self.0.normalize_mut()
- }
-
- pub fn normalize_nf(&self) -> Value {
- self.0.to_value().normalize()
- }
-
- pub fn to_value(&self) -> Value {
- self.0.to_value()
- }
-
- pub fn to_thunk(&self) -> Thunk {
- self.0.to_thunk()
- }
-
- pub fn to_type(&self) -> Type {
- self.0.to_type()
- }
-
- pub fn to_typed(&self) -> Typed {
- self.0.clone()
- }
-
- pub fn normalize_to_expr_maybe_alpha(&self, alpha: bool) -> OutputSubExpr {
- self.normalize_nf().normalize_to_expr_maybe_alpha(alpha)
- }
-}
-
-impl Shift for Thunk {
- fn shift(&self, delta: isize, var: &AlphaVar) -> Option<Self> {
- Some(self.0.borrow().shift(delta, var)?.into_thunk())
- }
-}
-
-impl Shift for ThunkInternal {
- fn shift(&self, delta: isize, var: &AlphaVar) -> Option<Self> {
- Some(match self {
- ThunkInternal::Unnormalized(ctx, e) => {
- ThunkInternal::Unnormalized(ctx.shift(delta, var)?, e.clone())
- }
- ThunkInternal::PartialExpr(e) => ThunkInternal::PartialExpr(
- e.traverse_ref_with_special_handling_of_binders(
- |v| Ok(v.shift(delta, var)?),
- |x, v| Ok(v.shift(delta, &var.under_binder(x))?),
- |x| Ok(X::clone(x)),
- )?,
- ),
- ThunkInternal::Value(m, v) => {
- ThunkInternal::Value(*m, v.shift(delta, var)?)
- }
- })
- }
-}
-
-impl Shift for TypeThunk {
- fn shift(&self, delta: isize, var: &AlphaVar) -> Option<Self> {
- Some(TypeThunk(self.0.shift(delta, var)?))
- }
-}
-
-impl Subst<Typed> for Thunk {
- fn subst_shift(&self, var: &AlphaVar, val: &Typed) -> Self {
- self.0.borrow().subst_shift(var, val).into_thunk()
- }
-}
-
-impl Subst<Typed> for ThunkInternal {
- fn subst_shift(&self, var: &AlphaVar, val: &Typed) -> Self {
- match self {
- ThunkInternal::Unnormalized(ctx, e) => ThunkInternal::Unnormalized(
- ctx.subst_shift(var, val),
- e.clone(),
- ),
- ThunkInternal::PartialExpr(e) => ThunkInternal::PartialExpr(
- e.map_ref_with_special_handling_of_binders(
- |v| v.subst_shift(var, val),
- |x, v| {
- v.subst_shift(
- &var.under_binder(x),
- &val.under_binder(x),
- )
- },
- X::clone,
- ),
- ),
- ThunkInternal::Value(_, v) => {
- // The resulting value may not stay in normal form after substitution
- ThunkInternal::Value(WHNF, v.subst_shift(var, val))
- }
- }
- }
-}
-
-impl Subst<Typed> for TypeThunk {
- fn subst_shift(&self, var: &AlphaVar, val: &Typed) -> Self {
- TypeThunk(self.0.subst_shift(var, val))
- }
-}
-
-impl std::cmp::PartialEq for Thunk {
- fn eq(&self, other: &Self) -> bool {
- *self.as_value() == *other.as_value()
- }
-}
-impl std::cmp::Eq for Thunk {}
-
-impl std::cmp::PartialEq for TypeThunk {
- fn eq(&self, other: &Self) -> bool {
- self.to_value() == other.to_value()
- }
-}
-impl std::cmp::Eq for TypeThunk {}
diff --git a/dhall/src/core/value.rs b/dhall/src/core/value.rs
index 0b68bf6..3cccb1d 100644
--- a/dhall/src/core/value.rs
+++ b/dhall/src/core/value.rs
@@ -1,595 +1,327 @@
-use std::collections::HashMap;
+use std::cell::{Ref, RefCell, RefMut};
+use std::rc::Rc;
-use dhall_syntax::{
- rc, Builtin, Const, ExprF, Integer, InterpolatedTextContents, Label,
- NaiveDouble, Natural, X,
-};
+use dhall_syntax::{Builtin, Const};
-use crate::core::thunk::{Thunk, TypeThunk};
-use crate::core::var::{AlphaLabel, AlphaVar, Shift, Subst};
-use crate::phase::normalize::{
- apply_builtin, normalize_one_layer, squash_textlit, OutputSubExpr,
-};
-use crate::phase::Typed;
+use crate::core::context::TypecheckContext;
+use crate::core::valuef::ValueF;
+use crate::core::var::{AlphaVar, Shift, Subst};
+use crate::error::{TypeError, TypeMessage};
+use crate::phase::normalize::{apply_any, normalize_whnf};
+use crate::phase::typecheck::{builtin_to_value, const_to_value};
+use crate::phase::{NormalizedExpr, Typed};
-/// A semantic value. The invariants ensure this value represents a Weak-Head
-/// Normal Form (WHNF). This means that this first constructor is the first constructor of the
-/// final Normal Form (NF).
-/// This WHNF must be preserved by operations on `Value`s. In particular, `subst_shift` could break
-/// the invariants so need to be careful to reevaluate as needed.
-/// Subexpressions are Thunks, which are partially evaluated expressions that are normalized
-/// on-demand. When all the Thunks in a Value are at least in WHNF, and recursively so, then the
-/// Value is in NF. This is because WHNF ensures that we have the first constructor of the NF; so
-/// if we have the first constructor of the NF at all levels, we actually have the NF.
-/// Equality is up to alpha-equivalence (renaming of bound variables) and beta-equivalence
-/// (normalization). Equality will normalize only as needed.
-#[derive(Debug, Clone, PartialEq, Eq)]
-pub enum Value {
- /// Closures
- Lam(AlphaLabel, TypeThunk, Thunk),
- Pi(AlphaLabel, TypeThunk, TypeThunk),
- // Invariant: the evaluation must not be able to progress further.
- AppliedBuiltin(Builtin, Vec<Thunk>),
- /// `λ(x: a) -> Some x`
- OptionalSomeClosure(TypeThunk),
- /// `λ(x : a) -> λ(xs : List a) -> [ x ] # xs`
- /// `λ(xs : List a) -> [ x ] # xs`
- ListConsClosure(TypeThunk, Option<Thunk>),
- /// `λ(x : Natural) -> x + 1`
- NaturalSuccClosure,
+#[derive(Debug, Clone, Copy)]
+pub(crate) enum Form {
+ /// No constraints; expression may not be normalized at all.
+ Unevaled,
+ /// Weak Head Normal Form, i.e. normalized up to the first constructor, but subexpressions may
+ /// not be normalized. This means that the first constructor of the contained ValueF is the first
+ /// constructor of the final Normal Form (NF).
+ WHNF,
+ /// Normal Form, i.e. completely normalized.
+ /// When all the Values in a ValueF are at least in WHNF, and recursively so, then the
+ /// ValueF is in NF. This is because WHNF ensures that we have the first constructor of the NF; so
+ /// if we have the first constructor of the NF at all levels, we actually have the NF.
+ NF,
+}
+use Form::{Unevaled, NF, WHNF};
- Var(AlphaVar),
- Const(Const),
- BoolLit(bool),
- NaturalLit(Natural),
- IntegerLit(Integer),
- DoubleLit(NaiveDouble),
- EmptyOptionalLit(TypeThunk),
- NEOptionalLit(Thunk),
- // EmptyListLit(t) means `[] : List t`, not `[] : t`
- EmptyListLit(TypeThunk),
- NEListLit(Vec<Thunk>),
- RecordLit(HashMap<Label, Thunk>),
- RecordType(HashMap<Label, TypeThunk>),
- UnionType(HashMap<Label, Option<TypeThunk>>),
- UnionConstructor(Label, HashMap<Label, Option<TypeThunk>>),
- UnionLit(Label, Thunk, HashMap<Label, Option<TypeThunk>>),
- // Invariant: this must not contain interpolations that are themselves TextLits, and
- // contiguous text values must be merged.
- TextLit(Vec<InterpolatedTextContents<Thunk>>),
- Equivalence(TypeThunk, TypeThunk),
- // Invariant: this must not contain a value captured by one of the variants above.
- PartialExpr(ExprF<Thunk, X>),
+/// Partially normalized value.
+/// Invariant: if `form` is `WHNF`, `value` must be in Weak Head Normal Form
+/// Invariant: if `form` is `NF`, `value` must be fully normalized
+#[derive(Debug)]
+struct ValueInternal {
+ form: Form,
+ value: ValueF,
+ /// This is None if and only if `value` is `Sort` (which doesn't have a type)
+ ty: Option<Value>,
}
-impl Value {
- pub fn into_thunk(self) -> Thunk {
- Thunk::from_value(self)
+/// Stores a possibly unevaluated value. Gets (partially) normalized on-demand,
+/// sharing computation automatically. Uses a RefCell to share computation.
+/// Can optionally store a type from typechecking to preserve type information.
+#[derive(Clone)]
+pub(crate) struct Value(Rc<RefCell<ValueInternal>>);
+
+#[derive(Copy, Clone)]
+/// Controls conversion from `Value` to `Expr`
+pub(crate) struct ToExprOptions {
+ /// Whether to convert all variables to `_`
+ pub(crate) alpha: bool,
+ /// Whether to normalize before converting
+ pub(crate) normalize: bool,
+}
+
+impl ValueInternal {
+ fn into_value(self) -> Value {
+ Value(Rc::new(RefCell::new(self)))
+ }
+ fn as_valuef(&self) -> &ValueF {
+ &self.value
}
- /// Convert the value to a fully normalized syntactic expression
- pub fn normalize_to_expr(&self) -> OutputSubExpr {
- self.normalize_to_expr_maybe_alpha(false)
- }
- /// Convert the value to a fully normalized syntactic expression. Also alpha-normalize
- /// if alpha is `true`
- pub fn normalize_to_expr_maybe_alpha(&self, alpha: bool) -> OutputSubExpr {
- // Ad-hoc macro to help construct the unapplied closures
- macro_rules! make_expr {
- (Natural) => { rc(ExprF::Builtin(Builtin::Natural)) };
- (var($var:ident)) => {
- rc(ExprF::Var(dhall_syntax::V(stringify!($var).into(), 0)))
- };
- ($var:ident) => { $var };
- (List $($rest:tt)*) => {
- rc(ExprF::App(
- rc(ExprF::Builtin(Builtin::List)),
- make_expr!($($rest)*)
- ))
- };
- (Some $($rest:tt)*) => {
- rc(ExprF::SomeLit(
- make_expr!($($rest)*)
- ))
- };
- (1 + $($rest:tt)*) => {
- rc(ExprF::BinOp(
- dhall_syntax::BinOp::NaturalPlus,
- rc(ExprF::NaturalLit(1)),
- make_expr!($($rest)*)
- ))
- };
- ([ $($head:tt)* ] # $($tail:tt)*) => {
- rc(ExprF::BinOp(
- dhall_syntax::BinOp::ListAppend,
- rc(ExprF::NEListLit(vec![make_expr!($($head)*)])),
- make_expr!($($tail)*)
- ))
- };
- (λ($var:ident : $($ty:tt)*) -> $($rest:tt)*) => {
- rc(ExprF::Pi(
- stringify!($var).into(),
- make_expr!($($ty)*),
- make_expr!($($rest)*)
- ))
- };
+ fn normalize_whnf(&mut self) {
+ take_mut::take_or_recover(
+ self,
+ // Dummy value in case the other closure panics
+ || ValueInternal {
+ form: Unevaled,
+ value: ValueF::Const(Const::Type),
+ ty: None,
+ },
+ |vint| match (&vint.form, &vint.ty) {
+ (Unevaled, Some(ty)) => ValueInternal {
+ form: WHNF,
+ value: normalize_whnf(vint.value, &ty),
+ ty: vint.ty,
+ },
+ // `value` is `Sort`
+ (Unevaled, None) => ValueInternal {
+ form: NF,
+ value: ValueF::Const(Const::Sort),
+ ty: None,
+ },
+ // Already in WHNF
+ (WHNF, _) | (NF, _) => vint,
+ },
+ )
+ }
+ fn normalize_nf(&mut self) {
+ match self.form {
+ Unevaled => {
+ self.normalize_whnf();
+ self.normalize_nf();
+ }
+ WHNF => {
+ self.value.normalize_mut();
+ self.form = NF;
+ }
+ // Already in NF
+ NF => {}
}
+ }
- match self {
- Value::Lam(x, t, e) => rc(ExprF::Lam(
- x.to_label_maybe_alpha(alpha),
- t.normalize_to_expr_maybe_alpha(alpha),
- e.normalize_to_expr_maybe_alpha(alpha),
- )),
- Value::AppliedBuiltin(b, args) => {
- let mut e = rc(ExprF::Builtin(*b));
- for v in args {
- e = rc(ExprF::App(
- e,
- v.normalize_to_expr_maybe_alpha(alpha),
- ));
- }
- e
- }
- Value::OptionalSomeClosure(n) => {
- let a = n.normalize_to_expr_maybe_alpha(alpha);
- make_expr!(λ(x: a) -> Some var(x))
- }
- Value::ListConsClosure(a, None) => {
- // Avoid accidental capture of the new `x` variable
- let a1 = a.under_binder(Label::from("x"));
- let a1 = a1.normalize_to_expr_maybe_alpha(alpha);
- let a = a.normalize_to_expr_maybe_alpha(alpha);
- make_expr!(λ(x : a) -> λ(xs : List a1) -> [ var(x) ] # var(xs))
- }
- Value::ListConsClosure(n, Some(v)) => {
- // Avoid accidental capture of the new `xs` variable
- let v = v.under_binder(Label::from("xs"));
- let v = v.normalize_to_expr_maybe_alpha(alpha);
- let a = n.normalize_to_expr_maybe_alpha(alpha);
- make_expr!(λ(xs : List a) -> [ v ] # var(xs))
- }
- Value::NaturalSuccClosure => {
- make_expr!(λ(x : Natural) -> 1 + var(x))
- }
- Value::Pi(x, t, e) => rc(ExprF::Pi(
- x.to_label_maybe_alpha(alpha),
- t.normalize_to_expr_maybe_alpha(alpha),
- e.normalize_to_expr_maybe_alpha(alpha),
- )),
- Value::Var(v) => rc(ExprF::Var(v.to_var(alpha))),
- Value::Const(c) => rc(ExprF::Const(*c)),
- Value::BoolLit(b) => rc(ExprF::BoolLit(*b)),
- Value::NaturalLit(n) => rc(ExprF::NaturalLit(*n)),
- Value::IntegerLit(n) => rc(ExprF::IntegerLit(*n)),
- Value::DoubleLit(n) => rc(ExprF::DoubleLit(*n)),
- Value::EmptyOptionalLit(n) => rc(ExprF::App(
- rc(ExprF::Builtin(Builtin::OptionalNone)),
- n.normalize_to_expr_maybe_alpha(alpha),
- )),
- Value::NEOptionalLit(n) => {
- rc(ExprF::SomeLit(n.normalize_to_expr_maybe_alpha(alpha)))
- }
- Value::EmptyListLit(n) => rc(ExprF::EmptyListLit(rc(ExprF::App(
- rc(ExprF::Builtin(Builtin::List)),
- n.normalize_to_expr_maybe_alpha(alpha),
- )))),
- Value::NEListLit(elts) => rc(ExprF::NEListLit(
- elts.iter()
- .map(|n| n.normalize_to_expr_maybe_alpha(alpha))
- .collect(),
- )),
- Value::RecordLit(kvs) => rc(ExprF::RecordLit(
- kvs.iter()
- .map(|(k, v)| {
- (k.clone(), v.normalize_to_expr_maybe_alpha(alpha))
- })
- .collect(),
- )),
- Value::RecordType(kts) => rc(ExprF::RecordType(
- kts.iter()
- .map(|(k, v)| {
- (k.clone(), v.normalize_to_expr_maybe_alpha(alpha))
- })
- .collect(),
- )),
- Value::UnionType(kts) => rc(ExprF::UnionType(
- kts.iter()
- .map(|(k, v)| {
- (
- k.clone(),
- v.as_ref().map(|v| {
- v.normalize_to_expr_maybe_alpha(alpha)
- }),
- )
- })
- .collect(),
- )),
- Value::UnionConstructor(l, kts) => {
- let kts = kts
- .iter()
- .map(|(k, v)| {
- (
- k.clone(),
- v.as_ref().map(|v| {
- v.normalize_to_expr_maybe_alpha(alpha)
- }),
- )
- })
- .collect();
- rc(ExprF::Field(rc(ExprF::UnionType(kts)), l.clone()))
- }
- Value::UnionLit(l, v, kts) => rc(ExprF::App(
- Value::UnionConstructor(l.clone(), kts.clone())
- .normalize_to_expr_maybe_alpha(alpha),
- v.normalize_to_expr_maybe_alpha(alpha),
- )),
- Value::TextLit(elts) => {
- use InterpolatedTextContents::{Expr, Text};
- rc(ExprF::TextLit(
- elts.iter()
- .map(|contents| match contents {
- Expr(e) => {
- Expr(e.normalize_to_expr_maybe_alpha(alpha))
- }
- Text(s) => Text(s.clone()),
- })
- .collect(),
- ))
- }
- Value::Equivalence(x, y) => rc(ExprF::BinOp(
- dhall_syntax::BinOp::Equivalence,
- x.normalize_to_expr_maybe_alpha(alpha),
- y.normalize_to_expr_maybe_alpha(alpha),
- )),
- Value::PartialExpr(e) => {
- rc(e.map_ref_simple(|v| v.normalize_to_expr_maybe_alpha(alpha)))
+ fn get_type(&self) -> Result<&Value, TypeError> {
+ match &self.ty {
+ Some(t) => Ok(t),
+ None => {
+ Err(TypeError::new(&TypecheckContext::new(), TypeMessage::Sort))
}
}
}
+}
- // Deprecated
- pub fn normalize(&self) -> Value {
- let mut v = self.clone();
- v.normalize_mut();
- v
+impl Value {
+ fn new(value: ValueF, form: Form, ty: Value) -> Value {
+ ValueInternal {
+ form,
+ value,
+ ty: Some(ty),
+ }
+ .into_value()
+ }
+ pub(crate) fn const_sort() -> Value {
+ ValueInternal {
+ form: NF,
+ value: ValueF::Const(Const::Sort),
+ ty: None,
+ }
+ .into_value()
+ }
+ pub(crate) fn from_valuef_and_type(v: ValueF, t: Value) -> Value {
+ Value::new(v, Unevaled, t)
+ }
+ pub(crate) fn from_valuef_and_type_whnf(v: ValueF, t: Value) -> Value {
+ Value::new(v, WHNF, t)
+ }
+ pub(crate) fn from_const(c: Const) -> Self {
+ const_to_value(c)
+ }
+ pub(crate) fn from_builtin(b: Builtin) -> Self {
+ builtin_to_value(b)
}
- pub fn normalize_mut(&mut self) {
- match self {
- Value::NaturalSuccClosure
- | Value::Var(_)
- | Value::Const(_)
- | Value::BoolLit(_)
- | Value::NaturalLit(_)
- | Value::IntegerLit(_)
- | Value::DoubleLit(_) => {}
+ pub(crate) fn as_const(&self) -> Option<Const> {
+ match &*self.as_whnf() {
+ ValueF::Const(c) => Some(*c),
+ _ => None,
+ }
+ }
- Value::EmptyOptionalLit(tth)
- | Value::OptionalSomeClosure(tth)
- | Value::EmptyListLit(tth) => {
- tth.normalize_mut();
- }
+ fn as_internal(&self) -> Ref<ValueInternal> {
+ self.0.borrow()
+ }
+ fn as_internal_mut(&self) -> RefMut<ValueInternal> {
+ self.0.borrow_mut()
+ }
+ /// WARNING: The returned ValueF may be entirely unnormalized, in aprticular it may just be an
+ /// unevaled PartialExpr. You probably want to use `as_whnf`.
+ fn as_valuef(&self) -> Ref<ValueF> {
+ Ref::map(self.as_internal(), ValueInternal::as_valuef)
+ }
+ /// This is what you want if you want to pattern-match on the value.
+ /// WARNING: drop this ref before normalizing the same value or you will run into BorrowMut
+ /// panics.
+ pub(crate) fn as_whnf(&self) -> Ref<ValueF> {
+ self.normalize_whnf();
+ self.as_valuef()
+ }
- Value::NEOptionalLit(th) => {
- th.normalize_mut();
- }
- Value::Lam(_, t, e) => {
- t.normalize_mut();
- e.normalize_mut();
- }
- Value::Pi(_, t, e) => {
- t.normalize_mut();
- e.normalize_mut();
- }
- Value::AppliedBuiltin(_, args) => {
- for x in args.iter_mut() {
- x.normalize_mut();
- }
- }
- Value::ListConsClosure(t, v) => {
- t.normalize_mut();
- for x in v.iter_mut() {
- x.normalize_mut();
- }
- }
- Value::NEListLit(elts) => {
- for x in elts.iter_mut() {
- x.normalize_mut();
- }
- }
- Value::RecordLit(kvs) => {
- for x in kvs.values_mut() {
- x.normalize_mut();
- }
- }
- Value::RecordType(kvs) => {
- for x in kvs.values_mut() {
- x.normalize_mut();
- }
- }
- Value::UnionType(kts) | Value::UnionConstructor(_, kts) => {
- for x in kts.values_mut().flat_map(|opt| opt) {
- x.normalize_mut();
- }
- }
- Value::UnionLit(_, v, kts) => {
- v.normalize_mut();
- for x in kts.values_mut().flat_map(|opt| opt) {
- x.normalize_mut();
- }
- }
- Value::TextLit(elts) => {
- for x in elts.iter_mut() {
- use InterpolatedTextContents::{Expr, Text};
- match x {
- Expr(n) => n.normalize_mut(),
- Text(_) => {}
- }
- }
- }
- Value::Equivalence(x, y) => {
- x.normalize_mut();
- y.normalize_mut();
- }
- Value::PartialExpr(e) => {
- // TODO: need map_mut_simple
- e.map_ref_simple(|v| {
- v.normalize_nf();
- });
- }
+ pub(crate) fn to_expr(&self, opts: ToExprOptions) -> NormalizedExpr {
+ if opts.normalize {
+ self.normalize_whnf();
}
+ self.as_valuef().to_expr(opts)
+ }
+ pub(crate) fn to_whnf_ignore_type(&self) -> ValueF {
+ self.as_whnf().clone()
+ }
+ /// Before discarding type information, check that it matches the expected return type.
+ pub(crate) fn to_whnf_check_type(&self, ty: &Value) -> ValueF {
+ self.check_type(ty);
+ self.to_whnf_ignore_type()
+ }
+ pub(crate) fn into_typed(self) -> Typed {
+ Typed::from_value(self)
}
- /// Apply to a value
- pub fn app(self, val: Value) -> Value {
- self.app_val(val)
+ /// Mutates the contents. If no one else shares this, this avoids a RefCell lock.
+ fn mutate_internal(&mut self, f: impl FnOnce(&mut ValueInternal)) {
+ match Rc::get_mut(&mut self.0) {
+ // Mutate directly if sole owner
+ Some(refcell) => f(RefCell::get_mut(refcell)),
+ // Otherwise mutate through the refcell
+ None => f(&mut self.as_internal_mut()),
+ }
+ }
+ /// Normalizes contents to normal form; faster than `normalize_nf` if
+ /// no one else shares this.
+ pub(crate) fn normalize_mut(&mut self) {
+ self.mutate_internal(|vint| vint.normalize_nf())
}
- /// Apply to a value
- pub fn app_val(self, val: Value) -> Value {
- self.app_thunk(val.into_thunk())
+ pub(crate) fn normalize_whnf(&self) {
+ let borrow = self.as_internal();
+ match borrow.form {
+ Unevaled => {
+ drop(borrow);
+ self.as_internal_mut().normalize_whnf();
+ }
+ // Already at least in WHNF
+ WHNF | NF => {}
+ }
+ }
+ pub(crate) fn normalize_nf(&self) {
+ let borrow = self.as_internal();
+ match borrow.form {
+ Unevaled | WHNF => {
+ drop(borrow);
+ self.as_internal_mut().normalize_nf();
+ }
+ // Already in NF
+ NF => {}
+ }
}
- /// Apply to a thunk
- pub fn app_thunk(self, th: Thunk) -> Value {
- Thunk::from_value(self).app_thunk(th)
+ pub(crate) fn app(&self, v: Value) -> Value {
+ let body_t = match &*self.get_type_not_sort().as_whnf() {
+ ValueF::Pi(x, t, e) => {
+ v.check_type(t);
+ e.subst_shift(&x.into(), &v)
+ }
+ _ => unreachable!("Internal type error"),
+ };
+ Value::from_valuef_and_type_whnf(
+ apply_any(self.clone(), v, &body_t),
+ body_t,
+ )
}
- pub fn from_builtin(b: Builtin) -> Value {
- Value::AppliedBuiltin(b, vec![])
+ /// In debug mode, panic if the provided type doesn't match the value's type.
+ /// Otherwise does nothing.
+ pub(crate) fn check_type(&self, ty: &Value) {
+ debug_assert_eq!(
+ Some(ty),
+ self.get_type().ok().as_ref(),
+ "Internal type error"
+ );
+ }
+ pub(crate) fn get_type(&self) -> Result<Value, TypeError> {
+ Ok(self.as_internal().get_type()?.clone())
+ }
+ /// When we know the value isn't `Sort`, this gets the type directly
+ pub(crate) fn get_type_not_sort(&self) -> Value {
+ self.get_type()
+ .expect("Internal type error: value is `Sort` but shouldn't be")
}
}
impl Shift for Value {
fn shift(&self, delta: isize, var: &AlphaVar) -> Option<Self> {
- Some(match self {
- Value::Lam(x, t, e) => Value::Lam(
- x.clone(),
- t.shift(delta, var)?,
- e.shift(delta, &var.under_binder(x))?,
- ),
- Value::AppliedBuiltin(b, args) => Value::AppliedBuiltin(
- *b,
- args.iter()
- .map(|v| Ok(v.shift(delta, var)?))
- .collect::<Result<_, _>>()?,
- ),
- Value::NaturalSuccClosure => Value::NaturalSuccClosure,
- Value::OptionalSomeClosure(tth) => {
- Value::OptionalSomeClosure(tth.shift(delta, var)?)
- }
- Value::ListConsClosure(t, v) => Value::ListConsClosure(
- t.shift(delta, var)?,
- v.as_ref().map(|v| Ok(v.shift(delta, var)?)).transpose()?,
- ),
- Value::Pi(x, t, e) => Value::Pi(
- x.clone(),
- t.shift(delta, var)?,
- e.shift(delta, &var.under_binder(x))?,
- ),
- Value::Var(v) => Value::Var(v.shift(delta, var)?),
- Value::Const(c) => Value::Const(*c),
- Value::BoolLit(b) => Value::BoolLit(*b),
- Value::NaturalLit(n) => Value::NaturalLit(*n),
- Value::IntegerLit(n) => Value::IntegerLit(*n),
- Value::DoubleLit(n) => Value::DoubleLit(*n),
- Value::EmptyOptionalLit(tth) => {
- Value::EmptyOptionalLit(tth.shift(delta, var)?)
- }
- Value::NEOptionalLit(th) => {
- Value::NEOptionalLit(th.shift(delta, var)?)
- }
- Value::EmptyListLit(tth) => {
- Value::EmptyListLit(tth.shift(delta, var)?)
- }
- Value::NEListLit(elts) => Value::NEListLit(
- elts.iter()
- .map(|v| Ok(v.shift(delta, var)?))
- .collect::<Result<_, _>>()?,
- ),
- Value::RecordLit(kvs) => Value::RecordLit(
- kvs.iter()
- .map(|(k, v)| Ok((k.clone(), v.shift(delta, var)?)))
- .collect::<Result<_, _>>()?,
- ),
- Value::RecordType(kvs) => Value::RecordType(
- kvs.iter()
- .map(|(k, v)| Ok((k.clone(), v.shift(delta, var)?)))
- .collect::<Result<_, _>>()?,
- ),
- Value::UnionType(kts) => Value::UnionType(
- kts.iter()
- .map(|(k, v)| {
- Ok((
- k.clone(),
- v.as_ref()
- .map(|v| Ok(v.shift(delta, var)?))
- .transpose()?,
- ))
- })
- .collect::<Result<_, _>>()?,
- ),
- Value::UnionConstructor(x, kts) => Value::UnionConstructor(
- x.clone(),
- kts.iter()
- .map(|(k, v)| {
- Ok((
- k.clone(),
- v.as_ref()
- .map(|v| Ok(v.shift(delta, var)?))
- .transpose()?,
- ))
- })
- .collect::<Result<_, _>>()?,
- ),
- Value::UnionLit(x, v, kts) => Value::UnionLit(
- x.clone(),
- v.shift(delta, var)?,
- kts.iter()
- .map(|(k, v)| {
- Ok((
- k.clone(),
- v.as_ref()
- .map(|v| Ok(v.shift(delta, var)?))
- .transpose()?,
- ))
- })
- .collect::<Result<_, _>>()?,
- ),
- Value::TextLit(elts) => Value::TextLit(
- elts.iter()
- .map(|contents| {
- use InterpolatedTextContents::{Expr, Text};
- Ok(match contents {
- Expr(th) => Expr(th.shift(delta, var)?),
- Text(s) => Text(s.clone()),
- })
- })
- .collect::<Result<_, _>>()?,
- ),
- Value::Equivalence(x, y) => {
- Value::Equivalence(x.shift(delta, var)?, y.shift(delta, var)?)
- }
- Value::PartialExpr(e) => Value::PartialExpr(
- e.traverse_ref_with_special_handling_of_binders(
- |v| Ok(v.shift(delta, var)?),
- |x, v| Ok(v.shift(delta, &var.under_binder(x))?),
- |x| Ok(X::clone(x)),
- )?,
- ),
+ Some(Value(self.0.shift(delta, var)?))
+ }
+}
+
+impl Shift for ValueInternal {
+ fn shift(&self, delta: isize, var: &AlphaVar) -> Option<Self> {
+ Some(ValueInternal {
+ form: self.form,
+ value: self.value.shift(delta, var)?,
+ ty: self.ty.shift(delta, var)?,
})
}
}
-impl Subst<Typed> for Value {
- fn subst_shift(&self, var: &AlphaVar, val: &Typed) -> Self {
- match self {
- // Retry normalizing since substituting may allow progress
- Value::AppliedBuiltin(b, args) => apply_builtin(
- *b,
- args.iter().map(|v| v.subst_shift(var, val)).collect(),
- ),
- // Retry normalizing since substituting may allow progress
- Value::PartialExpr(e) => {
- normalize_one_layer(e.map_ref_with_special_handling_of_binders(
- |v| v.subst_shift(var, val),
- |x, v| {
- v.subst_shift(
- &var.under_binder(x),
- &val.under_binder(x),
- )
- },
- X::clone,
- ))
- }
- // Retry normalizing since substituting may allow progress
- Value::TextLit(elts) => {
- Value::TextLit(squash_textlit(elts.iter().map(|contents| {
- use InterpolatedTextContents::{Expr, Text};
- match contents {
- Expr(th) => Expr(th.subst_shift(var, val)),
- Text(s) => Text(s.clone()),
- }
- })))
- }
- Value::Lam(x, t, e) => Value::Lam(
- x.clone(),
- t.subst_shift(var, val),
- e.subst_shift(&var.under_binder(x), &val.under_binder(x)),
- ),
- Value::NaturalSuccClosure => Value::NaturalSuccClosure,
- Value::OptionalSomeClosure(tth) => {
- Value::OptionalSomeClosure(tth.subst_shift(var, val))
- }
- Value::ListConsClosure(t, v) => Value::ListConsClosure(
- t.subst_shift(var, val),
- v.as_ref().map(|v| v.subst_shift(var, val)),
- ),
- Value::Pi(x, t, e) => Value::Pi(
- x.clone(),
- t.subst_shift(var, val),
- e.subst_shift(&var.under_binder(x), &val.under_binder(x)),
- ),
- Value::Var(v) => match v.shift(-1, var) {
- None => val.to_value().clone(),
- Some(newvar) => Value::Var(newvar),
- },
- Value::Const(c) => Value::Const(*c),
- Value::BoolLit(b) => Value::BoolLit(*b),
- Value::NaturalLit(n) => Value::NaturalLit(*n),
- Value::IntegerLit(n) => Value::IntegerLit(*n),
- Value::DoubleLit(n) => Value::DoubleLit(*n),
- Value::EmptyOptionalLit(tth) => {
- Value::EmptyOptionalLit(tth.subst_shift(var, val))
- }
- Value::NEOptionalLit(th) => {
- Value::NEOptionalLit(th.subst_shift(var, val))
- }
- Value::EmptyListLit(tth) => {
- Value::EmptyListLit(tth.subst_shift(var, val))
+impl Subst<Value> for Value {
+ fn subst_shift(&self, var: &AlphaVar, val: &Value) -> Self {
+ match &*self.as_valuef() {
+ // If the var matches, we can just reuse the provided value instead of copying it.
+ // We also check that the types match, if in debug mode.
+ ValueF::Var(v) if v == var => {
+ if let Ok(self_ty) = self.get_type() {
+ val.check_type(&self_ty.subst_shift(var, val));
+ }
+ val.clone()
}
- Value::NEListLit(elts) => Value::NEListLit(
- elts.iter().map(|v| v.subst_shift(var, val)).collect(),
- ),
- Value::RecordLit(kvs) => Value::RecordLit(
- kvs.iter()
- .map(|(k, v)| (k.clone(), v.subst_shift(var, val)))
- .collect(),
- ),
- Value::RecordType(kvs) => Value::RecordType(
- kvs.iter()
- .map(|(k, v)| (k.clone(), v.subst_shift(var, val)))
- .collect(),
- ),
- Value::UnionType(kts) => Value::UnionType(
- kts.iter()
- .map(|(k, v)| {
- (k.clone(), v.as_ref().map(|v| v.subst_shift(var, val)))
- })
- .collect(),
- ),
- Value::UnionConstructor(x, kts) => Value::UnionConstructor(
- x.clone(),
- kts.iter()
- .map(|(k, v)| {
- (k.clone(), v.as_ref().map(|v| v.subst_shift(var, val)))
- })
- .collect(),
- ),
- Value::UnionLit(x, v, kts) => Value::UnionLit(
- x.clone(),
- v.subst_shift(var, val),
- kts.iter()
- .map(|(k, v)| {
- (k.clone(), v.as_ref().map(|v| v.subst_shift(var, val)))
- })
- .collect(),
- ),
- Value::Equivalence(x, y) => Value::Equivalence(
- x.subst_shift(var, val),
- y.subst_shift(var, val),
- ),
+ _ => Value(self.0.subst_shift(var, val)),
+ }
+ }
+}
+
+impl Subst<Value> for ValueInternal {
+ fn subst_shift(&self, var: &AlphaVar, val: &Value) -> Self {
+ ValueInternal {
+ // The resulting value may not stay in wnhf after substitution
+ form: Unevaled,
+ value: self.value.subst_shift(var, val),
+ ty: self.ty.subst_shift(var, val),
+ }
+ }
+}
+
+// TODO: use Rc comparison to shortcut on identical pointers
+impl std::cmp::PartialEq for Value {
+ fn eq(&self, other: &Self) -> bool {
+ *self.as_whnf() == *other.as_whnf()
+ }
+}
+impl std::cmp::Eq for Value {}
+
+impl std::fmt::Debug for Value {
+ fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ let vint: &ValueInternal = &self.as_internal();
+ if let ValueF::Const(c) = &vint.value {
+ write!(fmt, "{:?}", c)
+ } else {
+ let mut x = fmt.debug_struct(&format!("Value@{:?}", &vint.form));
+ x.field("value", &vint.value);
+ if let Some(ty) = vint.ty.as_ref() {
+ x.field("type", &ty);
+ } else {
+ x.field("type", &None::<()>);
+ }
+ x.finish()
}
}
}
diff --git a/dhall/src/core/valuef.rs b/dhall/src/core/valuef.rs
new file mode 100644
index 0000000..7ecec86
--- /dev/null
+++ b/dhall/src/core/valuef.rs
@@ -0,0 +1,335 @@
+use std::collections::HashMap;
+
+use dhall_syntax::{
+ rc, Builtin, Const, ExprF, Integer, InterpolatedTextContents, Label,
+ NaiveDouble, Natural,
+};
+
+use crate::core::value::{ToExprOptions, Value};
+use crate::core::var::{AlphaLabel, AlphaVar, Shift, Subst};
+use crate::phase::{Normalized, NormalizedExpr};
+
+/// A semantic value. Subexpressions are Values, which are partially evaluated expressions that are
+/// normalized on-demand.
+/// If you compare for equality two `ValueF`s in WHNF, then equality will be up to
+/// alpha-equivalence (renaming of bound variables) and beta-equivalence (normalization). It will
+/// recursively normalize as needed.
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub(crate) enum ValueF {
+ /// Closures
+ Lam(AlphaLabel, Value, Value),
+ Pi(AlphaLabel, Value, Value),
+ // Invariant: in whnf, the evaluation must not be able to progress further.
+ AppliedBuiltin(Builtin, Vec<Value>),
+
+ Var(AlphaVar),
+ Const(Const),
+ BoolLit(bool),
+ NaturalLit(Natural),
+ IntegerLit(Integer),
+ DoubleLit(NaiveDouble),
+ EmptyOptionalLit(Value),
+ NEOptionalLit(Value),
+ // EmptyListLit(t) means `[] : List t`, not `[] : t`
+ EmptyListLit(Value),
+ NEListLit(Vec<Value>),
+ RecordType(HashMap<Label, Value>),
+ RecordLit(HashMap<Label, Value>),
+ UnionType(HashMap<Label, Option<Value>>),
+ UnionConstructor(Label, HashMap<Label, Option<Value>>),
+ UnionLit(Label, Value, HashMap<Label, Option<Value>>),
+ // Invariant: in whnf, this must not contain interpolations that are themselves TextLits, and
+ // contiguous text values must be merged.
+ TextLit(Vec<InterpolatedTextContents<Value>>),
+ Equivalence(Value, Value),
+ // Invariant: in whnf, this must not contain a value captured by one of the variants above.
+ PartialExpr(ExprF<Value, Normalized>),
+}
+
+impl ValueF {
+ pub(crate) fn into_value_with_type(self, t: Value) -> Value {
+ Value::from_valuef_and_type(self, t)
+ }
+
+ pub(crate) fn to_expr(&self, opts: ToExprOptions) -> NormalizedExpr {
+ match self {
+ ValueF::Lam(x, t, e) => rc(ExprF::Lam(
+ x.to_label_maybe_alpha(opts.alpha),
+ t.to_expr(opts),
+ e.to_expr(opts),
+ )),
+ ValueF::AppliedBuiltin(b, args) => args
+ .iter()
+ .map(|v| v.to_expr(opts))
+ .fold(rc(ExprF::Builtin(*b)), |acc, v| rc(ExprF::App(acc, v))),
+ ValueF::Pi(x, t, e) => rc(ExprF::Pi(
+ x.to_label_maybe_alpha(opts.alpha),
+ t.to_expr(opts),
+ e.to_expr(opts),
+ )),
+ ValueF::Var(v) => rc(ExprF::Var(v.to_var(opts.alpha))),
+ ValueF::Const(c) => rc(ExprF::Const(*c)),
+ ValueF::BoolLit(b) => rc(ExprF::BoolLit(*b)),
+ ValueF::NaturalLit(n) => rc(ExprF::NaturalLit(*n)),
+ ValueF::IntegerLit(n) => rc(ExprF::IntegerLit(*n)),
+ ValueF::DoubleLit(n) => rc(ExprF::DoubleLit(*n)),
+ ValueF::EmptyOptionalLit(n) => rc(ExprF::App(
+ rc(ExprF::Builtin(Builtin::OptionalNone)),
+ n.to_expr(opts),
+ )),
+ ValueF::NEOptionalLit(n) => rc(ExprF::SomeLit(n.to_expr(opts))),
+ ValueF::EmptyListLit(n) => rc(ExprF::EmptyListLit(rc(ExprF::App(
+ rc(ExprF::Builtin(Builtin::List)),
+ n.to_expr(opts),
+ )))),
+ ValueF::NEListLit(elts) => rc(ExprF::NEListLit(
+ elts.iter().map(|n| n.to_expr(opts)).collect(),
+ )),
+ ValueF::RecordLit(kvs) => rc(ExprF::RecordLit(
+ kvs.iter()
+ .map(|(k, v)| (k.clone(), v.to_expr(opts)))
+ .collect(),
+ )),
+ ValueF::RecordType(kts) => rc(ExprF::RecordType(
+ kts.iter()
+ .map(|(k, v)| (k.clone(), v.to_expr(opts)))
+ .collect(),
+ )),
+ ValueF::UnionType(kts) => rc(ExprF::UnionType(
+ kts.iter()
+ .map(|(k, v)| {
+ (k.clone(), v.as_ref().map(|v| v.to_expr(opts)))
+ })
+ .collect(),
+ )),
+ ValueF::UnionConstructor(l, kts) => rc(ExprF::Field(
+ ValueF::UnionType(kts.clone()).to_expr(opts),
+ l.clone(),
+ )),
+ ValueF::UnionLit(l, v, kts) => rc(ExprF::App(
+ ValueF::UnionConstructor(l.clone(), kts.clone()).to_expr(opts),
+ v.to_expr(opts),
+ )),
+ ValueF::TextLit(elts) => {
+ use InterpolatedTextContents::{Expr, Text};
+ rc(ExprF::TextLit(
+ elts.iter()
+ .map(|contents| match contents {
+ Expr(e) => Expr(e.to_expr(opts)),
+ Text(s) => Text(s.clone()),
+ })
+ .collect(),
+ ))
+ }
+ ValueF::Equivalence(x, y) => rc(ExprF::BinOp(
+ dhall_syntax::BinOp::Equivalence,
+ x.to_expr(opts),
+ y.to_expr(opts),
+ )),
+ ValueF::PartialExpr(e) => rc(e.map_ref(|v| v.to_expr(opts))),
+ }
+ }
+
+ pub(crate) fn normalize_mut(&mut self) {
+ match self {
+ ValueF::Var(_)
+ | ValueF::Const(_)
+ | ValueF::BoolLit(_)
+ | ValueF::NaturalLit(_)
+ | ValueF::IntegerLit(_)
+ | ValueF::DoubleLit(_) => {}
+
+ ValueF::EmptyOptionalLit(tth) | ValueF::EmptyListLit(tth) => {
+ tth.normalize_mut();
+ }
+
+ ValueF::NEOptionalLit(th) => {
+ th.normalize_mut();
+ }
+ ValueF::Lam(_, t, e) => {
+ t.normalize_mut();
+ e.normalize_mut();
+ }
+ ValueF::Pi(_, t, e) => {
+ t.normalize_mut();
+ e.normalize_mut();
+ }
+ ValueF::AppliedBuiltin(_, args) => {
+ for x in args.iter_mut() {
+ x.normalize_mut();
+ }
+ }
+ ValueF::NEListLit(elts) => {
+ for x in elts.iter_mut() {
+ x.normalize_mut();
+ }
+ }
+ ValueF::RecordLit(kvs) => {
+ for x in kvs.values_mut() {
+ x.normalize_mut();
+ }
+ }
+ ValueF::RecordType(kvs) => {
+ for x in kvs.values_mut() {
+ x.normalize_mut();
+ }
+ }
+ ValueF::UnionType(kts) | ValueF::UnionConstructor(_, kts) => {
+ for x in kts.values_mut().flat_map(|opt| opt) {
+ x.normalize_mut();
+ }
+ }
+ ValueF::UnionLit(_, v, kts) => {
+ v.normalize_mut();
+ for x in kts.values_mut().flat_map(|opt| opt) {
+ x.normalize_mut();
+ }
+ }
+ ValueF::TextLit(elts) => {
+ for x in elts.iter_mut() {
+ use InterpolatedTextContents::{Expr, Text};
+ match x {
+ Expr(n) => n.normalize_mut(),
+ Text(_) => {}
+ }
+ }
+ }
+ ValueF::Equivalence(x, y) => {
+ x.normalize_mut();
+ y.normalize_mut();
+ }
+ ValueF::PartialExpr(e) => {
+ // TODO: need map_mut
+ e.map_ref(|v| {
+ v.normalize_nf();
+ });
+ }
+ }
+ }
+
+ pub(crate) fn from_builtin(b: Builtin) -> ValueF {
+ ValueF::AppliedBuiltin(b, vec![])
+ }
+}
+
+impl Shift for ValueF {
+ fn shift(&self, delta: isize, var: &AlphaVar) -> Option<Self> {
+ Some(match self {
+ ValueF::Lam(x, t, e) => ValueF::Lam(
+ x.clone(),
+ t.shift(delta, var)?,
+ e.shift(delta, &var.under_binder(x))?,
+ ),
+ ValueF::AppliedBuiltin(b, args) => {
+ ValueF::AppliedBuiltin(*b, args.shift(delta, var)?)
+ }
+ ValueF::Pi(x, t, e) => ValueF::Pi(
+ x.clone(),
+ t.shift(delta, var)?,
+ e.shift(delta, &var.under_binder(x))?,
+ ),
+ ValueF::Var(v) => ValueF::Var(v.shift(delta, var)?),
+ ValueF::Const(c) => ValueF::Const(*c),
+ ValueF::BoolLit(b) => ValueF::BoolLit(*b),
+ ValueF::NaturalLit(n) => ValueF::NaturalLit(*n),
+ ValueF::IntegerLit(n) => ValueF::IntegerLit(*n),
+ ValueF::DoubleLit(n) => ValueF::DoubleLit(*n),
+ ValueF::EmptyOptionalLit(tth) => {
+ ValueF::EmptyOptionalLit(tth.shift(delta, var)?)
+ }
+ ValueF::NEOptionalLit(th) => {
+ ValueF::NEOptionalLit(th.shift(delta, var)?)
+ }
+ ValueF::EmptyListLit(tth) => {
+ ValueF::EmptyListLit(tth.shift(delta, var)?)
+ }
+ ValueF::NEListLit(elts) => {
+ ValueF::NEListLit(elts.shift(delta, var)?)
+ }
+ ValueF::RecordLit(kvs) => ValueF::RecordLit(kvs.shift(delta, var)?),
+ ValueF::RecordType(kvs) => {
+ ValueF::RecordType(kvs.shift(delta, var)?)
+ }
+ ValueF::UnionType(kts) => ValueF::UnionType(kts.shift(delta, var)?),
+ ValueF::UnionConstructor(x, kts) => {
+ ValueF::UnionConstructor(x.clone(), kts.shift(delta, var)?)
+ }
+ ValueF::UnionLit(x, v, kts) => ValueF::UnionLit(
+ x.clone(),
+ v.shift(delta, var)?,
+ kts.shift(delta, var)?,
+ ),
+ ValueF::TextLit(elts) => ValueF::TextLit(elts.shift(delta, var)?),
+ ValueF::Equivalence(x, y) => {
+ ValueF::Equivalence(x.shift(delta, var)?, y.shift(delta, var)?)
+ }
+ ValueF::PartialExpr(e) => ValueF::PartialExpr(e.shift(delta, var)?),
+ })
+ }
+}
+
+impl Subst<Value> for ValueF {
+ fn subst_shift(&self, var: &AlphaVar, val: &Value) -> Self {
+ match self {
+ ValueF::AppliedBuiltin(b, args) => {
+ ValueF::AppliedBuiltin(*b, args.subst_shift(var, val))
+ }
+ ValueF::PartialExpr(e) => {
+ ValueF::PartialExpr(e.subst_shift(var, val))
+ }
+ ValueF::TextLit(elts) => {
+ ValueF::TextLit(elts.subst_shift(var, val))
+ }
+ ValueF::Lam(x, t, e) => ValueF::Lam(
+ x.clone(),
+ t.subst_shift(var, val),
+ e.subst_shift(&var.under_binder(x), &val.under_binder(x)),
+ ),
+ ValueF::Pi(x, t, e) => ValueF::Pi(
+ x.clone(),
+ t.subst_shift(var, val),
+ e.subst_shift(&var.under_binder(x), &val.under_binder(x)),
+ ),
+ ValueF::Var(v) if v == var => val.to_whnf_ignore_type(),
+ ValueF::Var(v) => ValueF::Var(v.shift(-1, var).unwrap()),
+ ValueF::Const(c) => ValueF::Const(*c),
+ ValueF::BoolLit(b) => ValueF::BoolLit(*b),
+ ValueF::NaturalLit(n) => ValueF::NaturalLit(*n),
+ ValueF::IntegerLit(n) => ValueF::IntegerLit(*n),
+ ValueF::DoubleLit(n) => ValueF::DoubleLit(*n),
+ ValueF::EmptyOptionalLit(tth) => {
+ ValueF::EmptyOptionalLit(tth.subst_shift(var, val))
+ }
+ ValueF::NEOptionalLit(th) => {
+ ValueF::NEOptionalLit(th.subst_shift(var, val))
+ }
+ ValueF::EmptyListLit(tth) => {
+ ValueF::EmptyListLit(tth.subst_shift(var, val))
+ }
+ ValueF::NEListLit(elts) => {
+ ValueF::NEListLit(elts.subst_shift(var, val))
+ }
+ ValueF::RecordLit(kvs) => {
+ ValueF::RecordLit(kvs.subst_shift(var, val))
+ }
+ ValueF::RecordType(kvs) => {
+ ValueF::RecordType(kvs.subst_shift(var, val))
+ }
+ ValueF::UnionType(kts) => {
+ ValueF::UnionType(kts.subst_shift(var, val))
+ }
+ ValueF::UnionConstructor(x, kts) => {
+ ValueF::UnionConstructor(x.clone(), kts.subst_shift(var, val))
+ }
+ ValueF::UnionLit(x, v, kts) => ValueF::UnionLit(
+ x.clone(),
+ v.subst_shift(var, val),
+ kts.subst_shift(var, val),
+ ),
+ ValueF::Equivalence(x, y) => ValueF::Equivalence(
+ x.subst_shift(var, val),
+ y.subst_shift(var, val),
+ ),
+ }
+ }
+}
diff --git a/dhall/src/core/var.rs b/dhall/src/core/var.rs
index 35bff80..ce4d137 100644
--- a/dhall/src/core/var.rs
+++ b/dhall/src/core/var.rs
@@ -2,21 +2,21 @@ use std::collections::HashMap;
use dhall_syntax::{Label, V};
-/// Stores a pair of variables: a normal one and if relevant one
+/// Stores a pair of variables: a normal one and one
/// that corresponds to the alpha-normalized version of the first one.
-/// Equality is up to alpha-equivalence.
-#[derive(Debug, Clone, Eq)]
+/// Equality is up to alpha-equivalence (compares on the second one only).
+#[derive(Clone, Eq)]
pub struct AlphaVar {
normal: V<Label>,
- alpha: Option<V<()>>,
+ alpha: V<()>,
}
// Exactly like a Label, but equality returns always true.
-// This is so that Value equality is exactly alpha-equivalence.
-#[derive(Debug, Clone, Eq)]
+// This is so that ValueF equality is exactly alpha-equivalence.
+#[derive(Clone, Eq)]
pub struct AlphaLabel(Label);
-pub trait Shift: Sized {
+pub(crate) trait Shift: Sized {
// Shift an expression to move it around binders without changing the meaning of its free
// variables. Shift by 1 to move an expression under a binder. Shift by -1 to extract an
// expression from under a binder, if the expression does not refer to that bound variable.
@@ -50,34 +50,35 @@ pub trait Shift: Sized {
}
}
-pub trait Subst<T> {
- fn subst_shift(&self, var: &AlphaVar, val: &T) -> Self;
+pub(crate) trait Subst<S> {
+ fn subst_shift(&self, var: &AlphaVar, val: &S) -> Self;
}
impl AlphaVar {
- pub fn to_var(&self, alpha: bool) -> V<Label> {
- match (alpha, &self.alpha) {
- (true, Some(x)) => V("_".into(), x.1),
- _ => self.normal.clone(),
+ pub(crate) fn to_var(&self, alpha: bool) -> V<Label> {
+ if alpha {
+ V("_".into(), self.alpha.1)
+ } else {
+ self.normal.clone()
}
}
- pub fn from_var(normal: V<Label>) -> Self {
+ pub(crate) fn from_var_and_alpha(normal: V<Label>, alpha: usize) -> Self {
AlphaVar {
normal,
- alpha: None,
+ alpha: V((), alpha),
}
}
}
impl AlphaLabel {
- pub fn to_label_maybe_alpha(&self, alpha: bool) -> Label {
+ pub(crate) fn to_label_maybe_alpha(&self, alpha: bool) -> Label {
if alpha {
"_".into()
} else {
self.to_label()
}
}
- pub fn to_label(&self) -> Label {
+ pub(crate) fn to_label(&self) -> Label {
self.clone().into()
}
}
@@ -86,31 +87,15 @@ impl Shift for AlphaVar {
fn shift(&self, delta: isize, var: &AlphaVar) -> Option<Self> {
Some(AlphaVar {
normal: self.normal.shift(delta, &var.normal)?,
- alpha: match (&self.alpha, &var.alpha) {
- (Some(x), Some(v)) => Some(x.shift(delta, v)?),
- _ => None,
- },
+ alpha: self.alpha.shift(delta, &var.alpha)?,
})
}
}
-impl Shift for () {
- fn shift(&self, _delta: isize, _var: &AlphaVar) -> Option<Self> {
- Some(())
- }
-}
-
-impl<T> Subst<T> for () {
- fn subst_shift(&self, _var: &AlphaVar, _val: &T) -> Self {}
-}
-
+/// Equality up to alpha-equivalence
impl std::cmp::PartialEq for AlphaVar {
fn eq(&self, other: &Self) -> bool {
- match (&self.alpha, &other.alpha) {
- (Some(x), Some(y)) => x == y,
- (None, None) => self.normal == other.normal,
- _ => false,
- }
+ self.alpha == other.alpha
}
}
impl std::cmp::PartialEq for AlphaLabel {
@@ -119,11 +104,23 @@ impl std::cmp::PartialEq for AlphaLabel {
}
}
+impl std::fmt::Debug for AlphaVar {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ write!(f, "AlphaVar({}, {})", self.normal, self.alpha.1)
+ }
+}
+
+impl std::fmt::Debug for AlphaLabel {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ write!(f, "AlphaLabel({})", &self.0)
+ }
+}
+
impl From<Label> for AlphaVar {
fn from(x: Label) -> AlphaVar {
AlphaVar {
normal: V(x, 0),
- alpha: Some(V((), 0)),
+ alpha: V((), 0),
}
}
}
@@ -154,3 +151,153 @@ impl From<AlphaLabel> for Label {
x.0
}
}
+impl Shift for () {
+ fn shift(&self, _delta: isize, _var: &AlphaVar) -> Option<Self> {
+ Some(())
+ }
+}
+
+impl<A: Shift, B: Shift> Shift for (A, B) {
+ fn shift(&self, delta: isize, var: &AlphaVar) -> Option<Self> {
+ Some((self.0.shift(delta, var)?, self.1.shift(delta, var)?))
+ }
+}
+
+impl<T: Shift> Shift for Option<T> {
+ fn shift(&self, delta: isize, var: &AlphaVar) -> Option<Self> {
+ Some(match self {
+ None => None,
+ Some(x) => Some(x.shift(delta, var)?),
+ })
+ }
+}
+
+impl<T: Shift> Shift for Box<T> {
+ fn shift(&self, delta: isize, var: &AlphaVar) -> Option<Self> {
+ Some(Box::new(self.as_ref().shift(delta, var)?))
+ }
+}
+
+impl<T: Shift> Shift for std::rc::Rc<T> {
+ fn shift(&self, delta: isize, var: &AlphaVar) -> Option<Self> {
+ Some(std::rc::Rc::new(self.as_ref().shift(delta, var)?))
+ }
+}
+
+impl<T: Shift> Shift for std::cell::RefCell<T> {
+ fn shift(&self, delta: isize, var: &AlphaVar) -> Option<Self> {
+ Some(std::cell::RefCell::new(self.borrow().shift(delta, var)?))
+ }
+}
+
+impl<T: Shift, E: Clone> Shift for dhall_syntax::ExprF<T, E> {
+ fn shift(&self, delta: isize, var: &AlphaVar) -> Option<Self> {
+ Some(self.traverse_ref_with_special_handling_of_binders(
+ |v| Ok(v.shift(delta, var)?),
+ |x, v| Ok(v.shift(delta, &var.under_binder(x))?),
+ )?)
+ }
+}
+
+impl<T: Shift> Shift for Vec<T> {
+ fn shift(&self, delta: isize, var: &AlphaVar) -> Option<Self> {
+ Some(
+ self.iter()
+ .map(|v| Ok(v.shift(delta, var)?))
+ .collect::<Result<_, _>>()?,
+ )
+ }
+}
+
+impl<K, T: Shift> Shift for HashMap<K, T>
+where
+ K: Clone + std::hash::Hash + Eq,
+{
+ fn shift(&self, delta: isize, var: &AlphaVar) -> Option<Self> {
+ Some(
+ self.iter()
+ .map(|(k, v)| Ok((k.clone(), v.shift(delta, var)?)))
+ .collect::<Result<_, _>>()?,
+ )
+ }
+}
+
+impl<T: Shift> Shift for dhall_syntax::InterpolatedTextContents<T> {
+ fn shift(&self, delta: isize, var: &AlphaVar) -> Option<Self> {
+ use dhall_syntax::InterpolatedTextContents::{Expr, Text};
+ Some(match self {
+ Expr(x) => Expr(x.shift(delta, var)?),
+ Text(s) => Text(s.clone()),
+ })
+ }
+}
+
+impl<S> Subst<S> for () {
+ fn subst_shift(&self, _var: &AlphaVar, _val: &S) -> Self {}
+}
+
+impl<S, A: Subst<S>, B: Subst<S>> Subst<S> for (A, B) {
+ fn subst_shift(&self, var: &AlphaVar, val: &S) -> Self {
+ (self.0.subst_shift(var, val), self.1.subst_shift(var, val))
+ }
+}
+
+impl<S, T: Subst<S>> Subst<S> for Option<T> {
+ fn subst_shift(&self, var: &AlphaVar, val: &S) -> Self {
+ self.as_ref().map(|x| x.subst_shift(var, val))
+ }
+}
+
+impl<S, T: Subst<S>> Subst<S> for Box<T> {
+ fn subst_shift(&self, var: &AlphaVar, val: &S) -> Self {
+ Box::new(self.as_ref().subst_shift(var, val))
+ }
+}
+
+impl<S, T: Subst<S>> Subst<S> for std::rc::Rc<T> {
+ fn subst_shift(&self, var: &AlphaVar, val: &S) -> Self {
+ std::rc::Rc::new(self.as_ref().subst_shift(var, val))
+ }
+}
+
+impl<S, T: Subst<S>> Subst<S> for std::cell::RefCell<T> {
+ fn subst_shift(&self, var: &AlphaVar, val: &S) -> Self {
+ std::cell::RefCell::new(self.borrow().subst_shift(var, val))
+ }
+}
+
+impl<S: Shift, T: Subst<S>, E: Clone> Subst<S> for dhall_syntax::ExprF<T, E> {
+ fn subst_shift(&self, var: &AlphaVar, val: &S) -> Self {
+ self.map_ref_with_special_handling_of_binders(
+ |v| v.subst_shift(var, val),
+ |x, v| v.subst_shift(&var.under_binder(x), &val.under_binder(x)),
+ )
+ }
+}
+
+impl<S, T: Subst<S>> Subst<S> for Vec<T> {
+ fn subst_shift(&self, var: &AlphaVar, val: &S) -> Self {
+ self.iter().map(|v| v.subst_shift(var, val)).collect()
+ }
+}
+
+impl<S, T: Subst<S>> Subst<S> for dhall_syntax::InterpolatedTextContents<T> {
+ fn subst_shift(&self, var: &AlphaVar, val: &S) -> Self {
+ use dhall_syntax::InterpolatedTextContents::{Expr, Text};
+ match self {
+ Expr(x) => Expr(x.subst_shift(var, val)),
+ Text(s) => Text(s.clone()),
+ }
+ }
+}
+
+impl<S, K, T: Subst<S>> Subst<S> for HashMap<K, T>
+where
+ K: Clone + std::hash::Hash + Eq,
+{
+ fn subst_shift(&self, var: &AlphaVar, val: &S) -> Self {
+ self.iter()
+ .map(|(k, v)| (k.clone(), v.subst_shift(var, val)))
+ .collect()
+ }
+}
diff --git a/dhall/src/error/mod.rs b/dhall/src/error/mod.rs
index 3c00017..6d4e120 100644
--- a/dhall/src/error/mod.rs
+++ b/dhall/src/error/mod.rs
@@ -3,8 +3,9 @@ use std::io::Error as IOError;
use dhall_syntax::{BinOp, Import, Label, ParseError, V};
use crate::core::context::TypecheckContext;
+use crate::core::value::Value;
use crate::phase::resolve::ImportStack;
-use crate::phase::{Normalized, Type, Typed};
+use crate::phase::NormalizedExpr;
pub type Result<T> = std::result::Result<T, Error>;
@@ -17,14 +18,13 @@ pub enum Error {
Encode(EncodeError),
Resolve(ImportError),
Typecheck(TypeError),
- Deserialize(String),
}
#[derive(Debug)]
pub enum ImportError {
- Recursive(Import, Box<Error>),
- UnexpectedImport(Import),
- ImportCycle(ImportStack, Import),
+ Recursive(Import<NormalizedExpr>, Box<Error>),
+ UnexpectedImport(Import<NormalizedExpr>),
+ ImportCycle(ImportStack, Import<NormalizedExpr>),
}
#[derive(Debug)]
@@ -49,28 +49,26 @@ pub struct TypeError {
#[derive(Debug)]
pub(crate) enum TypeMessage {
UnboundVariable(V<Label>),
- InvalidInputType(Normalized),
- InvalidOutputType(Normalized),
- NotAFunction(Typed),
- TypeMismatch(Typed, Normalized, Typed),
- AnnotMismatch(Typed, Normalized),
- Untyped,
- FieldCollision(Label),
- InvalidListElement(usize, Normalized, Typed),
- InvalidListType(Normalized),
- InvalidOptionalType(Normalized),
- InvalidPredicate(Typed),
- IfBranchMismatch(Typed, Typed),
- IfBranchMustBeTerm(bool, Typed),
- InvalidFieldType(Label, Type),
- NotARecord(Label, Normalized),
- MustCombineRecord(Typed),
- MissingRecordField(Label, Typed),
- MissingUnionField(Label, Normalized),
- BinOpTypeMismatch(BinOp, Typed),
- InvalidTextInterpolation(Typed),
- Merge1ArgMustBeRecord(Typed),
- Merge2ArgMustBeUnion(Typed),
+ InvalidInputType(Value),
+ InvalidOutputType(Value),
+ NotAFunction(Value),
+ TypeMismatch(Value, Value, Value),
+ AnnotMismatch(Value, Value),
+ InvalidListElement(usize, Value, Value),
+ InvalidListType(Value),
+ InvalidOptionalType(Value),
+ InvalidPredicate(Value),
+ IfBranchMismatch(Value, Value),
+ IfBranchMustBeTerm(bool, Value),
+ InvalidFieldType(Label, Value),
+ NotARecord(Label, Value),
+ MustCombineRecord(Value),
+ MissingRecordField(Label, Value),
+ MissingUnionField(Label, Value),
+ BinOpTypeMismatch(BinOp, Value),
+ InvalidTextInterpolation(Value),
+ Merge1ArgMustBeRecord(Value),
+ Merge2ArgMustBeUnion(Value),
MergeEmptyNeedsAnnotation,
MergeHandlerMissingVariant(Label),
MergeVariantMissingHandler(Label),
@@ -80,14 +78,12 @@ pub(crate) enum TypeMessage {
ProjectionMustBeRecord,
ProjectionMissingEntry,
Sort,
- RecordMismatch(Typed, Typed),
RecordTypeDuplicateField,
- RecordTypeMergeRequiresRecordType(Type),
- RecordTypeMismatch(Type, Type, Type, Type),
+ RecordTypeMergeRequiresRecordType(Value),
UnionTypeDuplicateField,
- EquivalenceArgumentMustBeTerm(bool, Typed),
- EquivalenceTypeMismatch(Typed, Typed),
- AssertMismatch(Typed, Typed),
+ EquivalenceArgumentMustBeTerm(bool, Value),
+ EquivalenceTypeMismatch(Value, Value),
+ AssertMismatch(Value, Value),
AssertMustTakeEquivalence,
}
@@ -156,7 +152,6 @@ impl std::fmt::Display for Error {
Error::Encode(err) => write!(f, "{:?}", err),
Error::Resolve(err) => write!(f, "{:?}", err),
Error::Typecheck(err) => write!(f, "{:?}", err),
- Error::Deserialize(err) => write!(f, "{}", err),
}
}
}
diff --git a/dhall/src/lib.rs b/dhall/src/lib.rs
index ea29869..0a430e4 100644
--- a/dhall/src/lib.rs
+++ b/dhall/src/lib.rs
@@ -1,13 +1,7 @@
#![feature(trace_macros)]
-#![feature(proc_macro_hygiene)]
#![feature(slice_patterns)]
-#![feature(label_break_value)]
#![feature(non_exhaustive)]
-#![feature(bind_by_move_pattern_guards)]
-#![feature(try_trait)]
-#![feature(inner_deref)]
#![feature(never_type)]
-#![cfg_attr(test, feature(custom_inner_attributes))]
#![allow(
clippy::type_complexity,
clippy::infallible_destructuring_match,
@@ -17,118 +11,9 @@
clippy::ptr_arg
)]
-//! [Dhall][dhall] is a programmable configuration language that provides a non-repetitive
-//! alternative to JSON and YAML.
-//!
-//! You can think of Dhall as: JSON + types + imports + functions
-//!
-//! For a description of the dhall language, examples, tutorials, and more, see the [language
-//! website][dhall].
-//!
-//! This crate provides support for consuming dhall files the same way you would consume JSON or
-//! YAML. It uses the [Serde][serde] serialization library to provide drop-in support for dhall
-//! for any datatype that supports serde (and that's a lot of them !).
-//!
-//! This library is limited to deserializing (reading) dhall values; serializing (writing)
-//! values to dhall is not supported for now.
-//!
-//! # Examples
-//!
-//! ### Custom datatype
-//!
-//! If you have a custom datatype for which you derived [serde::Deserialize], chances are
-//! you will be able to derive [StaticType][de::StaticType] for it as well.
-//! This gives you access to a dhall representation of your datatype that can be outputted
-//! to users, and allows easy type-safe deserializing.
-//!
-//! ```edition2018
-//! use serde::Deserialize;
-//! use dhall::de::StaticType;
-//!
-//! #[derive(Debug, Deserialize, StaticType)]
-//! struct Point {
-//! x: u64,
-//! y: u64,
-//! }
-//!
-//! fn main() {
-//! // Some dhall data
-//! let data = "{ x = 1, y = 1 + 1 }";
-//!
-//! // Convert the dhall string to a Point.
-//! let point: Point =
-//! dhall::de::from_str_auto_type(&data)
-//! .expect("An error ocurred !");
-//!
-//! // Prints "point = Point { x: 1, y: 2 }"
-//! println!("point = {:?}", point);
-//! }
-//! ```
-//!
-//! ### Loosely typed
-//!
-//! If you used to consume JSON or YAML in a loosely typed way, you can continue to do so
-//! with dhall. You only need to replace [serde_json::from_str] or [serde_yaml::from_str]
-//! with [dhall::de::from_str][de::from_str].
-//! More generally, if the [StaticType][de::StaticType] derive doesn't suit your
-//! needs, you can still deserialize any valid dhall file that serde can handle.
-//!
-//! [serde_json::from_str]: https://docs.serde.rs/serde_json/de/fn.from_str.html
-//! [serde_yaml::from_str]: https://docs.serde.rs/serde_yaml/fn.from_str.html
-//!
-//! ```edition2018
-//! use std::collections::BTreeMap;
-//!
-//! let mut map = BTreeMap::new();
-//! map.insert("x".to_string(), 1);
-//! map.insert("y".to_string(), 2);
-//!
-//! // Some dhall data
-//! let data = "{ x = 1, y = 1 + 1 } : { x: Natural, y: Natural }";
-//!
-//! // Deserialize it to a Rust type.
-//! let deserialized_map: BTreeMap<String, usize> =
-//! dhall::de::from_str(&data, None)
-//! .expect("Failed reading the data !");
-//! assert_eq!(map, deserialized_map);
-//! ```
-//!
-//! You can of course specify a dhall type that the input should match.
-//!
-//! ```edition2018
-//! use std::collections::BTreeMap;
-//!
-//! let mut map = BTreeMap::new();
-//! map.insert("x".to_string(), 1);
-//! map.insert("y".to_string(), 2);
-//!
-//! // Some dhall data
-//! let point_data = "{ x = 1, y = 1 + 1 }";
-//! let point_type_data = "{ x: Natural, y: Natural }";
-//!
-//! // Construct a type
-//! let point_type =
-//! dhall::de::from_str(point_type_data, None)
-//! .expect("Could not parse the Point type");
-//!
-//! // Deserialize it to a Rust type.
-//! let deserialized_map: BTreeMap<String, usize> =
-//! dhall::de::from_str(&point_data, Some(&point_type))
-//! .expect("Failed reading the data !");
-//! assert_eq!(map, deserialized_map);
-//! ```
-//!
-//! [dhall]: https://dhall-lang.org/
-//! [serde]: https://docs.serde.rs/serde/
-//! [serde::Deserialize]: https://docs.serde.rs/serde/trait.Deserialize.html
-
-#[cfg(test)]
#[macro_use]
mod tests;
-pub(crate) mod api;
-pub(crate) mod core;
+pub mod core;
pub mod error;
-pub(crate) mod phase;
-
-pub use api::*;
+pub mod phase;
diff --git a/dhall/src/phase/.resolve.rs.swp b/dhall/src/phase/.resolve.rs.swp
new file mode 100644
index 0000000..5314300
--- /dev/null
+++ b/dhall/src/phase/.resolve.rs.swp
Binary files differ
diff --git a/dhall/src/phase/binary.rs b/dhall/src/phase/binary.rs
index 89b6db2..16e7ce9 100644
--- a/dhall/src/phase/binary.rs
+++ b/dhall/src/phase/binary.rs
@@ -5,30 +5,26 @@ use std::vec;
use dhall_syntax::map::DupTreeMap;
use dhall_syntax::{
- rc, ExprF, FilePrefix, Hash, Import, ImportHashed, ImportLocation,
- ImportMode, Integer, InterpolatedText, Label, Natural, Scheme, SubExpr,
- URL, V, File,
+ rc, Expr, ExprF, FilePrefix, Hash, Import, ImportLocation, ImportMode,
+ Integer, InterpolatedText, Label, Natural, Scheme, URL, V, File
};
use crate::error::{DecodeError, EncodeError};
-use crate::phase::{DecodedSubExpr, ParsedSubExpr};
+use crate::phase::DecodedExpr;
-pub fn decode(data: &[u8]) -> Result<DecodedSubExpr, DecodeError> {
+pub(crate) fn decode(data: &[u8]) -> Result<DecodedExpr, DecodeError> {
match serde_cbor::de::from_slice(data) {
Ok(v) => cbor_value_to_dhall(&v),
Err(e) => Err(DecodeError::CBORError(e)),
}
}
-//TODO: encode normalized expression too
-pub fn encode(expr: &ParsedSubExpr) -> Result<Vec<u8>, EncodeError> {
+pub(crate) fn encode<E>(expr: &Expr<E>) -> Result<Vec<u8>, EncodeError> {
serde_cbor::ser::to_vec(&Serialize::Expr(expr))
.map_err(|e| EncodeError::CBORError(e))
}
-fn cbor_value_to_dhall(
- data: &cbor::Value,
-) -> Result<DecodedSubExpr, DecodeError> {
+fn cbor_value_to_dhall(data: &cbor::Value) -> Result<DecodedExpr, DecodeError> {
use cbor::Value::*;
use dhall_syntax::{BinOp, Builtin, Const};
use ExprF::*;
@@ -262,20 +258,12 @@ fn cbor_value_to_dhall(
};
let headers = match rest.next() {
Some(Null) => None,
- // TODO
- // 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(),
- // ))?,
- // }
- // }
+ Some(x) => {
+ let x = cbor_value_to_dhall(&x)?;
+ Some(x)
+ }
_ => Err(DecodeError::WrongFormatError(
- "import/remote/headers is unimplemented"
- .to_owned(),
+ "import/remote/headers".to_owned(),
))?,
};
let authority = match rest.next() {
@@ -343,9 +331,10 @@ fn cbor_value_to_dhall(
"import/type".to_owned(),
))?,
};
- Embed(Import {
+ Import(dhall_syntax::Import {
mode,
- location_hashed: ImportHashed { hash, location },
+ hash,
+ location,
})
}
[U64(25), bindings..] => {
@@ -380,6 +369,15 @@ fn cbor_value_to_dhall(
let y = cbor_value_to_dhall(&y)?;
Annot(x, y)
}
+ [U64(27), x] => {
+ let x = cbor_value_to_dhall(&x)?;
+ ToMap(x, None)
+ }
+ [U64(27), x, y] => {
+ let x = cbor_value_to_dhall(&x)?;
+ let y = cbor_value_to_dhall(&y)?;
+ ToMap(x, Some(y))
+ }
[U64(28), x] => {
let x = cbor_value_to_dhall(&x)?;
EmptyListLit(x)
@@ -394,7 +392,7 @@ fn cbor_map_to_dhall_map<'a, T>(
map: impl IntoIterator<Item = (&'a cbor::ObjectKey, &'a cbor::Value)>,
) -> Result<T, DecodeError>
where
- T: FromIterator<(Label, DecodedSubExpr)>,
+ T: FromIterator<(Label, DecodedExpr)>,
{
map.into_iter()
.map(|(k, v)| -> Result<(_, _), _> {
@@ -411,7 +409,7 @@ fn cbor_map_to_dhall_opt_map<'a, T>(
map: impl IntoIterator<Item = (&'a cbor::ObjectKey, &'a cbor::Value)>,
) -> Result<T, DecodeError>
where
- T: FromIterator<(Label, Option<DecodedSubExpr>)>,
+ T: FromIterator<(Label, Option<DecodedExpr>)>,
{
map.into_iter()
.map(|(k, v)| -> Result<(_, _), _> {
@@ -427,11 +425,11 @@ where
.collect::<Result<_, _>>()
}
-enum Serialize<'a> {
- Expr(&'a ParsedSubExpr),
+enum Serialize<'a, E> {
+ Expr(&'a Expr<E>),
CBOR(cbor::Value),
- RecordMap(&'a DupTreeMap<Label, ParsedSubExpr>),
- UnionMap(&'a DupTreeMap<Label, Option<ParsedSubExpr>>),
+ RecordMap(&'a DupTreeMap<Label, Expr<E>>),
+ UnionMap(&'a DupTreeMap<Label, Option<Expr<E>>>),
}
macro_rules! count {
@@ -451,7 +449,7 @@ macro_rules! ser_seq {
}};
}
-fn serialize_subexpr<S>(ser: S, e: &ParsedSubExpr) -> Result<S::Ok, S::Error>
+fn serialize_subexpr<S, E>(ser: S, e: &Expr<E>) -> Result<S::Ok, S::Error>
where
S: serde::ser::Serializer,
{
@@ -461,21 +459,14 @@ where
use std::iter::once;
use self::Serialize::{RecordMap, UnionMap};
- fn expr(x: &ParsedSubExpr) -> self::Serialize<'_> {
+ fn expr<E>(x: &Expr<E>) -> self::Serialize<'_, E> {
self::Serialize::Expr(x)
}
- fn cbor<'a>(v: cbor::Value) -> self::Serialize<'a> {
- self::Serialize::CBOR(v)
- }
- fn tag<'a>(x: u64) -> self::Serialize<'a> {
- cbor(U64(x))
- }
- fn null<'a>() -> self::Serialize<'a> {
- cbor(cbor::Value::Null)
- }
- fn label<'a>(l: &Label) -> self::Serialize<'a> {
- cbor(cbor::Value::String(l.into()))
- }
+ let cbor =
+ |v: cbor::Value| -> self::Serialize<'_, E> { self::Serialize::CBOR(v) };
+ let tag = |x: u64| cbor(U64(x));
+ let null = || cbor(cbor::Value::Null);
+ let label = |l: &Label| cbor(cbor::Value::String(l.into()));
match e.as_ref() {
Const(c) => ser.serialize_str(&c.to_string()),
@@ -571,25 +562,33 @@ where
Merge(x, y, Some(z)) => {
ser_seq!(ser; tag(6), expr(x), expr(y), expr(z))
}
+ ToMap(x, None) => ser_seq!(ser; tag(27), expr(x)),
+ ToMap(x, Some(y)) => ser_seq!(ser; tag(27), expr(x), expr(y)),
Projection(x, ls) => ser.collect_seq(
once(tag(10))
.chain(once(expr(x)))
.chain(ls.iter().map(label)),
),
- Embed(import) => serialize_import(ser, import),
+ Import(import) => serialize_import(ser, import),
+ Embed(_) => unimplemented!(
+ "An expression with resolved imports cannot be binary-encoded"
+ ),
}
}
-fn serialize_import<S>(ser: S, import: &Import) -> Result<S::Ok, S::Error>
+fn serialize_import<S, E>(
+ ser: S,
+ import: &Import<Expr<E>>,
+) -> Result<S::Ok, S::Error>
where
S: serde::ser::Serializer,
{
use cbor::Value::{Bytes, Null, U64};
use serde::ser::SerializeSeq;
- let count = 4 + match &import.location_hashed.location {
- ImportLocation::Remote(url) => 3 + url.path.clone().into_iter().len(),
- ImportLocation::Local(_, path) => path.clone().into_iter().len(),
+ let count = 4 + match &import.location {
+ ImportLocation::Remote(url) => 3 + url.path.file_path.len(),
+ ImportLocation::Local(_, path) => path.file_path.len(),
ImportLocation::Env(_) => 1,
ImportLocation::Missing => 0,
};
@@ -597,7 +596,7 @@ where
ser_seq.serialize_element(&U64(24))?;
- let hash = match &import.location_hashed.hash {
+ let hash = match &import.hash {
None => Null,
Some(Hash::SHA256(h)) => {
let mut bytes = vec![18, 32];
@@ -614,7 +613,7 @@ where
};
ser_seq.serialize_element(&U64(mode))?;
- let scheme = match &import.location_hashed.location {
+ let scheme = match &import.location {
ImportLocation::Remote(url) => match url.scheme {
Scheme::HTTP => 0,
Scheme::HTTPS => 1,
@@ -630,21 +629,16 @@ where
};
ser_seq.serialize_element(&U64(scheme))?;
- match &import.location_hashed.location {
+ match &import.location {
ImportLocation::Remote(url) => {
match &url.headers {
None => ser_seq.serialize_element(&Null)?,
- Some(location_hashed) => {
- ser_seq.serialize_element(&self::Serialize::Expr(
- &SubExpr::from_expr_no_note(ExprF::Embed(Import {
- mode: ImportMode::Code,
- location_hashed: location_hashed.as_ref().clone(),
- })),
- ))?
+ Some(e) => {
+ ser_seq.serialize_element(&self::Serialize::Expr(e))?
}
};
ser_seq.serialize_element(&url.authority)?;
- for p in url.path.clone().into_iter() {
+ for p in url.path.file_path.iter() {
ser_seq.serialize_element(&p)?;
}
match &url.query {
@@ -653,7 +647,7 @@ where
};
}
ImportLocation::Local(_, path) => {
- for p in path.clone().into_iter() {
+ for p in path.file_path.iter() {
ser_seq.serialize_element(&p)?;
}
}
@@ -666,7 +660,7 @@ where
ser_seq.end()
}
-impl<'a> serde::ser::Serialize for Serialize<'a> {
+impl<'a, E> serde::ser::Serialize for Serialize<'a, E> {
fn serialize<S>(&self, ser: S) -> Result<S::Ok, S::Error>
where
S: serde::ser::Serializer,
@@ -692,13 +686,10 @@ impl<'a> serde::ser::Serialize for Serialize<'a> {
}
}
-fn collect_nested_applications<'a, N, E>(
- e: &'a SubExpr<N, E>,
-) -> (&'a SubExpr<N, E>, Vec<&'a SubExpr<N, E>>) {
- fn go<'a, N, E>(
- e: &'a SubExpr<N, E>,
- vec: &mut Vec<&'a SubExpr<N, E>>,
- ) -> &'a SubExpr<N, E> {
+fn collect_nested_applications<'a, E>(
+ e: &'a Expr<E>,
+) -> (&'a Expr<E>, Vec<&'a Expr<E>>) {
+ fn go<'a, E>(e: &'a Expr<E>, vec: &mut Vec<&'a Expr<E>>) -> &'a Expr<E> {
match e.as_ref() {
ExprF::App(f, a) => {
vec.push(a);
@@ -712,16 +703,15 @@ fn collect_nested_applications<'a, N, E>(
(e, vec)
}
-type LetBinding<'a, N, E> =
- (&'a Label, &'a Option<SubExpr<N, E>>, &'a SubExpr<N, E>);
+type LetBinding<'a, E> = (&'a Label, &'a Option<Expr<E>>, &'a Expr<E>);
-fn collect_nested_lets<'a, N, E>(
- e: &'a SubExpr<N, E>,
-) -> (&'a SubExpr<N, E>, Vec<LetBinding<'a, N, E>>) {
- fn go<'a, N, E>(
- e: &'a SubExpr<N, E>,
- vec: &mut Vec<LetBinding<'a, N, E>>,
- ) -> &'a SubExpr<N, E> {
+fn collect_nested_lets<'a, E>(
+ e: &'a Expr<E>,
+) -> (&'a Expr<E>, Vec<LetBinding<'a, E>>) {
+ fn go<'a, E>(
+ e: &'a Expr<E>,
+ vec: &mut Vec<LetBinding<'a, E>>,
+ ) -> &'a Expr<E> {
match e.as_ref() {
ExprF::Let(l, t, v, e) => {
vec.push((l, t, v));
diff --git a/dhall/src/phase/mod.rs b/dhall/src/phase/mod.rs
index ccedff2..2c5505c 100644
--- a/dhall/src/phase/mod.rs
+++ b/dhall/src/phase/mod.rs
@@ -1,17 +1,14 @@
-use std::borrow::Cow;
use std::fmt::Display;
use std::path::Path;
-use dhall_syntax::{Const, Import, Span, SubExpr, X};
+use dhall_syntax::{Builtin, Const, Expr};
-use crate::core::context::TypecheckContext;
-use crate::core::thunk::Thunk;
-use crate::core::value::Value;
+use crate::core::value::{ToExprOptions, Value};
+use crate::core::valuef::ValueF;
use crate::core::var::{AlphaVar, Shift, Subst};
-use crate::error::{EncodeError, Error, ImportError, TypeError, TypeMessage};
+use crate::error::{EncodeError, Error, ImportError, TypeError};
use resolve::ImportRoot;
-use typecheck::type_of_const;
pub(crate) mod binary;
pub(crate) mod normalize;
@@ -19,30 +16,23 @@ pub(crate) mod parse;
pub(crate) mod resolve;
pub(crate) mod typecheck;
-pub type ParsedSubExpr = SubExpr<Span, Import>;
-pub type DecodedSubExpr = SubExpr<X, Import>;
-pub type ResolvedSubExpr = SubExpr<Span, Normalized>;
-pub type NormalizedSubExpr = SubExpr<X, X>;
+pub type ParsedExpr = Expr<!>;
+pub type DecodedExpr = Expr<!>;
+pub type ResolvedExpr = Expr<Normalized>;
+pub type NormalizedExpr = Expr<Normalized>;
#[derive(Debug, Clone)]
-pub struct Parsed(ParsedSubExpr, ImportRoot);
+pub struct Parsed(ParsedExpr, ImportRoot);
/// An expression where all imports have been resolved
+///
+/// Invariant: there must be no `Import` nodes or `ImportAlt` operations left.
#[derive(Debug, Clone)]
-pub struct Resolved(ResolvedSubExpr);
+pub struct Resolved(ResolvedExpr);
/// A typed expression
#[derive(Debug, Clone)]
-pub enum Typed {
- // Any value, along with (optionally) its type
- Untyped(Thunk),
- Typed(Thunk, Box<Type>),
- // One of the base higher-kinded typed.
- // Used to avoid storing the same tower ot Type->Kind->Sort
- // over and over again. Also enables having Sort as a type
- // even though it doesn't itself have a type.
- Const(Const),
-}
+pub struct Typed(Value);
/// A normalized expression.
///
@@ -50,8 +40,6 @@ pub enum Typed {
#[derive(Debug, Clone)]
pub struct Normalized(Typed);
-pub type Type = Typed;
-
impl Parsed {
pub fn parse_file(f: &Path) -> Result<Parsed, Error> {
parse::parse_file(f)
@@ -59,11 +47,9 @@ impl Parsed {
pub fn parse_str(s: &str) -> Result<Parsed, Error> {
parse::parse_str(s)
}
- #[allow(dead_code)]
pub fn parse_binary_file(f: &Path) -> Result<Parsed, Error> {
parse::parse_binary_file(f)
}
- #[allow(dead_code)]
pub fn parse_binary(data: &[u8]) -> Result<Parsed, Error> {
parse::parse_binary(data)
}
@@ -71,12 +57,10 @@ impl Parsed {
pub fn resolve(self) -> Result<Resolved, ImportError> {
resolve::resolve(self)
}
- #[allow(dead_code)]
pub fn skip_resolve(self) -> Result<Resolved, ImportError> {
resolve::skip_resolve_expr(self)
}
- #[allow(dead_code)]
pub fn encode(&self) -> Result<Vec<u8>, EncodeError> {
crate::phase::binary::encode(&self.0)
}
@@ -84,15 +68,11 @@ impl Parsed {
impl Resolved {
pub fn typecheck(self) -> Result<Typed, TypeError> {
- typecheck::typecheck(self)
+ Ok(typecheck::typecheck(self.0)?.into_typed())
}
- pub fn typecheck_with(self, ty: &Type) -> Result<Typed, TypeError> {
- 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)
+ pub fn typecheck_with(self, ty: &Typed) -> Result<Typed, TypeError> {
+ Ok(typecheck::typecheck_with(self.0, ty.normalize_to_expr())?
+ .into_typed())
}
}
@@ -105,117 +85,110 @@ impl Typed {
///
/// 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::Const(_) => {}
- Typed::Untyped(thunk) | Typed::Typed(thunk, _) => {
- thunk.normalize_nf();
- }
- }
+ pub fn normalize(mut self) -> Normalized {
+ self.normalize_mut();
Normalized(self)
}
- pub fn from_thunk_and_type(th: Thunk, t: Type) -> Self {
- Typed::Typed(th, Box::new(t))
+ pub(crate) fn from_const(c: Const) -> Self {
+ Typed(Value::from_const(c))
}
- pub fn from_thunk_untyped(th: Thunk) -> Self {
- Typed::Untyped(th)
+ pub(crate) fn from_valuef_and_type(v: ValueF, t: Typed) -> Self {
+ Typed(Value::from_valuef_and_type(v, t.into_value()))
}
- pub fn from_const(c: Const) -> Self {
- Typed::Const(c)
+ pub(crate) fn from_value(th: Value) -> Self {
+ Typed(th)
}
- pub fn from_value_untyped(v: Value) -> Self {
- Typed::from_thunk_untyped(Thunk::from_value(v))
+ pub(crate) fn const_type() -> Self {
+ Typed::from_const(Const::Type)
}
- // TODO: Avoid cloning if possible
- pub fn to_value(&self) -> Value {
- match self {
- Typed::Untyped(th) | Typed::Typed(th, _) => th.to_value(),
- Typed::Const(c) => Value::Const(*c),
- }
- }
- pub fn to_expr(&self) -> NormalizedSubExpr {
- self.to_value().normalize_to_expr()
- }
- pub fn to_expr_alpha(&self) -> NormalizedSubExpr {
- self.to_value().normalize_to_expr_maybe_alpha(true)
+ pub(crate) fn to_expr(&self) -> NormalizedExpr {
+ self.0.to_expr(ToExprOptions {
+ alpha: false,
+ normalize: false,
+ })
}
- pub fn to_thunk(&self) -> Thunk {
- match self {
- Typed::Untyped(th) | Typed::Typed(th, _) => th.clone(),
- Typed::Const(c) => Thunk::from_value(Value::Const(*c)),
- }
+ pub fn normalize_to_expr(&self) -> NormalizedExpr {
+ self.0.to_expr(ToExprOptions {
+ alpha: false,
+ normalize: true,
+ })
}
- // Deprecated
- pub fn to_type(&self) -> Type {
- self.clone().into_type()
+ pub(crate) fn normalize_to_expr_alpha(&self) -> NormalizedExpr {
+ self.0.to_expr(ToExprOptions {
+ alpha: true,
+ normalize: true,
+ })
}
- // Deprecated
- pub fn into_type(self) -> Type {
- self
+ pub(crate) fn to_value(&self) -> Value {
+ self.0.clone()
}
- pub fn to_normalized(&self) -> Normalized {
- self.clone().normalize()
+ pub(crate) fn into_value(self) -> Value {
+ self.0
}
- pub fn as_const(&self) -> Option<Const> {
- // TODO: avoid clone
- match &self.to_value() {
- Value::Const(c) => Some(*c),
- _ => None,
- }
+
+ pub(crate) fn normalize_mut(&mut self) {
+ self.0.normalize_mut()
}
- pub fn normalize_mut(&mut self) {
- match self {
- Typed::Untyped(th) | Typed::Typed(th, _) => th.normalize_mut(),
- Typed::Const(_) => {}
- }
+ pub(crate) fn get_type(&self) -> Result<Typed, TypeError> {
+ Ok(self.0.get_type()?.into_typed())
}
- pub fn get_type(&self) -> Result<Cow<'_, Type>, TypeError> {
- match self {
- Typed::Untyped(_) => Err(TypeError::new(
- &TypecheckContext::new(),
- TypeMessage::Untyped,
- )),
- Typed::Typed(_, t) => Ok(Cow::Borrowed(t)),
- Typed::Const(c) => Ok(Cow::Owned(type_of_const(*c)?)),
- }
+ pub fn make_builtin_type(b: Builtin) -> Self {
+ Typed::from_value(Value::from_builtin(b))
+ }
+ pub fn make_optional_type(t: Typed) -> Self {
+ Typed::from_value(
+ Value::from_builtin(Builtin::Optional).app(t.to_value()),
+ )
+ }
+ pub fn make_list_type(t: Typed) -> Self {
+ Typed::from_value(Value::from_builtin(Builtin::List).app(t.to_value()))
+ }
+ pub fn make_record_type(
+ kts: impl Iterator<Item = (String, Typed)>,
+ ) -> Self {
+ Typed::from_valuef_and_type(
+ ValueF::RecordType(
+ kts.map(|(k, t)| (k.into(), t.into_value())).collect(),
+ ),
+ Typed::const_type(),
+ )
+ }
+ pub fn make_union_type(
+ kts: impl Iterator<Item = (String, Option<Typed>)>,
+ ) -> Self {
+ Typed::from_valuef_and_type(
+ ValueF::UnionType(
+ kts.map(|(k, t)| (k.into(), t.map(|t| t.into_value())))
+ .collect(),
+ ),
+ Typed::const_type(),
+ )
}
}
impl Normalized {
- #[allow(dead_code)]
- pub fn to_expr(&self) -> NormalizedSubExpr {
- self.0.to_expr()
- }
- #[allow(dead_code)]
- pub fn to_expr_alpha(&self) -> NormalizedSubExpr {
- self.0.to_expr_alpha()
+ pub fn encode(&self) -> Result<Vec<u8>, EncodeError> {
+ crate::phase::binary::encode(&self.to_expr())
}
- #[allow(dead_code)]
- pub fn to_type(&self) -> Type {
- self.0.to_type()
+
+ pub(crate) fn to_expr(&self) -> NormalizedExpr {
+ self.0.normalize_to_expr()
}
- pub fn to_value(&self) -> Value {
- self.0.to_value()
+ pub(crate) fn to_expr_alpha(&self) -> NormalizedExpr {
+ self.0.normalize_to_expr_alpha()
}
- pub fn into_typed(self) -> Typed {
+ pub(crate) fn into_typed(self) -> Typed {
self.0
}
}
impl Shift for Typed {
fn shift(&self, delta: isize, var: &AlphaVar) -> Option<Self> {
- Some(match self {
- Typed::Untyped(th) => Typed::Untyped(th.shift(delta, var)?),
- Typed::Typed(th, t) => Typed::Typed(
- th.shift(delta, var)?,
- Box::new(t.shift(delta, var)?),
- ),
- Typed::Const(c) => Typed::Const(*c),
- })
+ Some(Typed(self.0.shift(delta, var)?))
}
}
@@ -225,16 +198,9 @@ impl Shift for Normalized {
}
}
-impl Subst<Typed> for Typed {
- fn subst_shift(&self, var: &AlphaVar, val: &Typed) -> Self {
- match self {
- Typed::Untyped(th) => Typed::Untyped(th.subst_shift(var, val)),
- Typed::Typed(th, t) => Typed::Typed(
- th.subst_shift(var, val),
- Box::new(t.subst_shift(var, val)),
- ),
- Typed::Const(c) => Typed::Const(*c),
- }
+impl Subst<Value> for Typed {
+ fn subst_shift(&self, var: &AlphaVar, val: &Value) -> Self {
+ Typed(self.0.subst_shift(var, val))
}
}
@@ -263,10 +229,21 @@ derive_traits_for_wrapper_struct!(Parsed);
derive_traits_for_wrapper_struct!(Resolved);
derive_traits_for_wrapper_struct!(Normalized);
+impl std::hash::Hash for Normalized {
+ fn hash<H>(&self, state: &mut H)
+ where
+ H: std::hash::Hasher,
+ {
+ if let Ok(vec) = self.encode() {
+ vec.hash(state)
+ }
+ }
+}
+
impl Eq for Typed {}
impl PartialEq for Typed {
fn eq(&self, other: &Self) -> bool {
- self.to_value() == other.to_value()
+ self.0 == other.0
}
}
diff --git a/dhall/src/phase/normalize.rs b/dhall/src/phase/normalize.rs
index 405677a..3f6e99c 100644
--- a/dhall/src/phase/normalize.rs
+++ b/dhall/src/phase/normalize.rs
@@ -1,348 +1,431 @@
use std::collections::HashMap;
+use dhall_syntax::Const::Type;
use dhall_syntax::{
- BinOp, Builtin, ExprF, InterpolatedText, InterpolatedTextContents,
- NaiveDouble, X,
+ BinOp, Builtin, ExprF, InterpolatedText, InterpolatedTextContents, Label,
+ NaiveDouble,
};
-use crate::core::context::NormalizationContext;
-use crate::core::thunk::{Thunk, TypeThunk};
use crate::core::value::Value;
-use crate::core::var::Subst;
-use crate::phase::{NormalizedSubExpr, ResolvedSubExpr, Typed};
-
-pub type InputSubExpr = ResolvedSubExpr;
-pub type OutputSubExpr = NormalizedSubExpr;
+use crate::core::valuef::ValueF;
+use crate::core::var::{AlphaLabel, Shift, Subst};
+use crate::phase::Normalized;
+
+// Ad-hoc macro to help construct closures
+macro_rules! make_closure {
+ (#$var:ident) => { $var.clone() };
+ (var($var:ident, $n:expr, $($ty:tt)*)) => {{
+ let var = crate::core::var::AlphaVar::from_var_and_alpha(
+ Label::from(stringify!($var)).into(),
+ $n
+ );
+ ValueF::Var(var)
+ .into_value_with_type(make_closure!($($ty)*))
+ }};
+ // Warning: assumes that $ty, as a dhall value, has type `Type`
+ (λ($var:ident : $($ty:tt)*) -> $($body:tt)*) => {{
+ let var: AlphaLabel = Label::from(stringify!($var)).into();
+ let ty = make_closure!($($ty)*);
+ let body = make_closure!($($body)*);
+ let body_ty = body.get_type_not_sort();
+ let lam_ty = ValueF::Pi(var.clone(), ty.clone(), body_ty)
+ .into_value_with_type(Value::from_const(Type));
+ ValueF::Lam(var, ty, body).into_value_with_type(lam_ty)
+ }};
+ (Natural) => {
+ Value::from_builtin(Builtin::Natural)
+ };
+ (List $($rest:tt)*) => {
+ Value::from_builtin(Builtin::List)
+ .app(make_closure!($($rest)*))
+ };
+ (Some($($rest:tt)*)) => {{
+ let v = make_closure!($($rest)*);
+ let v_type = v.get_type_not_sort();
+ let opt_v_type = Value::from_builtin(Builtin::Optional).app(v_type);
+ ValueF::NEOptionalLit(v).into_value_with_type(opt_v_type)
+ }};
+ (1 + $($rest:tt)*) => {
+ ValueF::PartialExpr(ExprF::BinOp(
+ dhall_syntax::BinOp::NaturalPlus,
+ make_closure!($($rest)*),
+ Value::from_valuef_and_type(
+ ValueF::NaturalLit(1),
+ make_closure!(Natural)
+ ),
+ )).into_value_with_type(
+ make_closure!(Natural)
+ )
+ };
+ ([ $($head:tt)* ] # $($tail:tt)*) => {{
+ let head = make_closure!($($head)*);
+ let tail = make_closure!($($tail)*);
+ let list_type = tail.get_type_not_sort();
+ ValueF::PartialExpr(ExprF::BinOp(
+ dhall_syntax::BinOp::ListAppend,
+ ValueF::NEListLit(vec![head])
+ .into_value_with_type(list_type.clone()),
+ tail,
+ )).into_value_with_type(list_type)
+ }};
+}
#[allow(clippy::cognitive_complexity)]
-pub fn apply_builtin(b: Builtin, args: Vec<Thunk>) -> Value {
+pub(crate) fn apply_builtin(
+ b: Builtin,
+ args: Vec<Value>,
+ ty: &Value,
+) -> ValueF {
use dhall_syntax::Builtin::*;
- use Value::*;
+ use ValueF::*;
+
+ // Small helper enum
+ enum Ret<'a> {
+ ValueF(ValueF),
+ Value(Value),
+ // For applications that can return a function, it's important to keep the remaining
+ // arguments to apply them to the resulting function.
+ ValueWithRemainingArgs(&'a [Value], Value),
+ DoneAsIs,
+ }
- // Return Ok((unconsumed args, returned value)), or Err(()) if value could not be produced.
let ret = match (b, args.as_slice()) {
- (OptionalNone, [t, r..]) => {
- Ok((r, EmptyOptionalLit(TypeThunk::from_thunk(t.clone()))))
- }
- (NaturalIsZero, [n, r..]) => match &*n.as_value() {
- NaturalLit(n) => Ok((r, BoolLit(*n == 0))),
- _ => Err(()),
+ (OptionalNone, [t]) => Ret::ValueF(EmptyOptionalLit(t.clone())),
+ (NaturalIsZero, [n]) => match &*n.as_whnf() {
+ NaturalLit(n) => Ret::ValueF(BoolLit(*n == 0)),
+ _ => Ret::DoneAsIs,
},
- (NaturalEven, [n, r..]) => match &*n.as_value() {
- NaturalLit(n) => Ok((r, BoolLit(*n % 2 == 0))),
- _ => Err(()),
+ (NaturalEven, [n]) => match &*n.as_whnf() {
+ NaturalLit(n) => Ret::ValueF(BoolLit(*n % 2 == 0)),
+ _ => Ret::DoneAsIs,
},
- (NaturalOdd, [n, r..]) => match &*n.as_value() {
- NaturalLit(n) => Ok((r, BoolLit(*n % 2 != 0))),
- _ => Err(()),
+ (NaturalOdd, [n]) => match &*n.as_whnf() {
+ NaturalLit(n) => Ret::ValueF(BoolLit(*n % 2 != 0)),
+ _ => Ret::DoneAsIs,
},
- (NaturalToInteger, [n, r..]) => match &*n.as_value() {
- NaturalLit(n) => Ok((r, IntegerLit(*n as isize))),
- _ => Err(()),
+ (NaturalToInteger, [n]) => match &*n.as_whnf() {
+ NaturalLit(n) => Ret::ValueF(IntegerLit(*n as isize)),
+ _ => Ret::DoneAsIs,
},
- (NaturalShow, [n, r..]) => match &*n.as_value() {
- NaturalLit(n) => Ok((
- r,
- TextLit(vec![InterpolatedTextContents::Text(n.to_string())]),
- )),
- _ => Err(()),
+ (NaturalShow, [n]) => match &*n.as_whnf() {
+ NaturalLit(n) => {
+ Ret::ValueF(TextLit(vec![InterpolatedTextContents::Text(
+ n.to_string(),
+ )]))
+ }
+ _ => Ret::DoneAsIs,
},
- (NaturalSubtract, [a, b, r..]) => {
- match (&*a.as_value(), &*b.as_value()) {
- (NaturalLit(a), NaturalLit(b)) => {
- Ok((r, NaturalLit(if b > a { b - a } else { 0 })))
- }
- (NaturalLit(0), b) => Ok((r, b.clone())),
- (_, NaturalLit(0)) => Ok((r, NaturalLit(0))),
- _ if a == b => Ok((r, NaturalLit(0))),
- _ => Err(()),
+ (NaturalSubtract, [a, b]) => match (&*a.as_whnf(), &*b.as_whnf()) {
+ (NaturalLit(a), NaturalLit(b)) => {
+ Ret::ValueF(NaturalLit(if b > a { b - a } else { 0 }))
}
- }
- (IntegerShow, [n, r..]) => match &*n.as_value() {
+ (NaturalLit(0), _) => Ret::Value(b.clone()),
+ (_, NaturalLit(0)) => Ret::ValueF(NaturalLit(0)),
+ _ if a == b => Ret::ValueF(NaturalLit(0)),
+ _ => Ret::DoneAsIs,
+ },
+ (IntegerShow, [n]) => match &*n.as_whnf() {
IntegerLit(n) => {
let s = if *n < 0 {
n.to_string()
} else {
format!("+{}", n)
};
- Ok((r, TextLit(vec![InterpolatedTextContents::Text(s)])))
+ Ret::ValueF(TextLit(vec![InterpolatedTextContents::Text(s)]))
}
- _ => Err(()),
+ _ => Ret::DoneAsIs,
},
- (IntegerToDouble, [n, r..]) => match &*n.as_value() {
- IntegerLit(n) => Ok((r, DoubleLit(NaiveDouble::from(*n as f64)))),
- _ => Err(()),
+ (IntegerToDouble, [n]) => match &*n.as_whnf() {
+ IntegerLit(n) => {
+ Ret::ValueF(DoubleLit(NaiveDouble::from(*n as f64)))
+ }
+ _ => Ret::DoneAsIs,
},
- (DoubleShow, [n, r..]) => match &*n.as_value() {
- DoubleLit(n) => Ok((
- r,
- TextLit(vec![InterpolatedTextContents::Text(n.to_string())]),
- )),
- _ => Err(()),
+ (DoubleShow, [n]) => match &*n.as_whnf() {
+ DoubleLit(n) => {
+ Ret::ValueF(TextLit(vec![InterpolatedTextContents::Text(
+ n.to_string(),
+ )]))
+ }
+ _ => Ret::DoneAsIs,
},
- (TextShow, [v, r..]) => match &*v.as_value() {
+ (TextShow, [v]) => match &*v.as_whnf() {
TextLit(elts) => {
match elts.as_slice() {
// Empty string literal.
[] => {
// Printing InterpolatedText takes care of all the escaping
- let txt: InterpolatedText<X> =
+ let txt: InterpolatedText<Normalized> =
std::iter::empty().collect();
let s = txt.to_string();
- Ok((
- r,
- TextLit(vec![InterpolatedTextContents::Text(s)]),
- ))
+ Ret::ValueF(TextLit(vec![
+ InterpolatedTextContents::Text(s),
+ ]))
}
// If there are no interpolations (invariants ensure that when there are no
// interpolations, there is a single Text item) in the literal.
[InterpolatedTextContents::Text(s)] => {
// Printing InterpolatedText takes care of all the escaping
- let txt: InterpolatedText<X> = std::iter::once(
- InterpolatedTextContents::Text(s.clone()),
- )
- .collect();
+ let txt: InterpolatedText<Normalized> =
+ std::iter::once(InterpolatedTextContents::Text(
+ s.clone(),
+ ))
+ .collect();
let s = txt.to_string();
- Ok((
- r,
- TextLit(vec![InterpolatedTextContents::Text(s)]),
- ))
+ Ret::ValueF(TextLit(vec![
+ InterpolatedTextContents::Text(s),
+ ]))
}
- _ => Err(()),
+ _ => Ret::DoneAsIs,
}
}
- _ => Err(()),
+ _ => Ret::DoneAsIs,
},
- (ListLength, [_, l, r..]) => match &*l.as_value() {
- EmptyListLit(_) => Ok((r, NaturalLit(0))),
- NEListLit(xs) => Ok((r, NaturalLit(xs.len()))),
- _ => Err(()),
+ (ListLength, [_, l]) => match &*l.as_whnf() {
+ EmptyListLit(_) => Ret::ValueF(NaturalLit(0)),
+ NEListLit(xs) => Ret::ValueF(NaturalLit(xs.len())),
+ _ => Ret::DoneAsIs,
},
- (ListHead, [_, l, r..]) => match &*l.as_value() {
- EmptyListLit(n) => Ok((r, EmptyOptionalLit(n.clone()))),
+ (ListHead, [_, l]) => match &*l.as_whnf() {
+ EmptyListLit(n) => Ret::ValueF(EmptyOptionalLit(n.clone())),
NEListLit(xs) => {
- Ok((r, NEOptionalLit(xs.iter().next().unwrap().clone())))
+ Ret::ValueF(NEOptionalLit(xs.iter().next().unwrap().clone()))
}
- _ => Err(()),
+ _ => Ret::DoneAsIs,
},
- (ListLast, [_, l, r..]) => match &*l.as_value() {
- EmptyListLit(n) => Ok((r, EmptyOptionalLit(n.clone()))),
- NEListLit(xs) => {
- Ok((r, NEOptionalLit(xs.iter().rev().next().unwrap().clone())))
- }
- _ => Err(()),
+ (ListLast, [_, l]) => match &*l.as_whnf() {
+ EmptyListLit(n) => Ret::ValueF(EmptyOptionalLit(n.clone())),
+ NEListLit(xs) => Ret::ValueF(NEOptionalLit(
+ xs.iter().rev().next().unwrap().clone(),
+ )),
+ _ => Ret::DoneAsIs,
},
- (ListReverse, [_, l, r..]) => match &*l.as_value() {
- EmptyListLit(n) => Ok((r, EmptyListLit(n.clone()))),
+ (ListReverse, [_, l]) => match &*l.as_whnf() {
+ EmptyListLit(n) => Ret::ValueF(EmptyListLit(n.clone())),
NEListLit(xs) => {
- Ok((r, NEListLit(xs.iter().rev().cloned().collect())))
+ Ret::ValueF(NEListLit(xs.iter().rev().cloned().collect()))
}
- _ => Err(()),
+ _ => Ret::DoneAsIs,
},
- (ListIndexed, [_, l, r..]) => match &*l.as_value() {
- EmptyListLit(t) => {
- let mut kts = HashMap::new();
- kts.insert(
- "index".into(),
- TypeThunk::from_value(Value::from_builtin(Natural)),
- );
- kts.insert("value".into(), t.clone());
- Ok((r, EmptyListLit(TypeThunk::from_value(RecordType(kts)))))
- }
- NEListLit(xs) => {
- let xs = xs
- .iter()
- .enumerate()
- .map(|(i, e)| {
- let i = NaturalLit(i);
- let mut kvs = HashMap::new();
- kvs.insert("index".into(), Thunk::from_value(i));
- kvs.insert("value".into(), e.clone());
- Thunk::from_value(RecordLit(kvs))
- })
- .collect();
- Ok((r, NEListLit(xs)))
+ (ListIndexed, [_, l]) => {
+ let l_whnf = l.as_whnf();
+ match &*l_whnf {
+ EmptyListLit(_) | NEListLit(_) => {
+ // Extract the type of the list elements
+ let t = match &*l_whnf {
+ EmptyListLit(t) => t.clone(),
+ NEListLit(xs) => xs[0].get_type_not_sort(),
+ _ => unreachable!(),
+ };
+
+ // Construct the returned record type: { index: Natural, value: t }
+ let mut kts = HashMap::new();
+ kts.insert("index".into(), Value::from_builtin(Natural));
+ kts.insert("value".into(), t.clone());
+ let t = Value::from_valuef_and_type(
+ RecordType(kts),
+ Value::from_const(Type),
+ );
+
+ // Construct the new list, with added indices
+ let list = match &*l_whnf {
+ EmptyListLit(_) => EmptyListLit(t),
+ NEListLit(xs) => NEListLit(
+ xs.iter()
+ .enumerate()
+ .map(|(i, e)| {
+ let mut kvs = HashMap::new();
+ kvs.insert(
+ "index".into(),
+ Value::from_valuef_and_type(
+ NaturalLit(i),
+ Value::from_builtin(
+ Builtin::Natural,
+ ),
+ ),
+ );
+ kvs.insert("value".into(), e.clone());
+ Value::from_valuef_and_type(
+ RecordLit(kvs),
+ t.clone(),
+ )
+ })
+ .collect(),
+ ),
+ _ => unreachable!(),
+ };
+ Ret::ValueF(list)
+ }
+ _ => Ret::DoneAsIs,
}
- _ => Err(()),
- },
- (ListBuild, [t, f, r..]) => match &*f.as_value() {
+ }
+ (ListBuild, [t, f]) => match &*f.as_whnf() {
// fold/build fusion
- Value::AppliedBuiltin(ListFold, args) => {
+ ValueF::AppliedBuiltin(ListFold, args) => {
if args.len() >= 2 {
- Ok((r, args[1].to_value()))
+ Ret::Value(args[1].clone())
} else {
// Do we really need to handle this case ?
unimplemented!()
}
}
- _ => Ok((
- r,
- f.app_val(Value::from_builtin(List).app_thunk(t.clone()))
- .app_val(ListConsClosure(
- TypeThunk::from_thunk(t.clone()),
- None,
- ))
- .app_val(EmptyListLit(TypeThunk::from_thunk(t.clone()))),
- )),
+ _ => {
+ let list_t = Value::from_builtin(List).app(t.clone());
+ Ret::Value(
+ f.app(list_t.clone())
+ .app({
+ // Move `t` under new variables
+ let t1 = t.under_binder(Label::from("x"));
+ let t2 = t1.under_binder(Label::from("xs"));
+ make_closure!(
+ λ(x : #t) ->
+ λ(xs : List #t1) ->
+ [ var(x, 1, #t2) ] # var(xs, 0, List #t2)
+ )
+ })
+ .app(
+ EmptyListLit(t.clone())
+ .into_value_with_type(list_t),
+ ),
+ )
+ }
},
- (ListFold, [_, l, _, cons, nil, r..]) => match &*l.as_value() {
- EmptyListLit(_) => Ok((r, nil.to_value())),
+ (ListFold, [_, l, _, cons, nil, r..]) => match &*l.as_whnf() {
+ EmptyListLit(_) => Ret::ValueWithRemainingArgs(r, nil.clone()),
NEListLit(xs) => {
let mut v = nil.clone();
- for x in xs.iter().rev() {
- v = cons
- .clone()
- .app_thunk(x.clone())
- .app_thunk(v)
- .into_thunk();
+ for x in xs.iter().cloned().rev() {
+ v = cons.app(x).app(v);
}
- Ok((r, v.to_value()))
+ Ret::ValueWithRemainingArgs(r, v)
}
- _ => Err(()),
+ _ => Ret::DoneAsIs,
},
- (OptionalBuild, [t, f, r..]) => match &*f.as_value() {
+ (OptionalBuild, [t, f]) => match &*f.as_whnf() {
// fold/build fusion
- Value::AppliedBuiltin(OptionalFold, args) => {
+ ValueF::AppliedBuiltin(OptionalFold, args) => {
if args.len() >= 2 {
- Ok((r, args[1].to_value()))
+ Ret::Value(args[1].clone())
} else {
// Do we really need to handle this case ?
unimplemented!()
}
}
- _ => Ok((
- r,
- f.app_val(Value::from_builtin(Optional).app_thunk(t.clone()))
- .app_val(OptionalSomeClosure(TypeThunk::from_thunk(
- t.clone(),
- )))
- .app_val(EmptyOptionalLit(TypeThunk::from_thunk(
- t.clone(),
- ))),
- )),
+ _ => {
+ let optional_t = Value::from_builtin(Optional).app(t.clone());
+ Ret::Value(
+ f.app(optional_t.clone())
+ .app({
+ let t1 = t.under_binder(Label::from("x"));
+ make_closure!(λ(x: #t) -> Some(var(x, 0, #t1)))
+ })
+ .app(
+ EmptyOptionalLit(t.clone())
+ .into_value_with_type(optional_t),
+ ),
+ )
+ }
},
- (OptionalFold, [_, v, _, just, nothing, r..]) => match &*v.as_value() {
- EmptyOptionalLit(_) => Ok((r, nothing.to_value())),
- NEOptionalLit(x) => Ok((r, just.app_thunk(x.clone()))),
- _ => Err(()),
+ (OptionalFold, [_, v, _, just, nothing, r..]) => match &*v.as_whnf() {
+ EmptyOptionalLit(_) => {
+ Ret::ValueWithRemainingArgs(r, nothing.clone())
+ }
+ NEOptionalLit(x) => {
+ Ret::ValueWithRemainingArgs(r, just.app(x.clone()))
+ }
+ _ => Ret::DoneAsIs,
},
- (NaturalBuild, [f, r..]) => match &*f.as_value() {
+ (NaturalBuild, [f]) => match &*f.as_whnf() {
// fold/build fusion
- Value::AppliedBuiltin(NaturalFold, args) => {
+ ValueF::AppliedBuiltin(NaturalFold, args) => {
if !args.is_empty() {
- Ok((r, args[0].to_value()))
+ Ret::Value(args[0].clone())
} else {
// Do we really need to handle this case ?
unimplemented!()
}
}
- _ => Ok((
- r,
- f.app_val(Value::from_builtin(Natural))
- .app_val(NaturalSuccClosure)
- .app_val(NaturalLit(0)),
- )),
+ _ => Ret::Value(
+ f.app(Value::from_builtin(Natural))
+ .app(make_closure!(
+ λ(x : Natural) -> 1 + var(x, 0, Natural)
+ ))
+ .app(
+ NaturalLit(0)
+ .into_value_with_type(Value::from_builtin(Natural)),
+ ),
+ ),
},
- (NaturalFold, [n, t, succ, zero, r..]) => match &*n.as_value() {
- NaturalLit(0) => Ok((r, zero.to_value())),
+ (NaturalFold, [n, t, succ, zero, r..]) => match &*n.as_whnf() {
+ NaturalLit(0) => Ret::ValueWithRemainingArgs(r, zero.clone()),
NaturalLit(n) => {
let fold = Value::from_builtin(NaturalFold)
- .app(NaturalLit(n - 1))
- .app_thunk(t.clone())
- .app_thunk(succ.clone())
- .app_thunk(zero.clone());
- Ok((r, succ.app_val(fold)))
+ .app(
+ NaturalLit(n - 1)
+ .into_value_with_type(Value::from_builtin(Natural)),
+ )
+ .app(t.clone())
+ .app(succ.clone())
+ .app(zero.clone());
+ Ret::ValueWithRemainingArgs(r, succ.app(fold))
}
- _ => Err(()),
+ _ => Ret::DoneAsIs,
},
- _ => Err(()),
+ _ => Ret::DoneAsIs,
};
match ret {
- Ok((unconsumed_args, mut v)) => {
+ Ret::ValueF(v) => v,
+ Ret::Value(v) => v.to_whnf_check_type(ty),
+ Ret::ValueWithRemainingArgs(unconsumed_args, mut v) => {
let n_consumed_args = args.len() - unconsumed_args.len();
for x in args.into_iter().skip(n_consumed_args) {
- v = v.app_thunk(x);
+ v = v.app(x);
}
- v
+ v.to_whnf_check_type(ty)
}
- Err(()) => AppliedBuiltin(b, args),
+ Ret::DoneAsIs => AppliedBuiltin(b, args),
}
}
-pub fn apply_any(f: Thunk, a: Thunk) -> Value {
- let fallback = |f: Thunk, a: Thunk| Value::PartialExpr(ExprF::App(f, a));
-
- let f_borrow = f.as_value();
+pub(crate) fn apply_any(f: Value, a: Value, ty: &Value) -> ValueF {
+ let f_borrow = f.as_whnf();
match &*f_borrow {
- Value::Lam(x, _, e) => {
- let val = Typed::from_thunk_untyped(a);
- e.subst_shift(&x.into(), &val).to_value()
+ ValueF::Lam(x, _, e) => {
+ e.subst_shift(&x.into(), &a).to_whnf_check_type(ty)
}
- Value::AppliedBuiltin(b, args) => {
+ ValueF::AppliedBuiltin(b, args) => {
use std::iter::once;
let args = args.iter().cloned().chain(once(a.clone())).collect();
- apply_builtin(*b, args)
- }
- Value::OptionalSomeClosure(_) => Value::NEOptionalLit(a),
- Value::ListConsClosure(t, None) => {
- Value::ListConsClosure(t.clone(), Some(a))
+ apply_builtin(*b, args, ty)
}
- Value::ListConsClosure(_, Some(x)) => {
- let a_borrow = a.as_value();
- match &*a_borrow {
- Value::EmptyListLit(_) => Value::NEListLit(vec![x.clone()]),
- Value::NEListLit(xs) => {
- use std::iter::once;
- let xs =
- once(x.clone()).chain(xs.iter().cloned()).collect();
- Value::NEListLit(xs)
- }
- _ => {
- drop(f_borrow);
- drop(a_borrow);
- fallback(f, a)
- }
- }
- }
- Value::NaturalSuccClosure => {
- let a_borrow = a.as_value();
- match &*a_borrow {
- Value::NaturalLit(n) => Value::NaturalLit(n + 1),
- _ => {
- drop(f_borrow);
- drop(a_borrow);
- fallback(f, a)
- }
- }
- }
- Value::UnionConstructor(l, kts) => {
- Value::UnionLit(l.clone(), a, kts.clone())
+ ValueF::UnionConstructor(l, kts) => {
+ ValueF::UnionLit(l.clone(), a, kts.clone())
}
_ => {
drop(f_borrow);
- fallback(f, a)
+ ValueF::PartialExpr(ExprF::App(f, a))
}
}
}
-pub fn squash_textlit(
- elts: impl Iterator<Item = InterpolatedTextContents<Thunk>>,
-) -> Vec<InterpolatedTextContents<Thunk>> {
+pub(crate) fn squash_textlit(
+ elts: impl Iterator<Item = InterpolatedTextContents<Value>>,
+) -> Vec<InterpolatedTextContents<Value>> {
use std::mem::replace;
use InterpolatedTextContents::{Expr, Text};
fn inner(
- elts: impl Iterator<Item = InterpolatedTextContents<Thunk>>,
+ elts: impl Iterator<Item = InterpolatedTextContents<Value>>,
crnt_str: &mut String,
- ret: &mut Vec<InterpolatedTextContents<Thunk>>,
+ ret: &mut Vec<InterpolatedTextContents<Value>>,
) {
for contents in elts {
match contents {
Text(s) => crnt_str.push_str(&s),
Expr(e) => {
- let e_borrow = e.as_value();
+ let e_borrow = e.as_whnf();
match &*e_borrow {
- Value::TextLit(elts2) => {
+ ValueF::TextLit(elts2) => {
inner(elts2.iter().cloned(), crnt_str, ret)
}
_ => {
@@ -367,134 +450,20 @@ pub fn squash_textlit(
ret
}
-/// Reduces the imput expression to a Value. Evaluates as little as possible.
-pub fn normalize_whnf(ctx: NormalizationContext, expr: InputSubExpr) -> Value {
- match expr.as_ref() {
- ExprF::Embed(e) => return e.to_value(),
- ExprF::Var(v) => return ctx.lookup(v),
- _ => {}
- }
-
- // Thunk subexpressions
- let expr: ExprF<Thunk, X> =
- expr.as_ref().map_ref_with_special_handling_of_binders(
- |e| Thunk::new(ctx.clone(), e.clone()),
- |x, e| Thunk::new(ctx.skip(x), e.clone()),
- |_| unreachable!(),
- );
-
- normalize_one_layer(expr)
-}
-
-// Small helper enum to avoid repetition
-enum Ret<'a> {
- Value(Value),
- Thunk(Thunk),
- ThunkRef(&'a Thunk),
- Expr(ExprF<Thunk, X>),
-}
-
-/// Performs an intersection of two HashMaps.
-///
-/// # Arguments
-///
-/// * `f` - Will compute the final value from the intersecting
-/// key and the values from both maps.
-///
-/// # Description
-///
-/// If the key is present in both maps then the final value for
-/// that key is computed via the `f` function.
-///
-/// The final map will contain the shared keys from the
-/// two input maps with the final computed value from `f`.
-pub(crate) fn intersection_with_key<K, T, U, V>(
- mut f: impl FnMut(&K, &T, &U) -> V,
- map1: &HashMap<K, T>,
- map2: &HashMap<K, U>,
-) -> HashMap<K, V>
-where
- K: std::hash::Hash + Eq + Clone,
-{
- let mut kvs = HashMap::new();
-
- for (k, t) in map1 {
- // Only insert in the final map if the key exists in both
- if let Some(u) = map2.get(k) {
- kvs.insert(k.clone(), f(k, t, u));
- }
- }
-
- kvs
-}
-
-/// Performs an outer join of two HashMaps.
-///
-/// # Arguments
-///
-/// * `ft` - Will convert the values of the first map
-/// into the target value.
-///
-/// * `fu` - Will convert the values of the second map
-/// into the target value.
-///
-/// * `fktu` - Will convert the key and values from both maps
-/// into the target type.
-///
-/// # Description
-///
-/// If the key is present in both maps then the final value for
-/// that key is computed via the `fktu` function. Otherwise, the
-/// final value will be calculated by either the `ft` or `fu` value
-/// depending on which map the key is present in.
-///
-/// The final map will contain all keys from the two input maps with
-/// also values computed as per above.
-pub(crate) fn outer_join<K, T, U, V>(
- mut ft: impl FnMut(&T) -> V,
- mut fu: impl FnMut(&U) -> V,
- mut fktu: impl FnMut(&K, &T, &U) -> V,
- map1: &HashMap<K, T>,
- map2: &HashMap<K, U>,
-) -> HashMap<K, V>
-where
- K: std::hash::Hash + Eq + Clone,
-{
- let mut kvs = HashMap::new();
-
- for (k1, t) in map1 {
- let v = if let Some(u) = map2.get(k1) {
- // The key exists in both maps
- // so use all values for computation
- fktu(k1, t, u)
- } else {
- // Key only exists in map1
- ft(t)
- };
- kvs.insert(k1.clone(), v);
- }
-
- for (k1, u) in map2 {
- // Insert if key was missing in map1
- kvs.entry(k1.clone()).or_insert(fu(u));
- }
-
- kvs
-}
-
-pub(crate) fn merge_maps<K, V>(
+pub(crate) fn merge_maps<K, V, F, Err>(
map1: &HashMap<K, V>,
map2: &HashMap<K, V>,
- mut f: impl FnMut(&V, &V) -> V,
-) -> HashMap<K, V>
+ mut f: F,
+) -> Result<HashMap<K, V>, Err>
where
+ F: FnMut(&K, &V, &V) -> Result<V, Err>,
K: std::hash::Hash + Eq + Clone,
V: Clone,
{
let mut kvs = HashMap::new();
for (x, v2) in map2 {
let newv = if let Some(v1) = map1.get(x) {
- f(v1, v2)
+ f(x, v1, v2)?
} else {
v2.clone()
};
@@ -504,85 +473,98 @@ where
// Insert only if key not already present
kvs.entry(x.clone()).or_insert_with(|| v1.clone());
}
- kvs
+ Ok(kvs)
}
-fn apply_binop<'a>(o: BinOp, x: &'a Thunk, y: &'a Thunk) -> Option<Ret<'a>> {
+// Small helper enum to avoid repetition
+enum Ret<'a> {
+ ValueF(ValueF),
+ Value(Value),
+ ValueRef(&'a Value),
+ Expr(ExprF<Value, Normalized>),
+}
+
+fn apply_binop<'a>(
+ o: BinOp,
+ x: &'a Value,
+ y: &'a Value,
+ ty: &Value,
+) -> Option<Ret<'a>> {
use BinOp::{
BoolAnd, BoolEQ, BoolNE, BoolOr, Equivalence, ListAppend, NaturalPlus,
NaturalTimes, RecursiveRecordMerge, RecursiveRecordTypeMerge,
RightBiasedRecordMerge, TextAppend,
};
- use Value::{
+ use ValueF::{
BoolLit, EmptyListLit, NEListLit, NaturalLit, RecordLit, RecordType,
TextLit,
};
- let x_borrow = x.as_value();
- let y_borrow = y.as_value();
+ let x_borrow = x.as_whnf();
+ let y_borrow = y.as_whnf();
Some(match (o, &*x_borrow, &*y_borrow) {
- (BoolAnd, BoolLit(true), _) => Ret::ThunkRef(y),
- (BoolAnd, _, BoolLit(true)) => Ret::ThunkRef(x),
- (BoolAnd, BoolLit(false), _) => Ret::Value(BoolLit(false)),
- (BoolAnd, _, BoolLit(false)) => Ret::Value(BoolLit(false)),
- (BoolAnd, _, _) if x == y => Ret::ThunkRef(x),
- (BoolOr, BoolLit(true), _) => Ret::Value(BoolLit(true)),
- (BoolOr, _, BoolLit(true)) => Ret::Value(BoolLit(true)),
- (BoolOr, BoolLit(false), _) => Ret::ThunkRef(y),
- (BoolOr, _, BoolLit(false)) => Ret::ThunkRef(x),
- (BoolOr, _, _) if x == y => Ret::ThunkRef(x),
- (BoolEQ, BoolLit(true), _) => Ret::ThunkRef(y),
- (BoolEQ, _, BoolLit(true)) => Ret::ThunkRef(x),
- (BoolEQ, BoolLit(x), BoolLit(y)) => Ret::Value(BoolLit(x == y)),
- (BoolEQ, _, _) if x == y => Ret::Value(BoolLit(true)),
- (BoolNE, BoolLit(false), _) => Ret::ThunkRef(y),
- (BoolNE, _, BoolLit(false)) => Ret::ThunkRef(x),
- (BoolNE, BoolLit(x), BoolLit(y)) => Ret::Value(BoolLit(x != y)),
- (BoolNE, _, _) if x == y => Ret::Value(BoolLit(false)),
-
- (NaturalPlus, NaturalLit(0), _) => Ret::ThunkRef(y),
- (NaturalPlus, _, NaturalLit(0)) => Ret::ThunkRef(x),
+ (BoolAnd, BoolLit(true), _) => Ret::ValueRef(y),
+ (BoolAnd, _, BoolLit(true)) => Ret::ValueRef(x),
+ (BoolAnd, BoolLit(false), _) => Ret::ValueF(BoolLit(false)),
+ (BoolAnd, _, BoolLit(false)) => Ret::ValueF(BoolLit(false)),
+ (BoolAnd, _, _) if x == y => Ret::ValueRef(x),
+ (BoolOr, BoolLit(true), _) => Ret::ValueF(BoolLit(true)),
+ (BoolOr, _, BoolLit(true)) => Ret::ValueF(BoolLit(true)),
+ (BoolOr, BoolLit(false), _) => Ret::ValueRef(y),
+ (BoolOr, _, BoolLit(false)) => Ret::ValueRef(x),
+ (BoolOr, _, _) if x == y => Ret::ValueRef(x),
+ (BoolEQ, BoolLit(true), _) => Ret::ValueRef(y),
+ (BoolEQ, _, BoolLit(true)) => Ret::ValueRef(x),
+ (BoolEQ, BoolLit(x), BoolLit(y)) => Ret::ValueF(BoolLit(x == y)),
+ (BoolEQ, _, _) if x == y => Ret::ValueF(BoolLit(true)),
+ (BoolNE, BoolLit(false), _) => Ret::ValueRef(y),
+ (BoolNE, _, BoolLit(false)) => Ret::ValueRef(x),
+ (BoolNE, BoolLit(x), BoolLit(y)) => Ret::ValueF(BoolLit(x != y)),
+ (BoolNE, _, _) if x == y => Ret::ValueF(BoolLit(false)),
+
+ (NaturalPlus, NaturalLit(0), _) => Ret::ValueRef(y),
+ (NaturalPlus, _, NaturalLit(0)) => Ret::ValueRef(x),
(NaturalPlus, NaturalLit(x), NaturalLit(y)) => {
- Ret::Value(NaturalLit(x + y))
+ Ret::ValueF(NaturalLit(x + y))
}
- (NaturalTimes, NaturalLit(0), _) => Ret::Value(NaturalLit(0)),
- (NaturalTimes, _, NaturalLit(0)) => Ret::Value(NaturalLit(0)),
- (NaturalTimes, NaturalLit(1), _) => Ret::ThunkRef(y),
- (NaturalTimes, _, NaturalLit(1)) => Ret::ThunkRef(x),
+ (NaturalTimes, NaturalLit(0), _) => Ret::ValueF(NaturalLit(0)),
+ (NaturalTimes, _, NaturalLit(0)) => Ret::ValueF(NaturalLit(0)),
+ (NaturalTimes, NaturalLit(1), _) => Ret::ValueRef(y),
+ (NaturalTimes, _, NaturalLit(1)) => Ret::ValueRef(x),
(NaturalTimes, NaturalLit(x), NaturalLit(y)) => {
- Ret::Value(NaturalLit(x * y))
+ Ret::ValueF(NaturalLit(x * y))
}
- (ListAppend, EmptyListLit(_), _) => Ret::ThunkRef(y),
- (ListAppend, _, EmptyListLit(_)) => Ret::ThunkRef(x),
- (ListAppend, NEListLit(xs), NEListLit(ys)) => {
- Ret::Value(NEListLit(xs.iter().chain(ys.iter()).cloned().collect()))
- }
+ (ListAppend, EmptyListLit(_), _) => Ret::ValueRef(y),
+ (ListAppend, _, EmptyListLit(_)) => Ret::ValueRef(x),
+ (ListAppend, NEListLit(xs), NEListLit(ys)) => Ret::ValueF(NEListLit(
+ xs.iter().chain(ys.iter()).cloned().collect(),
+ )),
- (TextAppend, TextLit(x), _) if x.is_empty() => Ret::ThunkRef(y),
- (TextAppend, _, TextLit(y)) if y.is_empty() => Ret::ThunkRef(x),
- (TextAppend, TextLit(x), TextLit(y)) => Ret::Value(TextLit(
+ (TextAppend, TextLit(x), _) if x.is_empty() => Ret::ValueRef(y),
+ (TextAppend, _, TextLit(y)) if y.is_empty() => Ret::ValueRef(x),
+ (TextAppend, TextLit(x), TextLit(y)) => Ret::ValueF(TextLit(
squash_textlit(x.iter().chain(y.iter()).cloned()),
)),
(TextAppend, TextLit(x), _) => {
use std::iter::once;
let y = InterpolatedTextContents::Expr(y.clone());
- Ret::Value(TextLit(squash_textlit(
+ Ret::ValueF(TextLit(squash_textlit(
x.iter().cloned().chain(once(y)),
)))
}
(TextAppend, _, TextLit(y)) => {
use std::iter::once;
let x = InterpolatedTextContents::Expr(x.clone());
- Ret::Value(TextLit(squash_textlit(
+ Ret::ValueF(TextLit(squash_textlit(
once(x).chain(y.iter().cloned()),
)))
}
(RightBiasedRecordMerge, _, RecordLit(kvs)) if kvs.is_empty() => {
- Ret::ThunkRef(x)
+ Ret::ValueRef(x)
}
(RightBiasedRecordMerge, RecordLit(kvs), _) if kvs.is_empty() => {
- Ret::ThunkRef(y)
+ Ret::ValueRef(y)
}
(RightBiasedRecordMerge, RecordLit(kvs1), RecordLit(kvs2)) => {
let mut kvs = kvs2.clone();
@@ -590,92 +572,83 @@ fn apply_binop<'a>(o: BinOp, x: &'a Thunk, y: &'a Thunk) -> Option<Ret<'a>> {
// Insert only if key not already present
kvs.entry(x.clone()).or_insert_with(|| v.clone());
}
- Ret::Value(RecordLit(kvs))
+ Ret::ValueF(RecordLit(kvs))
}
(RecursiveRecordMerge, _, RecordLit(kvs)) if kvs.is_empty() => {
- Ret::ThunkRef(x)
+ Ret::ValueRef(x)
}
(RecursiveRecordMerge, RecordLit(kvs), _) if kvs.is_empty() => {
- Ret::ThunkRef(y)
+ Ret::ValueRef(y)
}
(RecursiveRecordMerge, RecordLit(kvs1), RecordLit(kvs2)) => {
- let kvs = merge_maps(kvs1, kvs2, |v1, v2| {
- Thunk::from_partial_expr(ExprF::BinOp(
- RecursiveRecordMerge,
- v1.clone(),
- v2.clone(),
+ let ty_borrow = ty.as_whnf();
+ let kts = match &*ty_borrow {
+ RecordType(kts) => kts,
+ _ => unreachable!("Internal type error"),
+ };
+ let kvs = merge_maps::<_, _, _, !>(kvs1, kvs2, |k, v1, v2| {
+ Ok(Value::from_valuef_and_type(
+ ValueF::PartialExpr(ExprF::BinOp(
+ RecursiveRecordMerge,
+ v1.clone(),
+ v2.clone(),
+ )),
+ kts.get(k).expect("Internal type error").clone(),
))
- });
- Ret::Value(RecordLit(kvs))
+ })?;
+ Ret::ValueF(RecordLit(kvs))
}
- (RecursiveRecordTypeMerge, _, RecordType(kvs)) if kvs.is_empty() => {
- Ret::ThunkRef(x)
- }
- (RecursiveRecordTypeMerge, RecordType(kvs), _) if kvs.is_empty() => {
- Ret::ThunkRef(y)
+ (RecursiveRecordTypeMerge, _, _) | (Equivalence, _, _) => {
+ unreachable!("This case should have been handled in typecheck")
}
- (RecursiveRecordTypeMerge, RecordType(kvs1), RecordType(kvs2)) => {
- let kvs = merge_maps(kvs1, kvs2, |v1, v2| {
- TypeThunk::from_thunk(Thunk::from_partial_expr(ExprF::BinOp(
- RecursiveRecordTypeMerge,
- v1.to_thunk(),
- v2.to_thunk(),
- )))
- });
- Ret::Value(RecordType(kvs))
- }
-
- (Equivalence, _, _) => Ret::Value(Value::Equivalence(
- TypeThunk::from_thunk(x.clone()),
- TypeThunk::from_thunk(y.clone()),
- )),
_ => return None,
})
}
-pub fn normalize_one_layer(expr: ExprF<Thunk, X>) -> Value {
- use Value::{
- AppliedBuiltin, BoolLit, DoubleLit, EmptyListLit, IntegerLit, Lam,
- NEListLit, NEOptionalLit, NaturalLit, Pi, RecordLit, RecordType,
- TextLit, UnionConstructor, UnionLit, UnionType,
+pub(crate) fn normalize_one_layer(
+ expr: ExprF<Value, Normalized>,
+ ty: &Value,
+) -> ValueF {
+ use ValueF::{
+ AppliedBuiltin, BoolLit, DoubleLit, EmptyListLit, IntegerLit,
+ NEListLit, NEOptionalLit, NaturalLit, RecordLit, TextLit,
+ UnionConstructor, UnionLit, UnionType,
};
let ret = match expr {
- ExprF::Embed(_) => unreachable!(),
- ExprF::Var(_) => unreachable!(),
- ExprF::Annot(x, _) => Ret::Thunk(x),
- ExprF::Assert(_) => Ret::Expr(expr),
- ExprF::Lam(x, t, e) => {
- Ret::Value(Lam(x.into(), TypeThunk::from_thunk(t), e))
+ ExprF::Import(_) => unreachable!(
+ "There should remain no imports in a resolved expression"
+ ),
+ // Those cases have already been completely handled in the typechecking phase (using
+ // `RetWhole`), so they won't appear here.
+ ExprF::Lam(_, _, _)
+ | ExprF::Pi(_, _, _)
+ | ExprF::Let(_, _, _, _)
+ | ExprF::Embed(_)
+ | ExprF::Const(_)
+ | ExprF::Builtin(_)
+ | ExprF::Var(_)
+ | ExprF::Annot(_, _)
+ | ExprF::RecordType(_)
+ | ExprF::UnionType(_) => {
+ unreachable!("This case should have been handled in typecheck")
}
- ExprF::Pi(x, t, e) => Ret::Value(Pi(
- x.into(),
- TypeThunk::from_thunk(t),
- TypeThunk::from_thunk(e),
- )),
- ExprF::Let(x, _, v, b) => {
- let v = Typed::from_thunk_untyped(v);
- Ret::Thunk(b.subst_shift(&x.into(), &v))
- }
- ExprF::App(v, a) => Ret::Value(v.app_thunk(a)),
- ExprF::Builtin(b) => Ret::Value(Value::from_builtin(b)),
- ExprF::Const(c) => Ret::Value(Value::Const(c)),
- ExprF::BoolLit(b) => Ret::Value(BoolLit(b)),
- ExprF::NaturalLit(n) => Ret::Value(NaturalLit(n)),
- ExprF::IntegerLit(n) => Ret::Value(IntegerLit(n)),
- ExprF::DoubleLit(n) => Ret::Value(DoubleLit(n)),
- ExprF::SomeLit(e) => Ret::Value(NEOptionalLit(e)),
+ ExprF::Assert(_) => Ret::Expr(expr),
+ ExprF::App(v, a) => Ret::Value(v.app(a)),
+ ExprF::BoolLit(b) => Ret::ValueF(BoolLit(b)),
+ ExprF::NaturalLit(n) => Ret::ValueF(NaturalLit(n)),
+ ExprF::IntegerLit(n) => Ret::ValueF(IntegerLit(n)),
+ ExprF::DoubleLit(n) => Ret::ValueF(DoubleLit(n)),
+ ExprF::SomeLit(e) => Ret::ValueF(NEOptionalLit(e)),
ExprF::EmptyListLit(ref t) => {
// Check if the type is of the form `List x`
- let t_borrow = t.as_value();
+ let t_borrow = t.as_whnf();
match &*t_borrow {
AppliedBuiltin(Builtin::List, args) if args.len() == 1 => {
- Ret::Value(EmptyListLit(TypeThunk::from_thunk(
- args[0].clone(),
- )))
+ Ret::ValueF(EmptyListLit(args[0].clone()))
}
_ => {
drop(t_borrow);
@@ -684,43 +657,33 @@ pub fn normalize_one_layer(expr: ExprF<Thunk, X>) -> Value {
}
}
ExprF::NEListLit(elts) => {
- Ret::Value(NEListLit(elts.into_iter().collect()))
+ Ret::ValueF(NEListLit(elts.into_iter().collect()))
}
ExprF::RecordLit(kvs) => {
- Ret::Value(RecordLit(kvs.into_iter().collect()))
+ Ret::ValueF(RecordLit(kvs.into_iter().collect()))
}
- ExprF::RecordType(kts) => Ret::Value(RecordType(
- kts.into_iter()
- .map(|(k, t)| (k, TypeThunk::from_thunk(t)))
- .collect(),
- )),
- ExprF::UnionType(kts) => Ret::Value(UnionType(
- kts.into_iter()
- .map(|(k, t)| (k, t.map(|t| TypeThunk::from_thunk(t))))
- .collect(),
- )),
ExprF::TextLit(elts) => {
use InterpolatedTextContents::Expr;
let elts: Vec<_> = squash_textlit(elts.into_iter());
// Simplify bare interpolation
if let [Expr(th)] = elts.as_slice() {
- Ret::Thunk(th.clone())
+ Ret::Value(th.clone())
} else {
- Ret::Value(TextLit(elts))
+ Ret::ValueF(TextLit(elts))
}
}
ExprF::BoolIf(ref b, ref e1, ref e2) => {
- let b_borrow = b.as_value();
+ let b_borrow = b.as_whnf();
match &*b_borrow {
- BoolLit(true) => Ret::ThunkRef(e1),
- BoolLit(false) => Ret::ThunkRef(e2),
+ BoolLit(true) => Ret::ValueRef(e1),
+ BoolLit(false) => Ret::ValueRef(e2),
_ => {
- let e1_borrow = e1.as_value();
- let e2_borrow = e2.as_value();
+ let e1_borrow = e1.as_whnf();
+ let e2_borrow = e2.as_whnf();
match (&*e1_borrow, &*e2_borrow) {
// Simplify `if b then True else False`
- (BoolLit(true), BoolLit(false)) => Ret::ThunkRef(b),
- _ if e1 == e2 => Ret::ThunkRef(e1),
+ (BoolLit(true), BoolLit(false)) => Ret::ValueRef(b),
+ _ if e1 == e2 => Ret::ValueRef(e1),
_ => {
drop(b_borrow);
drop(e1_borrow);
@@ -731,18 +694,18 @@ pub fn normalize_one_layer(expr: ExprF<Thunk, X>) -> Value {
}
}
}
- ExprF::BinOp(o, ref x, ref y) => match apply_binop(o, x, y) {
+ ExprF::BinOp(o, ref x, ref y) => match apply_binop(o, x, y, ty) {
Some(ret) => ret,
None => Ret::Expr(expr),
},
- ExprF::Projection(_, ls) if ls.is_empty() => {
- Ret::Value(RecordLit(HashMap::new()))
+ ExprF::Projection(_, ref ls) if ls.is_empty() => {
+ Ret::ValueF(RecordLit(HashMap::new()))
}
ExprF::Projection(ref v, ref ls) => {
- let v_borrow = v.as_value();
+ let v_borrow = v.as_whnf();
match &*v_borrow {
- RecordLit(kvs) => Ret::Value(RecordLit(
+ RecordLit(kvs) => Ret::ValueF(RecordLit(
ls.iter()
.filter_map(|l| {
kvs.get(l).map(|x| (l.clone(), x.clone()))
@@ -756,17 +719,17 @@ pub fn normalize_one_layer(expr: ExprF<Thunk, X>) -> Value {
}
}
ExprF::Field(ref v, ref l) => {
- let v_borrow = v.as_value();
+ let v_borrow = v.as_whnf();
match &*v_borrow {
RecordLit(kvs) => match kvs.get(l) {
- Some(r) => Ret::Thunk(r.clone()),
+ Some(r) => Ret::Value(r.clone()),
None => {
drop(v_borrow);
Ret::Expr(expr)
}
},
UnionType(kts) => {
- Ret::Value(UnionConstructor(l.clone(), kts.clone()))
+ Ret::ValueF(UnionConstructor(l.clone(), kts.clone()))
}
_ => {
drop(v_borrow);
@@ -776,11 +739,11 @@ pub fn normalize_one_layer(expr: ExprF<Thunk, X>) -> Value {
}
ExprF::Merge(ref handlers, ref variant, _) => {
- let handlers_borrow = handlers.as_value();
- let variant_borrow = variant.as_value();
+ let handlers_borrow = handlers.as_whnf();
+ let variant_borrow = variant.as_whnf();
match (&*handlers_borrow, &*variant_borrow) {
(RecordLit(kvs), UnionConstructor(l, _)) => match kvs.get(l) {
- Some(h) => Ret::Thunk(h.clone()),
+ Some(h) => Ret::Value(h.clone()),
None => {
drop(handlers_borrow);
drop(variant_borrow);
@@ -788,7 +751,7 @@ pub fn normalize_one_layer(expr: ExprF<Thunk, X>) -> Value {
}
},
(RecordLit(kvs), UnionLit(l, v, _)) => match kvs.get(l) {
- Some(h) => Ret::Value(h.app_thunk(v.clone())),
+ Some(h) => Ret::Value(h.app(v.clone())),
None => {
drop(handlers_borrow);
drop(variant_borrow);
@@ -802,12 +765,26 @@ pub fn normalize_one_layer(expr: ExprF<Thunk, X>) -> Value {
}
}
}
+ ExprF::ToMap(_, _) => unimplemented!("toMap"),
};
match ret {
- Ret::Value(v) => v,
- Ret::Thunk(th) => th.to_value(),
- Ret::ThunkRef(th) => th.to_value(),
- Ret::Expr(expr) => Value::PartialExpr(expr),
+ Ret::ValueF(v) => v,
+ Ret::Value(v) => v.to_whnf_check_type(ty),
+ Ret::ValueRef(v) => v.to_whnf_check_type(ty),
+ Ret::Expr(expr) => ValueF::PartialExpr(expr),
+ }
+}
+
+/// Normalize a ValueF into WHNF
+pub(crate) fn normalize_whnf(v: ValueF, ty: &Value) -> ValueF {
+ match v {
+ ValueF::AppliedBuiltin(b, args) => apply_builtin(b, args, ty),
+ ValueF::PartialExpr(e) => normalize_one_layer(e, ty),
+ ValueF::TextLit(elts) => {
+ ValueF::TextLit(squash_textlit(elts.into_iter()))
+ }
+ // All other cases are already in WHNF
+ v => v,
}
}
diff --git a/dhall/src/phase/parse.rs b/dhall/src/phase/parse.rs
index 734f6e1..540ceea 100644
--- a/dhall/src/phase/parse.rs
+++ b/dhall/src/phase/parse.rs
@@ -8,7 +8,7 @@ use crate::error::Error;
use crate::phase::resolve::ImportRoot;
use crate::phase::Parsed;
-pub fn parse_file(f: &Path) -> Result<Parsed, Error> {
+pub(crate) fn parse_file(f: &Path) -> Result<Parsed, Error> {
let mut buffer = String::new();
File::open(f)?.read_to_string(&mut buffer)?;
let expr = parse_expr(&*buffer)?;
@@ -16,22 +16,22 @@ pub fn parse_file(f: &Path) -> Result<Parsed, Error> {
Ok(Parsed(expr, root))
}
-pub fn parse_str(s: &str) -> Result<Parsed, Error> {
+pub(crate) fn parse_str(s: &str) -> Result<Parsed, Error> {
let expr = parse_expr(s)?;
let root = ImportRoot::LocalDir(std::env::current_dir()?);
Ok(Parsed(expr, root))
}
-pub fn parse_binary(data: &[u8]) -> Result<Parsed, Error> {
+pub(crate) fn parse_binary(data: &[u8]) -> Result<Parsed, Error> {
let expr = crate::phase::binary::decode(data)?;
let root = ImportRoot::LocalDir(std::env::current_dir()?);
- Ok(Parsed(expr.note_absurd(), root))
+ Ok(Parsed(expr, root))
}
-pub fn parse_binary_file(f: &Path) -> Result<Parsed, Error> {
+pub(crate) fn parse_binary_file(f: &Path) -> Result<Parsed, Error> {
let mut buffer = Vec::new();
File::open(f)?.read_to_end(&mut buffer)?;
let expr = crate::phase::binary::decode(&buffer)?;
let root = ImportRoot::LocalDir(f.parent().unwrap().to_owned());
- Ok(Parsed(expr.note_absurd(), root))
+ Ok(Parsed(expr, root))
}
diff --git a/dhall/src/phase/resolve.rs b/dhall/src/phase/resolve.rs
index ac264e6..5bde68a 100644
--- a/dhall/src/phase/resolve.rs
+++ b/dhall/src/phase/resolve.rs
@@ -1,20 +1,20 @@
use std::collections::HashMap;
use std::path::{Path, PathBuf};
-use dhall_syntax::Import;
-
use crate::error::{Error, ImportError};
-use crate::phase::{Normalized, Parsed, Resolved};
+use crate::phase::{Normalized, NormalizedExpr, Parsed, Resolved};
+
+type Import = dhall_syntax::Import<NormalizedExpr>;
/// A root from which to resolve relative imports.
#[derive(Debug, Clone, PartialEq, Eq)]
-pub enum ImportRoot {
+pub(crate) enum ImportRoot {
LocalDir(PathBuf),
}
type ImportCache = HashMap<Import, Normalized>;
-pub type ImportStack = Vec<Import>;
+pub(crate) type ImportStack = Vec<Import>;
fn resolve_import(
@@ -29,9 +29,9 @@ fn resolve_import(
let cwd = match root {
LocalDir(cwd) => cwd,
};
- match &import.location_hashed.location {
+ match &import.location {
Local(prefix, path) => {
- let path_buf: PathBuf = path.clone().into_iter().collect();
+ let path_buf: PathBuf = path.file_path.iter().collect();
let path_buf = match prefix {
// TODO: fail gracefully
Parent => cwd.parent().unwrap().join(path_buf),
@@ -91,11 +91,11 @@ fn do_resolve_expr(
Ok(Resolved(expr))
}
-pub fn resolve(e: Parsed) -> Result<Resolved, ImportError> {
+pub(crate) fn resolve(e: Parsed) -> Result<Resolved, ImportError> {
do_resolve_expr(e, &mut HashMap::new(), &Vec::new())
}
-pub fn skip_resolve_expr(
+pub(crate) fn skip_resolve_expr(
Parsed(expr, _root): Parsed,
) -> Result<Resolved, ImportError> {
let resolve = |import: &Import| -> Result<Normalized, ImportError> {
@@ -106,18 +106,26 @@ pub fn skip_resolve_expr(
}
#[cfg(test)]
+#[rustfmt::skip]
mod spec_tests {
- #![rustfmt::skip]
-
macro_rules! import_success {
($name:ident, $path:expr) => {
- make_spec_test!(Import, Success, $name, &("../dhall-lang/tests/import/success/".to_owned() + $path));
+ make_spec_test!(
+ ImportSuccess(
+ &("../dhall-lang/tests/import/success/".to_owned() + $path + "A.dhall"),
+ &("../dhall-lang/tests/import/success/".to_owned() + $path + "B.dhall")
+ ),
+ $name
+ );
};
}
// macro_rules! import_failure {
// ($name:ident, $path:expr) => {
- // make_spec_test!(Import, Failure, $name, &("../dhall-lang/tests/import/failure/".to_owned() + $path));
+ // make_spec_test!(
+ // ImportFailure(&("../dhall-lang/tests/import/failure/".to_owned() + $path + ".dhall")),
+ // $name
+ // );
// };
// }
diff --git a/dhall/src/phase/typecheck.rs b/dhall/src/phase/typecheck.rs
index 299997a..9013c1f 100644
--- a/dhall/src/phase/typecheck.rs
+++ b/dhall/src/phase/typecheck.rs
@@ -1,34 +1,29 @@
+use std::cmp::max;
use std::collections::HashMap;
use dhall_syntax::{
- rc, Builtin, Const, Expr, ExprF, InterpolatedTextContents, Label, Span,
- SubExpr, X,
+ rc, Builtin, Const, Expr, ExprF, InterpolatedTextContents, Label,
};
-use crate::core::context::{NormalizationContext, TypecheckContext};
-use crate::core::thunk::{Thunk, TypeThunk};
+use crate::core::context::TypecheckContext;
use crate::core::value::Value;
+use crate::core::valuef::ValueF;
use crate::core::var::{Shift, Subst};
use crate::error::{TypeError, TypeMessage};
-use crate::phase::{Normalized, Resolved, Type, Typed};
+use crate::phase::Normalized;
fn tck_pi_type(
ctx: &TypecheckContext,
x: Label,
- tx: Type,
- te: Type,
-) -> Result<Typed, TypeError> {
+ tx: Value,
+ te: Value,
+) -> Result<Value, TypeError> {
use crate::error::TypeMessage::*;
let ctx2 = ctx.insert_type(&x, tx.clone());
let ka = match tx.get_type()?.as_const() {
Some(k) => k,
- _ => {
- return Err(TypeError::new(
- ctx,
- InvalidInputType(tx.to_normalized()),
- ))
- }
+ _ => return Err(TypeError::new(ctx, InvalidInputType(tx))),
};
let kb = match te.get_type()?.as_const() {
@@ -36,64 +31,58 @@ fn tck_pi_type(
_ => {
return Err(TypeError::new(
&ctx2,
- InvalidOutputType(te.get_type()?.to_normalized()),
+ InvalidOutputType(te.get_type()?),
))
}
};
let k = function_check(ka, kb);
- Ok(Typed::from_thunk_and_type(
- Value::Pi(x.into(), TypeThunk::from_type(tx), TypeThunk::from_type(te))
- .into_thunk(),
- Type::from_const(k),
+ Ok(Value::from_valuef_and_type(
+ ValueF::Pi(x.into(), tx, te),
+ Value::from_const(k),
))
}
fn tck_record_type(
ctx: &TypecheckContext,
- kts: impl IntoIterator<Item = Result<(Label, Type), TypeError>>,
-) -> Result<Typed, TypeError> {
+ kts: impl IntoIterator<Item = Result<(Label, Value), TypeError>>,
+) -> Result<Value, TypeError> {
use crate::error::TypeMessage::*;
use std::collections::hash_map::Entry;
let mut new_kts = HashMap::new();
- // Check that all types are the same const
- let mut k = None;
+ // An empty record type has type Type
+ let mut k = Const::Type;
for e in kts {
let (x, t) = e?;
- match (k, t.get_type()?.as_const()) {
- (None, Some(k2)) => k = Some(k2),
- (Some(k1), Some(k2)) if k1 == k2 => {}
- _ => {
- return Err(TypeError::new(
- ctx,
- InvalidFieldType(x.clone(), t.clone()),
- ))
- }
+ // Construct the union of the contained `Const`s
+ match t.get_type()?.as_const() {
+ Some(k2) => k = max(k, k2),
+ None => return Err(TypeError::new(ctx, InvalidFieldType(x, t))),
}
- let entry = new_kts.entry(x.clone());
+ // Check for duplicated entries
+ let entry = new_kts.entry(x);
match &entry {
Entry::Occupied(_) => {
return Err(TypeError::new(ctx, RecordTypeDuplicateField))
}
- Entry::Vacant(_) => {
- entry.or_insert_with(|| TypeThunk::from_type(t.clone()))
- }
+ Entry::Vacant(_) => entry.or_insert_with(|| t),
};
}
- // An empty record type has type Type
- let k = k.unwrap_or(dhall_syntax::Const::Type);
- Ok(Typed::from_thunk_and_type(
- Value::RecordType(new_kts).into_thunk(),
- Type::from_const(k),
+ Ok(Value::from_valuef_and_type(
+ ValueF::RecordType(new_kts),
+ Value::from_const(k),
))
}
-fn tck_union_type(
+fn tck_union_type<Iter>(
ctx: &TypecheckContext,
- kts: impl IntoIterator<Item = Result<(Label, Option<Type>), TypeError>>,
-) -> Result<Typed, TypeError> {
+ kts: Iter,
+) -> Result<Value, TypeError>
+where
+ Iter: IntoIterator<Item = Result<(Label, Option<Value>), TypeError>>,
+{
use crate::error::TypeMessage::*;
use std::collections::hash_map::Entry;
let mut new_kts = HashMap::new();
@@ -108,49 +97,48 @@ fn tck_union_type(
_ => {
return Err(TypeError::new(
ctx,
- InvalidFieldType(x.clone(), t.clone()),
+ InvalidFieldType(x, t.clone()),
))
}
}
}
- let entry = new_kts.entry(x.clone());
+ let entry = new_kts.entry(x);
match &entry {
Entry::Occupied(_) => {
return Err(TypeError::new(ctx, UnionTypeDuplicateField))
}
- Entry::Vacant(_) => entry.or_insert_with(|| {
- t.as_ref().map(|t| TypeThunk::from_type(t.clone()))
- }),
+ Entry::Vacant(_) => entry.or_insert_with(|| t),
};
}
// An empty union type has type Type;
// an union type with only unary variants also has type Type
- let k = k.unwrap_or(dhall_syntax::Const::Type);
+ let k = k.unwrap_or(Const::Type);
- Ok(Typed::from_thunk_and_type(
- Value::UnionType(new_kts).into_thunk(),
- Type::from_const(k),
+ Ok(Value::from_valuef_and_type(
+ ValueF::UnionType(new_kts),
+ Value::from_const(k),
))
}
fn function_check(a: Const, b: Const) -> Const {
- use dhall_syntax::Const::Type;
- use std::cmp::max;
- if b == Type {
- Type
+ if b == Const::Type {
+ Const::Type
} else {
max(a, b)
}
}
-pub fn type_of_const(c: Const) -> Result<Type, TypeError> {
+pub(crate) fn const_to_value(c: Const) -> Value {
+ let v = ValueF::Const(c);
match c {
- Const::Type => Ok(Type::from_const(Const::Kind)),
- Const::Kind => Ok(Type::from_const(Const::Sort)),
- Const::Sort => {
- Err(TypeError::new(&TypecheckContext::new(), TypeMessage::Sort))
+ Const::Type => {
+ Value::from_valuef_and_type(v, const_to_value(Const::Kind))
}
+ Const::Kind => {
+ Value::from_valuef_and_type(v, const_to_value(Const::Sort))
+ }
+ Const::Sort => Value::const_sort(),
}
}
@@ -210,9 +198,9 @@ macro_rules! make_type {
};
}
-fn type_of_builtin(b: Builtin) -> Expr<X, X> {
+fn type_of_builtin<E>(b: Builtin) -> Expr<E> {
use dhall_syntax::Builtin::*;
- match b {
+ rc(match b {
Bool | Natural | Integer | Double | Text => make_type!(Type),
List | Optional => make_type!(
Type -> Type
@@ -292,28 +280,15 @@ fn type_of_builtin(b: Builtin) -> Expr<X, X> {
OptionalNone => make_type!(
forall (a: Type) -> Optional a
),
- }
-}
-
-/// Takes an expression that is meant to contain a Type
-/// and turn it into a type, typechecking it along the way.
-pub fn mktype(
- ctx: &TypecheckContext,
- e: SubExpr<Span, Normalized>,
-) -> Result<Type, TypeError> {
- Ok(type_with(ctx, e)?.to_type())
-}
-
-pub fn builtin_to_type(b: Builtin) -> Result<Type, TypeError> {
- mktype(&TypecheckContext::new(), SubExpr::from_builtin(b))
+ })
}
-/// Intermediary return type
-enum Ret {
- /// Returns the contained value as is
- RetWhole(Typed),
- /// Use the contained Type as the type of the input expression
- RetTypeOnly(Type),
+pub(crate) fn builtin_to_value(b: Builtin) -> Value {
+ let ctx = TypecheckContext::new();
+ Value::from_valuef_and_type(
+ ValueF::from_builtin(b),
+ type_with(&ctx, type_of_builtin(b)).unwrap(),
+ )
}
/// Type-check an expression and return the expression alongside its type if type-checking
@@ -322,29 +297,25 @@ enum Ret {
/// normalized as well.
fn type_with(
ctx: &TypecheckContext,
- e: SubExpr<Span, Normalized>,
-) -> Result<Typed, TypeError> {
+ e: Expr<Normalized>,
+) -> Result<Value, TypeError> {
use dhall_syntax::ExprF::{Annot, Embed, Lam, Let, Pi, Var};
- use Ret::*;
Ok(match e.as_ref() {
- Lam(x, t, b) => {
- let tx = mktype(ctx, t.clone())?;
- let ctx2 = ctx.insert_type(x, tx.clone());
- let b = type_with(&ctx2, b.clone())?;
- let v = Value::Lam(
- x.clone().into(),
- TypeThunk::from_type(tx.clone()),
- b.to_thunk(),
- );
- let tb = b.get_type()?.into_owned();
- let t = tck_pi_type(ctx, x.clone(), tx, tb)?.to_type();
- Typed::from_thunk_and_type(Thunk::from_value(v), t)
+ Lam(var, annot, body) => {
+ let annot = type_with(ctx, annot.clone())?;
+ let ctx2 = ctx.insert_type(var, annot.clone());
+ let body = type_with(&ctx2, body.clone())?;
+ let body_type = body.get_type()?;
+ Value::from_valuef_and_type(
+ ValueF::Lam(var.clone().into(), annot.clone(), body),
+ tck_pi_type(ctx, var.clone(), annot, body_type)?,
+ )
}
Pi(x, ta, tb) => {
- let ta = mktype(ctx, ta.clone())?;
+ let ta = type_with(ctx, ta.clone())?;
let ctx2 = ctx.insert_type(x, ta.clone());
- let tb = mktype(&ctx2, tb.clone())?;
+ let tb = type_with(&ctx2, tb.clone())?;
return tck_pi_type(ctx, x.clone(), ta, tb);
}
Let(x, t, v, e) => {
@@ -357,9 +328,9 @@ fn type_with(
let v = type_with(ctx, v)?;
return type_with(&ctx.insert_value(x, v.clone())?, e.clone());
}
- Embed(p) => p.clone().into_typed(),
+ Embed(p) => p.clone().into_typed().into_value(),
Var(var) => match ctx.lookup(&var) {
- Some(typed) => typed,
+ Some(typed) => typed.clone(),
None => {
return Err(TypeError::new(
ctx,
@@ -367,25 +338,13 @@ fn type_with(
))
}
},
- _ => {
+ e => {
// Typecheck recursively all subexpressions
- let expr =
- e.as_ref().traverse_ref_with_special_handling_of_binders(
- |e| type_with(ctx, e.clone()),
- |_, _| unreachable!(),
- |_| unreachable!(),
- )?;
- let ret = type_last_layer(ctx, &expr)?;
- match ret {
- RetTypeOnly(typ) => {
- let expr = expr.map_ref_simple(|typed| typed.to_thunk());
- Typed::from_thunk_and_type(
- Thunk::from_partial_expr(expr),
- typ,
- )
- }
- RetWhole(tt) => tt,
- }
+ let expr = e.traverse_ref_with_special_handling_of_binders(
+ |e| type_with(ctx, e.clone()),
+ |_, _| unreachable!(),
+ )?;
+ type_last_layer(ctx, expr)?
}
})
}
@@ -394,473 +353,305 @@ fn type_with(
/// layer.
fn type_last_layer(
ctx: &TypecheckContext,
- e: &ExprF<Typed, X>,
-) -> Result<Ret, TypeError> {
+ e: ExprF<Value, Normalized>,
+) -> Result<Value, TypeError> {
use crate::error::TypeMessage::*;
use dhall_syntax::BinOp::*;
use dhall_syntax::Builtin::*;
use dhall_syntax::Const::Type;
use dhall_syntax::ExprF::*;
+ let mkerr = |msg: TypeMessage| Err(TypeError::new(ctx, msg));
+
+ /// Intermediary return type
+ enum Ret {
+ /// Returns the contained value as is
+ RetWhole(Value),
+ /// Returns the input expression `e` with the contained value as its type
+ RetTypeOnly(Value),
+ }
use Ret::*;
- let mkerr = |msg: TypeMessage| TypeError::new(ctx, msg);
- match e {
+ let ret = match &e {
+ Import(_) => unreachable!(
+ "There should remain no imports in a resolved expression"
+ ),
Lam(_, _, _) | Pi(_, _, _) | Let(_, _, _, _) | Embed(_) | Var(_) => {
unreachable!()
}
App(f, a) => {
let tf = f.get_type()?;
- let (x, tx, tb) = match &tf.to_value() {
- Value::Pi(x, tx, tb) => (x.clone(), tx.to_type(), tb.to_type()),
- _ => return Err(mkerr(NotAFunction(f.clone()))),
+ let tf_borrow = tf.as_whnf();
+ let (x, tx, tb) = match &*tf_borrow {
+ ValueF::Pi(x, tx, tb) => (x, tx, tb),
+ _ => return mkerr(NotAFunction(f.clone())),
};
- if a.get_type()?.as_ref() != &tx {
- return Err(mkerr(TypeMismatch(
- f.clone(),
- tx.to_normalized(),
- a.clone(),
- )));
+ if &a.get_type()? != tx {
+ return mkerr(TypeMismatch(f.clone(), tx.clone(), a.clone()));
}
- Ok(RetTypeOnly(tb.subst_shift(&x.into(), &a)))
+ RetTypeOnly(tb.subst_shift(&x.into(), a))
}
Annot(x, t) => {
- let t = t.to_type();
- if &t != x.get_type()?.as_ref() {
- return Err(mkerr(AnnotMismatch(x.clone(), t.to_normalized())));
+ if &x.get_type()? != t {
+ return mkerr(AnnotMismatch(x.clone(), t.clone()));
}
- Ok(RetTypeOnly(x.get_type()?.into_owned()))
+ RetWhole(x.clone())
}
Assert(t) => {
- match t.to_value() {
- Value::Equivalence(x, y) if x == y => {}
- Value::Equivalence(x, y) => {
- return Err(mkerr(AssertMismatch(
- x.to_typed(),
- y.to_typed(),
- )))
+ match &*t.as_whnf() {
+ ValueF::Equivalence(x, y) if x == y => {}
+ ValueF::Equivalence(x, y) => {
+ return mkerr(AssertMismatch(x.clone(), y.clone()))
}
- _ => return Err(mkerr(AssertMustTakeEquivalence)),
+ _ => return mkerr(AssertMustTakeEquivalence),
}
- Ok(RetTypeOnly(t.to_type()))
+ RetTypeOnly(t.clone())
}
BoolIf(x, y, z) => {
- if x.get_type()?.as_ref() != &builtin_to_type(Bool)? {
- return Err(mkerr(InvalidPredicate(x.clone())));
+ if *x.get_type()?.as_whnf() != ValueF::from_builtin(Bool) {
+ return mkerr(InvalidPredicate(x.clone()));
}
if y.get_type()?.get_type()?.as_const() != Some(Type) {
- return Err(mkerr(IfBranchMustBeTerm(true, y.clone())));
+ return mkerr(IfBranchMustBeTerm(true, y.clone()));
}
if z.get_type()?.get_type()?.as_const() != Some(Type) {
- return Err(mkerr(IfBranchMustBeTerm(false, z.clone())));
+ return mkerr(IfBranchMustBeTerm(false, z.clone()));
}
if y.get_type()? != z.get_type()? {
- return Err(mkerr(IfBranchMismatch(y.clone(), z.clone())));
+ return mkerr(IfBranchMismatch(y.clone(), z.clone()));
}
- Ok(RetTypeOnly(y.get_type()?.into_owned()))
+ RetTypeOnly(y.get_type()?)
}
EmptyListLit(t) => {
- let t = t.to_type();
- match &t.to_value() {
- Value::AppliedBuiltin(dhall_syntax::Builtin::List, args)
+ match &*t.as_whnf() {
+ ValueF::AppliedBuiltin(dhall_syntax::Builtin::List, args)
if args.len() == 1 => {}
- _ => {
- return Err(TypeError::new(
- ctx,
- InvalidListType(t.to_normalized()),
- ))
- }
+ _ => return mkerr(InvalidListType(t.clone())),
}
- Ok(RetTypeOnly(t))
+ RetTypeOnly(t.clone())
}
NEListLit(xs) => {
let mut iter = xs.iter().enumerate();
let (_, x) = iter.next().unwrap();
for (i, y) in iter {
if x.get_type()? != y.get_type()? {
- return Err(mkerr(InvalidListElement(
+ return mkerr(InvalidListElement(
i,
- x.get_type()?.to_normalized(),
+ x.get_type()?,
y.clone(),
- )));
+ ));
}
}
let t = x.get_type()?;
if t.get_type()?.as_const() != Some(Type) {
- return Err(TypeError::new(
- ctx,
- InvalidListType(t.to_normalized()),
- ));
+ return mkerr(InvalidListType(t));
}
- Ok(RetTypeOnly(
- Typed::from_thunk_and_type(
- Value::from_builtin(dhall_syntax::Builtin::List)
- .app(t.to_value())
- .into_thunk(),
- Typed::from_const(Type),
- )
- .to_type(),
- ))
+ RetTypeOnly(Value::from_builtin(dhall_syntax::Builtin::List).app(t))
}
SomeLit(x) => {
- let t = x.get_type()?.into_owned();
+ let t = x.get_type()?;
if t.get_type()?.as_const() != Some(Type) {
- return Err(TypeError::new(
- ctx,
- InvalidOptionalType(t.to_normalized()),
- ));
+ return mkerr(InvalidOptionalType(t));
}
- Ok(RetTypeOnly(
- Typed::from_thunk_and_type(
- Value::from_builtin(dhall_syntax::Builtin::Optional)
- .app(t.to_value())
- .into_thunk(),
- Typed::from_const(Type).into_type(),
- )
- .to_type(),
- ))
+ RetTypeOnly(
+ Value::from_builtin(dhall_syntax::Builtin::Optional).app(t),
+ )
}
- RecordType(kts) => Ok(RetWhole(tck_record_type(
+ RecordType(kts) => RetWhole(tck_record_type(
ctx,
- kts.iter().map(|(x, t)| Ok((x.clone(), t.to_type()))),
- )?)),
- UnionType(kts) => Ok(RetWhole(tck_union_type(
+ kts.iter().map(|(x, t)| Ok((x.clone(), t.clone()))),
+ )?),
+ UnionType(kts) => RetWhole(tck_union_type(
ctx,
- kts.iter()
- .map(|(x, t)| Ok((x.clone(), t.as_ref().map(|t| t.to_type())))),
- )?)),
- RecordLit(kvs) => Ok(RetTypeOnly(
- tck_record_type(
- ctx,
- kvs.iter()
- .map(|(x, v)| Ok((x.clone(), v.get_type()?.into_owned()))),
- )?
- .into_type(),
- )),
+ kts.iter().map(|(x, t)| Ok((x.clone(), t.clone()))),
+ )?),
+ RecordLit(kvs) => RetTypeOnly(tck_record_type(
+ ctx,
+ kvs.iter().map(|(x, v)| Ok((x.clone(), v.get_type()?))),
+ )?),
Field(r, x) => {
- match &r.get_type()?.to_value() {
- Value::RecordType(kts) => match kts.get(&x) {
+ match &*r.get_type()?.as_whnf() {
+ ValueF::RecordType(kts) => match kts.get(&x) {
Some(tth) => {
- Ok(RetTypeOnly(tth.to_type()))
+ RetTypeOnly(tth.clone())
},
- None => Err(mkerr(MissingRecordField(x.clone(),
- r.clone()))),
+ None => return mkerr(MissingRecordField(x.clone(),
+ r.clone())),
},
// TODO: branch here only when r.get_type() is a Const
_ => {
- let r = r.to_type();
- match &r.to_value() {
- Value::UnionType(kts) => match kts.get(&x) {
+ match &*r.as_whnf() {
+ ValueF::UnionType(kts) => match kts.get(&x) {
// Constructor has type T -> < x: T, ... >
Some(Some(t)) => {
- // TODO: avoid capture
- Ok(RetTypeOnly(
+ RetTypeOnly(
tck_pi_type(
ctx,
"_".into(),
- t.to_type(),
- r.clone(),
- )?.to_type()
- ))
+ t.clone(),
+ r.under_binder(Label::from("_")),
+ )?
+ )
},
Some(None) => {
- Ok(RetTypeOnly(r.clone()))
+ RetTypeOnly(r.clone())
},
None => {
- Err(mkerr(MissingUnionField(
+ return mkerr(MissingUnionField(
x.clone(),
- r.to_normalized(),
- )))
+ r.clone(),
+ ))
},
},
_ => {
- Err(mkerr(NotARecord(
+ return mkerr(NotARecord(
x.clone(),
- r.to_normalized()
- )))
+ r.clone()
+ ))
},
}
}
- // _ => Err(mkerr(NotARecord(
+ // _ => mkerr(NotARecord(
// x,
- // r.to_type()?.to_normalized(),
- // ))),
+ // r?,
+ // )),
}
}
- Const(c) => Ok(RetWhole(Typed::from_const(*c))),
- Builtin(b) => {
- Ok(RetTypeOnly(mktype(ctx, rc(type_of_builtin(*b)).absurd())?))
- }
- BoolLit(_) => Ok(RetTypeOnly(builtin_to_type(Bool)?)),
- NaturalLit(_) => Ok(RetTypeOnly(builtin_to_type(Natural)?)),
- IntegerLit(_) => Ok(RetTypeOnly(builtin_to_type(Integer)?)),
- DoubleLit(_) => Ok(RetTypeOnly(builtin_to_type(Double)?)),
+ Const(c) => RetWhole(const_to_value(*c)),
+ Builtin(b) => RetWhole(builtin_to_value(*b)),
+ BoolLit(_) => RetTypeOnly(builtin_to_value(Bool)),
+ NaturalLit(_) => RetTypeOnly(builtin_to_value(Natural)),
+ IntegerLit(_) => RetTypeOnly(builtin_to_value(Integer)),
+ DoubleLit(_) => RetTypeOnly(builtin_to_value(Double)),
TextLit(interpolated) => {
- let text_type = builtin_to_type(Text)?;
+ let text_type = builtin_to_value(Text);
for contents in interpolated.iter() {
use InterpolatedTextContents::Expr;
if let Expr(x) = contents {
- if x.get_type()?.as_ref() != &text_type {
- return Err(mkerr(InvalidTextInterpolation(x.clone())));
+ if x.get_type()? != text_type {
+ return mkerr(InvalidTextInterpolation(x.clone()));
}
}
}
- Ok(RetTypeOnly(text_type))
+ RetTypeOnly(text_type)
}
BinOp(RightBiasedRecordMerge, l, r) => {
use crate::phase::normalize::merge_maps;
let l_type = l.get_type()?;
- let l_kind = l_type.get_type()?;
let r_type = r.get_type()?;
- let r_kind = r_type.get_type()?;
-
- // Check the equality of kinds.
- // This is to disallow expression such as:
- // "{ x = Text } // { y = 1 }"
- if l_kind != r_kind {
- return Err(mkerr(RecordMismatch(l.clone(), r.clone())));
- }
// Extract the LHS record type
- let kts_x = match l_type.to_value() {
- Value::RecordType(kts) => kts,
- _ => return Err(mkerr(MustCombineRecord(l.clone()))),
+ let l_type_borrow = l_type.as_whnf();
+ let kts_x = match &*l_type_borrow {
+ ValueF::RecordType(kts) => kts,
+ _ => return mkerr(MustCombineRecord(l.clone())),
};
// Extract the RHS record type
- let kts_y = match r_type.to_value() {
- Value::RecordType(kts) => kts,
- _ => return Err(mkerr(MustCombineRecord(r.clone()))),
+ let r_type_borrow = r_type.as_whnf();
+ let kts_y = match &*r_type_borrow {
+ ValueF::RecordType(kts) => kts,
+ _ => return mkerr(MustCombineRecord(r.clone())),
};
// Union the two records, prefering
// the values found in the RHS.
- let kts = merge_maps(&kts_x, &kts_y, |_, r_t| r_t.clone());
+ let kts = merge_maps::<_, _, _, !>(kts_x, kts_y, |_, _, r_t| {
+ Ok(r_t.clone())
+ })?;
// Construct the final record type from the union
- Ok(RetTypeOnly(
- tck_record_type(
- ctx,
- kts.iter().map(|(x, v)| Ok((x.clone(), v.to_type()))),
- )?
- .into_type(),
- ))
- }
- BinOp(RecursiveRecordMerge, l, r) => {
- // A recursive function to dig down into
- // records of records when merging.
- fn combine_record_types(
- ctx: &TypecheckContext,
- kts_l: HashMap<Label, TypeThunk>,
- kts_r: HashMap<Label, TypeThunk>,
- ) -> Result<Typed, TypeError> {
- use crate::phase::normalize::outer_join;
-
- // If the Label exists for both records and Type for the values
- // are records themselves, then we hit the recursive case.
- // Otherwise we have a field collision.
- let combine = |k: &Label,
- inner_l: &TypeThunk,
- inner_r: &TypeThunk|
- -> Result<Typed, TypeError> {
- match (inner_l.to_value(), inner_r.to_value()) {
- (
- Value::RecordType(inner_l_kvs),
- Value::RecordType(inner_r_kvs),
- ) => {
- combine_record_types(ctx, inner_l_kvs, inner_r_kvs)
- }
- (_, _) => {
- Err(TypeError::new(ctx, FieldCollision(k.clone())))
- }
- }
- };
-
- let kts: HashMap<Label, Result<Typed, TypeError>> = outer_join(
- |l| Ok(l.to_type()),
- |r| Ok(r.to_type()),
- |k: &Label, l: &TypeThunk, r: &TypeThunk| combine(k, l, r),
- &kts_l,
- &kts_r,
- );
-
- Ok(tck_record_type(
- ctx,
- kts.into_iter().map(|(x, v)| v.map(|r| (x.clone(), r))),
- )?
- .into_type())
- };
-
- let l_type = l.get_type()?;
- let l_kind = l_type.get_type()?;
- let r_type = r.get_type()?;
- let r_kind = r_type.get_type()?;
-
- // Check the equality of kinds.
- // This is to disallow expression such as:
- // "{ x = Text } // { y = 1 }"
- if l_kind != r_kind {
- return Err(mkerr(RecordMismatch(l.clone(), r.clone())));
- }
-
- // Extract the LHS record type
- let kts_x = match l_type.to_value() {
- Value::RecordType(kts) => kts,
- _ => return Err(mkerr(MustCombineRecord(l.clone()))),
- };
-
- // Extract the RHS record type
- let kts_y = match r_type.to_value() {
- Value::RecordType(kts) => kts,
- _ => return Err(mkerr(MustCombineRecord(r.clone()))),
- };
-
- combine_record_types(ctx, kts_x, kts_y).map(|r| RetTypeOnly(r))
+ RetTypeOnly(tck_record_type(
+ ctx,
+ kts.into_iter().map(|(x, v)| Ok((x.clone(), v))),
+ )?)
}
+ BinOp(RecursiveRecordMerge, l, r) => RetTypeOnly(type_last_layer(
+ ctx,
+ ExprF::BinOp(
+ RecursiveRecordTypeMerge,
+ l.get_type()?,
+ r.get_type()?,
+ ),
+ )?),
BinOp(RecursiveRecordTypeMerge, l, r) => {
- // A recursive function to dig down into
- // records of records when merging.
- fn combine_record_types(
- ctx: &TypecheckContext,
- kts_l: HashMap<Label, TypeThunk>,
- kts_r: HashMap<Label, TypeThunk>,
- ) -> Result<Typed, TypeError> {
- use crate::phase::normalize::intersection_with_key;
-
- // If the Label exists for both records and Type for the values
- // are records themselves, then we hit the recursive case.
- // Otherwise we have a field collision.
- let combine = |k: &Label,
- kts_l_inner: &TypeThunk,
- kts_r_inner: &TypeThunk|
- -> Result<Typed, TypeError> {
- match (kts_l_inner.to_value(), kts_r_inner.to_value()) {
- (
- Value::RecordType(kvs_l_inner),
- Value::RecordType(kvs_r_inner),
- ) => {
- combine_record_types(ctx, kvs_l_inner, kvs_r_inner)
- }
- (_, _) => {
- Err(TypeError::new(ctx, FieldCollision(k.clone())))
- }
- }
- };
-
- let kts = intersection_with_key(
- |k: &Label, l: &TypeThunk, r: &TypeThunk| combine(k, l, r),
- &kts_l,
- &kts_r,
- );
-
- Ok(tck_record_type(
- ctx,
- kts.into_iter().map(|(x, v)| v.map(|r| (x.clone(), r))),
- )?
- .into_type())
- };
-
- // Extract the Const of the LHS
- let k_l = match l.get_type()?.to_value() {
- Value::Const(k) => k,
- _ => {
- return Err(mkerr(RecordTypeMergeRequiresRecordType(
- l.clone(),
- )))
- }
- };
-
- // Extract the Const of the RHS
- let k_r = match r.get_type()?.to_value() {
- Value::Const(k) => k,
- _ => {
- return Err(mkerr(RecordTypeMergeRequiresRecordType(
- r.clone(),
- )))
- }
- };
-
- // Const values must match for the Records
- let k = if k_l == k_r {
- k_l
- } else {
- return Err(mkerr(RecordTypeMismatch(
- Typed::from_const(k_l),
- Typed::from_const(k_r),
- l.clone(),
- r.clone(),
- )));
- };
+ use crate::phase::normalize::merge_maps;
// Extract the LHS record type
- let kts_x = match l.to_value() {
- Value::RecordType(kts) => kts,
+ let borrow_l = l.as_whnf();
+ let kts_x = match &*borrow_l {
+ ValueF::RecordType(kts) => kts,
_ => {
- return Err(mkerr(RecordTypeMergeRequiresRecordType(
- l.clone(),
- )))
+ return mkerr(RecordTypeMergeRequiresRecordType(l.clone()))
}
};
// Extract the RHS record type
- let kts_y = match r.to_value() {
- Value::RecordType(kts) => kts,
+ let borrow_r = r.as_whnf();
+ let kts_y = match &*borrow_r {
+ ValueF::RecordType(kts) => kts,
_ => {
- return Err(mkerr(RecordTypeMergeRequiresRecordType(
- r.clone(),
- )))
+ return mkerr(RecordTypeMergeRequiresRecordType(r.clone()))
}
};
// Ensure that the records combine without a type error
- // and if not output the final Const value.
- combine_record_types(ctx, kts_x, kts_y)
- .and(Ok(RetTypeOnly(Typed::from_const(k))))
+ let kts = merge_maps(
+ kts_x,
+ kts_y,
+ // If the Label exists for both records, then we hit the recursive case.
+ |_, l: &Value, r: &Value| {
+ type_last_layer(
+ ctx,
+ ExprF::BinOp(
+ RecursiveRecordTypeMerge,
+ l.clone(),
+ r.clone(),
+ ),
+ )
+ },
+ )?;
+
+ RetWhole(tck_record_type(ctx, kts.into_iter().map(Ok))?)
}
BinOp(o @ ListAppend, l, r) => {
- match l.get_type()?.to_value() {
- Value::AppliedBuiltin(List, _) => {}
- _ => return Err(mkerr(BinOpTypeMismatch(*o, l.clone()))),
+ match &*l.get_type()?.as_whnf() {
+ ValueF::AppliedBuiltin(List, _) => {}
+ _ => return mkerr(BinOpTypeMismatch(*o, l.clone())),
}
if l.get_type()? != r.get_type()? {
- return Err(mkerr(BinOpTypeMismatch(*o, r.clone())));
+ return mkerr(BinOpTypeMismatch(*o, r.clone()));
}
- Ok(RetTypeOnly(l.get_type()?.into_owned()))
+ RetTypeOnly(l.get_type()?)
}
BinOp(Equivalence, l, r) => {
if l.get_type()?.get_type()?.as_const() != Some(Type) {
- return Err(mkerr(EquivalenceArgumentMustBeTerm(
- true,
- l.clone(),
- )));
+ return mkerr(EquivalenceArgumentMustBeTerm(true, l.clone()));
}
if r.get_type()?.get_type()?.as_const() != Some(Type) {
- return Err(mkerr(EquivalenceArgumentMustBeTerm(
- false,
- r.clone(),
- )));
+ return mkerr(EquivalenceArgumentMustBeTerm(false, r.clone()));
}
if l.get_type()? != r.get_type()? {
- return Err(mkerr(EquivalenceTypeMismatch(
- r.clone(),
- l.clone(),
- )));
+ return mkerr(EquivalenceTypeMismatch(r.clone(), l.clone()));
}
- Ok(RetTypeOnly(Typed::from_const(Type).into_type()))
+ RetWhole(Value::from_valuef_and_type(
+ ValueF::Equivalence(l.clone(), r.clone()),
+ Value::from_const(Type),
+ ))
}
BinOp(o, l, r) => {
- let t = builtin_to_type(match o {
+ let t = builtin_to_value(match o {
BoolAnd => Bool,
BoolOr => Bool,
BoolEQ => Bool,
@@ -874,143 +665,140 @@ fn type_last_layer(
RecursiveRecordTypeMerge => unreachable!(),
ImportAlt => unreachable!("There should remain no import alternatives in a resolved expression"),
Equivalence => unreachable!(),
- })?;
+ });
- if l.get_type()?.as_ref() != &t {
- return Err(mkerr(BinOpTypeMismatch(*o, l.clone())));
+ if l.get_type()? != t {
+ return mkerr(BinOpTypeMismatch(*o, l.clone()));
}
- if r.get_type()?.as_ref() != &t {
- return Err(mkerr(BinOpTypeMismatch(*o, r.clone())));
+ if r.get_type()? != t {
+ return mkerr(BinOpTypeMismatch(*o, r.clone()));
}
- Ok(RetTypeOnly(t))
+ RetTypeOnly(t)
}
Merge(record, union, type_annot) => {
- let handlers = match record.get_type()?.to_value() {
- Value::RecordType(kts) => kts,
- _ => return Err(mkerr(Merge1ArgMustBeRecord(record.clone()))),
+ let record_type = record.get_type()?;
+ let record_borrow = record_type.as_whnf();
+ let handlers = match &*record_borrow {
+ ValueF::RecordType(kts) => kts,
+ _ => return mkerr(Merge1ArgMustBeRecord(record.clone())),
};
- let variants = match union.get_type()?.to_value() {
- Value::UnionType(kts) => kts,
- _ => return Err(mkerr(Merge2ArgMustBeUnion(union.clone()))),
+ let union_type = union.get_type()?;
+ let union_borrow = union_type.as_whnf();
+ let variants = match &*union_borrow {
+ ValueF::UnionType(kts) => kts,
+ _ => return mkerr(Merge2ArgMustBeUnion(union.clone())),
};
let mut inferred_type = None;
- for (x, handler) in handlers.iter() {
- let handler_return_type = match variants.get(x) {
- // Union alternative with type
- Some(Some(variant_type)) => {
- let variant_type = variant_type.to_type();
- let handler_type = handler.to_type();
- let (x, tx, tb) = match &handler_type.to_value() {
- Value::Pi(x, tx, tb) => {
- (x.clone(), tx.to_type(), tb.to_type())
+ for (x, handler_type) in handlers {
+ let handler_return_type =
+ match variants.get(x) {
+ // Union alternative with type
+ Some(Some(variant_type)) => {
+ let handler_type_borrow = handler_type.as_whnf();
+ let (x, tx, tb) = match &*handler_type_borrow {
+ ValueF::Pi(x, tx, tb) => (x, tx, tb),
+ _ => {
+ return mkerr(NotAFunction(
+ handler_type.clone(),
+ ))
+ }
+ };
+
+ if variant_type != tx {
+ return mkerr(TypeMismatch(
+ handler_type.clone(),
+ tx.clone(),
+ variant_type.clone(),
+ ));
}
- _ => return Err(mkerr(NotAFunction(handler_type))),
- };
-
- if &variant_type != &tx {
- return Err(mkerr(TypeMismatch(
- handler_type,
- tx.to_normalized(),
- variant_type,
- )));
- }
- // Extract `tb` from under the `x` binder. Fails is `x` was free in `tb`.
- match tb.over_binder(x) {
- Some(x) => x,
- None => {
- return Err(mkerr(
+ // Extract `tb` from under the `x` binder. Fails is `x` was free in `tb`.
+ match tb.over_binder(x) {
+ Some(x) => x,
+ None => return mkerr(
MergeHandlerReturnTypeMustNotBeDependent,
- ))
+ ),
}
}
- }
- // Union alternative without type
- Some(None) => handler.to_type(),
- None => {
- return Err(mkerr(MergeHandlerMissingVariant(
- x.clone(),
- )))
- }
- };
+ // Union alternative without type
+ Some(None) => handler_type.clone(),
+ None => {
+ return mkerr(MergeHandlerMissingVariant(x.clone()))
+ }
+ };
match &inferred_type {
None => inferred_type = Some(handler_return_type),
Some(t) => {
if t != &handler_return_type {
- return Err(mkerr(MergeHandlerTypeMismatch));
+ return mkerr(MergeHandlerTypeMismatch);
}
}
}
}
for x in variants.keys() {
if !handlers.contains_key(x) {
- return Err(mkerr(MergeVariantMissingHandler(x.clone())));
+ return mkerr(MergeVariantMissingHandler(x.clone()));
}
}
match (inferred_type, type_annot) {
(Some(ref t1), Some(t2)) => {
- let t2 = t2.to_type();
- if t1 != &t2 {
- return Err(mkerr(MergeAnnotMismatch));
+ if t1 != t2 {
+ return mkerr(MergeAnnotMismatch);
}
- Ok(RetTypeOnly(t2))
+ RetTypeOnly(t2.clone())
}
- (Some(t), None) => Ok(RetTypeOnly(t)),
- (None, Some(t)) => Ok(RetTypeOnly(t.to_type())),
- (None, None) => Err(mkerr(MergeEmptyNeedsAnnotation)),
+ (Some(t), None) => RetTypeOnly(t),
+ (None, Some(t)) => RetTypeOnly(t.clone()),
+ (None, None) => return mkerr(MergeEmptyNeedsAnnotation),
}
}
+ ToMap(_, _) => unimplemented!("toMap"),
Projection(record, labels) => {
- let trecord = record.get_type()?;
- let kts = match trecord.to_value() {
- Value::RecordType(kts) => kts,
- _ => return Err(mkerr(ProjectionMustBeRecord)),
+ let record_type = record.get_type()?;
+ let record_borrow = record_type.as_whnf();
+ let kts = match &*record_borrow {
+ ValueF::RecordType(kts) => kts,
+ _ => return mkerr(ProjectionMustBeRecord),
};
let mut new_kts = HashMap::new();
for l in labels {
match kts.get(l) {
- None => return Err(mkerr(ProjectionMissingEntry)),
+ None => return mkerr(ProjectionMissingEntry),
Some(t) => new_kts.insert(l.clone(), t.clone()),
};
}
- Ok(RetTypeOnly(
- Typed::from_thunk_and_type(
- Value::RecordType(new_kts).into_thunk(),
- trecord.get_type()?.into_owned(),
- )
- .to_type(),
+ RetTypeOnly(Value::from_valuef_and_type(
+ ValueF::RecordType(new_kts),
+ record_type.get_type()?,
))
}
- }
+ };
+
+ Ok(match ret {
+ RetTypeOnly(typ) => {
+ Value::from_valuef_and_type(ValueF::PartialExpr(e), typ)
+ }
+ RetWhole(v) => v,
+ })
}
-/// `typeOf` is the same as `type_with` with an empty context, meaning that the
+/// `type_of` is the same as `type_with` with an empty context, meaning that the
/// expression must be closed (i.e. no free variables), otherwise type-checking
/// will fail.
-fn type_of(e: SubExpr<Span, Normalized>) -> Result<Typed, TypeError> {
- let ctx = TypecheckContext::new();
- let e = type_with(&ctx, e)?;
- // Ensure `e` has a type (i.e. `e` is not `Sort`)
- e.get_type()?;
- Ok(e)
+pub(crate) fn typecheck(e: Expr<Normalized>) -> Result<Value, TypeError> {
+ type_with(&TypecheckContext::new(), e)
}
-pub fn typecheck(e: Resolved) -> Result<Typed, TypeError> {
- type_of(e.0)
-}
-
-pub fn typecheck_with(e: Resolved, ty: &Type) -> Result<Typed, TypeError> {
- let expr: SubExpr<_, _> = e.0;
- let ty: SubExpr<_, _> = ty.to_expr().absurd();
- type_of(expr.rewrap(ExprF::Annot(expr.clone(), ty)))
-}
-pub fn skip_typecheck(e: Resolved) -> Typed {
- Typed::from_thunk_untyped(Thunk::new(NormalizationContext::new(), e.0))
+pub(crate) fn typecheck_with(
+ expr: Expr<Normalized>,
+ ty: Expr<Normalized>,
+) -> Result<Value, TypeError> {
+ typecheck(expr.rewrap(ExprF::Annot(expr.clone(), ty)))
}
diff --git a/dhall/src/tests.rs b/dhall/src/tests.rs
index 15bc97a..ae41038 100644
--- a/dhall/src/tests.rs
+++ b/dhall/src/tests.rs
@@ -1,3 +1,6 @@
+#[cfg(not(test))]
+use assert_eq as assert_eq_pretty;
+#[cfg(test)]
use pretty_assertions::assert_eq as assert_eq_pretty;
macro_rules! assert_eq_display {
@@ -19,13 +22,13 @@ right: `{}`"#,
#[macro_export]
macro_rules! make_spec_test {
- ($type:ident, $status:ident, $name:ident, $path:expr) => {
+ ($type:expr, $name:ident) => {
#[test]
#[allow(non_snake_case)]
fn $name() {
+ use crate::tests::Test::*;
use crate::tests::*;
- match run_test_stringy_error($path, Feature::$type, Status::$status)
- {
+ match run_test_stringy_error($type) {
Ok(_) => {}
Err(s) => panic!(s),
}
@@ -40,203 +43,183 @@ use std::path::PathBuf;
use crate::error::{Error, Result};
use crate::phase::Parsed;
-#[derive(Copy, Clone)]
-pub enum Feature {
- Parser,
- Printer,
- BinaryEncoding,
- BinaryDecoding,
- Import,
- Normalization,
- AlphaNormalization,
- Typecheck,
- TypeInference,
-}
-
-#[derive(Copy, Clone)]
-pub enum Status {
- Success,
- Failure,
+#[allow(dead_code)]
+#[derive(Clone)]
+pub enum Test<'a> {
+ ParserSuccess(&'a str, &'a str),
+ ParserFailure(&'a str),
+ Printer(&'a str, &'a str),
+ BinaryEncoding(&'a str, &'a str),
+ BinaryDecodingSuccess(&'a str, &'a str),
+ BinaryDecodingFailure(&'a str),
+ ImportSuccess(&'a str, &'a str),
+ ImportFailure(&'a str),
+ TypecheckSuccess(&'a str, &'a str),
+ TypecheckFailure(&'a str),
+ TypeInferenceSuccess(&'a str, &'a str),
+ TypeInferenceFailure(&'a str),
+ Normalization(&'a str, &'a str),
+ AlphaNormalization(&'a str, &'a str),
}
-fn parse_file_str<'i>(file_path: &str) -> Result<Parsed> {
+fn parse_file_str(file_path: &str) -> Result<Parsed> {
Parsed::parse_file(&PathBuf::from(file_path))
}
+#[allow(dead_code)]
pub fn run_test_stringy_error(
- base_path: &str,
- feature: Feature,
- status: Status,
+ test: Test<'_>,
) -> std::result::Result<(), String> {
- let base_path: String = base_path.to_string();
- run_test(&base_path, feature, status)
- .map_err(|e| e.to_string())
- .map(|_| ())
+ run_test(test).map_err(|e| e.to_string()).map(|_| ())
}
-pub fn run_test(
- base_path: &str,
- feature: Feature,
- status: Status,
-) -> Result<()> {
- use self::Feature::*;
- use self::Status::*;
- let base_path = base_path.to_owned();
- match status {
- Success => {
- match feature {
- BinaryDecoding => {
- let expr_file_path = base_path.clone() + "A.dhallb";
- let expr_file_path = PathBuf::from(&expr_file_path);
- let mut expr_data = Vec::new();
- {
- File::open(&expr_file_path)?
- .read_to_end(&mut expr_data)?;
- }
- let expr = Parsed::parse_binary(&expr_data)?;
- let expected_file_path = base_path + "B.dhall";
- let expected = parse_file_str(&expected_file_path)?;
- assert_eq_pretty!(expr, expected);
-
- return Ok(());
+pub fn run_test(test: Test<'_>) -> Result<()> {
+ use self::Test::*;
+ match test {
+ ParserSuccess(expr_file_path, expected_file_path) => {
+ let expr = parse_file_str(&expr_file_path)?;
+ // This exercices both parsing and binary decoding
+ // Compare parse/decoded
+ let expected =
+ Parsed::parse_binary_file(&PathBuf::from(expected_file_path))?;
+ assert_eq_pretty!(expr, expected);
+ }
+ ParserFailure(file_path) => {
+ let err = parse_file_str(&file_path).unwrap_err();
+ match &err {
+ Error::Parse(_) => {}
+ Error::IO(e) if e.kind() == std::io::ErrorKind::InvalidData => {
}
- _ => {}
+ e => panic!("Expected parse error, got: {:?}", e),
}
- let expr_file_path = base_path.clone() + "A.dhall";
+ }
+ BinaryEncoding(expr_file_path, expected_file_path) => {
let expr = parse_file_str(&expr_file_path)?;
-
- match feature {
- Parser => {
- // This exercices both parsing and binary decoding
- // Compare parse/decoded
- let expected_file_path = base_path + "B.dhallb";
- let expected_file_path = PathBuf::from(&expected_file_path);
- let mut expected_data = Vec::new();
- {
- File::open(&expected_file_path)?
- .read_to_end(&mut expected_data)?;
- }
- let expected = Parsed::parse_binary(&expected_data)?;
- assert_eq_pretty!(expr, expected);
-
- return Ok(());
- }
- Printer => {
- // Round-trip pretty-printer
- let expr_string = expr.to_string();
- let expected = expr;
- let expr: Parsed = Parsed::parse_str(&expr_string)?;
- assert_eq!(expr, expected);
-
- return Ok(());
- }
- BinaryEncoding => {
- let expected_file_path = base_path + "B.dhallb";
- let expected_file_path = PathBuf::from(&expected_file_path);
- let mut expected_data = Vec::new();
- {
- File::open(&expected_file_path)?
- .read_to_end(&mut expected_data)?;
- }
- let expr_data = expr.encode()?;
-
- // Compare bit-by-bit
- if expr_data != expected_data {
- // use std::io::Write;
- // File::create(&expected_file_path)?.write_all(&expr_data)?;
- // Pretty-print difference
- assert_eq_pretty!(
- serde_cbor::de::from_slice::<
- serde_cbor::value::Value,
- >(&expr_data)
- .unwrap(),
- serde_cbor::de::from_slice::<
- serde_cbor::value::Value,
- >(&expected_data)
- .unwrap()
- );
- // If difference was not visible in the cbor::Value
- assert_eq!(expr_data, expected_data);
- }
-
- return Ok(());
- }
- _ => {}
+ let mut expected_data = Vec::new();
+ {
+ File::open(&PathBuf::from(&expected_file_path))?
+ .read_to_end(&mut expected_data)?;
}
+ let expr_data = expr.encode()?;
+
+ // Compare bit-by-bit
+ if expr_data != expected_data {
+ // use std::io::Write;
+ // File::create(&expected_file_path)?.write_all(&expr_data)?;
+ // Pretty-print difference
+ assert_eq_pretty!(
+ serde_cbor::de::from_slice::<serde_cbor::value::Value>(
+ &expr_data
+ )
+ .unwrap(),
+ serde_cbor::de::from_slice::<serde_cbor::value::Value>(
+ &expected_data
+ )
+ .unwrap()
+ );
+ // If difference was not visible in the cbor::Value
+ assert_eq!(expr_data, expected_data);
+ }
+ }
+ BinaryDecodingSuccess(expr_file_path, expected_file_path) => {
+ let expr =
+ Parsed::parse_binary_file(&PathBuf::from(expr_file_path))?;
+ let expected = parse_file_str(&expected_file_path)?;
+ assert_eq_pretty!(expr, expected);
+ }
+ BinaryDecodingFailure(file_path) => {
+ Parsed::parse_binary_file(&PathBuf::from(file_path)).unwrap_err();
+ }
+ Printer(expr_file_path, _) => {
+ let expected = parse_file_str(&expr_file_path)?;
+ // Round-trip pretty-printer
+ let expr: Parsed = Parsed::parse_str(&expected.to_string())?;
+ assert_eq!(expr, expected);
+ }
+ ImportSuccess(expr_file_path, expected_file_path) => {
+ let expr = parse_file_str(&expr_file_path)?
+ .resolve()?
+ .typecheck()?
+ .normalize();
+ let expected = parse_file_str(&expected_file_path)?
+ .resolve()?
+ .typecheck()?
+ .normalize();
- let expr = expr.resolve()?;
-
- let expected_file_path = base_path + "B.dhall";
+ assert_eq_display!(expr, expected);
+ }
+ ImportFailure(file_path) => {
+ parse_file_str(&file_path)?.resolve().unwrap_err();
+ }
+ TypecheckSuccess(expr_file_path, expected_file_path) => {
+ let expr = parse_file_str(&expr_file_path)?.resolve()?;
let expected = parse_file_str(&expected_file_path)?
.resolve()?
- .skip_typecheck()
+ .typecheck()?
.normalize();
- match feature {
- Parser | Printer | BinaryEncoding | BinaryDecoding => {
- unreachable!()
- }
- Import => {
- let expr = expr.skip_typecheck().normalize();
- assert_eq_display!(expr, expected);
- }
- Typecheck => {
- expr.typecheck_with(&expected.to_type())?;
- }
- TypeInference => {
- let expr = expr.typecheck()?;
- let ty = expr.get_type()?.into_owned();
- assert_eq_display!(ty.to_normalized(), expected);
- }
- Normalization => {
- let expr = expr.skip_typecheck().normalize();
- assert_eq_display!(expr, expected);
- }
- AlphaNormalization => {
- let expr =
- expr.skip_typecheck().normalize().to_expr_alpha();
- assert_eq_display!(expr, expected.to_expr());
+ expr.typecheck_with(&expected.into_typed())?.get_type()?;
+ }
+ TypecheckFailure(file_path) => {
+ let res = parse_file_str(&file_path)?.skip_resolve()?.typecheck();
+ match res {
+ Err(_) => {}
+ // If e did typecheck, check that it doesn't have a type
+ Ok(e) => {
+ e.get_type().unwrap_err();
}
}
}
- Failure => {
- let file_path = base_path + ".dhall";
- match feature {
- Parser => {
- let err = parse_file_str(&file_path).unwrap_err();
- match err {
- Error::Parse(_) => {}
- Error::IO(e)
- if e.kind() == std::io::ErrorKind::InvalidData => {}
- e => panic!("Expected parse error, got: {:?}", e),
- }
- }
- Printer | BinaryEncoding => unreachable!(),
- BinaryDecoding => {
- let expr_file_path = file_path + "b";
- let mut expr_data = Vec::new();
- {
- File::open(&PathBuf::from(&expr_file_path))?
- .read_to_end(&mut expr_data)?;
- }
- Parsed::parse_binary(&expr_data).unwrap_err();
- }
- Import => {
- parse_file_str(&file_path)?.resolve().unwrap_err();
- }
- Normalization | AlphaNormalization => unreachable!(),
- Typecheck | TypeInference => {
- parse_file_str(&file_path)?
- .skip_resolve()?
- .typecheck()
- .unwrap_err();
+ TypeInferenceSuccess(expr_file_path, expected_file_path) => {
+ let expr =
+ parse_file_str(&expr_file_path)?.resolve()?.typecheck()?;
+ let ty = expr.get_type()?.normalize();
+ let expected = parse_file_str(&expected_file_path)?
+ .resolve()?
+ .typecheck()?
+ .normalize();
+ assert_eq_display!(ty, expected);
+ }
+ TypeInferenceFailure(file_path) => {
+ let res = parse_file_str(&file_path)?.skip_resolve()?.typecheck();
+ match res {
+ Err(_) => {}
+ // If e did typecheck, check that it doesn't have a type
+ Ok(e) => {
+ e.get_type().unwrap_err();
}
}
}
+ Normalization(expr_file_path, expected_file_path) => {
+ let expr = parse_file_str(&expr_file_path)?
+ .resolve()?
+ .typecheck()?
+ .normalize();
+ let expected = parse_file_str(&expected_file_path)?
+ .resolve()?
+ .typecheck()?
+ .normalize();
+
+ assert_eq_display!(expr, expected);
+ }
+ AlphaNormalization(expr_file_path, expected_file_path) => {
+ let expr = parse_file_str(&expr_file_path)?
+ .resolve()?
+ .typecheck()?
+ .normalize()
+ .to_expr_alpha();
+ let expected = parse_file_str(&expected_file_path)?
+ .resolve()?
+ .typecheck()?
+ .normalize();
+
+ assert_eq_display!(expr, expected.to_expr());
+ }
}
Ok(())
}
+#[cfg(test)]
mod spec {
// See build.rs
include!(concat!(env!("OUT_DIR"), "/spec_tests.rs"));
diff --git a/dhall_generated_parser/build.rs b/dhall_generated_parser/build.rs
index 5275097..c562fad 100644
--- a/dhall_generated_parser/build.rs
+++ b/dhall_generated_parser/build.rs
@@ -26,16 +26,15 @@ fn main() -> std::io::Result<()> {
rules.get_mut(&line[2..]).map(|x| x.silent = true);
}
}
- rules.remove("http");
- rules.remove("url_path");
- rules.remove("simple_label");
- rules.remove("nonreserved_label");
let mut file = File::create(pest_path)?;
writeln!(&mut file, "// AUTO-GENERATED FILE. See build.rs.")?;
- writeln!(&mut file, "{}", render_rules_to_pest(rules).pretty(80))?;
- writeln!(&mut file)?;
+ // TODO: this is a cheat; properly support RFC3986 URLs instead
+ rules.remove("url_path");
+ writeln!(&mut file, "url_path = _{{ path }}")?;
+
+ rules.remove("simple_label");
writeln!(
&mut file,
"simple_label = {{
@@ -43,30 +42,54 @@ fn main() -> std::io::Result<()> {
| !keyword ~ simple_label_first_char ~ simple_label_next_char*
}}"
)?;
- // TODO: this is a cheat; actually implement inline headers instead
- writeln!(
- &mut file,
- "http = {{
- http_raw
- ~ (whsp
- ~ using
- ~ whsp1
- ~ (import_hashed | ^\"(\" ~ whsp ~ import_hashed ~ whsp ~ ^\")\"))?
- }}"
- )?;
- // TODO: this is a cheat; properly support RFC3986 URLs instead
- writeln!(&mut file, "url_path = _{{ path }}")?;
+
+ rules.remove("nonreserved_label");
writeln!(
&mut file,
"nonreserved_label = _{{
!(builtin ~ !simple_label_next_char) ~ label
}}"
)?;
+
+ // Setup grammar for precedence climbing
+ rules.remove("operator_expression");
+ writeln!(&mut file, r##"
+ import_alt = {{ "?" ~ whsp1 }}
+ bool_or = {{ "||" }}
+ natural_plus = {{ "+" ~ whsp1 }}
+ text_append = {{ "++" }}
+ list_append = {{ "#" }}
+ bool_and = {{ "&&" }}
+ natural_times = {{ "*" }}
+ bool_eq = {{ "==" }}
+ bool_ne = {{ "!=" }}
+
+ operator = _{{
+ equivalent |
+ bool_ne |
+ bool_eq |
+ natural_times |
+ combine_types |
+ prefer |
+ combine |
+ bool_and |
+ list_append |
+ text_append |
+ natural_plus |
+ bool_or |
+ import_alt
+ }}
+ operator_expression = {{ application_expression ~ (whsp ~ operator ~ whsp ~ application_expression)* }}
+ "##)?;
+
writeln!(
&mut file,
"final_expression = ${{ SOI ~ complete_expression ~ EOI }}"
)?;
+ writeln!(&mut file)?;
+ writeln!(&mut file, "{}", render_rules_to_pest(rules).pretty(80))?;
+
// Generate pest parser manually to avoid spurious recompilations
let derived = {
let pest_path = "dhall.pest";
diff --git a/dhall_generated_parser/src/dhall.pest.visibility b/dhall_generated_parser/src/dhall.pest.visibility
index 6b4c974..dcebf45 100644
--- a/dhall_generated_parser/src/dhall.pest.visibility
+++ b/dhall_generated_parser/src/dhall.pest.visibility
@@ -84,10 +84,10 @@ Location
# Optional_fold
# Optional_build
# Text_show
-# combine
-# combine_types
-# equivalent
-# prefer
+combine
+combine_types
+equivalent
+prefer
lambda
forall
arrow
@@ -106,7 +106,7 @@ unquoted_path_component
quoted_path_component
path_component
path
-# local
+local
parent_path
here_path
home_path
@@ -141,10 +141,10 @@ hash
import_hashed
import
expression
-annotated_expression
+# annotated_expression
let_binding
empty_list_literal
-# operator_expression
+operator_expression
import_alt_expression
or_expression
plus_expression
@@ -160,7 +160,7 @@ not_equal_expression
equivalent_expression
application_expression
first_application_expression
-# import_expression
+import_expression
selector_expression
selector
labels
diff --git a/dhall_proc_macros/Cargo.toml b/dhall_proc_macros/Cargo.toml
index 76a749f..df1eda8 100644
--- a/dhall_proc_macros/Cargo.toml
+++ b/dhall_proc_macros/Cargo.toml
@@ -14,4 +14,3 @@ itertools = "0.8.0"
quote = "0.6.11"
proc-macro2 = "0.4.27"
syn = "0.15.29"
-dhall_syntax = { path = "../dhall_syntax" }
diff --git a/dhall_proc_macros/src/derive.rs b/dhall_proc_macros/src/derive.rs
index 725cdfb..140ff20 100644
--- a/dhall_proc_macros/src/derive.rs
+++ b/dhall_proc_macros/src/derive.rs
@@ -1,5 +1,4 @@
extern crate proc_macro;
-// use dhall_syntax::*;
use proc_macro::TokenStream;
use quote::{quote, quote_spanned};
use syn::spanned::Spanned;
@@ -18,7 +17,7 @@ where
T: quote::ToTokens,
{
quote!(
- <#ty as ::dhall::de::StaticType>::static_type()
+ <#ty as ::serde_dhall::StaticType>::static_type()
)
}
@@ -53,7 +52,7 @@ fn derive_for_struct(
let ty = static_type(ty);
quote!( (#name.to_owned(), #ty) )
});
- Ok(quote! { ::dhall::de::Type::make_record_type(
+ Ok(quote! { ::serde_dhall::value::Value::make_record_type(
vec![ #(#entries),* ].into_iter()
) })
}
@@ -90,7 +89,7 @@ fn derive_for_enum(
})
.collect::<Result<_, Error>>()?;
- Ok(quote! { ::dhall::de::Type::make_union_type(
+ Ok(quote! { ::serde_dhall::value::Value::make_union_type(
vec![ #(#entries),* ].into_iter()
) })
}
@@ -134,7 +133,7 @@ pub fn derive_static_type_inner(
let mut local_where_clause = orig_where_clause.clone();
local_where_clause
.predicates
- .push(parse_quote!(#ty: ::dhall::de::StaticType));
+ .push(parse_quote!(#ty: ::serde_dhall::StaticType));
let phantoms = generics.params.iter().map(|param| match param {
syn::GenericParam::Type(syn::TypeParam { ident, .. }) => {
quote!(#ident)
@@ -156,16 +155,16 @@ pub fn derive_static_type_inner(
for ty in constraints.iter() {
where_clause
.predicates
- .push(parse_quote!(#ty: ::dhall::de::StaticType));
+ .push(parse_quote!(#ty: ::serde_dhall::StaticType));
}
let ident = &input.ident;
let tokens = quote! {
- impl #impl_generics ::dhall::de::StaticType
+ impl #impl_generics ::serde_dhall::StaticType
for #ident #ty_generics
#where_clause {
fn static_type() ->
- ::dhall::de::Type {
+ ::serde_dhall::value::Value {
#(#assertions)*
#get_type
}
diff --git a/dhall_syntax/Cargo.toml b/dhall_syntax/Cargo.toml
index 6a61b09..1da10c7 100644
--- a/dhall_syntax/Cargo.toml
+++ b/dhall_syntax/Cargo.toml
@@ -10,10 +10,9 @@ doctest = false
[dependencies]
itertools = "0.8.0"
-percent-encoding = "1.0.1"
+percent-encoding = "2.1.0"
pest = "2.1"
either = "1.5.2"
take_mut = "0.2.2"
hex = "0.3.2"
dhall_generated_parser = { path = "../dhall_generated_parser" }
-improved_slice_patterns = { version = "2.0.0", path = "../improved_slice_patterns" }
diff --git a/dhall_syntax/src/core/context.rs b/dhall_syntax/src/core/context.rs
index eeec121..6844baa 100644
--- a/dhall_syntax/src/core/context.rs
+++ b/dhall_syntax/src/core/context.rs
@@ -4,7 +4,7 @@ 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)`
+/// The `Context` is used for type-checking when `(a = Expr)`
///
/// * You create a `Context` using `empty` and `insert`
/// * You transform a `Context` using `fmap`
diff --git a/dhall_syntax/src/core/expr.rs b/dhall_syntax/src/core/expr.rs
index 6522cb1..51b6c47 100644
--- a/dhall_syntax/src/core/expr.rs
+++ b/dhall_syntax/src/core/expr.rs
@@ -8,14 +8,34 @@ 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 {
+pub fn trivial_result<T>(x: Result<T, !>) -> T {
match x {
Ok(x) => x,
- Err(e) => match e {},
+ Err(e) => e,
+ }
+}
+
+/// A location in the source text
+#[derive(Debug, Clone)]
+pub struct Span {
+ input: Rc<str>,
+ /// # Safety
+ ///
+ /// Must be a valid character boundary index into `input`.
+ start: usize,
+ /// # Safety
+ ///
+ /// Must be a valid character boundary index into `input`.
+ end: usize,
+}
+
+impl Span {
+ pub(crate) fn make(input: Rc<str>, sp: pest::Span) -> Self {
+ Span {
+ input,
+ start: sp.start(),
+ end: sp.end(),
+ }
}
}
@@ -31,6 +51,15 @@ impl PartialEq for NaiveDouble {
impl Eq for NaiveDouble {}
+impl std::hash::Hash for NaiveDouble {
+ fn hash<H>(&self, state: &mut H)
+ where
+ H: std::hash::Hasher,
+ {
+ self.0.to_bits().hash(state)
+ }
+}
+
impl From<f64> for NaiveDouble {
fn from(x: f64) -> Self {
NaiveDouble(x)
@@ -44,7 +73,7 @@ impl From<NaiveDouble> for f64 {
}
/// Constants for a pure type system
-#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
+#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum Const {
Type,
Kind,
@@ -56,7 +85,7 @@ pub enum Const {
/// 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)]
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct V<Label>(pub Label, pub usize);
// This is only for the specific `Label` type, not generic
@@ -73,7 +102,7 @@ impl<'a> From<&'a Label> for V<Label> {
// Definition order must match precedence order for
// pretty-printing to work correctly
-#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
+#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum BinOp {
/// `x ? y`
ImportAlt,
@@ -104,7 +133,7 @@ pub enum BinOp {
}
/// Built-ins
-#[derive(Debug, Copy, Clone, PartialEq, Eq)]
+#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub enum Builtin {
Bool,
Natural,
@@ -137,29 +166,34 @@ pub enum Builtin {
TextShow,
}
-pub type ParsedExpr = SubExpr<X, Import>;
-pub type ResolvedExpr = SubExpr<X, X>;
-pub type DhallExpr = ResolvedExpr;
+// Each node carries an annotation.
+#[derive(Debug, Clone)]
+pub struct Expr<Embed>(Box<(RawExpr<Embed>, Option<Span>)>);
-// Each node carries an annotation. In practice it's either X (no annotation) or a Span.
-#[derive(Debug)]
-pub struct SubExpr<Note, Embed>(Rc<(Expr<Note, Embed>, Option<Note>)>);
+pub type RawExpr<Embed> = ExprF<Expr<Embed>, Embed>;
-impl<Note, Embed: PartialEq> std::cmp::PartialEq for SubExpr<Note, Embed> {
+impl<Embed: PartialEq> std::cmp::PartialEq for Expr<Embed> {
fn eq(&self, other: &Self) -> bool {
self.0.as_ref().0 == other.0.as_ref().0
}
}
-impl<Note, Embed: Eq> std::cmp::Eq for SubExpr<Note, Embed> {}
+impl<Embed: Eq> std::cmp::Eq for Expr<Embed> {}
-pub type Expr<Note, Embed> = ExprF<SubExpr<Note, Embed>, Embed>;
+impl<Embed: std::hash::Hash> std::hash::Hash for Expr<Embed> {
+ fn hash<H>(&self, state: &mut H)
+ where
+ H: std::hash::Hasher,
+ {
+ (self.0).0.hash(state)
+ }
+}
/// 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)]
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum ExprF<SubExpr, Embed> {
Const(Const),
/// `x`
@@ -209,11 +243,15 @@ pub enum ExprF<SubExpr, Embed> {
UnionType(DupTreeMap<Label, Option<SubExpr>>),
/// `merge x y : t`
Merge(SubExpr, SubExpr, Option<SubExpr>),
+ /// `toMap x : t`
+ ToMap(SubExpr, Option<SubExpr>),
/// `e.x`
Field(SubExpr, Label),
/// `e.{ x, y, z }`
Projection(SubExpr, DupTreeSet<Label>),
- /// Embeds an import or the result of resolving the import
+ /// `./some/path`
+ Import(Import<SubExpr>),
+ /// Embeds the result of resolving an import
Embed(Embed),
}
@@ -225,229 +263,134 @@ impl<SE, E> ExprF<SE, E> {
v.visit(self)
}
- pub fn traverse_ref_with_special_handling_of_binders<'a, SE2, E2, Err>(
+ pub fn traverse_ref_with_special_handling_of_binders<'a, SE2, Err>(
&'a self,
visit_subexpr: impl FnMut(&'a SE) -> Result<SE2, Err>,
visit_under_binder: impl FnOnce(&'a Label, &'a SE) -> Result<SE2, Err>,
- visit_embed: impl FnOnce(&'a E) -> Result<E2, Err>,
- ) -> Result<ExprF<SE2, E2>, Err> {
+ ) -> Result<ExprF<SE2, E>, Err>
+ where
+ E: Clone,
+ {
self.visit(visitor::TraverseRefWithBindersVisitor {
visit_subexpr,
visit_under_binder,
- visit_embed,
})
}
- fn traverse_ref<'a, SE2, E2, Err>(
+ fn traverse_ref<'a, SE2, Err>(
&'a self,
visit_subexpr: impl FnMut(&'a SE) -> Result<SE2, Err>,
- visit_embed: impl FnOnce(&'a E) -> Result<E2, Err>,
- ) -> Result<ExprF<SE2, E2>, Err> {
- self.visit(visitor::TraverseRefVisitor {
- visit_subexpr,
- visit_embed,
- })
+ ) -> Result<ExprF<SE2, E>, Err>
+ where
+ E: Clone,
+ {
+ self.visit(visitor::TraverseRefVisitor { visit_subexpr })
}
- pub fn map_ref_with_special_handling_of_binders<'a, SE2, E2>(
+ pub fn map_ref_with_special_handling_of_binders<'a, SE2>(
&'a self,
mut map_subexpr: impl FnMut(&'a SE) -> SE2,
mut map_under_binder: impl FnMut(&'a Label, &'a SE) -> SE2,
- map_embed: impl FnOnce(&'a E) -> E2,
- ) -> ExprF<SE2, E2> {
+ ) -> ExprF<SE2, E>
+ where
+ E: Clone,
+ {
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_embed(x)),
))
}
- pub fn map_ref<'a, SE2, E2>(
+ pub fn map_ref<'a, SE2>(
&'a self,
mut map_subexpr: impl FnMut(&'a SE) -> SE2,
- map_embed: impl FnOnce(&'a E) -> E2,
- ) -> ExprF<SE2, E2> {
- trivial_result(
- self.traverse_ref(|x| Ok(map_subexpr(x)), |x| Ok(map_embed(x))),
- )
- }
-
- pub fn traverse_ref_simple<'a, SE2, Err>(
- &'a self,
- visit_subexpr: impl FnMut(&'a SE) -> Result<SE2, Err>,
- ) -> Result<ExprF<SE2, E>, Err>
- where
- E: Clone,
- {
- self.traverse_ref(visit_subexpr, |x| Ok(E::clone(x)))
- }
-
- pub fn map_ref_simple<'a, SE2>(
- &'a self,
- map_subexpr: impl Fn(&'a SE) -> SE2,
) -> ExprF<SE2, E>
where
E: Clone,
{
- self.map_ref(map_subexpr, E::clone)
+ trivial_result(self.traverse_ref(|x| Ok(map_subexpr(x))))
}
}
-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))))
- }
-
+impl<E> RawExpr<E> {
pub fn traverse_resolve<E2, Err>(
&self,
- visit_embed: impl FnMut(&E) -> Result<E2, Err>,
- ) -> Result<Expr<N, E2>, Err>
- where
- N: Clone,
- {
+ visit_import: impl FnMut(&Import<Expr<E2>>) -> Result<E2, Err>,
+ ) -> Result<RawExpr<E2>, Err> {
self.traverse_resolve_with_visitor(&mut visitor::ResolveVisitor(
- visit_embed,
+ visit_import,
))
}
pub(crate) fn traverse_resolve_with_visitor<E2, Err, F1>(
&self,
visitor: &mut visitor::ResolveVisitor<F1>,
- ) -> Result<Expr<N, E2>, Err>
+ ) -> Result<RawExpr<E2>, Err>
where
- N: Clone,
- F1: FnMut(&E) -> Result<E2, Err>,
+ F1: FnMut(&Import<Expr<E2>>) -> Result<E2, Err>,
{
match self {
ExprF::BinOp(BinOp::ImportAlt, l, r) => l
.as_ref()
.traverse_resolve_with_visitor(visitor)
- .or(r.as_ref().traverse_resolve_with_visitor(visitor)),
- _ => self.visit(visitor),
+ .or_else(|_| r.as_ref().traverse_resolve_with_visitor(visitor)),
+ _ => {
+ let e = self.visit(&mut *visitor)?;
+ Ok(match &e {
+ ExprF::Import(import) => ExprF::Embed((visitor.0)(import)?),
+ _ => e,
+ })
+ }
}
}
}
-impl Expr<X, X> {
- pub fn absurd<N, E>(&self) -> Expr<N, E> {
- self.visit(&mut visitor::AbsurdVisitor)
- }
-}
-
-impl<E: Clone> Expr<X, E> {
- pub fn note_absurd<N>(&self) -> Expr<N, E> {
- self.visit(&mut visitor::NoteAbsurdVisitor)
- }
-}
-
-impl<N, E> SubExpr<N, E> {
- pub fn as_ref(&self) -> &Expr<N, E> {
+impl<E> Expr<E> {
+ pub fn as_ref(&self) -> &RawExpr<E> {
&self.0.as_ref().0
}
- pub fn new(x: Expr<N, E>, n: N) -> Self {
- SubExpr(Rc::new((x, Some(n))))
+ pub fn new(x: RawExpr<E>, n: Span) -> Self {
+ Expr(Box::new((x, Some(n))))
}
- pub fn from_expr_no_note(x: Expr<N, E>) -> Self {
- SubExpr(Rc::new((x, None)))
+ pub fn from_expr_no_span(x: RawExpr<E>) -> Self {
+ Expr(Box::new((x, None)))
}
pub fn from_builtin(b: Builtin) -> Self {
- SubExpr::from_expr_no_note(ExprF::Builtin(b))
+ Expr::from_expr_no_span(ExprF::Builtin(b))
}
- pub fn rewrap<E2>(&self, x: Expr<N, E2>) -> SubExpr<N, E2>
- where
- N: Clone,
- {
- SubExpr(Rc::new((x, (self.0).1.clone())))
- }
-
- pub fn traverse_embed<E2, Err>(
- &self,
- visit_embed: impl FnMut(&E) -> Result<E2, Err>,
- ) -> Result<SubExpr<N, E2>, Err>
- where
- N: Clone,
- {
- Ok(self.rewrap(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,
- {
- self.rewrap(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
- where
- N: Clone,
- {
- match self.as_ref() {
- ExprF::Embed(_) => SubExpr::clone(self),
- // This calls ExprF::map_ref
- e => self.rewrap(e.map_ref_with_special_handling_of_binders(
- map_expr,
- map_under_binder,
- |_| unreachable!(),
- )),
- }
+ pub fn rewrap<E2>(&self, x: RawExpr<E2>) -> Expr<E2> {
+ Expr(Box::new((x, (self.0).1.clone())))
}
+}
+impl<E> Expr<E> {
pub fn traverse_resolve<E2, Err>(
&self,
- visit_embed: impl FnMut(&E) -> Result<E2, Err>,
- ) -> Result<SubExpr<N, E2>, Err>
- where
- N: Clone,
- {
- Ok(self.rewrap(self.as_ref().traverse_resolve(visit_embed)?))
- }
-}
-
-impl SubExpr<X, X> {
- pub fn absurd<N: Clone, T>(&self) -> SubExpr<N, T> {
- SubExpr::from_expr_no_note(self.as_ref().absurd())
+ visit_import: impl FnMut(&Import<Expr<E2>>) -> Result<E2, Err>,
+ ) -> Result<Expr<E2>, Err> {
+ Ok(self.rewrap(self.as_ref().traverse_resolve(visit_import)?))
}
}
-impl<E: Clone> SubExpr<X, E> {
- pub fn note_absurd<N>(&self) -> SubExpr<N, E> {
- SubExpr::from_expr_no_note(self.as_ref().note_absurd())
- }
+// Should probably rename this
+pub fn rc<E>(x: RawExpr<E>) -> Expr<E> {
+ Expr::from_expr_no_span(x)
}
-impl<N, E> Clone for SubExpr<N, E> {
- fn clone(&self) -> Self {
- SubExpr(Rc::clone(&self.0))
- }
+pub(crate) fn spanned(
+ span: Span,
+ x: crate::parser::ParsedRawExpr,
+) -> crate::parser::ParsedExpr {
+ Expr::new(x, span)
}
-
-// Should probably rename this
-pub fn rc<E>(x: Expr<X, E>) -> SubExpr<X, E> {
- SubExpr::from_expr_no_note(x)
+pub(crate) fn unspanned(
+ x: crate::parser::ParsedRawExpr,
+) -> crate::parser::ParsedExpr {
+ Expr::from_expr_no_span(x)
}
/// Add an isize to an usize
diff --git a/dhall_syntax/src/core/import.rs b/dhall_syntax/src/core/import.rs
index 4aad70d..ef696b9 100644
--- a/dhall_syntax/src/core/import.rs
+++ b/dhall_syntax/src/core/import.rs
@@ -16,32 +16,22 @@ pub struct File {
pub file_path: Vec<String>,
}
-impl IntoIterator for File {
- type Item = String;
- type IntoIter = ::std::vec::IntoIter<Self::Item>;
-
- fn into_iter(self) -> Self::IntoIter {
- self.file_path.into_iter()
- }
-}
-
/// The location of import (i.e. local vs. remote vs. environment)
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
-pub enum ImportLocation {
+pub enum ImportLocation<SubExpr> {
Local(FilePrefix, File),
- Remote(URL),
+ Remote(URL<SubExpr>),
Env(String),
Missing,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
-pub struct URL {
+pub struct URL<SubExpr> {
pub scheme: Scheme,
pub authority: String,
pub path: File,
pub query: Option<String>,
- // TODO: implement inline headers
- pub headers: Option<Box<ImportHashed>>,
+ pub headers: Option<SubExpr>,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
@@ -63,17 +53,56 @@ pub enum Hash {
SHA256(Vec<u8>),
}
+/// Reference to an external resource
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
-pub struct ImportHashed {
- pub location: ImportLocation,
+pub struct Import<SubExpr> {
+ pub mode: ImportMode,
+ pub location: ImportLocation<SubExpr>,
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,
+impl<SE> URL<SE> {
+ pub fn visit_subexpr<'a, Err, SE2>(
+ &'a self,
+ f: impl FnOnce(&'a SE) -> Result<SE2, Err>,
+ ) -> Result<URL<SE2>, Err> {
+ let headers = self.headers.as_ref().map(f).transpose()?;
+ Ok(URL {
+ scheme: self.scheme,
+ authority: self.authority.clone(),
+ path: self.path.clone(),
+ query: self.query.clone(),
+ headers,
+ })
+ }
+}
+
+impl<SE> ImportLocation<SE> {
+ pub fn visit_subexpr<'a, Err, SE2>(
+ &'a self,
+ f: impl FnOnce(&'a SE) -> Result<SE2, Err>,
+ ) -> Result<ImportLocation<SE2>, Err> {
+ use ImportLocation::*;
+ Ok(match self {
+ Local(prefix, path) => Local(*prefix, path.clone()),
+ Remote(url) => Remote(url.visit_subexpr(f)?),
+ Env(env) => Env(env.clone()),
+ Missing => Missing,
+ })
+ }
+}
+
+impl<SE> Import<SE> {
+ pub fn visit_subexpr<'a, Err, SE2>(
+ &'a self,
+ f: impl FnOnce(&'a SE) -> Result<SE2, Err>,
+ ) -> Result<Import<SE2>, Err> {
+ Ok(Import {
+ mode: self.mode,
+ location: self.location.visit_subexpr(f)?,
+ hash: self.hash.clone(),
+ })
+ }
}
pub trait Canonicalize {
@@ -83,7 +112,7 @@ pub trait Canonicalize {
impl Canonicalize for File {
fn canonicalize(&self) -> File {
let mut file_path = Vec::new();
- let mut file_path_components = self.clone().into_iter();
+ let mut file_path_components = self.file_path.clone().into_iter();
loop {
let component = file_path_components.next();
@@ -128,8 +157,8 @@ impl Canonicalize for File {
}
}
-impl Canonicalize for ImportLocation {
- fn canonicalize(&self) -> ImportLocation {
+impl<SubExpr: Copy> Canonicalize for ImportLocation<SubExpr> {
+ fn canonicalize(&self) -> ImportLocation<SubExpr> {
match self {
ImportLocation::Local(prefix, file) => ImportLocation::Local(*prefix, file.canonicalize()),
ImportLocation::Remote(url) => ImportLocation::Remote(URL {
@@ -137,22 +166,10 @@ impl Canonicalize for ImportLocation {
authority: url.authority.clone(),
path: url.path.canonicalize(),
query: url.query.clone(),
- headers: url.headers.clone().map(|boxed_hash| Box::new(boxed_hash.canonicalize())),
+ headers: url.headers.clone(),
}),
ImportLocation::Env(name) => ImportLocation::Env(name.to_string()),
ImportLocation::Missing => ImportLocation::Missing,
}
}
}
-
-impl Canonicalize for ImportHashed {
- fn canonicalize(&self) -> ImportHashed {
- ImportHashed { hash: self.hash.clone(), location: self.location.canonicalize() }
- }
-}
-
-impl Canonicalize for Import {
- fn canonicalize(&self) -> Import {
- Import { mode: self.mode, location_hashed: self.location_hashed.canonicalize() }
- }
-}
diff --git a/dhall_syntax/src/core/text.rs b/dhall_syntax/src/core/text.rs
index 0ce1e6f..10fd68a 100644
--- a/dhall_syntax/src/core/text.rs
+++ b/dhall_syntax/src/core/text.rs
@@ -1,6 +1,6 @@
use std::iter::FromIterator;
-#[derive(Debug, Clone, PartialEq, Eq)]
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct InterpolatedText<SubExpr> {
head: String,
tail: Vec<(SubExpr, String)>,
diff --git a/dhall_syntax/src/core/visitor.rs b/dhall_syntax/src/core/visitor.rs
index 18f76d9..435771e 100644
--- a/dhall_syntax/src/core/visitor.rs
+++ b/dhall_syntax/src/core/visitor.rs
@@ -2,52 +2,56 @@ use crate::*;
use std::iter::FromIterator;
/// A way too generic Visitor trait.
-pub trait GenericVisitor<Input, Return>: Sized {
- fn visit(self, input: Input) -> Return;
+pub trait GenericVisitor<Input, Output>: Sized {
+ fn visit(self, input: Input) -> Output;
}
-/// 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, E1>: Sized {
+/// 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_ref_with_special_handling_of_binders` 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.
+pub trait ExprFFallibleVisitor<'a, SE1, SE2, E1, E2>: Sized {
type Error;
- type SE2;
- type E2;
- fn visit_subexpr(
- &mut self,
- subexpr: &'a SE1,
- ) -> Result<Self::SE2, Self::Error>;
+ fn visit_subexpr(&mut self, subexpr: &'a SE1) -> Result<SE2, Self::Error>;
+ fn visit_embed(self, embed: &'a E1) -> Result<E2, Self::Error>;
fn visit_subexpr_under_binder(
- self,
- label: &'a Label,
+ mut self,
+ _label: &'a Label,
subexpr: &'a SE1,
- ) -> Result<Self::SE2, Self::Error>;
+ ) -> Result<SE2, Self::Error> {
+ self.visit_subexpr(subexpr)
+ }
+}
- fn visit_embed_squash(self, embed: &'a E1) -> Result<Ret, Self::Error>;
+/// Like ExprFFallibleVisitor, but without the error handling.
+pub trait ExprFInFallibleVisitor<'a, SE1, SE2, E1, E2>: Sized {
+ fn visit_subexpr(&mut self, subexpr: &'a SE1) -> SE2;
+ fn visit_embed(self, embed: &'a E1) -> E2;
- // Called with the result of the map, in the non-embed case.
- // Useful to change the result type, and/or avoid some loss of info
- fn visit_resulting_exprf(
- result: ExprF<Self::SE2, Self::E2>,
- ) -> Result<Ret, Self::Error>;
+ fn visit_subexpr_under_binder(
+ mut self,
+ _label: &'a Label,
+ subexpr: &'a SE1,
+ ) -> SE2 {
+ self.visit_subexpr(subexpr)
+ }
}
-impl<'a, T, Ret, SE1, E1>
- GenericVisitor<&'a ExprF<SE1, E1>, Result<Ret, T::Error>> for T
+impl<'a, T, SE1, SE2, E1, E2>
+ GenericVisitor<&'a ExprF<SE1, E1>, Result<ExprF<SE2, E2>, T::Error>> for T
where
- T: ExprFVeryGenericVisitor<'a, Ret, SE1, E1>,
+ T: ExprFFallibleVisitor<'a, SE1, SE2, E1, E2>,
{
- fn visit(self, input: &'a ExprF<SE1, E1>) -> Result<Ret, T::Error> {
+ fn visit(
+ self,
+ input: &'a ExprF<SE1, E1>,
+ ) -> Result<ExprF<SE2, E2>, T::Error> {
fn vec<'a, T, U, Err, F: FnMut(&'a T) -> Result<U, Err>>(
x: &'a [T],
f: F,
@@ -63,27 +67,27 @@ where
None => None,
})
}
- fn dupmap<'a, V, Ret, SE, E, T>(
- x: impl IntoIterator<Item = (&'a Label, &'a SE)>,
+ fn dupmap<'a, V, SE1, SE2, E1, E2, T>(
+ x: impl IntoIterator<Item = (&'a Label, &'a SE1)>,
mut v: V,
) -> Result<T, V::Error>
where
- SE: 'a,
- T: FromIterator<(Label, V::SE2)>,
- V: ExprFVeryGenericVisitor<'a, Ret, SE, E>,
+ SE1: 'a,
+ T: FromIterator<(Label, SE2)>,
+ V: ExprFFallibleVisitor<'a, SE1, SE2, E1, E2>,
{
x.into_iter()
.map(|(k, x)| Ok((k.clone(), v.visit_subexpr(x)?)))
.collect()
}
- fn optdupmap<'a, V, Ret, SE, E, T>(
- x: impl IntoIterator<Item = (&'a Label, &'a Option<SE>)>,
+ fn optdupmap<'a, V, SE1, SE2, E1, E2, T>(
+ x: impl IntoIterator<Item = (&'a Label, &'a Option<SE1>)>,
mut v: V,
) -> Result<T, V::Error>
where
- SE: 'a,
- T: FromIterator<(Label, Option<V::SE2>)>,
- V: ExprFVeryGenericVisitor<'a, Ret, SE, E>,
+ SE1: 'a,
+ T: FromIterator<(Label, Option<SE2>)>,
+ V: ExprFFallibleVisitor<'a, SE1, SE2, E1, E2>,
{
x.into_iter()
.map(|(k, x)| {
@@ -100,7 +104,7 @@ where
let mut v = self;
use crate::ExprF::*;
- T::visit_resulting_exprf(match input {
+ Ok(match input {
Var(v) => Var(v.clone()),
Lam(l, t, e) => {
let t = v.visit_subexpr(t)?;
@@ -146,93 +150,25 @@ where
v.visit_subexpr(y)?,
opt(t, |e| v.visit_subexpr(e))?,
),
+ ToMap(x, t) => {
+ ToMap(v.visit_subexpr(x)?, opt(t, |e| v.visit_subexpr(e))?)
+ }
Field(e, l) => Field(v.visit_subexpr(e)?, l.clone()),
Projection(e, ls) => Projection(v.visit_subexpr(e)?, ls.clone()),
Assert(e) => Assert(v.visit_subexpr(e)?),
- Embed(a) => return v.visit_embed_squash(a),
+ Import(i) => Import(i.visit_subexpr(|e| v.visit_subexpr(e))?),
+ Embed(a) => Embed(v.visit_embed(a)?),
})
}
}
-/// Like ExprFVeryGenericVisitor, but sets the return
-/// type to ExprF<_>
-pub trait ExprFFallibleVisitor<'a, SE1, SE2, E1, E2>: Sized {
- type Error;
-
- fn visit_subexpr(&mut self, subexpr: &'a SE1) -> Result<SE2, Self::Error>;
- fn visit_embed(self, embed: &'a E1) -> Result<E2, Self::Error>;
-
- fn visit_subexpr_under_binder(
- mut self,
- _label: &'a Label,
- subexpr: &'a SE1,
- ) -> Result<SE2, Self::Error> {
- self.visit_subexpr(subexpr)
- }
-
- fn visit_embed_squash(
- self,
- embed: &'a E1,
- ) -> Result<ExprF<SE2, E2>, Self::Error> {
- Ok(ExprF::Embed(self.visit_embed(embed)?))
- }
-}
-
-impl<'a, T, SE1, SE2, E1, E2>
- ExprFVeryGenericVisitor<'a, ExprF<SE2, E2>, SE1, E1> for T
+impl<'a, T, SE1, SE2, E1, E2> GenericVisitor<&'a ExprF<SE1, E1>, ExprF<SE2, E2>>
+ for T
where
- T: ExprFFallibleVisitor<'a, SE1, SE2, E1, E2>,
+ T: ExprFInFallibleVisitor<'a, SE1, SE2, E1, E2>,
{
- type Error = T::Error;
- type SE2 = SE2;
- type E2 = E2;
-
- fn visit_subexpr(
- &mut self,
- subexpr: &'a SE1,
- ) -> Result<Self::SE2, Self::Error> {
- self.visit_subexpr(subexpr)
- }
-
- fn visit_subexpr_under_binder(
- self,
- label: &'a Label,
- subexpr: &'a SE1,
- ) -> Result<Self::SE2, Self::Error> {
- self.visit_subexpr_under_binder(label, subexpr)
- }
-
- fn visit_embed_squash(
- self,
- embed: &'a E1,
- ) -> Result<ExprF<SE2, E2>, Self::Error> {
- self.visit_embed_squash(embed)
- }
-
- // Called with the result of the map, in the non-embed case.
- // Useful to change the result type, and/or avoid some loss of info
- fn visit_resulting_exprf(
- result: ExprF<Self::SE2, Self::E2>,
- ) -> Result<ExprF<SE2, E2>, Self::Error> {
- Ok(result)
- }
-}
-
-/// Like ExprFFallibleVisitor, but without the error handling.
-pub trait ExprFInFallibleVisitor<'a, SE1, SE2, E1, E2>: Sized {
- fn visit_subexpr(&mut self, subexpr: &'a SE1) -> SE2;
- fn visit_embed(self, embed: &'a E1) -> E2;
-
- fn visit_subexpr_under_binder(
- mut self,
- _label: &'a Label,
- subexpr: &'a SE1,
- ) -> SE2 {
- self.visit_subexpr(subexpr)
- }
-
- fn visit_embed_squash(self, embed: &'a E1) -> ExprF<SE2, E2> {
- ExprF::Embed(self.visit_embed(embed))
+ fn visit(self, input: &'a ExprF<SE1, E1>) -> ExprF<SE2, E2> {
+ trivial_result(InfallibleWrapper(self).visit(input))
}
}
@@ -243,7 +179,7 @@ impl<'a, T, SE1, SE2, E1, E2> ExprFFallibleVisitor<'a, SE1, SE2, E1, E2>
where
T: ExprFInFallibleVisitor<'a, SE1, SE2, E1, E2>,
{
- type Error = X;
+ type Error = !;
fn visit_subexpr(&mut self, subexpr: &'a SE1) -> Result<SE2, Self::Error> {
Ok(self.0.visit_subexpr(subexpr))
@@ -259,40 +195,20 @@ where
) -> Result<SE2, Self::Error> {
Ok(self.0.visit_subexpr_under_binder(label, subexpr))
}
-
- fn visit_embed_squash(
- self,
- embed: &'a E1,
- ) -> Result<ExprF<SE2, E2>, Self::Error> {
- Ok(self.0.visit_embed_squash(embed))
- }
}
-impl<'a, T, SE1, SE2, E1, E2> GenericVisitor<&'a ExprF<SE1, E1>, ExprF<SE2, E2>>
- for T
-where
- T: ExprFInFallibleVisitor<'a, SE1, SE2, E1, E2>,
-{
- fn visit(self, input: &'a ExprF<SE1, E1>) -> ExprF<SE2, E2> {
- trivial_result(InfallibleWrapper(self).visit(input))
- }
-}
-
-pub struct TraverseRefWithBindersVisitor<F1, F2, F4> {
+pub struct TraverseRefWithBindersVisitor<F1, F2> {
pub visit_subexpr: F1,
pub visit_under_binder: F2,
- pub visit_embed: F4,
}
-impl<'a, SE, E, SE2, E2, Err, F1, F2, F4>
- ExprFFallibleVisitor<'a, SE, SE2, E, E2>
- for TraverseRefWithBindersVisitor<F1, F2, F4>
+impl<'a, SE, E, SE2, Err, F1, F2> ExprFFallibleVisitor<'a, SE, SE2, E, E>
+ for TraverseRefWithBindersVisitor<F1, F2>
where
SE: 'a,
- E: 'a,
+ E: 'a + Clone,
F1: FnMut(&'a SE) -> Result<SE2, Err>,
F2: FnOnce(&'a Label, &'a SE) -> Result<SE2, Err>,
- F4: FnOnce(&'a E) -> Result<E2, Err>,
{
type Error = Err;
@@ -306,107 +222,52 @@ where
) -> Result<SE2, Self::Error> {
(self.visit_under_binder)(label, subexpr)
}
- fn visit_embed(self, embed: &'a E) -> Result<E2, Self::Error> {
- (self.visit_embed)(embed)
+ fn visit_embed(self, embed: &'a E) -> Result<E, Self::Error> {
+ Ok(embed.clone())
}
}
-pub struct TraverseRefVisitor<F1, F3> {
+pub struct TraverseRefVisitor<F1> {
pub visit_subexpr: F1,
- pub visit_embed: F3,
}
-impl<'a, SE, E, SE2, E2, Err, F1, F3> ExprFFallibleVisitor<'a, SE, SE2, E, E2>
- for TraverseRefVisitor<F1, F3>
+impl<'a, SE, E, SE2, Err, F1> ExprFFallibleVisitor<'a, SE, SE2, E, E>
+ for TraverseRefVisitor<F1>
where
SE: 'a,
- E: 'a,
+ E: 'a + Clone,
F1: FnMut(&'a SE) -> Result<SE2, Err>,
- F3: FnOnce(&'a E) -> Result<E2, Err>,
{
type Error = Err;
fn visit_subexpr(&mut self, subexpr: &'a SE) -> Result<SE2, Self::Error> {
(self.visit_subexpr)(subexpr)
}
- fn visit_embed(self, embed: &'a E) -> Result<E2, Self::Error> {
- (self.visit_embed)(embed)
- }
-}
-
-pub struct TraverseEmbedVisitor<F1>(pub F1);
-
-impl<'a, 'b, N, E, E2, Err, F1>
- ExprFFallibleVisitor<'a, SubExpr<N, E>, SubExpr<N, E2>, 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(subexpr.rewrap(subexpr.as_ref().visit(&mut **self)?))
- }
- fn visit_embed(self, embed: &'a E) -> Result<E2, Self::Error> {
- (self.0)(embed)
+ fn visit_embed(self, embed: &'a E) -> Result<E, Self::Error> {
+ Ok(embed.clone())
}
}
pub struct ResolveVisitor<F1>(pub F1);
-impl<'a, 'b, N, E, E2, Err, F1>
- ExprFFallibleVisitor<'a, SubExpr<N, E>, SubExpr<N, E2>, E, E2>
+impl<'a, 'b, E, E2, Err, F1> ExprFFallibleVisitor<'a, Expr<E>, Expr<E2>, E, E2>
for &'b mut ResolveVisitor<F1>
where
- N: Clone + 'a,
- F1: FnMut(&E) -> Result<E2, Err>,
+ F1: FnMut(&Import<Expr<E2>>) -> Result<E2, Err>,
{
type Error = Err;
fn visit_subexpr(
&mut self,
- subexpr: &'a SubExpr<N, E>,
- ) -> Result<SubExpr<N, E2>, Self::Error> {
+ subexpr: &'a Expr<E>,
+ ) -> Result<Expr<E2>, Self::Error> {
Ok(subexpr.rewrap(
subexpr
.as_ref()
.traverse_resolve_with_visitor(&mut **self)?,
))
}
- fn visit_embed(self, embed: &'a E) -> Result<E2, Self::Error> {
- (self.0)(embed)
- }
-}
-pub struct NoteAbsurdVisitor;
-
-impl<'a, 'b, N, E>
- ExprFInFallibleVisitor<'a, SubExpr<X, E>, SubExpr<N, E>, E, E>
- for &'b mut NoteAbsurdVisitor
-where
- E: Clone + 'a,
-{
- fn visit_subexpr(&mut self, subexpr: &'a SubExpr<X, E>) -> SubExpr<N, E> {
- SubExpr::from_expr_no_note(subexpr.as_ref().visit(&mut **self))
- }
- fn visit_embed(self, embed: &'a E) -> E {
- E::clone(embed)
- }
-}
-
-pub struct AbsurdVisitor;
-
-impl<'a, 'b, N, E>
- ExprFInFallibleVisitor<'a, SubExpr<X, X>, SubExpr<N, E>, X, E>
- for &'b mut AbsurdVisitor
-{
- fn visit_subexpr(&mut self, subexpr: &'a SubExpr<X, X>) -> SubExpr<N, E> {
- SubExpr::from_expr_no_note(subexpr.as_ref().visit(&mut **self))
- }
- fn visit_embed(self, embed: &'a X) -> E {
- match *embed {}
+ fn visit_embed(self, _embed: &'a E) -> Result<E2, Self::Error> {
+ unimplemented!()
}
}
diff --git a/dhall_syntax/src/lib.rs b/dhall_syntax/src/lib.rs
index 193c6ea..e4a6077 100644
--- a/dhall_syntax/src/lib.rs
+++ b/dhall_syntax/src/lib.rs
@@ -1,5 +1,8 @@
#![feature(trace_macros)]
#![feature(slice_patterns)]
+#![feature(try_blocks)]
+#![feature(never_type)]
+#![feature(bind_by_move_pattern_guards)]
#![allow(
clippy::many_single_char_names,
clippy::should_implement_trait,
diff --git a/dhall_syntax/src/parser.rs b/dhall_syntax/src/parser.rs
index 71d0936..b70a236 100644
--- a/dhall_syntax/src/parser.rs
+++ b/dhall_syntax/src/parser.rs
@@ -1,7 +1,10 @@
use itertools::Itertools;
use pest::iterators::Pair;
+use pest::prec_climber as pcl;
+use pest::prec_climber::PrecClimber;
use pest::Parser;
use std::borrow::Cow;
+use std::collections::HashMap;
use std::rc::Rc;
use dhall_generated_parser::{DhallParser, Rule};
@@ -15,46 +18,15 @@ use crate::*;
// their own crate because they are quite general and useful. For now they
// are here and hopefully you can figure out how they work.
-type ParsedExpr = Expr<Span, Import>;
-type ParsedSubExpr = SubExpr<Span, Import>;
-type ParsedText = InterpolatedText<SubExpr<Span, Import>>;
-type ParsedTextContents = InterpolatedTextContents<SubExpr<Span, Import>>;
+pub(crate) type ParsedRawExpr = RawExpr<!>;
+pub(crate) type ParsedExpr = Expr<!>;
+type ParsedText = InterpolatedText<ParsedExpr>;
+type ParsedTextContents = InterpolatedTextContents<ParsedExpr>;
pub type ParseError = pest::error::Error<Rule>;
pub type ParseResult<T> = Result<T, ParseError>;
-fn unspanned(x: ParsedExpr) -> ParsedSubExpr {
- SubExpr::from_expr_no_note(x)
-}
-
-#[derive(Debug, Clone)]
-pub struct Span {
- input: Rc<str>,
- /// # Safety
- ///
- /// Must be a valid character boundary index into `input`.
- start: usize,
- /// # Safety
- ///
- /// Must be a valid character boundary index into `input`.
- end: usize,
-}
-
-impl Span {
- fn make(input: Rc<str>, sp: pest::Span) -> Self {
- Span {
- input,
- start: sp.start(),
- end: sp.end(),
- }
- }
-}
-
-fn spanned(span: Span, x: ParsedExpr) -> ParsedSubExpr {
- SubExpr::new(x, span)
-}
-
#[derive(Debug)]
enum Either<A, B> {
Left(A),
@@ -147,177 +119,271 @@ fn debug_pair(pair: Pair<Rule>) -> String {
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);
+macro_rules! parse_children {
+ // Variable length pattern with a common unary variant
+ (@match_forwards,
+ $parse_args:expr,
+ $iter:expr,
+ ($body:expr),
+ $variant:ident ($x:ident)..,
+ $($rest:tt)*
+ ) => {
+ parse_children!(@match_backwards,
+ $parse_args, $iter,
+ ({
+ let $x = $iter
+ .map(|x| Parsers::$variant($parse_args, x))
+ .collect::<Result<Vec<_>, _>>()?
+ .into_iter();
+ $body
+ }),
+ $($rest)*
+ )
+ };
+ // Single item pattern
+ (@match_forwards,
+ $parse_args:expr,
+ $iter:expr,
+ ($body:expr),
+ $variant:ident ($x:pat),
+ $($rest:tt)*
+ ) => {{
+ let p = $iter.next().unwrap();
+ let $x = Parsers::$variant($parse_args, p)?;
+ parse_children!(@match_forwards,
+ $parse_args, $iter,
+ ($body),
+ $($rest)*
+ )
+ }};
+ // Single item pattern after a variable length one: declare reversed and take from the end
+ (@match_backwards,
+ $parse_args:expr,
+ $iter:expr,
+ ($body:expr),
+ $variant:ident ($x:pat),
+ $($rest:tt)*
+ ) => {
+ parse_children!(@match_backwards, $parse_args, $iter, ({
+ let p = $iter.next_back().unwrap();
+ let $x = Parsers::$variant($parse_args, p)?;
+ $body
+ }), $($rest)*)
+ };
- (@body,
- ($($things:tt)*),
- rule!( $name:ident<$o:ty>; $($args:tt)* )
+ // Check no elements remain
+ (@match_forwards, $parse_args:expr, $iter:expr, ($body:expr) $(,)*) => {
+ $body
+ };
+ // After a variable length pattern, everything has already been consumed
+ (@match_backwards, $parse_args:expr, $iter:expr, ($body:expr) $(,)*) => {
+ $body
+ };
+
+ ($parse_args:expr, $iter:expr; [$($args:tt)*] => $body:expr) => {
+ parse_children!(@match_forwards,
+ $parse_args, $iter,
+ ($body),
+ $($args)*,
+ )
+ };
+}
+
+macro_rules! make_parser {
+ (@children_pattern,
+ $varpat:ident,
+ ($($acc:tt)*),
+ [$variant:ident ($x:pat), $($rest:tt)*]
) => (
- make_parser!(@body,
- ($($things)*),
- rule!( $name<$o> as $name; $($args)* )
+ make_parser!(@children_pattern,
+ $varpat,
+ ($($acc)* , Rule::$variant),
+ [$($rest)*]
+ )
+ );
+ (@children_pattern,
+ $varpat:ident,
+ ($($acc:tt)*),
+ [$variant:ident ($x:ident).., $($rest:tt)*]
+ ) => (
+ make_parser!(@children_pattern,
+ $varpat,
+ ($($acc)* , $varpat..),
+ [$($rest)*]
)
);
+ (@children_pattern,
+ $varpat:ident,
+ (, $($acc:tt)*), [$(,)*]
+ ) => ([$($acc)*]);
+ (@children_pattern,
+ $varpat:ident,
+ ($($acc:tt)*), [$(,)*]
+ ) => ([$($acc)*]);
+
+ (@children_filter,
+ $varpat:ident,
+ [$variant:ident ($x:pat), $($rest:tt)*]
+ ) => (
+ make_parser!(@children_filter, $varpat, [$($rest)*])
+ );
+ (@children_filter,
+ $varpat:ident,
+ [$variant:ident ($x:ident).., $($rest:tt)*]
+ ) => (
+ $varpat.iter().all(|r| r == &Rule::$variant) &&
+ make_parser!(@children_filter, $varpat, [$($rest)*])
+ );
+ (@children_filter, $varpat:ident, [$(,)*]) => (true);
+
(@body,
- ($_input:expr, $pair:expr, $_children:expr),
+ ($climbers:expr, $input:expr, $pair:expr),
rule!(
- $name:ident<$o:ty>
- as $group:ident;
+ $name:ident<$o:ty>;
+ $span:ident;
captured_str!($x:pat) => $body:expr
)
) => ({
+ let $span = Span::make($input.clone(), $pair.as_span());
let $x = $pair.as_str();
- let res: $o = $body;
- Ok(ParsedValue::$group(res))
+ let res: Result<_, String> = try { $body };
+ res.map_err(|msg| custom_parse_error(&$pair, msg))
});
(@body,
- ($_input:expr, $_pair:expr, $children:expr),
+ ($climbers:expr, $input:expr, $pair:expr),
rule!(
- $name:ident<$o:ty>
- as $group:ident;
+ $name:ident<$o:ty>;
+ $span:ident;
children!( $( [$($args:tt)*] => $body:expr ),* $(,)* )
)
) => ({
- #[allow(unused_imports)]
- use ParsedValue::*;
+ let children_rules: Vec<Rule> = $pair
+ .clone()
+ .into_inner()
+ .map(|p| p.as_rule())
+ .collect();
+
+ let $span = Span::make($input.clone(), $pair.as_span());
+ #[allow(unused_mut)]
+ let mut iter = $pair.clone().into_inner();
+
#[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))
+ match children_rules.as_slice() {
+ $(
+ make_parser!(@children_pattern, x, (), [$($args)*,])
+ if make_parser!(@children_filter, x, [$($args)*,])
+ => {
+ parse_children!(($climbers, $input.clone()), iter;
+ [$($args)*] => {
+ let res: Result<_, String> = try { $body };
+ res.map_err(|msg| custom_parse_error(&$pair, msg))
+ }
+ )
+ }
+ ,
+ )*
+ [..] => Err(custom_parse_error(
+ &$pair,
+ format!("Unexpected children: {:?}", children_rules)
+ )),
+ }
});
(@body,
- ($input:expr, $pair:expr, $children:expr),
+ ($climbers:expr, $input:expr, $pair:expr),
rule!(
- $name:ident<$o:ty>
- as $group:ident;
- $span:ident;
+ $name:ident<$o:ty>;
+ prec_climb!(
+ $other_rule:ident,
+ $_climber:expr,
+ $args:pat => $body:expr $(,)*
+ )
+ )
+ ) => ({
+ let climber = $climbers.get(&Rule::$name).unwrap();
+ climber.climb(
+ $pair.clone().into_inner(),
+ |p| Parsers::$other_rule(($climbers, $input.clone()), p),
+ |l, op, r| {
+ let $args = (l?, op, r?);
+ let res: Result<_, String> = try { $body };
+ res.map_err(|msg| custom_parse_error(&$pair, msg))
+ },
+ )
+ });
+ (@body,
+ ($($things:tt)*),
+ rule!(
+ $name:ident<$o:ty>;
$($args:tt)*
)
) => ({
- let $span = Span::make($input, $pair.as_span());
make_parser!(@body,
- ($input, $pair, $children),
+ ($($things)*),
rule!(
- $name<$o>
- as $group;
+ $name<$o>;
+ _span;
$($args)*
)
)
});
(@body,
($($things:tt)*),
- token_rule!($name:ident<$o:ty>)
+ rule!($name:ident<$o:ty>)
) => ({
- Ok(ParsedValue::$name(()))
+ Ok(())
});
- (@body, ($($things:tt)*), rule_group!( $name:ident<$o:ty> )) => (
- unreachable!()
- );
+
+ (@construct_climber,
+ ($map:expr),
+ rule!(
+ $name:ident<$o:ty>;
+ prec_climb!($other_rule:ident, $climber:expr, $($_rest:tt)* )
+ )
+ ) => ({
+ $map.insert(Rule::$name, $climber)
+ });
+ (@construct_climber, ($($things:tt)*), $($args:tt)*) => (());
($( $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), )*
+ struct Parsers;
+
+ impl Parsers {
+ $(
+ #[allow(non_snake_case, unused_variables, clippy::let_unit_value)]
+ fn $name<'a>(
+ (climbers, input): (&HashMap<Rule, PrecClimber<Rule>>, Rc<str>),
+ pair: Pair<'a, Rule>,
+ ) -> ParseResult<$o> {
+ make_parser!(@body, (climbers, input, pair),
+ $submac!( $name<$o> $($args)* ))
+ }
+ )*
}
- fn parse_any<'a>(
- input: Rc<str>,
- 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, (input, pair, children),
- $submac!( $name<$o> $($args)* ))
- ,
- )*
- r => Err(format!("Unexpected {:?}", r)),
- }
+ fn construct_precclimbers() -> HashMap<Rule, PrecClimber<Rule>> {
+ let mut map = HashMap::new();
+ $(
+ make_parser!(@construct_climber, (map),
+ $submac!( $name<$o> $($args)* ));
+ )*
+ map
}
- );
-}
-// Non-recursive implementation to avoid stack overflows
-fn do_parse<'a>(
- input: Rc<str>,
- 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(input.clone(), pair.clone(), children)
- {
- Ok(v) => v,
- Err(msg) => Err(custom_parse_error(&pair, msg))?,
- };
- values_stack.push(val);
+ struct EntryPoint;
+
+ impl EntryPoint {
+ $(
+ #[allow(non_snake_case, dead_code)]
+ fn $name<'a>(
+ input: Rc<str>,
+ pair: Pair<'a, Rule>,
+ ) -> ParseResult<$o> {
+ let climbers = construct_precclimbers();
+ Parsers::$name((&climbers, input), pair)
}
+ )*
}
- }
- 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
- | equivalent_expression
- | application_expression
- | first_application_expression
- | selector_expression
- | annotated_expression => true,
- _ => false,
- }
+ );
}
// Trim the shared indent off of a vec of lines, as defined by the Dhall semantics of multiline
@@ -362,7 +428,7 @@ fn trim_indent(lines: &mut Vec<ParsedText>) {
}
make_parser! {
- token_rule!(EOI<()>);
+ rule!(EOI<()>);
rule!(simple_label<Label>;
captured_str!(s) => Label::from(s.trim().to_owned())
@@ -442,7 +508,7 @@ make_parser! {
0xAFFFE..=0xAFFFF | 0xBFFFE..=0xBFFFF |
0xCFFFE..=0xCFFFF | 0xDFFFE..=0xDFFFF |
0xEFFFE..=0xEFFFF | 0xFFFFE..=0xFFFFF |
- 0x10FFFE..=0x10FFFF => {
+ 0x10_FFFE..=0x10_FFFF => {
let c_ecapsed = c.escape_unicode();
Err(format!("Escape sequences can't contain non-characters: \"{}\"", c_ecapsed))?
},
@@ -479,13 +545,13 @@ make_parser! {
rule!(single_quote_char<&'a str>;
captured_str!(s) => s
);
- rule!(escaped_quote_pair<&'a str> as single_quote_char;
+ rule!(escaped_quote_pair<&'a str>;
captured_str!(_) => "''"
);
- rule!(escaped_interpolation<&'a str> as single_quote_char;
+ rule!(escaped_interpolation<&'a str>;
captured_str!(_) => "${"
);
- rule!(interpolation<ParsedSubExpr>; children!(
+ rule!(interpolation<ParsedExpr>; children!(
[expression(e)] => e
));
@@ -497,21 +563,29 @@ make_parser! {
lines.last_mut().unwrap().push(c);
lines
},
- [single_quote_char("\n"), single_quote_continue(lines)] => {
+ [escaped_quote_pair(c), single_quote_continue(lines)] => {
let mut lines = lines;
- lines.push(vec![]);
+ // TODO: don't allocate for every char
+ let c = InterpolatedTextContents::Text(c.to_owned());
+ lines.last_mut().unwrap().push(c);
lines
},
- [single_quote_char("\r\n"), single_quote_continue(lines)] => {
+ [escaped_interpolation(c), single_quote_continue(lines)] => {
let mut lines = lines;
- lines.push(vec![]);
+ // TODO: don't allocate for every char
+ let c = InterpolatedTextContents::Text(c.to_owned());
+ lines.last_mut().unwrap().push(c);
lines
},
[single_quote_char(c), single_quote_continue(lines)] => {
- // TODO: don't allocate for every char
- let c = InterpolatedTextContents::Text(c.to_owned());
let mut lines = lines;
- lines.last_mut().unwrap().push(c);
+ if c == "\n" || c == "\r\n" {
+ lines.push(vec![]);
+ } else {
+ // TODO: don't allocate for every char
+ let c = InterpolatedTextContents::Text(c.to_owned());
+ lines.last_mut().unwrap().push(c);
+ }
lines
},
[] => {
@@ -519,7 +593,7 @@ make_parser! {
},
));
- rule!(builtin<ParsedSubExpr>; span;
+ rule!(builtin<ParsedExpr>; span;
captured_str!(s) => {
spanned(span, match crate::Builtin::parse(s) {
Some(b) => Builtin(b),
@@ -537,9 +611,9 @@ make_parser! {
}
);
- token_rule!(NaN<()>);
- token_rule!(minus_infinity_literal<()>);
- token_rule!(plus_infinity_literal<()>);
+ rule!(NaN<()>);
+ rule!(minus_infinity_literal<()>);
+ rule!(plus_infinity_literal<()>);
rule!(numeric_double_literal<core::Double>;
captured_str!(s) => {
@@ -576,7 +650,7 @@ make_parser! {
}
);
- rule!(identifier<ParsedSubExpr> as expression; span; children!(
+ rule!(identifier<ParsedExpr>; span; children!(
[variable(v)] => {
spanned(span, Var(v))
},
@@ -595,12 +669,29 @@ make_parser! {
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()
+ [unquoted_path_component(s)] => s.to_string(),
+ [quoted_path_component(s)] => {
+ const RESERVED: &percent_encoding::AsciiSet =
+ &percent_encoding::CONTROLS
+ .add(b'=').add(b':').add(b'/').add(b'?')
+ .add(b'#').add(b'[').add(b']').add(b'@')
+ .add(b'!').add(b'$').add(b'&').add(b'\'')
+ .add(b'(').add(b')').add(b'*').add(b'+')
+ .add(b',').add(b';');
+ s.chars()
+ .map(|c| {
+ // Percent-encode ascii chars
+ if c.is_ascii() {
+ percent_encoding::utf8_percent_encode(
+ &c.to_string(),
+ RESERVED,
+ ).to_string()
+ } else {
+ c.to_string()
+ }
+ })
+ .collect()
},
- [quoted_path_component(s)] => s.to_string(),
));
rule!(path<Vec<String>>; children!(
[path_component(components)..] => {
@@ -608,18 +699,23 @@ make_parser! {
}
));
- rule_group!(local<(FilePrefix, Vec<String>)>);
+ rule!(local<(FilePrefix, Vec<String>)>; children!(
+ [parent_path(l)] => l,
+ [here_path(l)] => l,
+ [home_path(l)] => l,
+ [absolute_path(l)] => l,
+ ));
- rule!(parent_path<(FilePrefix, Vec<String>)> as local; children!(
+ rule!(parent_path<(FilePrefix, Vec<String>)>; children!(
[path(p)] => (FilePrefix::Parent, p)
));
- rule!(here_path<(FilePrefix, Vec<String>)> as local; children!(
+ rule!(here_path<(FilePrefix, Vec<String>)>; children!(
[path(p)] => (FilePrefix::Here, p)
));
- rule!(home_path<(FilePrefix, Vec<String>)> as local; children!(
+ rule!(home_path<(FilePrefix, Vec<String>)>; children!(
[path(p)] => (FilePrefix::Home, p)
));
- rule!(absolute_path<(FilePrefix, Vec<String>)> as local; children!(
+ rule!(absolute_path<(FilePrefix, Vec<String>)>; children!(
[path(p)] => (FilePrefix::Absolute, p)
));
@@ -629,15 +725,13 @@ make_parser! {
_ => unreachable!(),
});
- rule!(http_raw<URL>; children!(
- [scheme(sch), authority(auth), path(file_path)] => {
- URL {
- scheme: sch,
- authority: auth,
- path: File { file_path },
- query: None,
- headers: None,
- }
+ rule!(http_raw<URL<ParsedExpr>>; children!(
+ [scheme(sch), authority(auth), path(file_path)] => URL {
+ scheme: sch,
+ authority: auth,
+ path: File { file_path },
+ query: None,
+ headers: None,
},
[scheme(sch), authority(auth), path(file_path), query(q)] => {
URL {
@@ -654,10 +748,10 @@ make_parser! {
rule!(query<String>; captured_str!(s) => s.to_owned());
- rule!(http<URL>; children!(
+ rule!(http<URL<ParsedExpr>>; children!(
[http_raw(url)] => url,
- [http_raw(url), import_hashed(ih)] =>
- URL { headers: Some(Box::new(ih)), ..url },
+ [http_raw(url), import_expression(e)] =>
+ URL { headers: Some(e), ..url },
));
rule!(env<String>; children!(
@@ -687,9 +781,9 @@ make_parser! {
}
);
- token_rule!(missing<()>);
+ rule!(missing<()>);
- rule!(import_type<ImportLocation>; children!(
+ rule!(import_type<ImportLocation<ParsedExpr>>; children!(
[missing(_)] => {
ImportLocation::Missing
},
@@ -714,52 +808,53 @@ make_parser! {
Hash::SHA256(hex::decode(hash).unwrap())
});
- rule!(import_hashed<ImportHashed>; children!(
+ rule!(import_hashed<crate::Import<ParsedExpr>>; children!(
[import_type(location)] =>
- ImportHashed { location, hash: None },
+ crate::Import {mode: ImportMode::Code, location, hash: None },
[import_type(location), hash(h)] =>
- ImportHashed { location, hash: Some(h) },
+ crate::Import {mode: ImportMode::Code, location, hash: Some(h) },
));
- token_rule!(Text<()>);
- token_rule!(Location<()>);
+ rule!(Text<()>);
+ rule!(Location<()>);
- rule!(import<ParsedSubExpr> as expression; span; children!(
- [import_hashed(location_hashed)] => {
- spanned(span, Embed(Import {
+ rule!(import<ParsedExpr>; span; children!(
+ [import_hashed(imp)] => {
+ spanned(span, Import(crate::Import {
mode: ImportMode::Code,
- location_hashed
+ ..imp
}))
},
- [import_hashed(location_hashed), Text(_)] => {
- spanned(span, Embed(Import {
+ [import_hashed(imp), Text(_)] => {
+ spanned(span, Import(crate::Import {
mode: ImportMode::RawText,
- location_hashed
+ ..imp
}))
},
- [import_hashed(location_hashed), Location(_)] => {
- spanned(span, Embed(Import {
+ [import_hashed(imp), Location(_)] => {
+ spanned(span, Import(crate::Import {
mode: ImportMode::Location,
- location_hashed
+ ..imp
}))
},
));
- token_rule!(lambda<()>);
- token_rule!(forall<()>);
- token_rule!(arrow<()>);
- token_rule!(merge<()>);
- token_rule!(assert<()>);
- token_rule!(if_<()>);
- token_rule!(in_<()>);
-
- rule!(empty_list_literal<ParsedSubExpr> as expression; span; children!(
- [expression(e)] => {
+ rule!(lambda<()>);
+ rule!(forall<()>);
+ rule!(arrow<()>);
+ rule!(merge<()>);
+ rule!(assert<()>);
+ rule!(if_<()>);
+ rule!(in_<()>);
+ rule!(toMap<()>);
+
+ rule!(empty_list_literal<ParsedExpr>; span; children!(
+ [application_expression(e)] => {
spanned(span, EmptyListLit(e))
},
));
- rule!(expression<ParsedSubExpr> as expression; span; children!(
+ rule!(expression<ParsedExpr>; span; children!(
[lambda(()), label(l), expression(typ),
arrow(()), expression(body)] => {
spanned(span, Lam(l, typ, body))
@@ -777,19 +872,27 @@ make_parser! {
arrow(()), expression(body)] => {
spanned(span, Pi(l, typ, body))
},
- [expression(typ), arrow(()), expression(body)] => {
+ [operator_expression(typ), arrow(()), expression(body)] => {
spanned(span, Pi("_".into(), typ, body))
},
- [merge(()), expression(x), expression(y), expression(z)] => {
+ [merge(()), import_expression(x), import_expression(y),
+ application_expression(z)] => {
spanned(span, Merge(x, y, Some(z)))
},
+ [empty_list_literal(e)] => e,
[assert(()), expression(x)] => {
spanned(span, Assert(x))
},
- [expression(e)] => e,
+ [toMap(()), import_expression(x), application_expression(y)] => {
+ spanned(span, ToMap(x, Some(y)))
+ },
+ [operator_expression(e)] => e,
+ [operator_expression(e), expression(annot)] => {
+ spanned(span, Annot(e, annot))
+ },
));
- rule!(let_binding<(Label, Option<ParsedSubExpr>, ParsedSubExpr)>;
+ rule!(let_binding<(Label, Option<ParsedExpr>, ParsedExpr)>;
children!(
[label(name), expression(annot), expression(expr)] =>
(name, Some(annot), expr),
@@ -797,137 +900,99 @@ make_parser! {
(name, None, expr),
));
- token_rule!(List<()>);
- token_rule!(Optional<()>);
-
- rule!(import_alt_expression<ParsedSubExpr> as expression; children!(
- [expression(e)] => e,
- [expression(first), expression(rest)..] => {
- let o = crate::BinOp::ImportAlt;
- rest.fold(first, |acc, e| unspanned(BinOp(o, acc, e)))
- },
- ));
- rule!(or_expression<ParsedSubExpr> as expression; children!(
- [expression(e)] => e,
- [expression(first), expression(rest)..] => {
- let o = crate::BinOp::BoolOr;
- rest.fold(first, |acc, e| unspanned(BinOp(o, acc, e)))
- },
- ));
- rule!(plus_expression<ParsedSubExpr> as expression; children!(
- [expression(e)] => e,
- [expression(first), expression(rest)..] => {
- let o = crate::BinOp::NaturalPlus;
- rest.fold(first, |acc, e| unspanned(BinOp(o, acc, e)))
- },
- ));
- rule!(text_append_expression<ParsedSubExpr> as expression; children!(
- [expression(e)] => e,
- [expression(first), expression(rest)..] => {
- let o = crate::BinOp::TextAppend;
- rest.fold(first, |acc, e| unspanned(BinOp(o, acc, e)))
- },
- ));
- rule!(list_append_expression<ParsedSubExpr> as expression; children!(
- [expression(e)] => e,
- [expression(first), expression(rest)..] => {
- let o = crate::BinOp::ListAppend;
- rest.fold(first, |acc, e| unspanned(BinOp(o, acc, e)))
- },
- ));
- rule!(and_expression<ParsedSubExpr> as expression; children!(
- [expression(e)] => e,
- [expression(first), expression(rest)..] => {
- let o = crate::BinOp::BoolAnd;
- rest.fold(first, |acc, e| unspanned(BinOp(o, acc, e)))
- },
- ));
- rule!(combine_expression<ParsedSubExpr> as expression; children!(
- [expression(e)] => e,
- [expression(first), expression(rest)..] => {
- let o = crate::BinOp::RecursiveRecordMerge;
- rest.fold(first, |acc, e| unspanned(BinOp(o, acc, e)))
- },
- ));
- rule!(prefer_expression<ParsedSubExpr> as expression; children!(
- [expression(e)] => e,
- [expression(first), expression(rest)..] => {
- let o = crate::BinOp::RightBiasedRecordMerge;
- rest.fold(first, |acc, e| unspanned(BinOp(o, acc, e)))
- },
- ));
- rule!(combine_types_expression<ParsedSubExpr> as expression; children!(
- [expression(e)] => e,
- [expression(first), expression(rest)..] => {
- let o = crate::BinOp::RecursiveRecordTypeMerge;
- rest.fold(first, |acc, e| unspanned(BinOp(o, acc, e)))
- },
- ));
- rule!(times_expression<ParsedSubExpr> as expression; children!(
- [expression(e)] => e,
- [expression(first), expression(rest)..] => {
- let o = crate::BinOp::NaturalTimes;
- rest.fold(first, |acc, e| unspanned(BinOp(o, acc, e)))
- },
- ));
- rule!(equal_expression<ParsedSubExpr> as expression; children!(
- [expression(e)] => e,
- [expression(first), expression(rest)..] => {
- let o = crate::BinOp::BoolEQ;
- rest.fold(first, |acc, e| unspanned(BinOp(o, acc, e)))
- },
- ));
- rule!(not_equal_expression<ParsedSubExpr> as expression; children!(
- [expression(e)] => e,
- [expression(first), expression(rest)..] => {
- let o = crate::BinOp::BoolNE;
- rest.fold(first, |acc, e| unspanned(BinOp(o, acc, e)))
- },
- ));
- rule!(equivalent_expression<ParsedSubExpr> as expression; children!(
- [expression(e)] => e,
- [expression(first), expression(rest)..] => {
- let o = crate::BinOp::Equivalence;
- rest.fold(first, |acc, e| unspanned(BinOp(o, acc, e)))
- },
- ));
-
- rule!(annotated_expression<ParsedSubExpr> as expression; span; children!(
- [expression(e)] => e,
- [expression(e), expression(annot)] => {
- spanned(span, Annot(e, annot))
+ rule!(List<()>);
+ rule!(Optional<()>);
+
+ rule!(operator_expression<ParsedExpr>; prec_climb!(
+ application_expression,
+ {
+ use Rule::*;
+ // In order of precedence
+ let operators = vec![
+ import_alt,
+ bool_or,
+ natural_plus,
+ text_append,
+ list_append,
+ bool_and,
+ combine,
+ prefer,
+ combine_types,
+ natural_times,
+ bool_eq,
+ bool_ne,
+ equivalent,
+ ];
+ PrecClimber::new(
+ operators
+ .into_iter()
+ .map(|op| pcl::Operator::new(op, pcl::Assoc::Left))
+ .collect(),
+ )
},
+ (l, op, r) => {
+ use crate::BinOp::*;
+ use Rule::*;
+ let op = match op.as_rule() {
+ import_alt => ImportAlt,
+ bool_or => BoolOr,
+ natural_plus => NaturalPlus,
+ text_append => TextAppend,
+ list_append => ListAppend,
+ bool_and => BoolAnd,
+ combine => RecursiveRecordMerge,
+ prefer => RightBiasedRecordMerge,
+ combine_types => RecursiveRecordTypeMerge,
+ natural_times => NaturalTimes,
+ bool_eq => BoolEQ,
+ bool_ne => BoolNE,
+ equivalent => Equivalence,
+ r => Err(
+ format!("Rule {:?} isn't an operator", r),
+ )?,
+ };
+
+ unspanned(BinOp(op, l, r))
+ }
));
- token_rule!(Some_<()>);
- token_rule!(toMap<()>);
+ rule!(Some_<()>);
- rule!(application_expression<ParsedSubExpr> as expression; children!(
- [expression(e)] => e,
- [expression(first), expression(rest)..] => {
+ rule!(application_expression<ParsedExpr>; children!(
+ [first_application_expression(e)] => e,
+ [first_application_expression(first), import_expression(rest)..] => {
rest.fold(first, |acc, e| unspanned(App(acc, e)))
},
));
- rule!(first_application_expression<ParsedSubExpr> as expression; span;
+ rule!(first_application_expression<ParsedExpr>; span;
children!(
- [expression(e)] => e,
- [Some_(()), expression(e)] => {
+ [Some_(()), import_expression(e)] => {
spanned(span, SomeLit(e))
},
- [merge(()), expression(x), expression(y)] => {
+ [merge(()), import_expression(x), import_expression(y)] => {
spanned(span, Merge(x, y, None))
},
+ [toMap(()), import_expression(x)] => {
+ spanned(span, ToMap(x, None))
+ },
+ [import_expression(e)] => e,
));
- rule!(selector_expression<ParsedSubExpr> as expression; children!(
- [expression(e)] => e,
- [expression(first), selector(rest)..] => {
+ rule!(import_expression<ParsedExpr>; span;
+ children!(
+ [selector_expression(e)] => e,
+ [import(e)] => e,
+ ));
+
+ rule!(selector_expression<ParsedExpr>; children!(
+ [primitive_expression(e)] => e,
+ [primitive_expression(first), selector(rest)..] => {
rest.fold(first, |acc, e| unspanned(match e {
Either::Left(l) => Field(acc, l),
Either::Right(ls) => Projection(acc, ls),
}))
- }
+ },
));
rule!(selector<Either<Label, DupTreeSet<Label>>>; children!(
@@ -940,24 +1005,30 @@ make_parser! {
[label(ls)..] => ls.collect(),
));
- rule!(primitive_expression<ParsedSubExpr> as expression; span; children!(
+ rule!(primitive_expression<ParsedExpr>; 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)),
+ [empty_record_type(e)] => e,
+ [empty_record_literal(e)] => e,
+ [non_empty_record_type_or_literal(e)] => e,
+ [union_type(e)] => e,
+ [non_empty_list_literal(e)] => e,
+ [identifier(e)] => e,
[expression(e)] => e,
));
- rule!(empty_record_literal<ParsedSubExpr> as expression; span;
+ rule!(empty_record_literal<ParsedExpr>; span;
captured_str!(_) => spanned(span, RecordLit(Default::default()))
);
- rule!(empty_record_type<ParsedSubExpr> as expression; span;
+ rule!(empty_record_type<ParsedExpr>; span;
captured_str!(_) => spanned(span, RecordType(Default::default()))
);
- rule!(non_empty_record_type_or_literal<ParsedSubExpr> as expression; span;
+ rule!(non_empty_record_type_or_literal<ParsedExpr>; span;
children!(
[label(first_label), non_empty_record_type(rest)] => {
let (first_expr, mut map) = rest;
@@ -972,28 +1043,28 @@ make_parser! {
));
rule!(non_empty_record_type
- <(ParsedSubExpr, DupTreeMap<Label, ParsedSubExpr>)>; children!(
+ <(ParsedExpr, DupTreeMap<Label, ParsedExpr>)>; children!(
[expression(expr), record_type_entry(entries)..] => {
(expr, entries.collect())
}
));
- rule!(record_type_entry<(Label, ParsedSubExpr)>; children!(
+ rule!(record_type_entry<(Label, ParsedExpr)>; children!(
[label(name), expression(expr)] => (name, expr)
));
rule!(non_empty_record_literal
- <(ParsedSubExpr, DupTreeMap<Label, ParsedSubExpr>)>; children!(
+ <(ParsedExpr, DupTreeMap<Label, ParsedExpr>)>; children!(
[expression(expr), record_literal_entry(entries)..] => {
(expr, entries.collect())
}
));
- rule!(record_literal_entry<(Label, ParsedSubExpr)>; children!(
+ rule!(record_literal_entry<(Label, ParsedExpr)>; children!(
[label(name), expression(expr)] => (name, expr)
));
- rule!(union_type<ParsedSubExpr> as expression; span; children!(
+ rule!(union_type<ParsedExpr>; span; children!(
[empty_union_type(_)] => {
spanned(span, UnionType(Default::default()))
},
@@ -1002,14 +1073,14 @@ make_parser! {
},
));
- token_rule!(empty_union_type<()>);
+ rule!(empty_union_type<()>);
- rule!(union_type_entry<(Label, Option<ParsedSubExpr>)>; children!(
+ rule!(union_type_entry<(Label, Option<ParsedExpr>)>; children!(
[label(name), expression(expr)] => (name, Some(expr)),
[label(name)] => (name, None),
));
- rule!(non_empty_list_literal<ParsedSubExpr> as expression; span;
+ rule!(non_empty_list_literal<ParsedExpr>; span;
children!(
[expression(items)..] => spanned(
span,
@@ -1017,35 +1088,15 @@ make_parser! {
)
));
- rule!(final_expression<ParsedSubExpr> as expression; children!(
- [expression(e), EOI(_eoi)] => e
+ rule!(final_expression<ParsedExpr>; children!(
+ [expression(e), EOI(_)] => e
));
}
-pub fn parse_expr(s: &str) -> ParseResult<ParsedSubExpr> {
+pub fn parse_expr(s: &str) -> ParseResult<ParsedExpr> {
let mut pairs = DhallParser::parse(Rule::final_expression, s)?;
let rc_input = s.to_string().into();
- let expr = do_parse(rc_input, pairs.next().unwrap())?;
+ let expr = EntryPoint::final_expression(rc_input, pairs.next().unwrap())?;
assert_eq!(pairs.next(), None);
- match expr {
- ParsedValue::expression(e) => Ok(e),
- _ => unreachable!(),
- }
- // Ok(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);
+ Ok(expr)
}
diff --git a/dhall_syntax/src/printer.rs b/dhall_syntax/src/printer.rs
index 5312f23..1e3b7f2 100644
--- a/dhall_syntax/src/printer.rs
+++ b/dhall_syntax/src/printer.rs
@@ -41,6 +41,12 @@ impl<SE: Display + Clone, E: Display> Display for ExprF<SE, E> {
write!(f, " : {}", c)?;
}
}
+ ToMap(a, b) => {
+ write!(f, "toMap {}", a)?;
+ if let Some(b) = b {
+ write!(f, " : {}", b)?;
+ }
+ }
Annot(a, b) => {
write!(f, "{} : {}", a, b)?;
}
@@ -88,6 +94,7 @@ impl<SE: Display + Clone, E: Display> Display for ExprF<SE, E> {
}
Ok(())
})?,
+ Import(a) => a.fmt(f)?,
Embed(a) => a.fmt(f)?,
}
Ok(())
@@ -111,21 +118,21 @@ enum PrintPhase {
// 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);
+struct PhasedExpr<'a, A>(&'a Expr<A>, PrintPhase);
-impl<'a, S: Clone, A: Display + Clone> Display for PhasedExpr<'a, S, A> {
+impl<'a, A: Display + Clone> Display for PhasedExpr<'a, 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> {
+impl<'a, A: Display + Clone> PhasedExpr<'a, A> {
+ fn phase(self, phase: PrintPhase) -> PhasedExpr<'a, A> {
PhasedExpr(self.0, phase)
}
}
-impl<S: Clone, A: Display + Clone> Expr<S, A> {
+impl<A: Display + Clone> RawExpr<A> {
fn fmt_phase(
&self,
f: &mut fmt::Formatter,
@@ -143,6 +150,7 @@ impl<S: Clone, A: Display + Clone> Expr<S, A> {
| NEListLit(_)
| SomeLit(_)
| Merge(_, _, _)
+ | ToMap(_, _)
| Annot(_, _)
if phase > Base =>
{
@@ -151,12 +159,14 @@ impl<S: Clone, A: Display + Clone> Expr<S, A> {
// 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,
+ Field(_, _) | Projection(_, _) if phase > PrintPhase::Import => {
+ true
+ }
_ => false,
};
// Annotate subexpressions with the appropriate phase, defaulting to Base
- let phased_self = match self.map_ref_simple(|e| PhasedExpr(e, Base)) {
+ let phased_self = match self.map_ref(|e| PhasedExpr(e, Base)) {
Pi(a, b, c) => {
if &String::from(&a) == "_" {
Pi(a, b.phase(Operator), c)
@@ -165,18 +175,25 @@ impl<S: Clone, A: Display + Clone> Expr<S, A> {
}
}
Merge(a, b, c) => Merge(
- a.phase(Import),
- b.phase(Import),
+ a.phase(PrintPhase::Import),
+ b.phase(PrintPhase::Import),
c.map(|x| x.phase(PrintPhase::App)),
),
+ ToMap(a, b) => ToMap(
+ a.phase(PrintPhase::Import),
+ b.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)),
),
- SomeLit(e) => SomeLit(e.phase(Import)),
- ExprF::App(f, a) => ExprF::App(f.phase(Import), a.phase(Import)),
+ SomeLit(e) => SomeLit(e.phase(PrintPhase::Import)),
+ ExprF::App(f, a) => ExprF::App(
+ f.phase(PrintPhase::Import),
+ a.phase(PrintPhase::Import),
+ ),
Field(a, b) => Field(a.phase(Primitive), b),
Projection(e, ls) => Projection(e.phase(Primitive), ls),
e => e,
@@ -196,7 +213,7 @@ impl<S: Clone, A: Display + Clone> Expr<S, A> {
}
}
-impl<S: Clone, A: Display + Clone> Display for SubExpr<S, A> {
+impl<A: Display + Clone> Display for Expr<A> {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
self.as_ref().fmt_phase(f, PrintPhase::Base)
}
@@ -224,7 +241,7 @@ where
f.write_str(close)
}
-impl<SubExpr: Display + Clone> Display for InterpolatedText<SubExpr> {
+impl<SubExpr: Display> Display for InterpolatedText<SubExpr> {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
f.write_str("\"")?;
for x in self.iter() {
@@ -338,19 +355,14 @@ impl Display for Hash {
}
}
}
-impl Display for ImportHashed {
+impl<SubExpr: Display> Display for Import<SubExpr> {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
use FilePrefix::*;
use ImportLocation::*;
- let fmt_remote_path_component = |s: &str| -> String {
- use percent_encoding::{
- utf8_percent_encode, PATH_SEGMENT_ENCODE_SET,
- };
- utf8_percent_encode(s, PATH_SEGMENT_ENCODE_SET).to_string()
- };
- let fmt_local_path_component = |s: &str| -> String {
+ use ImportMode::*;
+ let quote_if_needed = |s: &str| -> String {
if s.chars().all(|c| c.is_ascii_alphanumeric()) {
- s.to_owned()
+ s.to_string()
} else {
format!("\"{}\"", s)
}
@@ -365,21 +377,13 @@ impl Display for ImportHashed {
Absolute => "",
};
write!(f, "{}/", prefix)?;
- let full_path: String = path
- .clone()
- .into_iter()
- .map(|c| fmt_local_path_component(c.as_ref()))
- .join("/");
- f.write_str(&full_path)?;
+ let path: String =
+ path.file_path.iter().map(|c| quote_if_needed(&*c)).join("/");
+ f.write_str(&path)?;
}
Remote(url) => {
write!(f, "{}://{}/", url.scheme, url.authority,)?;
- let path: String = url
- .path
- .clone()
- .into_iter()
- .map(|c| fmt_remote_path_component(c.as_ref()))
- .join("/");
+ let path: String = url.path.file_path.iter().join("/");
f.write_str(&path)?;
if let Some(q) = &url.query {
write!(f, "?{}", q)?
@@ -419,14 +423,6 @@ impl Display for ImportHashed {
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")?,
@@ -493,9 +489,3 @@ impl<Label: Display> Display for V<Label> {
Ok(())
}
}
-
-impl Display for X {
- fn fmt(&self, _: &mut fmt::Formatter) -> Result<(), fmt::Error> {
- match *self {}
- }
-}
diff --git a/serde_dhall/Cargo.toml b/serde_dhall/Cargo.toml
new file mode 100644
index 0000000..c61ddcd
--- /dev/null
+++ b/serde_dhall/Cargo.toml
@@ -0,0 +1,12 @@
+[package]
+name = "serde_dhall"
+version = "0.1.0"
+authors = ["Nadrieril <nadrieril@users.noreply.github.com>"]
+license = "BSD-2-Clause"
+edition = "2018"
+
+[dependencies]
+serde = { version = "1.0", features = ["derive"] }
+dhall = { path = "../dhall" }
+dhall_syntax = { path = "../dhall_syntax" }
+dhall_proc_macros = { path = "../dhall_proc_macros" }
diff --git a/serde_dhall/src/lib.rs b/serde_dhall/src/lib.rs
new file mode 100644
index 0000000..3d07539
--- /dev/null
+++ b/serde_dhall/src/lib.rs
@@ -0,0 +1,279 @@
+#![feature(non_exhaustive)]
+
+//! [Dhall][dhall] is a programmable configuration language that provides a non-repetitive
+//! alternative to JSON and YAML.
+//!
+//! You can think of Dhall as: JSON + types + imports + functions
+//!
+//! For a description of the Dhall language, examples, tutorials, and more, see the [language
+//! website][dhall].
+//!
+//! This crate provides support for consuming Dhall files the same way you would consume JSON or
+//! YAML. It uses the [Serde][serde] serialization library to provide drop-in support for Dhall
+//! for any datatype that supports serde (and that's a lot of them !).
+//!
+//! This library is limited to deserializing (reading) Dhall values; serializing (writing)
+//! values to Dhall is not supported for now.
+//!
+//! # Examples
+//!
+//! ### Custom datatype
+//!
+//! If you have a custom datatype for which you derived [serde::Deserialize], chances are
+//! you will be able to derive [StaticType] for it as well.
+//! This allows easy type-safe deserializing.
+//!
+//! ```edition2018
+//! use serde::Deserialize;
+//! use serde_dhall::{de::Error, StaticType};
+//!
+//! #[derive(Debug, Deserialize, StaticType)]
+//! struct Point {
+//! x: u64,
+//! y: u64,
+//! }
+//!
+//! fn main() -> Result<(), Error> {
+//! // Some Dhall data
+//! let data = "{ x = 1, y = 1 + 1 }";
+//!
+//! // Convert the Dhall string to a Point.
+//! let point: Point = serde_dhall::from_str_auto_type(data)?;
+//! assert_eq!(point.x, 1);
+//! assert_eq!(point.y, 2);
+//!
+//! // Invalid data fails the type validation
+//! let invalid_data = "{ x = 1, z = 0.3 }";
+//! assert!(serde_dhall::from_str_auto_type::<Point>(invalid_data).is_err());
+//!
+//! Ok(())
+//! }
+//! ```
+//!
+//! ### Loosely typed
+//!
+//! If you used to consume JSON or YAML in a loosely typed way, you can continue to do so
+//! with Dhall. You only need to replace [serde_json::from_str] or [serde_yaml::from_str]
+//! with [serde_dhall::from_str][from_str].
+//! More generally, if the [StaticType] derive doesn't suit your
+//! needs, you can still deserialize any valid Dhall file that serde can handle.
+//!
+//! [serde_json::from_str]: https://docs.serde.rs/serde_json/de/fn.from_str.html
+//! [serde_yaml::from_str]: https://docs.serde.rs/serde_yaml/fn.from_str.html
+//!
+//! ```edition2018
+//! # fn main() -> serde_dhall::de::Result<()> {
+//! use std::collections::BTreeMap;
+//!
+//! // Some Dhall data
+//! let data = "{ x = 1, y = 1 + 1 } : { x: Natural, y: Natural }";
+//!
+//! // Deserialize it to a Rust type.
+//! let deserialized_map: BTreeMap<String, usize> = serde_dhall::from_str(data)?;
+//!
+//! let mut expected_map = BTreeMap::new();
+//! expected_map.insert("x".to_string(), 1);
+//! expected_map.insert("y".to_string(), 2);
+//!
+//! assert_eq!(deserialized_map, expected_map);
+//! # Ok(())
+//! # }
+//! ```
+//!
+//! You can alternatively specify a Dhall type that the input should match.
+//!
+//! ```edition2018
+//! # fn main() -> serde_dhall::de::Result<()> {
+//! use std::collections::BTreeMap;
+//!
+//! // Parse a Dhall type
+//! let point_type_str = "{ x: Natural, y: Natural }";
+//! let point_type = serde_dhall::from_str(point_type_str)?;
+//!
+//! // Some Dhall data
+//! let point_data = "{ x = 1, y = 1 + 1 }";
+//!
+//! // Deserialize the data to a Rust type. This ensures that
+//! // the data matches the point type.
+//! let deserialized_map: BTreeMap<String, usize> =
+//! serde_dhall::from_str_check_type(point_data, &point_type)?;
+//!
+//! let mut expected_map = BTreeMap::new();
+//! expected_map.insert("x".to_string(), 1);
+//! expected_map.insert("y".to_string(), 2);
+//!
+//! assert_eq!(deserialized_map, expected_map);
+//! # Ok(())
+//! # }
+//! ```
+//!
+//! [dhall]: https://dhall-lang.org/
+//! [serde]: https://docs.serde.rs/serde/
+//! [serde::Deserialize]: https://docs.serde.rs/serde/trait.Deserialize.html
+
+mod serde;
+mod static_type;
+
+#[doc(inline)]
+pub use de::{from_str, from_str_auto_type, from_str_check_type};
+#[doc(hidden)]
+pub use dhall_proc_macros::StaticType;
+pub use static_type::StaticType;
+#[doc(inline)]
+pub use value::Value;
+
+// A Dhall value.
+pub mod value {
+ use dhall::phase::{NormalizedExpr, Parsed, Typed};
+ use dhall_syntax::Builtin;
+
+ use super::de::{Error, Result};
+
+ /// A Dhall value
+ #[derive(Debug, Clone, PartialEq, Eq)]
+ pub struct Value(Typed);
+
+ impl Value {
+ pub fn from_str(s: &str, ty: Option<&Value>) -> Result<Self> {
+ Value::from_str_using_dhall_error_type(s, ty).map_err(Error::Dhall)
+ }
+ fn from_str_using_dhall_error_type(
+ s: &str,
+ ty: Option<&Value>,
+ ) -> dhall::error::Result<Self> {
+ let resolved = Parsed::parse_str(s)?.resolve()?;
+ let typed = match ty {
+ None => resolved.typecheck()?,
+ Some(t) => resolved.typecheck_with(t.as_typed())?,
+ };
+ Ok(Value(typed))
+ }
+ pub(crate) fn to_expr(&self) -> NormalizedExpr {
+ self.0.normalize_to_expr()
+ }
+ pub(crate) fn as_typed(&self) -> &Typed {
+ &self.0
+ }
+
+ pub(crate) fn make_builtin_type(b: Builtin) -> Self {
+ Value(Typed::make_builtin_type(b))
+ }
+ pub(crate) fn make_optional_type(t: Value) -> Self {
+ Value(Typed::make_optional_type(t.0))
+ }
+ pub(crate) fn make_list_type(t: Value) -> Self {
+ Value(Typed::make_list_type(t.0))
+ }
+ // Made public for the StaticType derive macro
+ #[doc(hidden)]
+ pub fn make_record_type(
+ kts: impl Iterator<Item = (String, Value)>,
+ ) -> Self {
+ Value(Typed::make_record_type(kts.map(|(k, t)| (k, t.0))))
+ }
+ #[doc(hidden)]
+ pub fn make_union_type(
+ kts: impl Iterator<Item = (String, Option<Value>)>,
+ ) -> Self {
+ Value(Typed::make_union_type(
+ kts.map(|(k, t)| (k, t.map(|t| t.0))),
+ ))
+ }
+ }
+
+ impl super::de::Deserialize for Value {
+ fn from_dhall(v: &Value) -> Result<Self> {
+ Ok(v.clone())
+ }
+ }
+}
+
+/// Deserialize Dhall data to a Rust data structure.
+pub mod de {
+ use super::StaticType;
+ use super::Value;
+ pub use error::{Error, Result};
+
+ mod error {
+ use dhall::error::Error as DhallError;
+
+ pub type Result<T> = std::result::Result<T, Error>;
+
+ #[derive(Debug)]
+ #[non_exhaustive]
+ pub enum Error {
+ Dhall(DhallError),
+ Deserialize(String),
+ }
+
+ impl std::fmt::Display for Error {
+ fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
+ match self {
+ Error::Dhall(err) => write!(f, "{}", err),
+ Error::Deserialize(err) => write!(f, "{}", err),
+ }
+ }
+ }
+
+ impl std::error::Error for Error {}
+
+ impl serde::de::Error for Error {
+ fn custom<T>(msg: T) -> Self
+ where
+ T: std::fmt::Display,
+ {
+ Error::Deserialize(msg.to_string())
+ }
+ }
+ }
+
+ /// A data structure that can be deserialized from a Dhall expression
+ ///
+ /// This is automatically implemented for any type that [serde][serde]
+ /// can deserialize.
+ ///
+ /// This trait cannot be implemented manually.
+ // TODO: seal trait
+ pub trait Deserialize: Sized {
+ /// See [serde_dhall::from_str][crate::from_str]
+ fn from_dhall(v: &Value) -> Result<Self>;
+ }
+
+ /// Deserialize an instance of type `T` from a string of Dhall text.
+ ///
+ /// This will recursively resolve all imports in the expression, and
+ /// typecheck it before deserialization. Relative imports will be resolved relative to the
+ /// provided file. More control over this process is not yet available
+ /// but will be in a coming version of this crate.
+ pub fn from_str<T>(s: &str) -> Result<T>
+ where
+ T: Deserialize,
+ {
+ T::from_dhall(&Value::from_str(s, None)?)
+ }
+
+ /// Deserialize an instance of type `T` from a string of Dhall text,
+ /// additionally checking that it matches the supplied type.
+ ///
+ /// Like [from_str], but this additionally checks that
+ /// the type of the provided expression matches the supplied type.
+ pub fn from_str_check_type<T>(s: &str, ty: &Value) -> Result<T>
+ where
+ T: Deserialize,
+ {
+ T::from_dhall(&Value::from_str(s, Some(ty))?)
+ }
+
+ /// Deserialize an instance of type `T` from a string of Dhall text,
+ /// additionally checking that it matches the type of `T`.
+ ///
+ /// Like [from_str], but this additionally checks that
+ /// the type of the provided expression matches the output type `T`. The [StaticType] trait
+ /// captures Rust types that are valid Dhall types.
+ pub fn from_str_auto_type<T>(s: &str) -> Result<T>
+ where
+ T: Deserialize + StaticType,
+ {
+ from_str_check_type(s, &<T as StaticType>::static_type())
+ }
+}
diff --git a/dhall/src/api/serde.rs b/serde_dhall/src/serde.rs
index e1c8eef..26708c1 100644
--- a/dhall/src/api/serde.rs
+++ b/serde_dhall/src/serde.rs
@@ -1,8 +1,11 @@
-use crate::api::de::{Deserialize, Value};
-use crate::error::{Error, Result};
-use dhall_syntax::{ExprF, SubExpr, X};
use std::borrow::Cow;
+use dhall::phase::NormalizedExpr;
+use dhall_syntax::ExprF;
+
+use crate::de::{Deserialize, Error, Result};
+use crate::Value;
+
impl<'a, T> Deserialize for T
where
T: serde::Deserialize<'a>,
@@ -12,16 +15,7 @@ where
}
}
-struct Deserializer<'a>(Cow<'a, SubExpr<X, X>>);
-
-impl serde::de::Error for Error {
- fn custom<T>(msg: T) -> Self
- where
- T: std::fmt::Display,
- {
- Error::Deserialize(msg.to_string())
- }
-}
+struct Deserializer<'a>(Cow<'a, NormalizedExpr>);
impl<'de: 'a, 'a> serde::de::IntoDeserializer<'de, Error> for Deserializer<'a> {
type Deserializer = Deserializer<'a>;
@@ -39,20 +33,24 @@ impl<'de: 'a, 'a> serde::Deserializer<'de> for Deserializer<'a> {
use std::convert::TryInto;
use ExprF::*;
match self.0.as_ref().as_ref() {
- NaturalLit(n) => match (*n).try_into() {
- Ok(n64) => visitor.visit_u64(n64),
- Err(_) => match (*n).try_into() {
- Ok(n32) => visitor.visit_u32(n32),
- Err(_) => unimplemented!(),
- },
- },
- IntegerLit(n) => match (*n).try_into() {
- Ok(n64) => visitor.visit_i64(n64),
- Err(_) => match (*n).try_into() {
- Ok(n32) => visitor.visit_i32(n32),
- Err(_) => unimplemented!(),
- },
- },
+ NaturalLit(n) => {
+ if let Ok(n64) = (*n).try_into() {
+ visitor.visit_u64(n64)
+ } else if let Ok(n32) = (*n).try_into() {
+ visitor.visit_u32(n32)
+ } else {
+ unimplemented!()
+ }
+ }
+ IntegerLit(n) => {
+ if let Ok(n64) = (*n).try_into() {
+ visitor.visit_i64(n64)
+ } else if let Ok(n32) = (*n).try_into() {
+ visitor.visit_i32(n32)
+ } else {
+ unimplemented!()
+ }
+ }
RecordLit(m) => visitor.visit_map(
serde::de::value::MapDeserializer::new(m.iter().map(
|(k, v)| (k.as_ref(), Deserializer(Cow::Borrowed(v))),
diff --git a/dhall/src/api/static_type.rs b/serde_dhall/src/static_type.rs
index 906bcef..67a7bc4 100644
--- a/dhall/src/api/static_type.rs
+++ b/serde_dhall/src/static_type.rs
@@ -1,6 +1,6 @@
use dhall_syntax::{Builtin, Integer, Natural};
-use crate::api::Type;
+use crate::Value;
/// A Rust type that can be represented as a Dhall type.
///
@@ -14,14 +14,14 @@ use crate::api::Type;
/// [StaticType] because each different value would
/// have a different Dhall record type.
pub trait StaticType {
- fn static_type() -> Type;
+ fn static_type() -> Value;
}
macro_rules! derive_builtin {
($ty:ty, $builtin:ident) => {
impl StaticType for $ty {
- fn static_type() -> Type {
- Type::make_builtin_type(Builtin::$builtin)
+ fn static_type() -> Value {
+ Value::make_builtin_type(Builtin::$builtin)
}
}
};
@@ -38,8 +38,8 @@ where
A: StaticType,
B: StaticType,
{
- fn static_type() -> Type {
- Type::make_record_type(
+ fn static_type() -> Value {
+ Value::make_record_type(
vec![
("_1".to_owned(), A::static_type()),
("_2".to_owned(), B::static_type()),
@@ -54,8 +54,8 @@ where
T: StaticType,
E: StaticType,
{
- fn static_type() -> Type {
- Type::make_union_type(
+ fn static_type() -> Value {
+ Value::make_union_type(
vec![
("Ok".to_owned(), Some(T::static_type())),
("Err".to_owned(), Some(E::static_type())),
@@ -69,8 +69,8 @@ impl<T> StaticType for Option<T>
where
T: StaticType,
{
- fn static_type() -> Type {
- Type::make_optional_type(T::static_type())
+ fn static_type() -> Value {
+ Value::make_optional_type(T::static_type())
}
}
@@ -78,8 +78,8 @@ impl<T> StaticType for Vec<T>
where
T: StaticType,
{
- fn static_type() -> Type {
- Type::make_list_type(T::static_type())
+ fn static_type() -> Value {
+ Value::make_list_type(T::static_type())
}
}
@@ -87,7 +87,7 @@ impl<'a, T> StaticType for &'a T
where
T: StaticType,
{
- fn static_type() -> Type {
+ fn static_type() -> Value {
T::static_type()
}
}
diff --git a/dhall/tests/traits.rs b/serde_dhall/tests/traits.rs
index 0f75553..15a91ed 100644
--- a/dhall/tests/traits.rs
+++ b/serde_dhall/tests/traits.rs
@@ -1,10 +1,9 @@
-#![feature(proc_macro_hygiene)]
-use dhall::de::{from_str, StaticType, Type};
+use serde_dhall::{from_str, StaticType, Value};
#[test]
fn test_static_type() {
- fn parse(s: &str) -> Type {
- from_str(s, None).unwrap()
+ fn parse(s: &str) -> Value {
+ from_str(s).unwrap()
}
assert_eq!(bool::static_type(), parse("Bool"));
@@ -15,14 +14,14 @@ fn test_static_type() {
parse("{ _1: Bool, _2: List Text }")
);
- #[derive(dhall::de::StaticType)]
+ #[derive(serde_dhall::StaticType)]
#[allow(dead_code)]
struct A {
field1: bool,
field2: Option<bool>,
}
assert_eq!(
- <A as dhall::de::StaticType>::static_type(),
+ <A as serde_dhall::StaticType>::static_type(),
parse("{ field1: Bool, field2: Optional Bool }")
);
diff --git a/tests_buffer b/tests_buffer
index c6366ba..6597d69 100644
--- a/tests_buffer
+++ b/tests_buffer
@@ -3,12 +3,16 @@ parser:
./"a%20b"
text interpolation and escapes
projection by expression unit tests
+fix fakeurlencode test
success/
operators/
PrecedenceAll1 a ? b || c + d ++ e # f && g ∧ h ⫽ i ⩓ j * k == l != m n.o
PrecedenceAll2 a b != c == d * e ⩓ f ⫽ g ∧ h && i # j ++ k + l || m ? n
LetNoAnnot let x = y in e
LetAnnot let x: T = y in e
+ EmptyRecordLiteral {=}
+ ToMap toMap x
+ ToMapAnnot toMap x : T
failure/
AssertNoAnnotation assert
@@ -28,11 +32,18 @@ variables across import boundaries
TextLitNested1 "${""}${x}"
TextLitNested2 "${"${x}"}"
TextLitNested3 "${"${""}"}${x}"
+ regression/
+ NaturalFoldExtraArg Natural/fold 0 (Bool -> Bool) (λ(_ : (Bool -> Bool)) → λ(_ : Bool) → True) (λ(_ : Bool) → False) True
typecheck:
something that involves destructuring a recordtype after merge
add some of the more complicated Prelude tests back, like List/enumerate
-failure on old-style optional literal
+success/
+ regression/
+ RecursiveRecordTypeMergeTripleCollision { x : { a : Bool } } ⩓ { x : { b : Bool } } ⩓ { x : { c : Bool } }
+ somehow test that ({ x = { z = 1 } } ∧ { x = { y = 2 } }).x has a type
+ somehow test that the recordtype from List/indexed has a type in both empty and nonempty cases
+ somehow test types added to the Foo/build closures
failure/
merge { x = λ(x : Bool) → x } (< x: Bool | y: Natural >.x True)
merge { x = λ(_ : Bool) → _, y = 1 } < x = True | y >
@@ -41,5 +52,6 @@ failure/
merge {x=...,y=...} <x:T>.x
MergeBoolIsNotUnion merge x True
MergeOptionalIsNotUnion merge x (Some 1)
+ SortInLet let x = Sort in 1
equivalence: