The pipeline is finally reasonably clean

This commit is contained in:
2023-09-12 01:26:46 +01:00
parent 6693d93944
commit 8c866967a9
86 changed files with 1959 additions and 1393 deletions

View File

@@ -1,14 +1,17 @@
//! Generic module tree structure
//!
//! Used by various stages of the pipeline with different parameters
use std::fmt::{Debug, Display};
use std::ops::Add;
use std::rc::Rc;
use duplicate::duplicate_item;
use hashbrown::HashMap;
use super::sourcefile::Import;
use super::Location;
use crate::error::ProjectError;
use crate::interner::Tok;
use crate::utils::Substack;
use crate::utils::{BoxedIter, Substack};
use crate::{Interner, VName};
/// The member in a [ModEntry] which is associated with a name in a [Module]
#[derive(Debug, Clone, PartialEq, Eq)]
@@ -27,121 +30,247 @@ pub struct ModEntry<TItem: Clone, TExt: Clone> {
/// Whether the member is visible to modules other than the parent
pub exported: bool,
}
impl<TItem: Clone, TExt: Clone> ModEntry<TItem, TExt> {
/// Returns the item in this entry if it contains one.
pub fn item(&self) -> Option<&TItem> {
match &self.member {
ModMember::Item(it) => Some(it),
ModMember::Sub(_) => None,
}
}
}
/// A module, containing imports,
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Module<TItem: Clone, TExt: Clone> {
/// Import statements present this module
pub imports: Vec<Import>,
/// Submodules and items by name
pub items: HashMap<Tok<String>, ModEntry<TItem, TExt>>,
pub entries: HashMap<Tok<String>, ModEntry<TItem, TExt>>,
/// Additional information associated with the module
pub extra: TExt,
}
/// Possible causes why the path could not be walked
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum WalkErrorKind {
/// `require_exported` was set to `true` and a module wasn't exported
Private,
/// A module was not found
Missing,
}
/// Error produced by [Module::walk]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct WalkError {
/// The 0-based index of the offending segment
pub pos: usize,
/// The cause of the error
pub kind: WalkErrorKind,
}
/// The path taken to reach a given module
pub type ModPath<'a> = Substack<'a, Tok<String>>;
impl<TItem: Clone, TExt: Clone> Module<TItem, TExt> {
/// If the argument is false, returns all child names.
/// If the argument is true, returns all public child names.
pub fn keys(&self, public: bool) -> BoxedIter<Tok<String>> {
match public {
false => Box::new(self.entries.keys().cloned()),
true => Box::new(
(self.entries.iter())
.filter(|(_, v)| v.exported)
.map(|(k, _)| k.clone()),
),
}
}
/// Return the module at the end of the given path
#[allow(clippy::needless_arbitrary_self_type)] // duplicate
#[duplicate_item(
method reference(type) dereference(expr) map_method;
[walk] [type] [expr] [remove];
[walk_ref] [&type] [*expr] [get];
[walk_mut] [&mut type] [*expr] [get_mut];
)]
pub fn method(
self: reference([Self]),
path: &[Tok<String>],
require_exported: bool,
) -> Result<reference([Self]), WalkError> {
let mut cur = self;
pub fn walk_ref<'a: 'b, 'b>(
&'a self,
prefix: &'b [Tok<String>],
path: &'b [Tok<String>],
public: bool,
) -> Result<&'a Self, WalkError<'b>> {
let mut module = self;
for (pos, step) in path.iter().enumerate() {
if let Some(ModEntry { member: ModMember::Sub(next), exported }) =
cur.items.map_method(step)
{
if require_exported && !dereference([exported]) {
return Err(WalkError { pos, kind: WalkErrorKind::Private });
}
cur = next
let kind = match module.entries.get(step) {
None => ErrKind::Missing,
Some(ModEntry { exported: false, .. }) if public => ErrKind::Private,
Some(ModEntry { member: ModMember::Item(_), .. }) => ErrKind::NotModule,
Some(ModEntry { member: ModMember::Sub(next), .. }) => {
module = next;
continue;
},
};
let options = module.keys(public);
return Err(WalkError { kind, prefix, path, pos, options });
}
Ok(module)
}
/// Return the member at the end of the given path
///
/// # Panics
///
/// if path is empty, since the reference cannot be forwarded that way
#[allow(clippy::needless_arbitrary_self_type)] // duplicate
pub fn walk1_ref<'a: 'b, 'b>(
&'a self,
prefix: &'b [Tok<String>],
path: &'b [Tok<String>],
public: bool,
) -> Result<(&'a ModEntry<TItem, TExt>, &'a Self), WalkError<'b>> {
let (last, parent) = path.split_last().expect("Path cannot be empty");
let pos = path.len() - 1;
let module = self.walk_ref(prefix, parent, public)?;
if let Some(entry) = &module.entries.get(last) {
if !entry.exported && public {
let options = module.keys(public);
Err(WalkError { kind: ErrKind::Private, options, prefix, path, pos })
} else {
return Err(WalkError { pos, kind: WalkErrorKind::Missing });
Ok((entry, module))
}
} else {
let options = module.keys(public);
Err(WalkError { kind: ErrKind::Missing, options, prefix, path, pos })
}
Ok(cur)
}
fn visit_all_imports_rec<E>(
&self,
fn search_all_rec<'a, T, E>(
&'a self,
path: ModPath,
callback: &mut impl FnMut(ModPath, &Self, &Import) -> Result<(), E>,
) -> Result<(), E> {
for import in self.imports.iter() {
callback(path.clone(), self, import)?
}
for (name, entry) in self.items.iter() {
mut state: T,
callback: &mut impl FnMut(ModPath, &'a Self, T) -> Result<T, E>,
) -> Result<T, E> {
state = callback(path.clone(), self, state)?;
for (name, entry) in &self.entries {
if let ModMember::Sub(module) = &entry.member {
module.visit_all_imports_rec(path.push(name.clone()), callback)?
state =
module.search_all_rec(path.push(name.clone()), state, callback)?;
}
}
Ok(())
Ok(state)
}
/// Call the provided function on every import in the tree. Can be
/// short-circuited by returning Err
pub fn visit_all_imports<E>(
&self,
callback: &mut impl FnMut(ModPath, &Self, &Import) -> Result<(), E>,
) -> Result<(), E> {
self.visit_all_imports_rec(Substack::Bottom, callback)
/// Visit every element in the tree with the provided function
///
/// * init - can be used for reduce, otherwise pass `()`
/// * callback - a callback applied on every module. Can return [Err] to
/// short-circuit the walk
/// * [ModPath] - a substack indicating the path to the current module from
/// wherever the walk begun
/// * [Module] - the current module
/// * T - data for reduce. If not used, destructure `()`
pub fn search_all<'a, T, E>(
&'a self,
init: T,
callback: &mut impl FnMut(ModPath, &'a Self, T) -> Result<T, E>,
) -> Result<T, E> {
self.search_all_rec(Substack::Bottom, init, callback)
}
/// Combine two module trees; wherever they conflict, the overlay is
/// preferred.
pub fn overlay(mut self, overlay: Self) -> Self
pub fn overlay<E>(mut self, overlay: Self) -> Result<Self, E>
where
TExt: Add<TExt, Output = TExt>,
TExt: Add<TExt, Output = Result<TExt, E>>,
{
let Module { extra, imports, items } = overlay;
let Module { extra, entries: items } = overlay;
let mut new_items = HashMap::new();
for (key, right) in items {
// if both contain a submodule
match (self.items.remove(&key), right) {
match (self.entries.remove(&key), right) {
(
Some(ModEntry { member: ModMember::Sub(lsub), .. }),
ModEntry { member: ModMember::Sub(rsub), exported },
) => new_items.insert(key, ModEntry {
exported,
member: ModMember::Sub(lsub.overlay(rsub)),
member: ModMember::Sub(lsub.overlay(rsub)?),
}),
(_, right) => new_items.insert(key, right),
};
}
new_items.extend(self.items.into_iter());
self.imports.extend(imports.into_iter());
Module {
items: new_items,
imports: self.imports,
extra: self.extra + extra,
}
new_items.extend(self.entries.into_iter());
Ok(Module { entries: new_items, extra: (self.extra + extra)? })
}
}
impl<TItem: Clone + Display, TExt: Clone + Display> Display
for Module<TItem, TExt>
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Module {{\nchildren:")?;
for (name, entry) in &self.entries {
match entry.exported {
true => write!(f, "\npublic {name} = "),
false => write!(f, "\n{name} = "),
}?;
match &entry.member {
ModMember::Sub(module) => write!(f, "{module}"),
ModMember::Item(item) => write!(f, "{item}"),
}?;
}
write!(f, "\nextra: {}\n}}", &self.extra)
}
}
/// Possible causes why the path could not be walked
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum ErrKind {
/// `require_exported` was set to `true` and a module wasn't exported
Private,
/// A module was not found
Missing,
/// The path leads into a leaf node
NotModule,
}
/// All details about a failed tree-walk
pub struct WalkError<'a> {
/// Failure mode
pub kind: ErrKind,
/// Path to the module where the walk started
pub prefix: &'a [Tok<String>],
/// Planned walk path
pub path: &'a [Tok<String>],
/// Index into walked path where the error occurred
pub pos: usize,
/// Alternatives to the failed steps
pub options: BoxedIter<'a, Tok<String>>,
}
impl<'a> WalkError<'a> {
/// Total length of the path represented by this error
pub fn depth(&self) -> usize {
self.prefix.len() + self.pos + 1
}
/// Attach a location to the error and convert into trait object for reporting
pub fn at(self, location: &Location) -> Rc<dyn ProjectError> {
// panic!("hello");
WalkErrorWithLocation {
kind: self.kind,
location: location.clone(),
path: (self.prefix.iter())
.chain(self.path.iter().take(self.pos + 1))
.cloned()
.collect(),
options: self.options.collect(),
}
.rc()
}
}
/// Error produced by [WalkError::at]
struct WalkErrorWithLocation {
path: VName,
kind: ErrKind,
options: VName,
location: Location,
}
impl ProjectError for WalkErrorWithLocation {
fn description(&self) -> &str {
match self.kind {
ErrKind::Missing => "Nonexistent path",
ErrKind::NotModule => "The path leads into a leaf",
ErrKind::Private => "The path leads into a private module",
}
}
fn message(&self) -> String {
let paths = Interner::extern_all(&self.path).join("::");
let options = Interner::extern_all(&self.options).join(", ");
match &self.kind {
ErrKind::Missing =>
format!("{paths} does not exist, options are {options}"),
ErrKind::NotModule =>
format!("{paths} is not a module, options are {options}"),
ErrKind::Private => format!("{paths} is private, options are {options}"),
}
}
fn one_position(&self) -> Location {
self.location.clone()
}
}