nice errors

This commit is contained in:
nora 2021-10-07 22:15:10 +02:00
parent bea961bbbd
commit 40a7aaac71
5 changed files with 181 additions and 43 deletions

78
src/errors.rs Normal file
View file

@ -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<String>;
}
pub fn display_error<E>(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");

View file

@ -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::<f64>()
.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<String> {
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 {

View file

@ -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<Vec<_>, _> = lexer.collect();
println!(
"{:#?}",
tokens.map(|tokens| tokens
let (success, errors) = lexer.partition::<Vec<_>, _>(|result| result.is_ok());
if errors.is_empty() {
println!(
"{:#?}",
success
.into_iter()
.map(Result::unwrap)
.map(|token| token.kind)
.collect::<Vec<_>>()
);
} else {
errors
.into_iter()
.map(|token| token.kind)
.collect::<Vec<_>>())
);
.map(Result::unwrap_err)
.for_each(|err| crate::errors::display_error(program, err));
}
}

View file

@ -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);

View file

@ -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 {
}
}