Most files suffered major changes
- Less ambiguous syntax - Better parser (Chumsky only does tokenization now) - Tidy(|ier) error handling - Facade for simplified embedding - External code grouped in (fairly) self-contained Systems - Dynamic action dispatch - Many STL additions
This commit is contained in:
34
src/error/import_all.rs
Normal file
34
src/error/import_all.rs
Normal file
@@ -0,0 +1,34 @@
|
||||
use std::rc::Rc;
|
||||
|
||||
use super::{ErrorPosition, ProjectError};
|
||||
use crate::representations::location::Location;
|
||||
use crate::utils::iter::box_once;
|
||||
use crate::utils::BoxedIter;
|
||||
use crate::{Interner, VName};
|
||||
|
||||
/// Error produced for the statement `import *`
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct ImportAll {
|
||||
/// The file containing the offending import
|
||||
pub offender_file: Rc<Vec<String>>,
|
||||
/// The module containing the offending import
|
||||
pub offender_mod: Rc<VName>,
|
||||
}
|
||||
impl ProjectError for ImportAll {
|
||||
fn description(&self) -> &str {
|
||||
"a top-level glob import was used"
|
||||
}
|
||||
fn message(&self, i: &Interner) -> String {
|
||||
format!("{} imports *", i.extern_all(&self.offender_mod).join("::"))
|
||||
}
|
||||
|
||||
fn positions(&self, i: &Interner) -> BoxedIter<ErrorPosition> {
|
||||
box_once(ErrorPosition {
|
||||
location: Location::File(self.offender_file.clone()),
|
||||
message: Some(format!(
|
||||
"{} imports *",
|
||||
i.extern_all(&self.offender_mod).join("::")
|
||||
)),
|
||||
})
|
||||
}
|
||||
}
|
||||
20
src/error/mod.rs
Normal file
20
src/error/mod.rs
Normal file
@@ -0,0 +1,20 @@
|
||||
//! Various errors the pipeline can produce
|
||||
mod import_all;
|
||||
mod no_targets;
|
||||
mod not_exported;
|
||||
mod not_found;
|
||||
mod parse_error_with_tokens;
|
||||
mod project_error;
|
||||
mod too_many_supers;
|
||||
mod unexpected_directory;
|
||||
mod visibility_mismatch;
|
||||
|
||||
pub use import_all::ImportAll;
|
||||
pub use no_targets::NoTargets;
|
||||
pub use not_exported::NotExported;
|
||||
pub use not_found::NotFound;
|
||||
pub use parse_error_with_tokens::ParseErrorWithTokens;
|
||||
pub use project_error::{ErrorPosition, ProjectError, ProjectResult};
|
||||
pub use too_many_supers::TooManySupers;
|
||||
pub use unexpected_directory::UnexpectedDirectory;
|
||||
pub use visibility_mismatch::VisibilityMismatch;
|
||||
23
src/error/no_targets.rs
Normal file
23
src/error/no_targets.rs
Normal file
@@ -0,0 +1,23 @@
|
||||
use super::{ErrorPosition, ProjectError};
|
||||
#[allow(unused)] // for doc
|
||||
use crate::parse_layer;
|
||||
use crate::utils::iter::box_empty;
|
||||
use crate::utils::BoxedIter;
|
||||
use crate::Interner;
|
||||
|
||||
/// Error produced when [parse_layer] is called without targets. This function
|
||||
/// produces an error instead of returning a straightforward empty tree because
|
||||
/// the edge case of no targets is often an error and should generally be
|
||||
/// handled explicitly
|
||||
#[derive(Debug)]
|
||||
pub struct NoTargets;
|
||||
|
||||
impl ProjectError for NoTargets {
|
||||
fn description(&self) -> &str {
|
||||
"No targets were specified for layer parsing"
|
||||
}
|
||||
|
||||
fn positions(&self, _i: &Interner) -> BoxedIter<ErrorPosition> {
|
||||
box_empty()
|
||||
}
|
||||
}
|
||||
45
src/error/not_exported.rs
Normal file
45
src/error/not_exported.rs
Normal file
@@ -0,0 +1,45 @@
|
||||
use std::rc::Rc;
|
||||
|
||||
use super::{ErrorPosition, ProjectError};
|
||||
use crate::representations::location::Location;
|
||||
use crate::utils::BoxedIter;
|
||||
use crate::{Interner, VName};
|
||||
|
||||
/// An import refers to a symbol which exists but is not exported.
|
||||
#[derive(Debug)]
|
||||
pub struct NotExported {
|
||||
/// The containing file - files are always exported
|
||||
pub file: VName,
|
||||
/// The path leading to the unexported module
|
||||
pub subpath: VName,
|
||||
/// The offending file
|
||||
pub referrer_file: VName,
|
||||
/// The module containing the offending import
|
||||
pub referrer_subpath: VName,
|
||||
}
|
||||
impl ProjectError for NotExported {
|
||||
fn description(&self) -> &str {
|
||||
"An import refers to a symbol that exists but isn't exported"
|
||||
}
|
||||
fn positions(&self, i: &Interner) -> BoxedIter<ErrorPosition> {
|
||||
Box::new(
|
||||
[
|
||||
ErrorPosition {
|
||||
location: Location::File(Rc::new(i.extern_all(&self.file))),
|
||||
message: Some(format!(
|
||||
"{} isn't exported",
|
||||
i.extern_all(&self.subpath).join("::")
|
||||
)),
|
||||
},
|
||||
ErrorPosition {
|
||||
location: Location::File(Rc::new(i.extern_all(&self.referrer_file))),
|
||||
message: Some(format!(
|
||||
"{} cannot see this symbol",
|
||||
i.extern_all(&self.referrer_subpath).join("::")
|
||||
)),
|
||||
},
|
||||
]
|
||||
.into_iter(),
|
||||
)
|
||||
}
|
||||
}
|
||||
70
src/error/not_found.rs
Normal file
70
src/error/not_found.rs
Normal file
@@ -0,0 +1,70 @@
|
||||
use super::{ErrorPosition, ProjectError};
|
||||
use crate::representations::project::ProjectModule;
|
||||
#[allow(unused)] // For doc
|
||||
use crate::tree::Module;
|
||||
use crate::tree::WalkError;
|
||||
use crate::utils::iter::box_once;
|
||||
use crate::utils::BoxedIter;
|
||||
use crate::{Interner, NameLike, Tok, VName};
|
||||
|
||||
/// Error produced when an import refers to a nonexistent module
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct NotFound {
|
||||
/// The module that imported the invalid path
|
||||
pub source: Option<VName>,
|
||||
/// The file not containing the expected path
|
||||
pub file: VName,
|
||||
/// The invalid import path
|
||||
pub subpath: VName,
|
||||
}
|
||||
impl NotFound {
|
||||
/// Produce this error from the parameters of [Module]`::walk_ref` and a
|
||||
/// [WalkError]
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// - if `path` is shorter than the `pos` of the error
|
||||
/// - if a walk up to but not including `pos` fails
|
||||
///
|
||||
/// Basically, if `e` was not produced by the `walk*` methods called on
|
||||
/// `path`.
|
||||
pub fn from_walk_error(
|
||||
source: &[Tok<String>],
|
||||
prefix: &[Tok<String>],
|
||||
path: &[Tok<String>],
|
||||
orig: &ProjectModule<impl NameLike>,
|
||||
e: WalkError,
|
||||
) -> Self {
|
||||
let last_mod =
|
||||
orig.walk_ref(&path[..e.pos], false).expect("error occured on next step");
|
||||
let mut whole_path = prefix.iter().chain(path.iter()).copied();
|
||||
if let Some(file) = &last_mod.extra.file {
|
||||
Self {
|
||||
source: Some(source.to_vec()),
|
||||
file: whole_path.by_ref().take(file.len()).collect(),
|
||||
subpath: whole_path.collect(),
|
||||
}
|
||||
} else {
|
||||
Self {
|
||||
source: Some(source.to_vec()),
|
||||
file: whole_path.collect(),
|
||||
subpath: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
impl ProjectError for NotFound {
|
||||
fn description(&self) -> &str {
|
||||
"an import refers to a nonexistent module"
|
||||
}
|
||||
fn message(&self, i: &Interner) -> String {
|
||||
format!(
|
||||
"module {} in {} was not found",
|
||||
i.extern_all(&self.subpath).join("::"),
|
||||
i.extern_all(&self.file).join("/"),
|
||||
)
|
||||
}
|
||||
fn positions(&self, i: &Interner) -> BoxedIter<ErrorPosition> {
|
||||
box_once(ErrorPosition::just_file(i.extern_all(&self.file)))
|
||||
}
|
||||
}
|
||||
34
src/error/parse_error_with_tokens.rs
Normal file
34
src/error/parse_error_with_tokens.rs
Normal file
@@ -0,0 +1,34 @@
|
||||
use std::rc::Rc;
|
||||
|
||||
use itertools::Itertools;
|
||||
|
||||
use super::{ErrorPosition, ProjectError};
|
||||
use crate::interner::InternedDisplay;
|
||||
use crate::parse::Entry;
|
||||
use crate::utils::BoxedIter;
|
||||
use crate::Interner;
|
||||
|
||||
/// Produced by stages that parse text when it fails.
|
||||
pub struct ParseErrorWithTokens {
|
||||
/// The complete source of the faulty file
|
||||
pub full_source: String,
|
||||
/// Tokens, if the error did not occur during tokenization
|
||||
pub tokens: Vec<Entry>,
|
||||
/// The parse error produced by Chumsky
|
||||
pub error: Rc<dyn ProjectError>,
|
||||
}
|
||||
impl ProjectError for ParseErrorWithTokens {
|
||||
fn description(&self) -> &str {
|
||||
self.error.description()
|
||||
}
|
||||
fn message(&self, i: &Interner) -> String {
|
||||
format!(
|
||||
"Failed to parse code: {}\nTokenized source for context:\n{}",
|
||||
self.error.message(i),
|
||||
self.tokens.iter().map(|t| t.to_string_i(i)).join(" "),
|
||||
)
|
||||
}
|
||||
fn positions(&self, i: &Interner) -> BoxedIter<ErrorPosition> {
|
||||
self.error.positions(i)
|
||||
}
|
||||
}
|
||||
68
src/error/project_error.rs
Normal file
68
src/error/project_error.rs
Normal file
@@ -0,0 +1,68 @@
|
||||
use std::rc::Rc;
|
||||
|
||||
use crate::interner::InternedDisplay;
|
||||
use crate::representations::location::Location;
|
||||
use crate::utils::BoxedIter;
|
||||
use crate::Interner;
|
||||
|
||||
/// A point of interest in resolving the error, such as the point where
|
||||
/// processing got stuck, a command that is likely to be incorrect
|
||||
pub struct ErrorPosition {
|
||||
/// The suspected location
|
||||
pub location: Location,
|
||||
/// Any information about the role of this location
|
||||
pub message: Option<String>,
|
||||
}
|
||||
|
||||
impl ErrorPosition {
|
||||
/// An error position referring to an entire file with no comment
|
||||
pub fn just_file(file: Vec<String>) -> Self {
|
||||
Self { message: None, location: Location::File(Rc::new(file)) }
|
||||
}
|
||||
}
|
||||
|
||||
/// Errors addressed to the developer which are to be resolved with
|
||||
/// code changes
|
||||
pub trait ProjectError {
|
||||
/// A general description of this type of error
|
||||
fn description(&self) -> &str;
|
||||
/// A formatted message that includes specific parameters
|
||||
fn message(&self, _i: &Interner) -> String {
|
||||
self.description().to_string()
|
||||
}
|
||||
/// Code positions relevant to this error
|
||||
fn positions(&self, i: &Interner) -> BoxedIter<ErrorPosition>;
|
||||
/// Convert the error into an `Rc<dyn ProjectError>` to be able to
|
||||
/// handle various errors together
|
||||
fn rc(self) -> Rc<dyn ProjectError>
|
||||
where
|
||||
Self: Sized + 'static,
|
||||
{
|
||||
Rc::new(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl InternedDisplay for dyn ProjectError {
|
||||
fn fmt_i(
|
||||
&self,
|
||||
f: &mut std::fmt::Formatter<'_>,
|
||||
i: &Interner,
|
||||
) -> std::fmt::Result {
|
||||
let description = self.description();
|
||||
let message = self.message(i);
|
||||
let positions = self.positions(i);
|
||||
writeln!(f, "Project error: {description}\n{message}")?;
|
||||
for ErrorPosition { location, message } in positions {
|
||||
writeln!(
|
||||
f,
|
||||
"@{location}: {}",
|
||||
message.unwrap_or("location of interest".to_string())
|
||||
)?
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Alias for a result with an error of [Rc] of [ProjectError] trait object.
|
||||
/// This is the type of result most commonly returned by pre-run operations.
|
||||
pub type ProjectResult<T> = Result<T, Rc<dyn ProjectError>>;
|
||||
43
src/error/too_many_supers.rs
Normal file
43
src/error/too_many_supers.rs
Normal file
@@ -0,0 +1,43 @@
|
||||
use std::rc::Rc;
|
||||
|
||||
use super::{ErrorPosition, ProjectError};
|
||||
use crate::representations::location::Location;
|
||||
use crate::utils::iter::box_once;
|
||||
use crate::utils::BoxedIter;
|
||||
use crate::{Interner, VName};
|
||||
|
||||
/// Error produced when an import path starts with more `super` segments
|
||||
/// than the current module's absolute path
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct TooManySupers {
|
||||
/// The offending import path
|
||||
pub path: VName,
|
||||
/// The file containing the offending import
|
||||
pub offender_file: VName,
|
||||
/// The module containing the offending import
|
||||
pub offender_mod: VName,
|
||||
}
|
||||
impl ProjectError for TooManySupers {
|
||||
fn description(&self) -> &str {
|
||||
"an import path starts with more `super` segments than the current \
|
||||
module's absolute path"
|
||||
}
|
||||
fn message(&self, i: &Interner) -> String {
|
||||
format!(
|
||||
"path {} in {} contains too many `super` steps.",
|
||||
i.extern_all(&self.path).join("::"),
|
||||
i.extern_all(&self.offender_mod).join("::")
|
||||
)
|
||||
}
|
||||
|
||||
fn positions(&self, i: &Interner) -> BoxedIter<ErrorPosition> {
|
||||
box_once(ErrorPosition {
|
||||
location: Location::File(Rc::new(i.extern_all(&self.offender_file))),
|
||||
message: Some(format!(
|
||||
"path {} in {} contains too many `super` steps.",
|
||||
i.extern_all(&self.path).join("::"),
|
||||
i.extern_all(&self.offender_mod).join("::")
|
||||
)),
|
||||
})
|
||||
}
|
||||
}
|
||||
27
src/error/unexpected_directory.rs
Normal file
27
src/error/unexpected_directory.rs
Normal file
@@ -0,0 +1,27 @@
|
||||
use super::{ErrorPosition, ProjectError};
|
||||
use crate::utils::iter::box_once;
|
||||
use crate::utils::BoxedIter;
|
||||
use crate::{Interner, VName};
|
||||
|
||||
/// Produced when a stage that deals specifically with code encounters
|
||||
/// a path that refers to a directory
|
||||
#[derive(Debug)]
|
||||
pub struct UnexpectedDirectory {
|
||||
/// Path to the offending collection
|
||||
pub path: VName,
|
||||
}
|
||||
impl ProjectError for UnexpectedDirectory {
|
||||
fn description(&self) -> &str {
|
||||
"A stage that deals specifically with code encountered a path that refers \
|
||||
to a directory"
|
||||
}
|
||||
fn positions(&self, i: &Interner) -> BoxedIter<ErrorPosition> {
|
||||
box_once(ErrorPosition::just_file(i.extern_all(&self.path)))
|
||||
}
|
||||
fn message(&self, i: &Interner) -> String {
|
||||
format!(
|
||||
"{} was expected to be a file but a directory was found",
|
||||
i.extern_all(&self.path).join("/")
|
||||
)
|
||||
}
|
||||
}
|
||||
30
src/error/visibility_mismatch.rs
Normal file
30
src/error/visibility_mismatch.rs
Normal file
@@ -0,0 +1,30 @@
|
||||
use std::rc::Rc;
|
||||
|
||||
use super::project_error::{ErrorPosition, ProjectError};
|
||||
use crate::representations::location::Location;
|
||||
use crate::utils::iter::box_once;
|
||||
use crate::utils::BoxedIter;
|
||||
use crate::{Interner, VName};
|
||||
|
||||
/// Multiple occurences of the same namespace with different visibility
|
||||
#[derive(Debug)]
|
||||
pub struct VisibilityMismatch {
|
||||
/// The namespace with ambiguous visibility
|
||||
pub namespace: VName,
|
||||
/// The file containing the namespace
|
||||
pub file: Rc<Vec<String>>,
|
||||
}
|
||||
impl ProjectError for VisibilityMismatch {
|
||||
fn description(&self) -> &str {
|
||||
"Some occurences of a namespace are exported but others are not"
|
||||
}
|
||||
fn positions(&self, i: &Interner) -> BoxedIter<ErrorPosition> {
|
||||
box_once(ErrorPosition {
|
||||
location: Location::File(self.file.clone()),
|
||||
message: Some(format!(
|
||||
"{} is opened multiple times with different visibilities",
|
||||
i.extern_all(&self.namespace).join("::")
|
||||
)),
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user