diff --git a/schala-lang/language/src/error.rs b/schala-lang/language/src/error.rs new file mode 100644 index 0000000..7323ca5 --- /dev/null +++ b/schala-lang/language/src/error.rs @@ -0,0 +1,99 @@ +use crate::schala::{SourceReference, Stage}; +use crate::source_map::Location; +use crate::tokenizing::{Token, TokenKind}; +use crate::parsing::ParseError; +use crate::typechecking::TypeError; + +pub struct SchalaError { + errors: Vec, + //TODO unify these sometime + formatted_parse_error: Option, +} + +impl SchalaError { + + pub(crate) fn display(&self) -> String { + if let Some(ref err) = self.formatted_parse_error { + err.clone() + } else { + self.errors[0].text.as_ref().cloned().unwrap_or_default() + } + } + + pub(crate) fn from_type_error(err: TypeError) -> Self { + Self { + formatted_parse_error: None, + errors: vec![ + Error { location: None, text: Some(err.msg), stage: Stage::Typechecking } + ] + } + } + + pub(crate) fn from_string(text: String, stage: Stage) -> Self { + Self { + formatted_parse_error: None, + errors: vec![ + Error { location: None, text: Some(text), stage } + ] + } + } + + pub(crate) fn from_parse_error(parse_error: ParseError, source_reference: &SourceReference) -> Self { + Self { + formatted_parse_error: Some(format_parse_error(parse_error, source_reference)), + errors: vec![], + } + } + + pub(crate) fn from_tokens(tokens: &[Token]) -> Option { + let token_errors: Vec = tokens.iter() + .filter_map(|tok| match tok.kind { + TokenKind::Error(ref err) => Some(Error { + location: Some(tok.location), + text: Some(err.clone()), + stage: Stage::Tokenizing, + }), + _ => None + }).collect(); + + if token_errors.is_empty() { + None + } else { + Some(SchalaError { + errors: token_errors, + formatted_parse_error: None, + }) + } + } +} + +#[allow(dead_code)] +struct Error { + location: Option, + text: Option, + stage: Stage, +} + + +fn format_parse_error(error: ParseError, source_reference: &SourceReference) -> String { + let line_num = error.token.location.line_num; + let ch = error.token.location.char_num; + let line_from_program = source_reference.get_line(line_num); + let location_pointer = format!("{}^", " ".repeat(ch)); + + let line_num_digits = format!("{}", line_num).chars().count(); + let space_padding = " ".repeat(line_num_digits); + + let production = match error.production_name { + Some(n) => format!("\n(from production \"{}\")", n), + None => "".to_string() + }; + + format!(r#" +{error_msg}{production} +{space_padding} | +{line_num} | {} +{space_padding} | {} +"#, line_from_program, location_pointer, error_msg=error.msg, space_padding=space_padding, line_num=line_num, production=production +) +} diff --git a/schala-lang/language/src/lib.rs b/schala-lang/language/src/lib.rs index 6c7f100..54b45b9 100644 --- a/schala-lang/language/src/lib.rs +++ b/schala-lang/language/src/lib.rs @@ -31,6 +31,7 @@ mod builtin; mod reduced_ast; mod eval; mod source_map; +mod error; mod schala; diff --git a/schala-lang/language/src/schala.rs b/schala-lang/language/src/schala.rs index 11e428f..07f7b73 100644 --- a/schala-lang/language/src/schala.rs +++ b/schala-lang/language/src/schala.rs @@ -7,6 +7,7 @@ use schala_repl::{ProgrammingLanguageInterface, ComputationRequest, ComputationResponse, LangMetaRequest, LangMetaResponse, GlobalOutputStats}; use crate::{reduced_ast, tokenizing, parsing, eval, typechecking, symbol_table, source_map}; +use crate::error::SchalaError; pub type SymbolTableHandle = Rc>; pub type SourceMapHandle = Rc>; @@ -60,8 +61,8 @@ impl Schala { let mut env = Schala::new_blank_env(); let response = env.run_pipeline(prelude); - if let Err(msg) = response { - panic!("Error in prelude, panicking: {}", msg); + if let Err(err) = response { + panic!("Error in prelude, panicking: {}", err.display()); } env } @@ -69,36 +70,33 @@ impl Schala { /// This is where the actual action of interpreting/compilation happens. /// Note: this should eventually use a query-based system for parallelization, cf. /// https://rustc-dev-guide.rust-lang.org/overview.html - fn run_pipeline(&mut self, source: &str) -> Result { - //TODO `run_pipeline` should return a formatted error struct - - //TODO every stage should have a common error-format that gets turned into a repl-appropriate - //string in `run_computation` + fn run_pipeline(&mut self, source: &str) -> Result { // 1st stage - tokenization // TODO tokenize should return its own error type let tokens = tokenizing::tokenize(source); - let token_errors: Vec = tokens.iter().filter_map(|t| t.get_error()).collect(); - if token_errors.len() > 0 { - return Err(format!("{:?}", token_errors)); + if let Some(err) = SchalaError::from_tokens(&tokens) { + return Err(err) } //2nd stage - parsing self.active_parser.add_new_tokens(tokens); let mut ast = self.active_parser.parse() - .map_err(|err| format_parse_error(err, &self.source_reference))?; + .map_err(|err| SchalaError::from_parse_error(err, &self.source_reference))?; // Symbol table - self.symbol_table.borrow_mut().add_top_level_symbols(&ast)?; + self.symbol_table.borrow_mut().add_top_level_symbols(&ast) + .map_err(|err| SchalaError::from_string(err, Stage::Symbols))?; // Scope resolution - requires mutating AST - self.resolver.resolve(&mut ast)?; + self.resolver.resolve(&mut ast) + .map_err(|err| SchalaError::from_string(err, Stage::ScopeResolution))?; // Typechecking // TODO typechecking not working let _overall_type = self.type_context.typecheck(&ast) - .map_err(|err| format!("Type error: {}", err.msg)); + .map_err(|err| SchalaError::from_type_error(err)); - // Reduce AST - this doesn't produce an error yet, but probably should + // Reduce AST - TODO this doesn't produce an error yet, but probably should let symbol_table = self.symbol_table.borrow(); let reduced_ast = reduced_ast::reduce(&ast, &symbol_table); @@ -108,6 +106,9 @@ impl Schala { .into_iter() .collect(); + let text_output: Result, SchalaError> = text_output + .map_err(|err| SchalaError::from_string(err, Stage::Evaluation)); + let eval_output: String = text_output .map(|v| { Iterator::intersperse(v.into_iter(), "\n".to_owned()).collect() })?; @@ -115,31 +116,9 @@ impl Schala { } } -fn format_parse_error(error: parsing::ParseError, source_reference: &SourceReference) -> String { - let line_num = error.token.location.line_num; - let ch = error.token.location.char_num; - let line_from_program = source_reference.get_line(line_num); - let location_pointer = format!("{}^", " ".repeat(ch)); - - let line_num_digits = format!("{}", line_num).chars().count(); - let space_padding = " ".repeat(line_num_digits); - - let production = match error.production_name { - Some(n) => format!("\n(from production \"{}\")", n), - None => "".to_string() - }; - - format!(r#" -{error_msg}{production} -{space_padding} | -{line_num} | {} -{space_padding} | {} -"#, line_from_program, location_pointer, error_msg=error.msg, space_padding=space_padding, line_num=line_num, production=production -) -} /// Represents lines of source code -struct SourceReference { +pub(crate) struct SourceReference { lines: Option> } @@ -152,11 +131,23 @@ impl SourceReference { //TODO this is a lot of heap allocations - maybe there's a way to make it more efficient? self.lines = Some(source.lines().map(|s| s.to_string()).collect()); } - fn get_line(&self, line: usize) -> String { + pub fn get_line(&self, line: usize) -> String { self.lines.as_ref().and_then(|x| x.get(line).map(|s| s.to_string())).unwrap_or(format!("NO LINE FOUND")) } } +#[allow(dead_code)] +#[derive(Clone, Copy, Debug)] +pub(crate) enum Stage { + Tokenizing, + Parsing, + Symbols, + ScopeResolution, + Typechecking, + AstReduction, + Evaluation, +} + fn stage_names() -> Vec<&'static str> { vec![ "tokenizing", @@ -185,7 +176,8 @@ impl ProgrammingLanguageInterface for Schala { self.source_reference.load_new_source(source); let sw = Stopwatch::start_new(); - let main_output = self.run_pipeline(source); + let main_output = self.run_pipeline(source) + .map_err(|schala_err| schala_err.display()); let global_output_stats = GlobalOutputStats { total_duration: sw.elapsed(), diff --git a/schala-lang/language/src/tokenizing.rs b/schala-lang/language/src/tokenizing.rs index 80fa90c..3410e9b 100644 --- a/schala-lang/language/src/tokenizing.rs +++ b/schala-lang/language/src/tokenizing.rs @@ -99,12 +99,6 @@ pub struct Token { } impl Token { - pub fn get_error(&self) -> Option { - match self.kind { - TokenKind::Error(ref s) => Some(s.clone()), - _ => None, - } - } pub fn to_string_with_metadata(&self) -> String { format!("{}({})", self.kind, self.location) }