forked from Orchid/orchid
The pipeline is finally reasonably clean
This commit is contained in:
59
src/pipeline/dealias/alias_cache.rs
Normal file
59
src/pipeline/dealias/alias_cache.rs
Normal file
@@ -0,0 +1,59 @@
|
||||
use std::slice;
|
||||
|
||||
use chumsky::primitive::Container;
|
||||
use hashbrown::HashMap;
|
||||
|
||||
use crate::representations::project::{ProjectMod, ItemKind, ProjectEntry};
|
||||
use crate::tree::ModMember;
|
||||
use crate::utils::{pushed, unwrap_or};
|
||||
use crate::{ProjectTree, VName, Tok, NameLike};
|
||||
|
||||
use super::walk_with_links::{walk_with_links, Target};
|
||||
|
||||
pub struct AliasCache {
|
||||
data: HashMap<Vec<Tok<String>>, Option<Vec<Tok<String>>>>,
|
||||
}
|
||||
impl AliasCache {
|
||||
pub fn new() -> Self {
|
||||
Self { data: HashMap::new() }
|
||||
}
|
||||
|
||||
/// Finds the absolute nsname corresponding to the given name in the given
|
||||
/// context, if it's imported. If the name is defined locally, returns None
|
||||
/// to avoid allocating several vectors for every local variable.
|
||||
pub fn resolv_name<'a>(
|
||||
&'a mut self,
|
||||
root: &ProjectMod<VName>,
|
||||
location: &[Tok<String>],
|
||||
name: Tok<String>
|
||||
) -> Option<&'a [Tok<String>]> {
|
||||
let full_path = pushed(location, name);
|
||||
if let Some(result) = self.data.get(&full_path) {
|
||||
return result.as_deref();
|
||||
}
|
||||
let (ent, finalp) = walk_with_links(root, location.iter().cloned())
|
||||
.expect("This path should be valid");
|
||||
let m = unwrap_or!{ent => Target::Mod; panic!("Must be a module")};
|
||||
let result = m.extra.imports_from.get(&name).map(|next| {
|
||||
self.resolv_name(root, &next, name).unwrap_or(&next)
|
||||
});
|
||||
self.data.insert(full_path, result.map(|s| s.to_vec()));
|
||||
return result;
|
||||
}
|
||||
|
||||
/// Find the absolute target of a
|
||||
pub fn resolv_vec<'a>(
|
||||
&'a mut self,
|
||||
root: &ProjectMod<VName>,
|
||||
modname: &[Tok<String>],
|
||||
vname: &[Tok<String>],
|
||||
) -> Option<&'a [Tok<String>]> {
|
||||
let (name, ns) = vname.split_last().expect("name cannot be empty");
|
||||
if ns.is_empty() {
|
||||
self.resolv_name(modname, name)
|
||||
} else {
|
||||
let origin = self.resolv_vec(modname, ns)?;
|
||||
self.resolv_name(origin, name)
|
||||
}
|
||||
}
|
||||
}
|
||||
5
src/pipeline/dealias/mod.rs
Normal file
5
src/pipeline/dealias/mod.rs
Normal file
@@ -0,0 +1,5 @@
|
||||
// mod alias_cache;
|
||||
mod resolve_aliases;
|
||||
mod walk_with_links;
|
||||
|
||||
pub use resolve_aliases::resolve_aliases;
|
||||
95
src/pipeline/dealias/resolve_aliases.rs
Normal file
95
src/pipeline/dealias/resolve_aliases.rs
Normal file
@@ -0,0 +1,95 @@
|
||||
use std::iter;
|
||||
|
||||
use itertools::Itertools;
|
||||
|
||||
use super::walk_with_links::walk_with_links;
|
||||
use crate::ast::{Expr, Rule};
|
||||
use crate::representations::project::{
|
||||
ItemKind, ProjectExt, ProjectItem, ProjectMod,
|
||||
};
|
||||
use crate::tree::{ModEntry, ModMember, Module};
|
||||
use crate::utils::pushed::pushed;
|
||||
use crate::{Interner, ProjectTree, Tok, VName};
|
||||
|
||||
fn resolve_aliases_rec(
|
||||
root: &ProjectMod<VName>,
|
||||
module: &ProjectMod<VName>,
|
||||
updated: &impl Fn(&[Tok<String>]) -> bool,
|
||||
is_root: bool,
|
||||
) -> ProjectMod<VName> {
|
||||
if !is_root && !updated(&module.extra.path) {
|
||||
return module.clone();
|
||||
}
|
||||
let process_expr = |expr: &Expr<VName>| {
|
||||
expr
|
||||
.map_names(&|n| {
|
||||
let full_name = (module.extra.path.iter()).chain(n.iter()).cloned();
|
||||
match walk_with_links(root, full_name, false) {
|
||||
Ok(rep) => Some(rep.abs_path),
|
||||
// Ok(_) => None,
|
||||
Err(e) => {
|
||||
let leftovers = e.tail.collect::<Vec<_>>();
|
||||
if !leftovers.is_empty() {
|
||||
let full_name = (module.extra.path.iter())
|
||||
.chain(n.iter())
|
||||
.cloned()
|
||||
.collect::<Vec<_>>();
|
||||
let _ = walk_with_links(root, full_name.iter().cloned(), true);
|
||||
panic!(
|
||||
"Invalid path {} while resolving {} should have been noticed \
|
||||
earlier",
|
||||
(e.abs_path.into_iter())
|
||||
.chain(iter::once(e.name))
|
||||
.chain(leftovers.into_iter())
|
||||
.join("::"),
|
||||
Interner::extern_all(&full_name).join("::"),
|
||||
);
|
||||
}
|
||||
Some(pushed(e.abs_path, e.name))
|
||||
},
|
||||
}
|
||||
})
|
||||
.unwrap_or_else(|| expr.clone())
|
||||
};
|
||||
Module {
|
||||
extra: ProjectExt {
|
||||
path: module.extra.path.clone(),
|
||||
file: module.extra.file.clone(),
|
||||
imports_from: module.extra.imports_from.clone(),
|
||||
rules: (module.extra.rules.iter())
|
||||
.map(|Rule { pattern, prio, template }| Rule {
|
||||
pattern: pattern.iter().map(process_expr).collect(),
|
||||
template: template.iter().map(process_expr).collect(),
|
||||
prio: *prio,
|
||||
})
|
||||
.collect(),
|
||||
},
|
||||
entries: module
|
||||
.entries
|
||||
.iter()
|
||||
.map(|(k, v)| {
|
||||
(k.clone(), ModEntry {
|
||||
exported: v.exported,
|
||||
member: match &v.member {
|
||||
ModMember::Sub(module) =>
|
||||
ModMember::Sub(resolve_aliases_rec(root, module, updated, false)),
|
||||
ModMember::Item(item) => ModMember::Item(ProjectItem {
|
||||
is_op: item.is_op,
|
||||
kind: match &item.kind {
|
||||
ItemKind::Const(value) => ItemKind::Const(process_expr(value)),
|
||||
other => other.clone(),
|
||||
},
|
||||
}),
|
||||
},
|
||||
})
|
||||
})
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn resolve_aliases(
|
||||
project: ProjectTree<VName>,
|
||||
updated: &impl Fn(&[Tok<String>]) -> bool,
|
||||
) -> ProjectTree<VName> {
|
||||
ProjectTree(resolve_aliases_rec(&project.0, &project.0, updated, true))
|
||||
}
|
||||
124
src/pipeline/dealias/walk_with_links.rs
Normal file
124
src/pipeline/dealias/walk_with_links.rs
Normal file
@@ -0,0 +1,124 @@
|
||||
#[allow(unused)] // for doc
|
||||
use crate::representations::project::ProjectEntry;
|
||||
use crate::representations::project::{ItemKind, ProjectItem, ProjectMod};
|
||||
use crate::tree::ModMember;
|
||||
use crate::utils::{unwrap_or, BoxedIter};
|
||||
use crate::{Interner, NameLike, Tok, VName};
|
||||
|
||||
/// The destination of a linked walk. [ProjectEntry] cannot be used for this
|
||||
/// purpose because it might be the project root.
|
||||
pub enum Target<'a, N: NameLike> {
|
||||
Mod(&'a ProjectMod<N>),
|
||||
Leaf(&'a ProjectItem<N>),
|
||||
}
|
||||
|
||||
pub struct WalkReport<'a, N: NameLike> {
|
||||
pub target: Target<'a, N>,
|
||||
pub abs_path: VName,
|
||||
pub aliased: bool,
|
||||
}
|
||||
|
||||
pub struct LinkWalkError<'a> {
|
||||
/// The last known valid path
|
||||
pub abs_path: VName,
|
||||
/// The name that wasn't found
|
||||
pub name: Tok<String>,
|
||||
/// Leftover steps
|
||||
pub tail: BoxedIter<'a, Tok<String>>,
|
||||
/// Whether an alias was ever encountered
|
||||
pub aliased: bool,
|
||||
}
|
||||
|
||||
fn walk_with_links_rec<'a, 'b, N: NameLike>(
|
||||
mut abs_path: VName,
|
||||
root: &'a ProjectMod<N>,
|
||||
cur: &'a ProjectMod<N>,
|
||||
prev_tgt: Target<'a, N>,
|
||||
aliased: bool,
|
||||
mut path: impl Iterator<Item = Tok<String>> + 'b,
|
||||
l: bool,
|
||||
) -> Result<WalkReport<'a, N>, LinkWalkError<'b>> {
|
||||
let name = unwrap_or! {path.next();
|
||||
// ends on this module
|
||||
return Ok(WalkReport{ target: prev_tgt, abs_path, aliased })
|
||||
};
|
||||
if l {
|
||||
eprintln!(
|
||||
"Resolving {} in {}",
|
||||
name,
|
||||
Interner::extern_all(&abs_path).join("::")
|
||||
)
|
||||
}
|
||||
let entry = unwrap_or! {cur.entries.get(&name); {
|
||||
// panic!("No entry {name} on {}", Interner::extern_all(&cur.extra.path).join("::"));
|
||||
// leads into a missing branch
|
||||
return Err(LinkWalkError{ abs_path, aliased, name, tail: Box::new(path) })
|
||||
}};
|
||||
match &entry.member {
|
||||
ModMember::Sub(m) => {
|
||||
// leads into submodule
|
||||
abs_path.push(name);
|
||||
walk_with_links_rec(abs_path, root, m, Target::Mod(m), aliased, path, l)
|
||||
},
|
||||
ModMember::Item(item) => match &item.kind {
|
||||
ItemKind::Alias(alias) => {
|
||||
// leads into alias (reset acc, cur, cur_entry)
|
||||
if l {
|
||||
eprintln!(
|
||||
"{} points to {}",
|
||||
Interner::extern_all(&abs_path).join("::"),
|
||||
Interner::extern_all(alias).join("::")
|
||||
)
|
||||
}
|
||||
abs_path.clone_from(alias);
|
||||
abs_path.extend(path);
|
||||
let path_acc = Vec::with_capacity(abs_path.len());
|
||||
let new_path = abs_path.into_iter();
|
||||
let tgt = Target::Mod(root);
|
||||
walk_with_links_rec(path_acc, root, root, tgt, true, new_path, l)
|
||||
},
|
||||
ItemKind::Const(_) | ItemKind::None => {
|
||||
abs_path.push(name);
|
||||
match path.next() {
|
||||
Some(name) => {
|
||||
// leads into leaf
|
||||
let tail = Box::new(path);
|
||||
Err(LinkWalkError { abs_path, aliased, name, tail })
|
||||
},
|
||||
None => {
|
||||
// ends on leaf
|
||||
let target = Target::Leaf(item);
|
||||
Ok(WalkReport { target, abs_path, aliased })
|
||||
},
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Execute a walk down the tree, following aliases.
|
||||
/// If the path ends on an alias, that alias is also resolved.
|
||||
/// If the path leads out of the tree, the shortest failing path is returned
|
||||
pub fn walk_with_links<'a, N: NameLike>(
|
||||
root: &ProjectMod<N>,
|
||||
path: impl Iterator<Item = Tok<String>> + 'a,
|
||||
l: bool,
|
||||
) -> Result<WalkReport<'_, N>, LinkWalkError<'a>> {
|
||||
let path_acc = path.size_hint().1.map_or_else(Vec::new, Vec::with_capacity);
|
||||
let mut result = walk_with_links_rec(
|
||||
path_acc,
|
||||
root,
|
||||
root,
|
||||
Target::Mod(root),
|
||||
false,
|
||||
path,
|
||||
l,
|
||||
);
|
||||
// cut off excess preallocated space within normal vector growth policy
|
||||
let abs_path = match &mut result {
|
||||
Ok(rep) => &mut rep.abs_path,
|
||||
Err(err) => &mut err.abs_path,
|
||||
};
|
||||
abs_path.shrink_to(abs_path.len().next_power_of_two());
|
||||
result
|
||||
}
|
||||
Reference in New Issue
Block a user