From 40a7aaac714c7efd7b50925226993c7741e46e5e Mon Sep 17 00:00:00 2001 From: Nilstrieb Date: Thu, 7 Oct 2021 22:15:10 +0200 Subject: [PATCH] nice errors --- src/errors.rs | 78 ++++++++++++++++++++++++++++++++++ src/lex.rs | 114 +++++++++++++++++++++++++++++++++++--------------- src/lib.rs | 24 +++++++---- src/main.rs | 1 - test.sl | 7 +++- 5 files changed, 181 insertions(+), 43 deletions(-) create mode 100644 src/errors.rs diff --git a/src/errors.rs b/src/errors.rs new file mode 100644 index 0000000..dec0070 --- /dev/null +++ b/src/errors.rs @@ -0,0 +1,78 @@ +use std::fmt::Debug; + +#[derive(Debug, Copy, Clone, PartialOrd, PartialEq, Ord, Eq, Hash)] +pub struct Span { + start: usize, + len: usize, +} + +impl Span { + pub fn new(start: usize, len: usize) -> Self { + Self { start, len } + } + + pub fn start_end(start: usize, end: usize) -> Self { + Self::new(start, end - start) + } + + pub fn single(start: usize) -> Self { + Self { start, len: 1 } + } +} + +pub trait CompilerError { + fn span(&self) -> Span; + fn message(&self) -> String; + fn note(&self) -> Option; +} + +pub fn display_error(source: &str, error: E) +where + E: CompilerError + Debug, +{ + let mut chars = 0; + let mut lines = source.split_inclusive('\n').enumerate(); + while let Some((idx, line)) = lines.next() { + if chars + line.len() + 1 > error.span().start { + let offset_on_line = error.span().start - chars; + + println!("{}error: {}{}", RED, error.message(), RESET); + println!(" {}|{}", CYAN, RESET); + println!( + "{}{:>5} |{} {}", + CYAN, + idx + 1, + RESET, + &line[..line.len() - 1] + ); + print!(" {}|{} ", CYAN, RESET); + println!( + "{}{}{}{}", + " ".repeat(offset_on_line), + RED, + "^".repeat(error.span().len), + RESET, + ); + if let Some(note) = error.note() { + println!(" {}|{}", CYAN, RESET); + println!( + " {}|{} {}note: {}{}", + CYAN, RESET, GREEN, note, RESET + ); + } + break; + } + chars += line.len(); + } +} + +macro_rules! color { + ($name:ident: $value:literal) => { + const $name: &str = concat!("\x1B[", $value); + }; +} + +color!(RED: "0;31m"); +color!(RESET: "0m"); +color!(CYAN: "0;36m"); +color!(GREEN: "0;32m"); diff --git a/src/lex.rs b/src/lex.rs index 20f61b5..5e83902 100644 --- a/src/lex.rs +++ b/src/lex.rs @@ -1,26 +1,15 @@ +use crate::errors::{CompilerError, Span}; use std::iter::Peekable; use std::str::CharIndices; -#[derive(Debug, Copy, Clone, PartialOrd, PartialEq, Ord, Eq, Hash)] -pub struct Span { - start: usize, - len: usize, -} - -impl Span { - pub fn new(start: usize, len: usize) -> Self { - Self { start, len } - } - - pub fn single(start: usize) -> Self { - Self { start, len: 1 } - } -} - +/// +/// A single token generated from the lexer +/// +/// For example `for`, `"hello"`, `main` or `.` #[derive(Debug, Clone)] pub struct Token<'code> { - span: Span, - pub(crate) kind: TokenType<'code>, + pub span: Span, + pub kind: TokenType<'code>, } impl<'code> Token<'code> { @@ -195,11 +184,14 @@ impl<'code> Iterator for Lexer<'code> { if self.expect('=') { let _ = self.code.next(); // consume =; break Token { - span: Span::single(start), + span: Span::new(start, 2), kind: TokenType::BangEqual, }; } else { - return Some(Err(LexError("Expected '=' after '!'".to_string()))); + return Some(Err(LexError::new( + Span::single(start), + LexErrorKind::SingleBang, + ))); }; } '>' => { @@ -230,13 +222,14 @@ impl<'code> Iterator for Lexer<'code> { buffer.push(char); } None => { - return Some(Err(LexError( - "reached EOF expecting '\"'".to_string(), - ))) + return Some(Err(LexError::new( + Span::single(start), // no not show the whole literal, this does not make sense + LexErrorKind::UnclosedStringLiteral, + ))); } } }; - break Token::new(Span::new(start, end - start), TokenType::String(buffer)); + break Token::new(Span::start_end(start, end), TokenType::String(buffer)); } char => { if char.is_ascii_digit() { @@ -256,16 +249,18 @@ impl<'code> Iterator for Lexer<'code> { } }; let number_str = &self.src[start..end]; + let span = Span::start_end(start, end); let number = number_str .parse::() - .map_err(|err| LexError(err.to_string())); + .map_err(|err| LexError::new(span, LexErrorKind::InvalidFloat(err))); match number { - Ok(number) => { - break Token::new( - Span::new(start, end - start), - TokenType::Number(number), - ) + Ok(number) if number.is_infinite() => { + return Some(Err(LexError::new( + span, + LexErrorKind::FloatInfiniteLiteral, + ))) } + Ok(number) => break Token::new(span, TokenType::Number(number)), Err(err) => return Some(Err(err)), } } else if is_valid_ident_start(char) { @@ -280,11 +275,14 @@ impl<'code> Iterator for Lexer<'code> { } }; break Token::new( - Span::new(start, end), + Span::start_end(start, end), keyword_or_ident(&self.src[start..end]), ); } else { - return Some(Err(LexError(format!("Invalid character: {}", char)))); + return Some(Err(LexError::new( + Span::single(start), + LexErrorKind::InvalidCharacter(char), + ))); } } } @@ -349,7 +347,57 @@ fn is_valid_ident_start(char: char) -> bool { } #[derive(Debug)] -pub struct LexError(String); +pub struct LexError { + pub span: Span, + pub kind: LexErrorKind, +} + +impl LexError { + fn new(span: Span, kind: LexErrorKind) -> Self { + Self { span, kind } + } +} + +impl CompilerError for LexError { + fn span(&self) -> Span { + self.span + } + + fn message(&self) -> String { + match &self.kind { + LexErrorKind::InvalidCharacter(char) => format!("Unexpected character: '{}'", char), + LexErrorKind::InvalidFloat(_) => format!("Invalid number"), + LexErrorKind::FloatInfiniteLiteral => "Number literal too long".to_string(), + LexErrorKind::UnclosedStringLiteral => "String literal not closed".to_string(), + LexErrorKind::SingleBang => "Expected '=' after '!'".to_string(), + } + } + + fn note(&self) -> Option { + match &self.kind { + LexErrorKind::InvalidCharacter(_) => { + Some("Character is not allowed outside of string literals and comments".to_string()) + } + LexErrorKind::InvalidFloat(err) => Some(err.to_string()), + LexErrorKind::FloatInfiniteLiteral => Some( + "A number literal cannot be larger than a 64 bit float can represent".to_string(), + ), + LexErrorKind::UnclosedStringLiteral => Some("Close the literal using '\"'".to_string()), + LexErrorKind::SingleBang => { + Some("If you meant to use it for negation, use `not`".to_string()) + } + } + } +} + +#[derive(Debug)] +pub enum LexErrorKind { + InvalidCharacter(char), + InvalidFloat(std::num::ParseFloatError), + FloatInfiniteLiteral, + UnclosedStringLiteral, + SingleBang, +} #[cfg(test)] mod test { diff --git a/src/lib.rs b/src/lib.rs index 6579117..623b275 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,15 +1,25 @@ mod alloc; +mod errors; mod lex; mod parse; pub fn run_program(program: &str) { let lexer = lex::Lexer::lex(program); - let tokens: Result, _> = lexer.collect(); - println!( - "{:#?}", - tokens.map(|tokens| tokens + let (success, errors) = lexer.partition::, _>(|result| result.is_ok()); + + if errors.is_empty() { + println!( + "{:#?}", + success + .into_iter() + .map(Result::unwrap) + .map(|token| token.kind) + .collect::>() + ); + } else { + errors .into_iter() - .map(|token| token.kind) - .collect::>()) - ); + .map(Result::unwrap_err) + .for_each(|err| crate::errors::display_error(program, err)); + } } diff --git a/src/main.rs b/src/main.rs index ff5d6ab..f527586 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,5 @@ fn main() { if let Some(filename) = std::env::args().skip(1).next() { - println!("'{}'", filename); match std::fs::read_to_string(filename) { Ok(contents) => { script_lang::run_program(&contents); diff --git a/test.sl b/test.sl index b6b2de4..c0b36fb 100644 --- a/test.sl +++ b/test.sl @@ -1,7 +1,10 @@ fn main() { - let number = 5 + let number = 43908574628594760249385748930574230895743289057243895742308957443908574628594760249385748930574230895743289057243895742308957443908574628594760249385748930574230895743289057243895742308957443908574628594760249385748930574230895743289057243895742308957443908574628594760249385748930574462859476024938574893057405742308957432890572438957423089574 + let number = let number2 = 5325 let is_equal = number == number2 - print("Is Equal: " + is_equal) + if !is_equal { + + } }