Changed most everything

This commit is contained in:
2022-06-20 12:47:20 +01:00
parent 7856424ad5
commit 6fb4b581b1
12 changed files with 518 additions and 126 deletions

35
Cargo.lock generated
View File

@@ -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"

View File

@@ -9,3 +9,4 @@ edition = "2021"
thiserror = "1.0"
chumsky = "0.8"
derivative = "2.2"
hashbrown = "0.12"

View File

@@ -17,6 +17,7 @@ pub enum Expr {
S(Vec<Expr>),
Lambda(String, Option<Box<Expr>>, Vec<Expr>),
Auto(Option<String>, Option<Box<Expr>>, Vec<Expr>),
Typed(Box<Expr>, Box<Expr>)
}

View File

@@ -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;

25
src/project/expr.rs Normal file
View File

@@ -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<String>,
local: Option<String>
},
S(Vec<Expr>),
Lambda(String, Option<Box<Expr>>, Vec<Expr>),
Auto(Option<String>, Option<Box<Expr>>, Vec<Expr>)
}
#[derive(Debug, Clone)]
pub struct Expr {
pub token: Token,
pub typ: Option<Box<Expr>>
}

View File

@@ -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<Vec<String>, Module>,
}
#[derive(Debug, Clone)]
pub struct Export {
isSymbol: bool,
subpaths: HashMap<String, Export>
}
#[derive(Debug, Clone)]
pub struct Module {
pub substitutions: Vec<Substitution>,
pub exports: HashMap<String, Export>,
pub all_ops: Vec<String>
pub exports: Vec<String>,
pub references: Vec<Vec<String>>
}
#[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<Expr>),
Lambda(Vec<Vec<usize>>, Option<Box<Expr>>, Vec<Expr>),
Auto(Option<Vec<Vec<usize>>>, Option<Box<Expr>>, Vec<Expr>)
}
#[derive(Debug, Clone)]
pub struct Expr {
pub token: Token,
pub typ: Box<Expr>
pub target: expr::Expr
}

View File

@@ -0,0 +1,122 @@
use std::{collections::HashMap};
use thiserror::Error;
use crate::utils::Substack;
use super::expr::{Expr, Token};
type ImportMap = HashMap<String, Vec<String>>;
#[derive(Debug, Clone, Error)]
pub enum ResolutionError<Err> {
#[error("Reference cycle at {0:?}")]
Cycle(Vec<Vec<String>>),
#[error("No module provides {0:?}")]
NoModule(Vec<String>),
#[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<FSplit, FImps, E> {
cache: HashMap<Vec<String>, Result<Vec<String>, ResolutionError<E>>>,
get_modname: FSplit,
get_imports: FImps
}
impl<FSplit, FImps, E> NameResolver<FSplit, FImps, E>
where
FSplit: FnMut(&Vec<String>) -> Option<Vec<String>>,
FImps: FnMut(&Vec<String>) -> Result<ImportMap, E>,
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<String>,
import_path: &Substack<'_, &Vec<String>>
) -> Result<Vec<String>, ResolutionError<E>> {
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<String> = 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<Vec<Expr>, ResolutionError<E>> {
exv.iter().map(|ex| self.process_expression_rec(ex)).collect()
}
fn process_exprboxopt_rec(&mut self,
exbo: &Option<Box<Expr>>
) -> Result<Option<Box<Expr>>, ResolutionError<E>> {
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<Token, ResolutionError<E>> {
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::<Result<Vec<Expr>, ResolutionError<E>>>()?
),
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<Expr, ResolutionError<E>> {
Ok(Expr {
token: self.process_token_rec(&ex.token)?,
typ: self.process_exprboxopt_rec(&ex.typ)?
})
}
pub fn find_origin(&mut self, symbol: &Vec<String>) -> Result<Vec<String>, ResolutionError<E>> {
self.find_origin_rec(symbol, &Substack::new(symbol))
}
pub fn process_token(&mut self, tok: &Token) -> Result<Token, ResolutionError<E>> {
self.process_token_rec(tok)
}
pub fn process_expression(&mut self, ex: &Expr) -> Result<Expr, ResolutionError<E>> {
self.process_expression_rec(ex)
}
}

61
src/project/prefix.rs Normal file
View File

@@ -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<String>,
prefixes: &HashMap<String, Vec<String>>
) -> Option<Vec<String>> {
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<String>
) -> 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<String>) -> 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),
},
}
}

View File

@@ -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<String>)
Namespace(Vec<String>),
}
#[derive(Error, Debug)]
pub enum ParseError {
#[error("Not found: {0}")]
NotFound(String),
#[error("Failed to parse {file}: {errors:?}")]
Syntax {
file: String,
errors: Vec<Simple<char>>
},
#[error("Expected {0}, found {1}")]
Mismatch(String, String),
#[derive(Error, Debug, Clone)]
pub enum ParseError<ELoad> where ELoad: Clone {
#[error("Resolution cycle")]
ResolutionCycle,
#[error("File not found: {0}")]
Load(ELoad),
#[error("Failed to parse: {0:?}")]
Syntax(Vec<Simple<char>>),
#[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<Simple<char>>) -> ParseError {
ParseError::Syntax { file: file.to_string(), errors }
impl<T> From<Vec<Simple<char>>> for ParseError<T> where T: Clone {
fn from(simp: Vec<Simple<char>>) -> Self { Self::Syntax(simp) }
}
impl<T> From<ResolutionError<ParseError<T>>> for ParseError<T> where T: Clone {
fn from(res: ResolutionError<ParseError<T>>) -> Self {
match res {
ResolutionError::Cycle(_) => ParseError::ResolutionCycle,
ResolutionError::NoModule(_) => ParseError::None,
ResolutionError::Delegate(d) => d
}
pub fn mismatch(expected: &str, found: &str) -> ParseError {
ParseError::Mismatch(expected.to_string(), found.to_string())
}
}
// 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<String, Vec<String>>;
type ParseResult<T, ELoad> = Result<T, ParseError<ELoad>>;
type AnyParseResult<T, ELoad> = Result<T, Vec<ParseError<ELoad>>>;
type GetLoaded<'a> = dyn FnMut(&'a [&str]) -> &'a Option<Loaded>;
type GetPreparsed<'a> = dyn FnMut(&'a [&str]) -> &'a Option<Vec<FileEntry>>;
pub fn load_project<'a, F>(
mut load_mod: F, prelude: &[&'a str], entry: &str
) -> Result<super::Project, ParseError>
where F: FnMut(&[&str]) -> Option<Loaded> {
// TODO: Welcome to Kamino!
pub fn load_project<'a, F, ELoad>(
mut load_mod: F,
prelude: &[&'a str],
entry: (Vec<String>, expr::Expr),
) -> Result<super::Project, ParseError<ELoad>>
where
F: FnMut(&[&str]) -> Result<Loaded, ELoad>,
ELoad: Clone
{
let prelude_vec: Vec<String> = prelude.iter().map(|s| s.to_string()).collect();
let preparser = file_parser(prelude, &[]);
let loaded_cell = RefCell::new(Cache::new(|path: Vec<String>| {
// Map paths to a namespace with name list (folder) or module with source text (file)
let loaded_cell = RefCell::new(Cache::new(|path: Vec<String>|
-> ParseResult<Loaded, ELoad> {
load_mod(&path.iter().map(|s| s.as_str()).collect::<Vec<_>>())
.map_err(ParseError::Load)
}));
let preparsed_cell = RefCell::new(Cache::new(|path: Vec<String>| {
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<String>| {
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<String>|
-> AnyParseResult<Vec<String>, ELoad> {
let mut local_loaded = loaded_cell.borrow_mut();
let mut errv: Vec<ParseError<ELoad>> = 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<String>| {
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<String>|
-> ParseResult<Vec<FileEntry>, 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<String>|
-> ParseResult<Vec<String>, 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<String>|
-> ParseResult<ImportMap, ELoad> {
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<String, Vec<String>> = 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,22 +127,87 @@ where F: FnMut(&[&str]) -> Option<Loaded> {
}
}
}
Some(imported_symbols)
Ok(imported_symbols)
}));
let parsed = RefCell::new(Cache::new(|path: Vec<String>| {
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<String>|
-> ParseResult<Vec<FileEntry>, 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<String>| { modname_cell.borrow_mut().by_clone_fallible(path).cloned().ok() },
|path: &Vec<String>| { 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<String>|
-> ParseResult<super::Module, ELoad> {
let mut parsed = parsed_cell.borrow_mut();
let parsed_entries = parsed.by_clone_fallible(&path)?;
let subs: Vec<super::Substitution> = 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::<ParseResult<Vec<super::Substitution>, 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<String>|
-> ParseResult<Vec<super::Substitution>, ELoad> {
let mut processed: HashSet<Vec<String>> = HashSet::new();
let mut subs: Vec<super::Substitution> = Vec::new();
let mut pending: VecDeque<Vec<String>> = 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) {
@@ -125,7 +217,5 @@ where F: FnMut(&[&str]) -> Option<Loaded> {
// let mut project = super::Project {
// modules: HashMap::new()
// };
// Some(project)
todo!("Finish this function")
}

View File

@@ -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,22 +8,26 @@ pub struct Cache<I, O, F> {
closure: F
}
impl<I, O, F> Cache<I, O, F> where
impl<I: 'static, O, F> Cache<I, O, F> 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)
}
@@ -32,3 +36,36 @@ impl<I, O, F> Cache<I, O, F> where
self.store.remove(i).is_some()
}
}
impl<I: 'static, O, E, F> Cache<I, Result<O, E>, F> where
I: Eq + Hash,
E: Clone,
F: FnMut(I) -> Result<O, E>
{
/// 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<I: 'static, O, F> Cache<I, Option<O>, F> where
I: Eq + Hash,
F: FnMut(I) -> Option<O>
{
/// 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()
}
}

View File

@@ -1,2 +1,8 @@
mod cache;
mod substack;
pub use cache::Cache;
pub use substack::Substack;
pub fn as_modpath(path: &Vec<String>) -> String {
path.join("::")
}

43
src/utils/substack.rs Normal file
View File

@@ -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)
}
}