diff --git a/Cargo.lock b/Cargo.lock index ba42476..e04dec1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11,6 +11,17 @@ dependencies = [ "const-random", ] +[[package]] +name = "ahash" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" +dependencies = [ + "getrandom", + "once_cell", + "version_check", +] + [[package]] name = "cfg-if" version = "1.0.0" @@ -23,7 +34,7 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d02796e4586c6c41aeb68eae9bfb4558a522c35f1430c14b40136c3706e09e4" dependencies = [ - "ahash", + "ahash 0.3.8", ] [[package]] @@ -76,6 +87,15 @@ dependencies = [ "wasi", ] +[[package]] +name = "hashbrown" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db0d4cf898abf0081f964436dc980e96670a0f36863e4b83aaacdb65c9d7ccc3" +dependencies = [ + "ahash 0.7.6", +] + [[package]] name = "lazy_static" version = "1.4.0" @@ -88,12 +108,19 @@ version = "0.2.126" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836" +[[package]] +name = "once_cell" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7709cef83f0c1f58f666e746a08b21e0085f7440fa6a29cc194d68aac97a4225" + [[package]] name = "orchid" version = "0.1.0" dependencies = [ "chumsky", "derivative", + "hashbrown", "thiserror", ] @@ -167,6 +194,12 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d22af068fba1eb5edcb4aea19d382b2a3deb4c8f9d475c589b6ada9e0fd493ee" +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + [[package]] name = "wasi" version = "0.10.2+wasi-snapshot-preview1" diff --git a/Cargo.toml b/Cargo.toml index 90e40b3..f9b58e0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,4 +8,5 @@ edition = "2021" [dependencies] thiserror = "1.0" chumsky = "0.8" -derivative = "2.2" \ No newline at end of file +derivative = "2.2" +hashbrown = "0.12" \ No newline at end of file diff --git a/src/parse/expression.rs b/src/parse/expression.rs index da1210f..c57aca0 100644 --- a/src/parse/expression.rs +++ b/src/parse/expression.rs @@ -17,6 +17,7 @@ pub enum Expr { S(Vec), Lambda(String, Option>, Vec), Auto(Option, Option>, Vec), + Typed(Box, Box) } diff --git a/src/parse/mod.rs b/src/parse/mod.rs index 730085c..95b25ab 100644 --- a/src/parse/mod.rs +++ b/src/parse/mod.rs @@ -7,6 +7,7 @@ mod name; mod substitution; mod sourcefile; +pub use substitution::Substitution; pub use expression::Expr; pub use expression::expression_parser; pub use sourcefile::FileEntry; diff --git a/src/project/expr.rs b/src/project/expr.rs new file mode 100644 index 0000000..1d0fb43 --- /dev/null +++ b/src/project/expr.rs @@ -0,0 +1,25 @@ +#[derive(Debug, Clone)] +pub enum Literal { + Num(f64), + Int(u64), + Char(char), + Str(String), +} + +#[derive(Debug, Clone)] +pub enum Token { + Literal(Literal), + Name { + qualified: Vec, + local: Option + }, + S(Vec), + Lambda(String, Option>, Vec), + Auto(Option, Option>, Vec) +} + +#[derive(Debug, Clone)] +pub struct Expr { + pub token: Token, + pub typ: Option> +} \ No newline at end of file diff --git a/src/project/mod.rs b/src/project/mod.rs index c0eafd0..c40f4c3 100644 --- a/src/project/mod.rs +++ b/src/project/mod.rs @@ -1,53 +1,25 @@ use std::collections::HashMap; mod resolve_names; - +mod prefix; +mod name_resolver; +mod expr; #[derive(Debug, Clone)] pub struct Project { pub modules: HashMap, Module>, } -#[derive(Debug, Clone)] -pub struct Export { - isSymbol: bool, - subpaths: HashMap -} - #[derive(Debug, Clone)] pub struct Module { pub substitutions: Vec, - pub exports: HashMap, - pub all_ops: Vec + pub exports: Vec, + pub references: Vec> } #[derive(Debug, Clone)] pub struct Substitution { - pub source: Expr, + pub source: expr::Expr, pub priority: f64, - pub target: Expr -} - -#[derive(Debug, Clone)] -pub enum Literal { - Num(f64), - Int(u64), - Char(char), - Str(String), -} - -#[derive(Debug, Clone)] -pub enum Token { - Literal(Literal), - Name(String), - Bound, - S(Vec), - Lambda(Vec>, Option>, Vec), - Auto(Option>>, Option>, Vec) -} - -#[derive(Debug, Clone)] -pub struct Expr { - pub token: Token, - pub typ: Box + pub target: expr::Expr } diff --git a/src/project/name_resolver.rs b/src/project/name_resolver.rs new file mode 100644 index 0000000..f635590 --- /dev/null +++ b/src/project/name_resolver.rs @@ -0,0 +1,122 @@ +use std::{collections::HashMap}; +use thiserror::Error; + +use crate::utils::Substack; + +use super::expr::{Expr, Token}; + +type ImportMap = HashMap>; + +#[derive(Debug, Clone, Error)] +pub enum ResolutionError { + #[error("Reference cycle at {0:?}")] + Cycle(Vec>), + #[error("No module provides {0:?}")] + NoModule(Vec), + #[error(transparent)] + Delegate(#[from] Err) +} + +/// Recursively resolves symbols to their original names in expressions while caching every +/// resolution. This makes the resolution process lightning fast and invalidation completely +/// impossible since the intermediate steps of a resolution aren't stored. +pub struct NameResolver { + cache: HashMap, Result, ResolutionError>>, + get_modname: FSplit, + get_imports: FImps +} + + +impl NameResolver +where + FSplit: FnMut(&Vec) -> Option>, + FImps: FnMut(&Vec) -> Result, + E: Clone +{ + pub fn new(get_modname: FSplit, get_imports: FImps) -> Self { + Self { + cache: HashMap::new(), + get_modname, + get_imports + } + } + + /// Obtains a symbol's originnal name + /// Uses a substack to detect loops + fn find_origin_rec( + &mut self, + symbol: &Vec, + import_path: &Substack<'_, &Vec> + ) -> Result, ResolutionError> { + if let Some(cached) = self.cache.get(symbol) { return cached.clone() } + // The imports and path of the referenced file and the local name + let mut splitpoint = symbol.len(); + let path = (self.get_modname)(symbol).ok_or(ResolutionError::NoModule(symbol.clone()))?; + let name = symbol.split_at(path.len()).1; + let imports = (self.get_imports)(&path)?; + let result = if let Some(source) = imports.get(&name[0]) { + let new_sym: Vec = source.iter().chain(name.iter()).cloned().collect(); + if import_path.iter().any(|el| el == &&new_sym) { + Err(ResolutionError::Cycle(import_path.iter().cloned().cloned().collect())) + } else { + self.find_origin_rec(&new_sym, &import_path.push(symbol)) + } + } else { + Ok(symbol.clone()) // If not imported, it must be locally defined + }; + self.cache.insert(symbol.clone(), result.clone()); + return result + } + + fn process_exprv_rec(&mut self, exv: &[Expr]) -> Result, ResolutionError> { + exv.iter().map(|ex| self.process_expression_rec(ex)).collect() + } + + fn process_exprboxopt_rec(&mut self, + exbo: &Option> + ) -> Result>, ResolutionError> { + exbo.iter().map(|exb| Ok(Box::new(self.process_expression_rec(exb.as_ref())?))) + .next().transpose() + } + + fn process_token_rec(&mut self, tok: &Token) -> Result> { + Ok(match tok { + Token::Literal(l) => Token::Literal(l.clone()), + Token::S(exv) => Token::S( + exv.iter().map(|e| self.process_expression_rec(e)) + .collect::, ResolutionError>>()? + ), + Token::Lambda(name, typ, body) => Token::Lambda(name.clone(), + self.process_exprboxopt_rec(typ)?, + self.process_exprv_rec(body)? + ), + Token::Auto(name, typ, body) => Token::Auto(name.clone(), + self.process_exprboxopt_rec(typ)?, + self.process_exprv_rec(body)? + ), + Token::Name { qualified, local } => Token::Name { + local: local.clone(), + qualified: self.find_origin(qualified)? + } + }) + } + + fn process_expression_rec(&mut self, ex: &Expr) -> Result> { + Ok(Expr { + token: self.process_token_rec(&ex.token)?, + typ: self.process_exprboxopt_rec(&ex.typ)? + }) + } + + pub fn find_origin(&mut self, symbol: &Vec) -> Result, ResolutionError> { + self.find_origin_rec(symbol, &Substack::new(symbol)) + } + + pub fn process_token(&mut self, tok: &Token) -> Result> { + self.process_token_rec(tok) + } + + pub fn process_expression(&mut self, ex: &Expr) -> Result> { + self.process_expression_rec(ex) + } +} \ No newline at end of file diff --git a/src/project/prefix.rs b/src/project/prefix.rs new file mode 100644 index 0000000..b9968a6 --- /dev/null +++ b/src/project/prefix.rs @@ -0,0 +1,61 @@ +use std::collections::HashMap; + +use crate::parse; +use super::expr; + +/// Replaces the first element of a name with the matching prefix from a prefix map +fn qualify( + name: &Vec, + prefixes: &HashMap> +) -> Option> { + let value = prefixes.iter().find(|(k, _)| &&name[0] == k)?.1; + Some(value.iter().chain(name.iter().skip(1)).cloned().collect()) +} + +/// Produce a Token object for any value of parse::Expr other than Typed. +/// Called by [#prefix] which handles Typed. +fn prefix_token( + expr: &parse::Expr, + namespace: &Vec +) -> expr::Token { + match expr { + parse::Expr::Typed(_, _) => panic!("This function should only be called by prefix!"), + parse::Expr::Char(c) => expr::Token::Literal(expr::Literal::Char(*c)), + parse::Expr::Int(i) => expr::Token::Literal(expr::Literal::Int(*i)), + parse::Expr::Num(n) => expr::Token::Literal(expr::Literal::Num(*n)), + parse::Expr::Str(s) => expr::Token::Literal(expr::Literal::Str(s.clone())), + parse::Expr::S(v) => expr::Token::S(v.iter().map(|e| prefix(e, namespace)).collect()), + parse::Expr::Auto(name, typ, body) => expr::Token::Auto( + name.clone(), + typ.clone().map(|expr| Box::new(prefix(&expr, namespace))), + body.iter().map(|e| prefix(e, namespace)).collect(), + ), + parse::Expr::Lambda(name, typ, body) => expr::Token::Lambda( + name.clone(), + typ.clone().map(|expr| Box::new(prefix(&expr, namespace))), + body.iter().map(|e| prefix(e, namespace)).collect(), + ), + parse::Expr::Name(name) => expr::Token::Name { + qualified: namespace.iter().chain(name.iter()).cloned().collect(), + local: if name.len() == 1 { + Some(name[0].clone()) + } else { + None + }, + }, + } +} + +/// Produce an Expr object for any value of parse::Expr +pub fn prefix(expr: &parse::Expr, namespace: &Vec) -> expr::Expr { + match expr { + parse::Expr::Typed(x, t) => expr::Expr { + typ: Some(Box::new(prefix(t, namespace))), + token: prefix_token(x, namespace), + }, + _ => expr::Expr { + typ: None, + token: prefix_token(expr, namespace), + }, + } +} diff --git a/src/project/resolve_names.rs b/src/project/resolve_names.rs index 094ccbe..8dda6a0 100644 --- a/src/project/resolve_names.rs +++ b/src/project/resolve_names.rs @@ -1,95 +1,122 @@ -use std::borrow::Borrow; use std::cell::RefCell; -use std::collections::HashMap; -use std::rc::Rc; -use std::{iter, clone}; +use std::collections::{HashMap, HashSet, VecDeque}; +use std::error; -use chumsky::{Parser, prelude::Simple}; +use chumsky::{prelude::Simple, Parser}; use thiserror::Error; -use crate::parse::{self, file_parser, exported_names, FileEntry}; -use crate::utils::Cache; +use crate::parse::{self, file_parser, FileEntry}; +use crate::utils::{Cache, as_modpath}; + +use super::expr; +use super::name_resolver::{NameResolver, ResolutionError}; +use super::prefix::prefix; #[derive(Debug, Clone)] pub enum Loaded { Module(String), - Namespace(Vec) + Namespace(Vec), } -#[derive(Error, Debug)] -pub enum ParseError { - #[error("Not found: {0}")] - NotFound(String), - #[error("Failed to parse {file}: {errors:?}")] - Syntax { - file: String, - errors: Vec> - }, - #[error("Expected {0}, found {1}")] - Mismatch(String, String), - +#[derive(Error, Debug, Clone)] +pub enum ParseError where ELoad: Clone { + #[error("Resolution cycle")] + ResolutionCycle, + #[error("File not found: {0}")] + Load(ELoad), + #[error("Failed to parse: {0:?}")] + Syntax(Vec>), + #[error("Not a module")] + None } -impl ParseError { - pub fn not_found(name: &str) -> ParseError { ParseError::NotFound(name.to_string()) } - pub fn syntax(file: &str, errors: Vec>) -> ParseError { - ParseError::Syntax { file: file.to_string(), errors } - } - pub fn mismatch(expected: &str, found: &str) -> ParseError { - ParseError::Mismatch(expected.to_string(), found.to_string()) +impl From>> for ParseError where T: Clone { + fn from(simp: Vec>) -> Self { Self::Syntax(simp) } +} + +impl From>> for ParseError where T: Clone { + fn from(res: ResolutionError>) -> Self { + match res { + ResolutionError::Cycle(_) => ParseError::ResolutionCycle, + ResolutionError::NoModule(_) => ParseError::None, + ResolutionError::Delegate(d) => d + } } } -// Loading a module: -// 1. [x] Parse the imports -// 2. [x] Build a mapping of all imported symbols to full paths -// -> [x] Parse the exported symbols from all imported modules -// 3. [x] Parse everything using the full list of operators -// 4. [ ] Traverse and remap elements +type ImportMap = HashMap>; +type ParseResult = Result>; +type AnyParseResult = Result>>; -type GetLoaded<'a> = dyn FnMut(&'a [&str]) -> &'a Option; -type GetPreparsed<'a> = dyn FnMut(&'a [&str]) -> &'a Option>; - -pub fn load_project<'a, F>( - mut load_mod: F, prelude: &[&'a str], entry: &str -) -> Result -where F: FnMut(&[&str]) -> Option { - // TODO: Welcome to Kamino! +pub fn load_project<'a, F, ELoad>( + mut load_mod: F, + prelude: &[&'a str], + entry: (Vec, expr::Expr), +) -> Result> +where + F: FnMut(&[&str]) -> Result, + ELoad: Clone +{ let prelude_vec: Vec = prelude.iter().map(|s| s.to_string()).collect(); let preparser = file_parser(prelude, &[]); - let loaded_cell = RefCell::new(Cache::new(|path: Vec| { + // Map paths to a namespace with name list (folder) or module with source text (file) + let loaded_cell = RefCell::new(Cache::new(|path: Vec| + -> ParseResult { load_mod(&path.iter().map(|s| s.as_str()).collect::>()) + .map_err(ParseError::Load) })); - let preparsed_cell = RefCell::new(Cache::new(|path: Vec| { - let mut loaded = loaded_cell.borrow_mut(); - loaded.by_clone(path).as_ref().map(|loaded| match loaded { - Loaded::Module(source) => Some(preparser.parse(source.as_str()).ok()?), - _ => return None - }).flatten() - })); - let exports_cell = RefCell::new(Cache::new(|path: Vec| { - let mut loaded = loaded_cell.borrow_mut(); - loaded.by_clone(path.clone()).as_ref().map(|data| { - let mut preparsed = preparsed_cell.borrow_mut(); - match data { - Loaded::Namespace(names) => Some(names.clone()), - Loaded::Module(source) => preparsed.by_clone(path).as_ref().map(|data| { - parse::exported_names(&data).into_iter() - .map(|n| n[0].clone()) - .collect() - }), - _ => None + let modname_cell = RefCell::new(Cache::new(|symbol: Vec| + -> AnyParseResult, ELoad> { + let mut local_loaded = loaded_cell.borrow_mut(); + let mut errv: Vec> = Vec::new(); + loop { + let (path, name) = symbol.split_at(symbol.len() - errv.len()); + let pathv = path.to_vec(); + match local_loaded.by_clone_fallible(&pathv) { + Ok(imports) => break Ok(pathv.clone()), + Err(err) => { + errv.push(err); + if symbol.len() == errv.len() { + break Err(errv); + } + } } - }).flatten() + } })); - let imports_cell = RefCell::new(Cache::new(|path: Vec| { - let mut preparsed = preparsed_cell.borrow_mut(); - let entv = preparsed.by_clone(path).clone()?; + // Preliminarily parse a file, substitution patterns and imports are valid + let preparsed_cell = RefCell::new(Cache::new(|path: Vec| + -> ParseResult, ELoad> { + let mut loaded = loaded_cell.borrow_mut(); + let loaded = loaded.by_clone_fallible(&path)?; + if let Loaded::Module(source) = loaded { + Ok(preparser.parse(source.as_str())?) + } else {Err(ParseError::None)} + })); + // Collect all toplevel names exported from a given file + let exports_cell = RefCell::new(Cache::new(|path: Vec| + -> ParseResult, ELoad> { + let mut local_loaded = loaded_cell.borrow_mut(); + let loaded = local_loaded.by_clone_fallible(&path)?; + let mut local_preparsed = preparsed_cell.borrow_mut(); + if let Loaded::Namespace(names) = loaded { + return Ok(names.clone()); + } + let preparsed = local_preparsed.by_clone_fallible(&path)?; + Ok(parse::exported_names(&preparsed) + .into_iter() + .map(|n| n[0].clone()) + .collect()) + })); + // Collect all toplevel names imported by a given file + let imports_cell = RefCell::new(Cache::new(|path: Vec| + -> ParseResult { + let mut local_preparsed = preparsed_cell.borrow_mut(); + let entv = local_preparsed.by_clone_fallible(&path)?.clone(); let import_entries = parse::imports(entv.iter()); let mut imported_symbols: HashMap> = HashMap::new(); for imp in import_entries { let mut exports = exports_cell.borrow_mut(); - let export = exports.by_clone(imp.path.clone()).as_ref()?; + let export = exports.by_clone_fallible(&imp.path)?; if let Some(ref name) = imp.name { if export.contains(&name) { imported_symbols.insert(name.clone(), imp.path.clone()); @@ -100,32 +127,95 @@ where F: FnMut(&[&str]) -> Option { } } } - Some(imported_symbols) + Ok(imported_symbols) })); - let parsed = RefCell::new(Cache::new(|path: Vec| { - let mut imports = imports_cell.borrow_mut(); - let mut loaded = loaded_cell.borrow_mut(); - let data = loaded.by_clone(path.clone()).as_ref()?; - let text = match data { Loaded::Module(s) => Some(s), _ => None }?; - let imported_symbols = imports.by_clone(path).as_ref()?; - let imported_ops: Vec<&str> = imported_symbols.keys() + // Final parse, operators are correctly separated + let parsed_cell = RefCell::new(Cache::new(|path: Vec| + -> ParseResult, ELoad> { + let mut local_imports = imports_cell.borrow_mut(); + let imports = local_imports.by_clone_fallible(&path)?; + let mut local_loaded = loaded_cell.borrow_mut(); + let imported_ops: Vec<&str> = imports + .keys() .chain(prelude_vec.iter()) .map(|s| s.as_str()) .filter(|s| parse::is_op(s)) .collect(); - let file_parser = file_parser(prelude, &imported_ops); - file_parser.parse(text.as_str()).ok() + let parser = file_parser(prelude, &imported_ops); + if let Loaded::Module(source) = local_loaded.by_clone_fallible(&path)? { + Ok(parser.parse(source.as_str())?) + } else {Err(ParseError::None)} })); + let mut name_resolver = NameResolver::new( + |path: &Vec| { modname_cell.borrow_mut().by_clone_fallible(path).cloned().ok() }, + |path: &Vec| { imports_cell.borrow_mut().by_clone_fallible(path).cloned() } + ); + // Turn parsed files into a bag of substitutions and a list of toplevel export names + let resolved_cell = RefCell::new(Cache::new(|path: Vec| + -> ParseResult { + let mut parsed = parsed_cell.borrow_mut(); + let parsed_entries = parsed.by_clone_fallible(&path)?; + let subs: Vec = parsed_entries + .iter() + .filter_map(|ent| { + if let FileEntry::Export(s) | FileEntry::Substitution(s) = ent { + Some(super::Substitution { + source: prefix(&s.source, &path), + target: prefix(&s.target, &path), + priority: s.priority, + }) + } else { None } + }) + .map(|sub| Ok(super::Substitution { + source: name_resolver.process_expression(&sub.source)?, + target: name_resolver.process_expression(&sub.target)?, + ..sub + })) + .collect::, ELoad>>()?; + let module = super::Module { + substitutions: subs, + exports: exports_cell + .borrow_mut() + .by_clone_fallible(&path)? + .clone(), + references: imports_cell + .borrow_mut() + .by_clone_fallible(&path)? + .values() + .filter_map(|imps| modname_cell.borrow_mut().by_clone_fallible(imps).ok().cloned()) + .collect() + }; + Ok(module) + })); + let all_subs_cell = RefCell::new(Cache::new(|path: Vec| + -> ParseResult, ELoad> { + let mut processed: HashSet> = HashSet::new(); + let mut subs: Vec = Vec::new(); + let mut pending: VecDeque> = VecDeque::new(); + while let Some(el) = pending.pop_front() { + let mut local_resolved = resolved_cell.borrow_mut(); + let resolved = local_resolved.by_clone_fallible(&el)?; + processed.insert(el.clone()); + pending.extend( + resolved.references.iter() + .filter(|&v| !processed.contains(v)) + .cloned() + ); + subs.extend( + resolved.substitutions.iter().cloned() + ) + }; + Ok(subs) + })); + // let substitutions = // let main = preparsed.get(&[entry]); // for imp in parse::imports(main) { // if !modules.contains_key(&imp.path) { - // if modules[&imp.path] + // if modules[&imp.path] // } // } // let mut project = super::Project { // modules: HashMap::new() // }; - - // Some(project) todo!("Finish this function") -} \ No newline at end of file +} diff --git a/src/utils/cache.rs b/src/utils/cache.rs index ddff5e1..befefd6 100644 --- a/src/utils/cache.rs +++ b/src/utils/cache.rs @@ -1,5 +1,5 @@ -use std::collections::HashMap; use std::hash::Hash; +use hashbrown::HashMap; /// Cache the return values of an effectless closure in a hashmap /// Inspired by the closure_cacher crate. @@ -8,27 +8,64 @@ pub struct Cache { closure: F } -impl Cache where +impl Cache where I: Eq + Hash, F: FnMut(I) -> O { pub fn new(closure: F) -> Self { Self { store: HashMap::new(), closure } } - pub fn by_copy(&mut self, i: I) -> &O where I: Copy { + /// Produce and cache a result by copying I if necessary + pub fn by_copy(&mut self, i: &I) -> &O where I: Copy { let closure = &mut self.closure; - self.store.entry(i).or_insert_with(|| closure(i)) + self.store.raw_entry_mut().from_key(i) + .or_insert_with(|| (*i, closure(*i))).1 } - pub fn by_clone(&mut self, i: I) -> &O where I: Clone { + /// Produce and cache a result by cloning I if necessary + pub fn by_clone(&mut self, i: &I) -> &O where I: Clone { let closure = &mut self.closure; - // Make sure we only clone if necessary - self.store.entry(i).or_insert_with_key(|k| closure(k.clone())) + self.store.raw_entry_mut().from_key(i) + .or_insert_with(|| (i.clone(), closure(i.clone()))).1 } + /// Return the result if it has already been computed pub fn known(&self, i: &I) -> Option<&O> { self.store.get(i) } /// Forget the output for the given input pub fn drop(&mut self, i: &I) -> bool { self.store.remove(i).is_some() + } +} + +impl Cache, F> where + I: Eq + Hash, + E: Clone, + F: FnMut(I) -> Result +{ + /// Sink the ref from a Result into the Ok value, such that copying only occurs on the sad path + /// but the return value can be short-circuited + pub fn by_copy_fallible(&mut self, i: &I) -> Result<&O, E> where I: Copy { + self.by_clone(i).as_ref().map_err(|e| e.clone()) + } + /// Sink the ref from a Result into the Ok value, such that cloning only occurs on the sad path + /// but the return value can be short-circuited + pub fn by_clone_fallible(&mut self, i: &I) -> Result<&O, E> where I: Clone { + self.by_clone(i).as_ref().map_err(|e| e.clone()) + } +} + +impl Cache, F> where + I: Eq + Hash, + F: FnMut(I) -> Option +{ + /// Sink the ref from an Option into the Some value such that the return value can be + /// short-circuited + pub fn by_copy_fallible(&mut self, i: &I) -> Option<&O> where I: Copy { + self.by_copy(i).as_ref() + } + /// Sink the ref from an Option into the Some value such that the return value can be + /// short-circuited + pub fn by_clone_fallible(&mut self, i: &I) -> Option<&O> where I: Clone { + self.by_clone(i).as_ref() } -} \ No newline at end of file +} diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 7869800..4f15ccf 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -1,2 +1,8 @@ mod cache; -pub use cache::Cache; \ No newline at end of file +mod substack; +pub use cache::Cache; +pub use substack::Substack; + +pub fn as_modpath(path: &Vec) -> String { + path.join("::") +} \ No newline at end of file diff --git a/src/utils/substack.rs b/src/utils/substack.rs new file mode 100644 index 0000000..f2ffc76 --- /dev/null +++ b/src/utils/substack.rs @@ -0,0 +1,43 @@ + +/// Implement a FILO stack that lives on the regular call stack as a linked list. +/// Mainly useful to detect loops in recursive algorithms where the recursion isn't +/// deep enough to warrant a heap-allocated set +#[derive(Debug, Clone, Copy)] +pub struct Substack<'a, T> { + pub item: T, + pub prev: Option<&'a Self> +} + +impl<'a, T> Substack<'a, T> { + pub fn item(&self) -> &T { &self.item } + pub fn prev(&self) -> Option<&'a Substack<'a, T>> { self.prev } + + pub fn new(item: T) -> Self { + Self { + item, + prev: None + } + } + pub fn push(&'a self, item: T) -> Self { + Self { + item, + prev: Some(self) + } + } + pub fn iter(&'a self) -> SubstackIterator<'a, T> { + SubstackIterator { curr: Some(self) } + } +} + +pub struct SubstackIterator<'a, T> { + curr: Option<&'a Substack<'a, T>> +} + +impl<'a, T> Iterator for SubstackIterator<'a, T> { + type Item = &'a T; + fn next(&mut self) -> Option<&'a T> { + let Substack{ item, prev } = self.curr?; + self.curr = *prev; + Some(item) + } +} \ No newline at end of file