mirror of
https://github.com/Noratrieb/dilaria.git
synced 2026-01-14 17:35:03 +01:00
nice errors
This commit is contained in:
parent
bea961bbbd
commit
40a7aaac71
5 changed files with 181 additions and 43 deletions
78
src/errors.rs
Normal file
78
src/errors.rs
Normal 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");
|
||||||
114
src/lex.rs
114
src/lex.rs
|
|
@ -1,26 +1,15 @@
|
||||||
|
use crate::errors::{CompilerError, Span};
|
||||||
use std::iter::Peekable;
|
use std::iter::Peekable;
|
||||||
use std::str::CharIndices;
|
use std::str::CharIndices;
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, PartialOrd, PartialEq, Ord, Eq, Hash)]
|
///
|
||||||
pub struct Span {
|
/// A single token generated from the lexer
|
||||||
start: usize,
|
///
|
||||||
len: usize,
|
/// For example `for`, `"hello"`, `main` or `.`
|
||||||
}
|
|
||||||
|
|
||||||
impl Span {
|
|
||||||
pub fn new(start: usize, len: usize) -> Self {
|
|
||||||
Self { start, len }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn single(start: usize) -> Self {
|
|
||||||
Self { start, len: 1 }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Token<'code> {
|
pub struct Token<'code> {
|
||||||
span: Span,
|
pub span: Span,
|
||||||
pub(crate) kind: TokenType<'code>,
|
pub kind: TokenType<'code>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'code> Token<'code> {
|
impl<'code> Token<'code> {
|
||||||
|
|
@ -195,11 +184,14 @@ impl<'code> Iterator for Lexer<'code> {
|
||||||
if self.expect('=') {
|
if self.expect('=') {
|
||||||
let _ = self.code.next(); // consume =;
|
let _ = self.code.next(); // consume =;
|
||||||
break Token {
|
break Token {
|
||||||
span: Span::single(start),
|
span: Span::new(start, 2),
|
||||||
kind: TokenType::BangEqual,
|
kind: TokenType::BangEqual,
|
||||||
};
|
};
|
||||||
} else {
|
} 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);
|
buffer.push(char);
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
return Some(Err(LexError(
|
return Some(Err(LexError::new(
|
||||||
"reached EOF expecting '\"'".to_string(),
|
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 => {
|
char => {
|
||||||
if char.is_ascii_digit() {
|
if char.is_ascii_digit() {
|
||||||
|
|
@ -256,16 +249,18 @@ impl<'code> Iterator for Lexer<'code> {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let number_str = &self.src[start..end];
|
let number_str = &self.src[start..end];
|
||||||
|
let span = Span::start_end(start, end);
|
||||||
let number = number_str
|
let number = number_str
|
||||||
.parse::<f64>()
|
.parse::<f64>()
|
||||||
.map_err(|err| LexError(err.to_string()));
|
.map_err(|err| LexError::new(span, LexErrorKind::InvalidFloat(err)));
|
||||||
match number {
|
match number {
|
||||||
Ok(number) => {
|
Ok(number) if number.is_infinite() => {
|
||||||
break Token::new(
|
return Some(Err(LexError::new(
|
||||||
Span::new(start, end - start),
|
span,
|
||||||
TokenType::Number(number),
|
LexErrorKind::FloatInfiniteLiteral,
|
||||||
)
|
)))
|
||||||
}
|
}
|
||||||
|
Ok(number) => break Token::new(span, TokenType::Number(number)),
|
||||||
Err(err) => return Some(Err(err)),
|
Err(err) => return Some(Err(err)),
|
||||||
}
|
}
|
||||||
} else if is_valid_ident_start(char) {
|
} else if is_valid_ident_start(char) {
|
||||||
|
|
@ -280,11 +275,14 @@ impl<'code> Iterator for Lexer<'code> {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
break Token::new(
|
break Token::new(
|
||||||
Span::new(start, end),
|
Span::start_end(start, end),
|
||||||
keyword_or_ident(&self.src[start..end]),
|
keyword_or_ident(&self.src[start..end]),
|
||||||
);
|
);
|
||||||
} else {
|
} 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)]
|
#[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)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
|
|
|
||||||
24
src/lib.rs
24
src/lib.rs
|
|
@ -1,15 +1,25 @@
|
||||||
mod alloc;
|
mod alloc;
|
||||||
|
mod errors;
|
||||||
mod lex;
|
mod lex;
|
||||||
mod parse;
|
mod parse;
|
||||||
|
|
||||||
pub fn run_program(program: &str) {
|
pub fn run_program(program: &str) {
|
||||||
let lexer = lex::Lexer::lex(program);
|
let lexer = lex::Lexer::lex(program);
|
||||||
let tokens: Result<Vec<_>, _> = lexer.collect();
|
let (success, errors) = lexer.partition::<Vec<_>, _>(|result| result.is_ok());
|
||||||
println!(
|
|
||||||
"{:#?}",
|
if errors.is_empty() {
|
||||||
tokens.map(|tokens| tokens
|
println!(
|
||||||
|
"{:#?}",
|
||||||
|
success
|
||||||
|
.into_iter()
|
||||||
|
.map(Result::unwrap)
|
||||||
|
.map(|token| token.kind)
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
errors
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|token| token.kind)
|
.map(Result::unwrap_err)
|
||||||
.collect::<Vec<_>>())
|
.for_each(|err| crate::errors::display_error(program, err));
|
||||||
);
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
fn main() {
|
fn main() {
|
||||||
if let Some(filename) = std::env::args().skip(1).next() {
|
if let Some(filename) = std::env::args().skip(1).next() {
|
||||||
println!("'{}'", filename);
|
|
||||||
match std::fs::read_to_string(filename) {
|
match std::fs::read_to_string(filename) {
|
||||||
Ok(contents) => {
|
Ok(contents) => {
|
||||||
script_lang::run_program(&contents);
|
script_lang::run_program(&contents);
|
||||||
|
|
|
||||||
7
test.sl
7
test.sl
|
|
@ -1,7 +1,10 @@
|
||||||
fn main() {
|
fn main() {
|
||||||
let number = 5
|
let number = 43908574628594760249385748930574230895743289057243895742308957443908574628594760249385748930574230895743289057243895742308957443908574628594760249385748930574230895743289057243895742308957443908574628594760249385748930574230895743289057243895742308957443908574628594760249385748930574462859476024938574893057405742308957432890572438957423089574
|
||||||
|
let number =
|
||||||
let number2 = 5325
|
let number2 = 5325
|
||||||
let is_equal = number == number2
|
let is_equal = number == number2
|
||||||
|
|
||||||
print("Is Equal: " + is_equal)
|
if !is_equal {
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue