use crate::error::Error; use crate::expr::*; use dhall_core::*; use std::collections::HashMap; use std::fs::File; use std::io::Read; use std::path::Path; use std::path::PathBuf; #[derive(Debug)] pub enum ImportError { Recursive(Import, Box), UnexpectedImport(Import), } /// A root from which to resolve relative imports. #[derive(Debug, Clone, PartialEq, Eq)] pub enum ImportRoot { LocalDir(PathBuf), } type ImportCache = HashMap>; fn resolve_import( import: &Import, root: &ImportRoot, import_cache: &mut ImportCache, ) -> Result, ImportError> { use self::ImportRoot::*; use dhall_core::FilePrefix::*; use dhall_core::ImportLocation::*; let cwd = match root { LocalDir(cwd) => cwd, }; match &import.location_hashed.location { Local(prefix, path) => { let path = match prefix { // TODO: fail gracefully Parent => cwd.parent().unwrap().join(path), Here => cwd.join(path), _ => unimplemented!("{:?}", import), }; Ok(load_import(&path, import_cache).map_err(|e| { ImportError::Recursive(import.clone(), Box::new(e)) })?) } _ => unimplemented!("{:?}", import), } } fn load_import( f: &Path, import_cache: &mut ImportCache, ) -> Result, Error> { Ok(do_resolve_expr(Parsed::parse_file(f)?, import_cache)? .typecheck()? .normalize()) } fn do_resolve_expr<'a>( Parsed(expr, root): Parsed<'a>, import_cache: &mut ImportCache, ) -> Result, ImportError> { let resolve = |import: &Import| -> Result, ImportError> { match import_cache.get(import) { Some(expr) => Ok(expr.clone()), None => { let expr = resolve_import(import, &root, import_cache)?; import_cache.insert(import.clone(), expr.clone()); Ok(expr) } } }; let expr = expr.as_ref().traverse_embed(resolve)?; Ok(Resolved(rc(expr))) } fn skip_resolve_expr( Parsed(expr, _root): Parsed<'_>, ) -> Result, ImportError> { let resolve = |import: &Import| -> Result, ImportError> { Err(ImportError::UnexpectedImport(import.clone())) }; let expr = expr.as_ref().traverse_embed(&resolve)?; Ok(Resolved(rc(expr))) } impl<'a> Parsed<'a> { pub fn parse_file(f: &Path) -> Result, Error> { let mut buffer = String::new(); File::open(f)?.read_to_string(&mut buffer)?; let expr = parse_expr(&*buffer)?; let root = ImportRoot::LocalDir(f.parent().unwrap().to_owned()); Ok(Parsed(expr.unnote().note_absurd(), root)) } pub fn parse_str(s: &'a str) -> Result, Error> { let expr = parse_expr(s)?; let root = ImportRoot::LocalDir(std::env::current_dir()?); Ok(Parsed(expr, root)) } #[allow(dead_code)] pub fn parse_binary_file(f: &Path) -> Result, Error> { let mut buffer = Vec::new(); File::open(f)?.read_to_end(&mut buffer)?; let expr = crate::binary::decode(&buffer)?; let root = ImportRoot::LocalDir(f.parent().unwrap().to_owned()); Ok(Parsed(expr.note_absurd(), root)) } pub fn resolve(self) -> Result, ImportError> { crate::imports::do_resolve_expr(self, &mut HashMap::new()) } #[allow(dead_code)] pub fn skip_resolve(self) -> Result, ImportError> { crate::imports::skip_resolve_expr(self) } } #[cfg(test)] mod spec_tests { #![rustfmt::skip] macro_rules! import_success { ($name:ident, $path:expr) => { make_spec_test!(Import, Success, $name, $path); }; } // macro_rules! import_failure { // ($name:ident, $path:expr) => { // make_spec_test!(Import, Failure, $name, $path); // }; // } // import_success!(success_alternativeEnvNatural, "alternativeEnvNatural"); // import_success!(success_alternativeEnvSimple, "alternativeEnvSimple"); // import_success!(success_alternativeNatural, "alternativeNatural"); // import_success!(success_asText, "asText"); import_success!(success_fieldOrder, "fieldOrder"); // import_failure!(failure_alternativeEnv, "alternativeEnv"); // import_failure!(failure_alternativeEnvMissing, "alternativeEnvMissing"); // import_failure!(failure_cycle, "cycle"); // import_failure!(failure_missing, "missing"); // import_failure!(failure_referentiallyInsane, "referentiallyInsane"); }