diff --git a/CHANGELOG.md b/CHANGELOG.md index 0539f41..33d0587 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,7 @@ - [x] Typecheck for loops - [x] Typecheck while loops - [x] Include Ariadne for error reporting +- [x] Migrate lexic errors to new error interface ## v0.1.1 diff --git a/error_codes.yaml b/error_codes.yaml index 106af20..894dd04 100644 --- a/error_codes.yaml +++ b/error_codes.yaml @@ -1,4 +1,9 @@ -- A map of error codes to error messages - -0x000001: Incomplete string +0x000000: Incomplete string +0x000001: Invalid hex number +0x000002: Invalid octal number +0x000003: Invalid binary number +0x000004: Invalid floating point number +0x000005: Invalid scientific number +0x000006: Incomplete multiline comment diff --git a/src/codegen/php/expression/primary_expression.rs b/src/codegen/php/expression/primary_expression.rs index b728fb1..069d858 100644 --- a/src/codegen/php/expression/primary_expression.rs +++ b/src/codegen/php/expression/primary_expression.rs @@ -7,7 +7,7 @@ impl Transpilable for PPrimary<'_> { PPrimary::FloatingLiteral(value) => value.to_string(), PPrimary::StringLiteral(value) => format!("\"{}\"", value), PPrimary::Variable(name) => format!("${}", name), - PPrimary::Symbol(name) => format!("{}", name), + // PPrimary::Symbol(name) => format!("{}", name), PPrimary::BoolLiteral(bool) => { if *bool { String::from("true") diff --git a/src/error_handling/error_messages.rs b/src/error_handling/error_messages.rs index da61168..1e1c1c5 100644 --- a/src/error_handling/error_messages.rs +++ b/src/error_handling/error_messages.rs @@ -1,3 +1,9 @@ //! Contains constants that point to error messages pub const LEX_INCOMPLETE_STRING: u32 = 0; +pub const LEX_INVALID_HEX_NUMBER: u32 = 1; +pub const LEX_INVALID_OCTAL_NUMBER: u32 = 2; +pub const LEX_INVALID_BINARY_NUMBER: u32 = 3; +pub const LEX_INVALID_FLOATING_NUMBER: u32 = 4; +pub const LEX_INVALID_SCIENTIFIC_NUMBER: u32 = 5; +pub const LEX_INCOMPLETE_MULTILINE_COMMENT: u32 = 6; diff --git a/src/error_handling/lex_error.rs b/src/error_handling/lex_error.rs index 4053b62..614a5ec 100644 --- a/src/error_handling/lex_error.rs +++ b/src/error_handling/lex_error.rs @@ -31,7 +31,7 @@ impl PrintableError for LexError { .with_color(Color::Red), ) .finish(); - report.eprint(("sample.thp", Source::from(source))); + report.eprint(("sample.thp", Source::from(source))).unwrap(); } } diff --git a/src/error_handling/mod.rs b/src/error_handling/mod.rs index 7ac26bf..84ac347 100644 --- a/src/error_handling/mod.rs +++ b/src/error_handling/mod.rs @@ -1,3 +1,6 @@ +use std::ops::Range; + +use ariadne::{Label, Report, ReportKind, Source}; use serde::Serialize; use self::semantic_error::SemanticError; @@ -56,7 +59,7 @@ pub struct SyntaxError { impl PrintableError for MistiError { fn get_error_str(&self, chars: &Vec) -> String { match self { - Self::Lex(err) => panic!("REMOVED: manually generating an error message"), + Self::Lex(_) => panic!("REMOVED: manually generating an error message"), Self::Syntax(err) => err.get_error_str(chars), Self::Semantic(err) => err.get_error_str(chars), } @@ -70,3 +73,26 @@ impl PrintableError for MistiError { } } } + +impl PrintableError for ErrorContainer { + fn get_error_str(&self, _: &Vec) -> String { + panic!("REMOVED: manually generating an error message") + } + + fn print_ariadne(&self, source: &String) { + let mut report: ariadne::ReportBuilder<'_, (&str, Range)> = + Report::build(ReportKind::Error, "sample.thp", self.error_offset); + + for label in self.labels.iter() { + let l = Label::new(("sample.thp", label.start..label.end)) + .with_message(label.message.clone()); + report = report.with_label(l) + } + + report + .with_code(self.error_code) + .finish() + .eprint(("sample.thp", Source::from(source))) + .unwrap() + } +} diff --git a/src/error_handling/semantic_error.rs b/src/error_handling/semantic_error.rs index 2319b6d..9b010b6 100644 --- a/src/error_handling/semantic_error.rs +++ b/src/error_handling/semantic_error.rs @@ -41,6 +41,6 @@ impl PrintableError for SemanticError { ) .finish(); - report.eprint(("sample.thp", Source::from(source))); + report.eprint(("sample.thp", Source::from(source))).unwrap(); } } diff --git a/src/error_handling/syntax_error.rs b/src/error_handling/syntax_error.rs index 55e78bc..ff42e3a 100644 --- a/src/error_handling/syntax_error.rs +++ b/src/error_handling/syntax_error.rs @@ -33,7 +33,7 @@ impl PrintableError for SyntaxError { ) .finish(); - report.eprint(("sample.thp", Source::from(source))); + report.eprint(("sample.thp", Source::from(source))).unwrap(); } } diff --git a/src/lexic/mod.rs b/src/lexic/mod.rs index 43d4d5a..20c3d4e 100755 --- a/src/lexic/mod.rs +++ b/src/lexic/mod.rs @@ -3,7 +3,7 @@ mod utils; pub mod token; -use crate::error_handling::{ErrorContainer, ErrorLabel, LexError, MistiError}; +use crate::error_handling::{ErrorContainer, ErrorLabel, MistiError}; use token::Token; use self::token::TokenType; diff --git a/src/lexic/scanner/new_comment.rs b/src/lexic/scanner/new_comment.rs index fd990c7..3e03c31 100644 --- a/src/lexic/scanner/new_comment.rs +++ b/src/lexic/scanner/new_comment.rs @@ -1,6 +1,8 @@ use super::token::Token; use crate::{ - error_handling::LexError, + error_handling::{ + error_messages::LEX_INCOMPLETE_MULTILINE_COMMENT, ErrorContainer, ErrorLabel, + }, lexic::{utils, LexResult}, }; @@ -40,12 +42,21 @@ pub fn scan_multiline(chars: &Vec, start_pos: usize) -> LexResult { ), Err(last_position) => { // Throw an error: Incomplete multiline comment - LexResult::Err(LexError { - position: start_pos, - // TODO: add an end_position - end_position: last_position, - reason: "Unfinished multiline commend".into(), - }) + let label = ErrorLabel { + message: String::from("The code ends here without closing the multiline comment"), + // This is minus 1 so we are pointing at something, and not at EOF + start: last_position - 1, + end: last_position, + }; + let econtainer = ErrorContainer { + error_code: LEX_INCOMPLETE_MULTILINE_COMMENT, + error_offset: last_position, + labels: vec![label], + note: None, + help: Some(String::from("End the multiline comment with `*/`")), + }; + + LexResult::Err(econtainer) } } } @@ -228,7 +239,7 @@ mod tests { let result = scan_multiline(&input, 0); match result { LexResult::Err(error) => { - assert_eq!(0, error.position) + assert_eq!(error.error_code, LEX_INCOMPLETE_MULTILINE_COMMENT); } _ => { panic!("Expected an error scannning an incomplete multiline comment") diff --git a/src/lexic/scanner/number.rs b/src/lexic/scanner/number.rs index b00c3a6..8c498e8 100755 --- a/src/lexic/scanner/number.rs +++ b/src/lexic/scanner/number.rs @@ -1,4 +1,8 @@ -use crate::error_handling::LexError; +use crate::error_handling::error_messages::{ + LEX_INVALID_BINARY_NUMBER, LEX_INVALID_FLOATING_NUMBER, LEX_INVALID_HEX_NUMBER, + LEX_INVALID_OCTAL_NUMBER, LEX_INVALID_SCIENTIFIC_NUMBER, +}; +use crate::error_handling::{ErrorContainer, ErrorLabel}; use crate::lexic::{token::Token, utils, LexResult}; /// Function to scan an int/float @@ -57,11 +61,22 @@ fn scan_hex(chars: &[char], start_pos: usize, current: String) -> LexResult { let (t, next) = scan_hex_digits(chars, start_pos + 1, utils::str_append(current, *c)); LexResult::Some(t, next) } - _ => LexResult::Err(LexError { - position: start_pos, - end_position: start_pos + 1, - reason: String::from("Tried to scan an incomplete hex value"), - }), + _ => { + let label = ErrorLabel { + message: String::from("The hex number ends here, without any digit"), + start: start_pos, + end: start_pos + 1, + }; + let econtainer = ErrorContainer { + error_code: LEX_INVALID_HEX_NUMBER, + error_offset: start_pos, + labels: vec![label], + note: None, + help: None, + }; + + LexResult::Err(econtainer) + } } } @@ -82,12 +97,21 @@ fn scan_octal(chars: &[char], start_pos: usize) -> LexResult { } if token_vec.is_empty() { - LexResult::Err(LexError { + let label = ErrorLabel { + message: String::from("The octal number ends here, without any digit"), + start: current_pos, + end: current_pos + 1, + }; + let econtainer = ErrorContainer { + error_code: LEX_INVALID_OCTAL_NUMBER, // minus 2 to account for the opening '0o' - position: start_pos - 2, - end_position: current_pos, - reason: String::from("Found an incomplete octal number"), - }) + error_offset: current_pos - 2, + labels: vec![label], + note: None, + help: None, + }; + + LexResult::Err(econtainer) } else { let octal_numbers = format!("0o{}", token_vec.iter().collect::()); let new_token = Token::new_int(octal_numbers, start_pos - 2); @@ -113,12 +137,21 @@ fn scan_binary(chars: &[char], start_pos: usize) -> LexResult { } if token_vec.is_empty() { - LexResult::Err(LexError { + let label = ErrorLabel { + message: String::from("The binary number ends here, without any digit"), + start: current_pos, + end: current_pos + 1, + }; + let econtainer = ErrorContainer { + error_code: LEX_INVALID_BINARY_NUMBER, // minus 2 to account for the opening '0b' - position: start_pos - 2, - end_position: current_pos, - reason: String::from("Found an incomplete binary number"), - }) + error_offset: current_pos - 2, + labels: vec![label], + note: None, + help: None, + }; + + LexResult::Err(econtainer) } else { let octal_numbers = format!("0b{}", token_vec.iter().collect::()); let new_token = Token::new_int(octal_numbers, start_pos - 2); @@ -135,18 +168,45 @@ fn scan_binary(chars: &[char], start_pos: usize) -> LexResult { fn scan_double(chars: &Vec, start_pos: usize, current: String) -> LexResult { match chars.get(start_pos) { Some(c) if utils::is_digit(*c) => scan_double_impl(chars, start_pos, current), - Some(_) => LexResult::Err(LexError { - position: start_pos, - end_position: start_pos + 1, - reason: String::from( - "The character after the dot when scanning a double is not a number.", - ), - }), - _ => LexResult::Err(LexError { - position: start_pos, - end_position: start_pos + 1, - reason: String::from("EOF when scanning a double number."), - }), + Some(_) => { + let label = ErrorLabel { + message: String::from("The floating number ends here, without any digit"), + start: start_pos, + end: start_pos + 1, + }; + let econtainer = ErrorContainer { + error_code: LEX_INVALID_FLOATING_NUMBER, + // minus 2 to account for the opening '0b' + error_offset: start_pos, + labels: vec![label], + note: Some(String::from( + "Floating point numbers must always have at least 1 digit after the period", + )), + help: None, + }; + + LexResult::Err(econtainer) + } + _ => { + let label = ErrorLabel { + message: String::from( + "The code ends here, without completing the floating point number", + ), + start: start_pos, + end: start_pos + 1, + }; + let econtainer = ErrorContainer { + error_code: LEX_INVALID_FLOATING_NUMBER, + error_offset: start_pos, + labels: vec![label], + note: Some(String::from( + "Floating point numbers must always have at least 1 digit after the period", + )), + help: None, + }; + + LexResult::Err(econtainer) + } } } @@ -190,13 +250,24 @@ fn scan_scientific(chars: &Vec, start_pos: usize, current: String) -> LexR let (t, next) = scan_digits(chars, start_pos + 2, new_value); LexResult::Some(t, next) } - _ => LexResult::Err(LexError { - position: start_pos, - end_position: start_pos + 1, - reason: String::from( - "The characters after 'e' are not + or -, or are not followed by a number", - ), - }), + _ => { + let label = ErrorLabel { + message: String::from("The scientific number ends here, incorrectly"), + start: start_pos, + end: start_pos + 1, + }; + let econtainer = ErrorContainer { + error_code: LEX_INVALID_SCIENTIFIC_NUMBER, + error_offset: start_pos, + labels: vec![label], + note: Some(String::from( + "Scientific numbers must always have a sign (+ or -) and a digit afterwards: `3.22e+2`" + )), + help: None, + }; + + LexResult::Err(econtainer) + } } } @@ -334,7 +405,7 @@ mod tests { match scan(&input, start_pos) { LexResult::Err(reason) => { - assert_eq!("Tried to scan an incomplete hex value", reason.reason) + assert_eq!(reason.error_code, LEX_INVALID_HEX_NUMBER) } _ => panic!(), } @@ -383,9 +454,8 @@ mod tests { let result = scan(&input, 0); match result { LexResult::Err(error) => { - assert_eq!(error.position, 0); - assert_eq!(error.end_position, 2); - assert_eq!(error.reason, "Found an incomplete octal number"); + assert_eq!(error.error_offset, 0); + assert_eq!(error.error_code, LEX_INVALID_OCTAL_NUMBER) } _ => panic!("Expected an error, got {:?}", result), } @@ -412,9 +482,8 @@ mod tests { let result = scan(&input, 0); match result { LexResult::Err(error) => { - assert_eq!(error.position, 0); - assert_eq!(error.end_position, 2); - assert_eq!(error.reason, "Found an incomplete binary number"); + assert_eq!(error.error_offset, 0); + assert_eq!(error.error_code, LEX_INVALID_BINARY_NUMBER) } _ => panic!("Expected an error, got {:?}", result), } @@ -453,10 +522,7 @@ mod tests { let start_pos = 0; match scan(&input, start_pos) { - LexResult::Err(reason) => assert_eq!( - "The character after the dot when scanning a double is not a number.", - reason.reason - ), + LexResult::Err(reason) => assert_eq!(reason.error_code, LEX_INVALID_FLOATING_NUMBER), _ => panic!(), } @@ -464,9 +530,7 @@ mod tests { let start_pos = 0; match scan(&input, start_pos) { - LexResult::Err(reason) => { - assert_eq!("EOF when scanning a double number.", reason.reason) - } + LexResult::Err(reason) => assert_eq!(reason.error_code, LEX_INVALID_FLOATING_NUMBER), _ => panic!(), } } @@ -567,10 +631,7 @@ mod tests { match scan(&input, start_pos) { LexResult::Err(reason) => { - assert_eq!( - "The characters after 'e' are not + or -, or are not followed by a number", - reason.reason - ) + assert_eq!(reason.error_code, LEX_INVALID_SCIENTIFIC_NUMBER) } _ => panic!("Expected an error"), } @@ -583,10 +644,7 @@ mod tests { match scan(&input, start_pos) { LexResult::Err(reason) => { - assert_eq!( - "The characters after 'e' are not + or -, or are not followed by a number", - reason.reason - ) + assert_eq!(reason.error_code, LEX_INVALID_SCIENTIFIC_NUMBER) } _ => panic!("Expected an error"), } diff --git a/src/php_ast/mod.rs b/src/php_ast/mod.rs index 11f706a..a9d3802 100644 --- a/src/php_ast/mod.rs +++ b/src/php_ast/mod.rs @@ -59,6 +59,6 @@ pub enum PPrimary<'a> { /// /// This is a $variable Variable(&'a String), - /// This is a symbol, e.g. a function name - Symbol(&'a String), + // This is a symbol, e.g. a function name + // Symbol(&'a String), } diff --git a/src/repl/mod.rs b/src/repl/mod.rs index 79439d7..ec44d89 100755 --- a/src/repl/mod.rs +++ b/src/repl/mod.rs @@ -1,7 +1,5 @@ use std::io::{self, Write}; -use colored::Colorize; - use crate::codegen::Transpilable; use crate::error_handling::PrintableError; diff --git a/src/semantic/types/expression.rs b/src/semantic/types/expression.rs index 39db649..e0a38fb 100644 --- a/src/semantic/types/expression.rs +++ b/src/semantic/types/expression.rs @@ -1,7 +1,7 @@ use crate::{ error_handling::{semantic_error::SemanticError, MistiError}, semantic::symbol_table::SymbolTable, - syntax::ast::{Expression, Positionable}, + syntax::ast::Expression, }; use super::{Type, Typed};