diff --git a/Cargo.lock b/Cargo.lock index 7d348ca8..a2387364 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -23,6 +23,10 @@ version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "080e9890a082662b09c1ad45f567faeeb47f22b5fb23895fbe1e651e718e25ca" +[[package]] +name = "append_bind" +version = "0.1.0" + [[package]] name = "arrayref" version = "0.3.7" @@ -385,7 +389,6 @@ dependencies = [ "name_resolve", "nom", "nom_locate", - "recursive_typecheck", "structopt", "symbol", "typecheck", @@ -687,19 +690,6 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" -[[package]] -name = "recursive_typecheck" -version = "0.1.0" -dependencies = [ - "error", - "fir", - "flatten", - "location", - "name_resolve", - "symbol", - "xparser", -] - [[package]] name = "redox_syscall" version = "0.1.57" diff --git a/Cargo.toml b/Cargo.toml index 5549db76..82810bd7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,9 +25,9 @@ members = [ "ast-sanitizer", "debug-fir", "typecheck", - "recursive_typecheck", + # "recursive_typecheck", "fire", - "xrepl", + "xrepl", "append_bind", ] [dependencies] @@ -45,7 +45,7 @@ loop_desugar = { path = "loop_desugar" } builtins = { path = "builtins" } xparser = { path = "xparser" } typecheck = { path = "typecheck" } -recursive_typecheck = { path = "recursive_typecheck" } +# recursive_typecheck = { path = "recursive_typecheck" } fire = { path = "fire" } xrepl = { path = "xrepl" } structopt = "0.3" diff --git a/error/src/lib.rs b/error/src/lib.rs index e037d674..30103778 100644 --- a/error/src/lib.rs +++ b/error/src/lib.rs @@ -16,6 +16,39 @@ pub struct ErrorHandler { file: PathBuf, } +pub trait Emitter { + fn emit(&self); +} + +impl Emitter for Error { + fn emit(&self) { + match &self.kind { + ErrKind::Multiple(errs) => errs.iter().for_each(|e| e.emit()), + ErrKind::Hint => self.emit_hint(), + ErrKind::Sanitizer => self.emit_sanity_error(), + _ => { + if let Some(loc) = &self.loc { + self.emit_full_loc(loc); + } else if let Some(msg) = &self.msg { + eprintln!("{msg}") + } + + if let Some(first_hint) = self.hints.first() { + first_hint.emit_hint(); + } + + self.hints.iter().skip(1).for_each(|hint| hint.emit_hint()); + } + } + } +} + +impl Emitter for Vec { + fn emit(&self) { + self.iter().for_each(|err| err.emit()) + } +} + impl ErrorHandler { /// Emit all the errors contained in a handler pub fn emit(&self) { @@ -76,6 +109,7 @@ pub enum ErrKind { IO, Debug, UTF8, + // FIXME: Remove Multiple(Vec), } @@ -181,27 +215,6 @@ impl Error { } } - pub fn emit(&self) { - match &self.kind { - ErrKind::Multiple(errs) => errs.iter().for_each(|e| e.emit()), - ErrKind::Hint => self.emit_hint(), - ErrKind::Sanitizer => self.emit_sanity_error(), - _ => { - if let Some(loc) = &self.loc { - self.emit_full_loc(loc); - } else if let Some(msg) = &self.msg { - eprintln!("{msg}") - } - - if let Some(first_hint) = self.hints.first() { - first_hint.emit_hint(); - } - - self.hints.iter().skip(1).for_each(|hint| hint.emit_hint()); - } - } - } - /// Emit a debug interpreter error - this is only useful for debugging the /// interpreter itself pub fn emit_debug(&self) { diff --git a/fir/src/iter.rs b/fir/src/iter.rs index 8e700f7c..9b3deae4 100644 --- a/fir/src/iter.rs +++ b/fir/src/iter.rs @@ -29,13 +29,62 @@ pub use traverse::Traversal; use crate::Fir; +// we want the API to look something like this + +// let fir = fir.pass1().bind(Pass2::pass).bind(Pass3::pass)?; +// same as +// let fir = fir.pass1().bind(|fir| fir.pass2()).bind(|fir| fir.pass3())?; + +pub trait AccumulateBind { + fn bind(self, bind_fn: F) -> Result, IncompleteFir> + where + F: Fn(Fir) -> Result, IncompleteFir>; +} + +impl AccumulateBind for Result, IncompleteFir> { + fn bind(self, bind_fn: F) -> Result, IncompleteFir> + where + F: Fn(Fir) -> Result, IncompleteFir>, + { + match self { + Ok(t) => bind_fn(t), + Err(IncompleteFir { carcass, mut errs }) => { + let res = bind_fn(carcass); + + let (carcass, errs) = match res { + Ok(fir) => (fir, errs), + Err(IncompleteFir { + carcass, + errs: new_errs, + }) => { + new_errs.into_iter().for_each(|e| errs.push(e)); + + (carcass, errs) + } + }; + + Err(IncompleteFir { carcass, errs }) + } + } + } +} + +impl AccumulateBind for Fir { + fn bind(self, bind_fn: F) -> Result, IncompleteFir> + where + F: Fn(Fir) -> Result, IncompleteFir>, + { + bind_fn(self) + } +} + /// An incomplete [`Fir`]. This is returned by the various mappers within this module. The /// incomplete [`Fir`] contains all of the nodes for which the mapping operation succeeded, /// which enables you to still perform error reporting, hinting, or to keep going with more /// operations. You can also access the errors emitted by the mapping pass. This vector of /// errors is guaranteed to not be empty. #[derive(Debug)] -pub struct Incomplete { +pub struct IncompleteFir { pub carcass: Fir, pub errs: Vec, } diff --git a/fir/src/iter/mapper.rs b/fir/src/iter/mapper.rs index 610645d8..5297c887 100644 --- a/fir/src/iter/mapper.rs +++ b/fir/src/iter/mapper.rs @@ -1,4 +1,4 @@ -use crate::{Fir, Incomplete, Kind, Node, OriginIdx, RefIdx}; +use crate::{Fir, IncompleteFir, Kind, Node, OriginIdx, RefIdx}; pub trait Mapper, E> { fn map_constant(&mut self, data: T, origin: OriginIdx, constant: RefIdx) -> Result, E> { @@ -276,7 +276,7 @@ pub trait Mapper, E> { /// In the [`Err`] case, this returns an incomplete [`Fir`] which contains /// all valid mapped nodes. This allows an interpreter to keep trying /// passes and emit as many errors as possible - fn map(&mut self, fir: Fir) -> Result, Incomplete> { + fn map(&mut self, fir: Fir) -> Result, IncompleteFir> { let (fir, errs) = fir.nodes .into_values() @@ -294,7 +294,7 @@ pub trait Mapper, E> { if errs.is_empty() { Ok(fir) } else { - Err(Incomplete { carcass: fir, errs }) + Err(IncompleteFir { carcass: fir, errs }) } } } diff --git a/fir/src/iter/multi_mapper.rs b/fir/src/iter/multi_mapper.rs index d8d6c723..425b4a76 100644 --- a/fir/src/iter/multi_mapper.rs +++ b/fir/src/iter/multi_mapper.rs @@ -1,4 +1,4 @@ -use crate::{Fir, Incomplete, Kind, Node, OriginIdx, RefIdx}; +use crate::{Fir, IncompleteFir, Kind, Node, OriginIdx, RefIdx}; pub trait MultiMapper, E> { /// Each implementer of [`Pass`] should keep its own [`OriginIdx`] counter in order to supply the [`Fir`] @@ -286,7 +286,7 @@ pub trait MultiMapper, E> { /// In the [`Err`] case, this returns an incomplete [`Fir`] which contains /// all valid mapped nodes. This allows an interpreter to keep trying /// passes and emit as many errors as possible - fn multi_map(&mut self, fir: Fir) -> Result, Incomplete> { + fn multi_map(&mut self, fir: Fir) -> Result, IncompleteFir> { let (fir, errs) = fir.nodes.into_values().fold( (Fir::default(), Vec::new()), |(new_fir, mut errs), node| match self.map_node(node) { @@ -305,7 +305,7 @@ pub trait MultiMapper, E> { if errs.is_empty() { Ok(fir) } else { - Err(Incomplete { carcass: fir, errs }) + Err(IncompleteFir { carcass: fir, errs }) } } } diff --git a/fir/src/lib.rs b/fir/src/lib.rs index eb2a7425..1fac3d58 100644 --- a/fir/src/lib.rs +++ b/fir/src/lib.rs @@ -102,7 +102,7 @@ use std::{collections::BTreeMap, ops::Index}; mod checks; pub mod iter; -pub use iter::{Fallible, Incomplete, Mapper, MultiMapper, Traversal}; +pub use iter::{Fallible, IncompleteFir, Mapper, MultiMapper, Traversal}; /// A reference to another [`Node`] in the [`Fir`]. These references can be either resolved or unresolved, based /// on the state of the [`Fir`]. @@ -294,7 +294,7 @@ pub trait Pass { fn post_condition(fir: &Fir); /// The actual pass algorithm which transforms the [`Fir`] and returns a new one. - fn transform(&mut self, fir: Fir) -> Result, E>; + fn transform(&mut self, fir: Fir) -> Result, IncompleteFir>; /// The [`pass`] function is implemented by default in the [`Pass`] trait. It calls into the /// [`pre_condition`] function, then executes the [`transform`] one, before finally executing @@ -305,7 +305,7 @@ pub trait Pass { /// The fallible traits in the Fir's `iter` module return an instance of the [`Incomplete`] type /// on error. Should you want to return this incomplete [`Fir`], make sure to return it in the /// [`Ok`] case. This trait is to be used as high level interface into your [`Fir`] pass. - fn pass(&mut self, fir: Fir) -> Result, E> { + fn pass(&mut self, fir: Fir) -> Result, IncompleteFir> { // FIXME: Add a #[cfg(not(release))] here Self::pre_condition(&fir); diff --git a/interpreter/jinko.rs b/interpreter/jinko.rs index 3d6d6598..01482914 100644 --- a/interpreter/jinko.rs +++ b/interpreter/jinko.rs @@ -7,8 +7,10 @@ mod repl; use colored::Colorize; use builtins::AppendAstBuiltins; -use fire::instance::Instance; -use fire::Interpret; +use error::Emitter; +use fir::iter::AccumulateBind; +use fir::IncompleteFir; +use fire::{instance::Instance, Interpret}; use flatten::{FlattenAst, FlattenData}; use include_code::IncludeCode; use loop_desugar::DesugarLoops; @@ -111,9 +113,10 @@ fn experimental_pipeline(input: &str, file: &Path) -> InteractResult { ($res:expr) => { match $res { Ok(inner) => inner, - Err(e) => { - e.emit(); - return Err(Error::new(ErrKind::Context)); + Err(Incomplete { carcass, errs }) => { + errs.emit(); + + carcass } } }; @@ -142,25 +145,39 @@ fn experimental_pipeline(input: &str, file: &Path) -> InteractResult { }; let fir = ast.flatten(); - FirDebug::default() - .header("flattened") - .show_data(data_fmt) - .display(&fir); - let fir = x_try!(fir.name_resolve()); - FirDebug::default() - .header("name_resolved") - .show_data(data_fmt) - .display(&fir); + let result = ast + .flatten() + .bind(|fir| { + FirDebug::default() + .header("flattened") + .show_data(data_fmt) + .display(&fir); + + fir.name_resolve() + }) + .bind(|fir| { + FirDebug::default() + .header("name_resolved") + .show_data(data_fmt) + .display(&fir); - let fir = x_try!(fir.type_check()); - let result = fir.interpret(); + fir.type_check() + }) + .bind(Interpret::interpret)?; let exit_code = match result { - // convert `true` to `0` and `false` to `1` - Some(Instance::Bool(b)) => !b as i32, - Some(Instance::Int(inner)) => inner as i32, - _ => 0, + Ok(instance) => match instance { + // convert `true` to `0` and `false` to `1` + Some(Instance::Bool(b)) => !b as i32, + Some(Instance::Int(inner)) => inner as i32, + _ => 0, + }, + Err(IncompleteFir { errs, .. }) => { + errs.emit(); + + 1 + } }; process::exit(exit_code); diff --git a/name_resolve/src/lib.rs b/name_resolve/src/lib.rs index 16e7c98f..255862cd 100644 --- a/name_resolve/src/lib.rs +++ b/name_resolve/src/lib.rs @@ -39,7 +39,7 @@ use std::{collections::HashMap, mem, ops::Index}; use colored::Colorize; use error::{ErrKind, Error}; -use fir::{Fallible, Fir, Incomplete, Kind, Mapper, OriginIdx, Pass, Traversal}; +use fir::{Fallible, Fir, IncompleteFir, Kind, Mapper, OriginIdx, Pass, Traversal}; use flatten::FlattenData; use location::SpanTuple; use symbol::Symbol; @@ -200,7 +200,11 @@ enum NameResolutionError { } impl NameResolutionError { - fn finalize(self, fir: &Fir, _mappings: &ScopeMap) -> Error { + fn finalize( + self, + fir: Fir, + _mappings: &ScopeMap, + ) -> IncompleteFir, Error> { // we need the FIR... how do we access nodes otherwise?? match self { NameResolutionError::Multiple(errs) => Error::new(ErrKind::Multiple( @@ -342,7 +346,7 @@ impl<'enclosing> NameResolveCtx<'enclosing> { fn resolve_nodes<'ast>( &mut self, fir: Fir>, - ) -> Result>, Incomplete, NameResolutionError>> { + ) -> Result>, IncompleteFir, NameResolutionError>> { Resolver(self).map(fir) } } @@ -354,7 +358,10 @@ impl<'ast, 'enclosing> Pass, FlattenData<'ast>, Error> fn post_condition(_fir: &Fir) {} - fn transform(&mut self, fir: Fir>) -> Result>, Error> { + fn transform( + &mut self, + fir: Fir>, + ) -> Result>, IncompleteFir, Error>> { let definition = self.insert_definitions(&fir); let definition = definition .map_err(|errs| NameResolutionError::Multiple(errs).finalize(&fir, &self.mappings)); @@ -363,11 +370,11 @@ impl<'ast, 'enclosing> Pass, FlattenData<'ast>, Error> match (definition, resolution) { (Ok(_), Ok(fir)) => Ok(fir), - (Ok(_), Err(Incomplete { carcass, errs })) => { + (Ok(_), Err(IncompleteFir { carcass, errs })) => { Err(NameResolutionError::Multiple(errs).finalize(&carcass, &self.mappings)) } (Err(e), Ok(_fir)) => Err(e), - (Err(e1), Err(Incomplete { carcass, errs })) => { + (Err(e1), Err(IncompleteFir { carcass, errs })) => { let multi_err = Error::new(ErrKind::Multiple(vec![ e1, NameResolutionError::Multiple(errs).finalize(&carcass, &self.mappings), @@ -379,11 +386,15 @@ impl<'ast, 'enclosing> Pass, FlattenData<'ast>, Error> } pub trait NameResolve<'ast> { - fn name_resolve(self) -> Result>, Error>; + fn name_resolve( + self, + ) -> Result>, IncompleteFir, Error>>; } impl<'ast> NameResolve<'ast> for Fir> { - fn name_resolve(self) -> Result>, Error> { + fn name_resolve( + self, + ) -> Result>, IncompleteFir, Error>> { // TODO: Ugly asf let enclosing_scope = NameResolveCtx::scope(&self); let mut ctx = NameResolveCtx::new(EnclosingScope(&enclosing_scope)); @@ -536,7 +547,7 @@ mod tests { let fir = ast.flatten().name_resolve(); if let Err(e) = &fir { - e.emit(); + e.errs.emit(); } assert!(fir.is_ok()); diff --git a/recursive_typecheck/src/lib.rs b/recursive_typecheck/src/lib.rs index 3ee8c5ef..0f61585f 100644 --- a/recursive_typecheck/src/lib.rs +++ b/recursive_typecheck/src/lib.rs @@ -4,7 +4,7 @@ // are supposed to do things? use error::{ErrKind, Error}; -use fir::{Fallible, Fir, Kind, Mapper, Node, OriginIdx, Pass, RefIdx, Traversal}; +use fir::{Fallible, Fir, IncompleteFir, Kind, Mapper, Node, OriginIdx, Pass, RefIdx, Traversal}; use flatten::FlattenData; use location::SpanTuple; use symbol::Symbol; @@ -17,7 +17,7 @@ pub trait TypeCheck: Sized { } impl TypeCheck> for Fir> { - fn type_check(self) -> Result, Error> { + fn type_check(self) -> Result, IncompleteFir> { TypeCtx.pass(self) } } @@ -441,12 +441,20 @@ impl Pass, TypeData, Error> for TypeCtx { fn post_condition(_fir: &Fir) {} - fn transform(&mut self, fir: Fir) -> Result, Error> { + fn transform( + &mut self, + fir: Fir, + ) -> Result, IncompleteFir> { // Typing pass let typed_fir = self.map(fir).unwrap(); /* FIXME: No unwrap */ // Checking pass - self.traverse(&typed_fir)?; + if let Err(errs) = self.traverse(&typed_fir) { + return Err(IncompleteFir { + carcass: typed_fir, + errs, + }); + } Ok(typed_fir) } diff --git a/typecheck/src/actual.rs b/typecheck/src/actual.rs index c5d50fa5..635aa348 100644 --- a/typecheck/src/actual.rs +++ b/typecheck/src/actual.rs @@ -4,7 +4,7 @@ use std::ops::ControlFlow; use error::Error; -use fir::{Fallible, Fir, Kind, Node, OriginIdx, RefIdx, Traversal}; +use fir::{Fallible, Fir, IncompleteFir, Kind, Node, OriginIdx, RefIdx, Traversal}; use flatten::FlattenData; use crate::{typemap::TypeMap, Type, TypeCtx, TypeLinkMap, TypeVariable}; @@ -20,10 +20,10 @@ struct TypeLinkResolver<'ctx> { } impl Actual { - pub fn resolve_type_links( + pub fn resolve_type_links<'ast>( ctx: &TypeCtx, - fir: &Fir>, - ) -> Result, Error> { + fir: &Fir>, + ) -> Result, Vec> { let mut resolver = TypeLinkResolver { old: ctx, new: TypeCtx { diff --git a/typecheck/src/lib.rs b/typecheck/src/lib.rs index e1e90d56..0927353b 100644 --- a/typecheck/src/lib.rs +++ b/typecheck/src/lib.rs @@ -6,8 +6,8 @@ mod typer; use std::collections::{HashMap, HashSet}; -use error::{ErrKind, Error}; -use fir::{Fir, Incomplete, Mapper, OriginIdx, Pass, RefIdx, Traversal}; +use error::Error; +use fir::{Fir, IncompleteFir, Mapper, OriginIdx, Pass, RefIdx, Traversal}; use flatten::FlattenData; use actual::Actual; @@ -84,13 +84,22 @@ pub(crate) struct TypeCtx { pub(crate) types: T, } -pub trait TypeCheck: Sized { - fn type_check(self) -> Result; +pub trait TypeCheck<'ast, T>: Sized { + fn type_check(self) -> Result, Error>>; } -impl<'ast> TypeCheck>> for Fir> { - fn type_check(self) -> Result>, Error> { - let primitives = primitives::find(&self)?; +impl<'ast> TypeCheck<'ast, Fir>> for Fir> { + fn type_check(self) -> Result>, IncompleteFir, Error>> { + // FIXME: Ugly? + let primitives = match primitives::find(&self) { + Ok(p) => p, + Err(e) => { + return Err(IncompleteFir { + carcass: self, + errs: vec![e], + }) + } + }; TypeCtx { primitives, @@ -105,26 +114,36 @@ impl<'ast> Pass, FlattenData<'ast>, Error> for TypeCtx) {} - fn transform(&mut self, fir: Fir>) -> Result>, Error> { + fn transform( + &mut self, + fir: Fir>, + ) -> Result>, IncompleteFir, Error>> { // Typing pass let fir = Typer(self).map(fir); + let to_incomplete = |errs, carcass| IncompleteFir { carcass, errs }; + let mut type_errs = None; let fir = match fir { Ok(fir) => fir, - Err(Incomplete { carcass, errs }) => { - type_errs = Some(Error::new(ErrKind::Multiple(errs))); + Err(IncompleteFir { carcass, errs }) => { + type_errs = Some(errs); carcass } }; - let mut actual_ctx = Actual::resolve_type_links(self, &fir)?; + let mut actual_ctx = match Actual::resolve_type_links(self, &fir) { + Ok(ctx) => ctx, + Err(errs) => return Err(IncompleteFir { carcass: fir, errs }), + }; - Checker(&mut actual_ctx).traverse(&fir)?; + if let Err(errs) = Checker(&mut actual_ctx).traverse(&fir) { + return Err(IncompleteFir { carcass: fir, errs }); + } match type_errs { - Some(e) => Err(e), + Some(e) => Err(to_incomplete(e, fir)), None => Ok(fir), } }