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:
2023-08-17 20:47:08 +01:00
parent 751a02a1ec
commit 3fdabc29da
139 changed files with 4269 additions and 1783 deletions

34
src/error/import_all.rs Normal file
View 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
View 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
View 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
View 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
View 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)))
}
}

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

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

View 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("::")
)),
})
}
}

View 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("/")
)
}
}

View 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("::")
)),
})
}
}