Skip to content

Commit

Permalink
feat: diagnostics for lexer
Browse files Browse the repository at this point in the history
  • Loading branch information
MilkeeyCat committed Dec 29, 2024
1 parent bf4fb60 commit f644cce
Show file tree
Hide file tree
Showing 7 changed files with 225 additions and 45 deletions.
17 changes: 15 additions & 2 deletions src/compile.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use crate::{
codegen::{amd64_asm::Amd64Asm, Codegen},
diagnostics::Diagnostics,
lexer::Lexer,
lowering::Lowering,
parser, Context,
Expand Down Expand Up @@ -45,8 +46,20 @@ pub fn compile(args: CompileArgs) -> Result<(), Box<dyn std::error::Error>> {

file.read_to_string(&mut source_code)?;

let lexer = Lexer::new(source_code);
let ast = parser::Parser::new(lexer)?.parse()?;
let report_diag_and_exit = |diag: &Diagnostics| {
println!("{diag}");

std::process::exit(0x45);
};

let mut diagnostics = Diagnostics::new(&source_code);
let lexer = Lexer::new(&source_code);
let ast = parser::Parser::new(lexer, &mut diagnostics)?.parse()?;

if diagnostics.has_errors() {
report_diag_and_exit(&mut diagnostics);
}

let allocator = Bump::new();
let mut ctx = Context::new(&allocator);

Expand Down
132 changes: 132 additions & 0 deletions src/diagnostics.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
use crate::lexer::span::Span;
use derive_more::derive::Display;

#[derive(Debug, Display)]
pub enum Diagnostic {
#[display("syntax error: unknown character")]
UnknownChar,
}

#[derive(Debug, Eq, PartialEq, Display)]
enum Level {
#[display("error")]
Error,
#[display("warning")]
Warning,
}

#[derive(Debug)]
struct Message {
level: Level,
diag: Diagnostic,
span: Span,
}

#[derive(Debug)]
pub struct Diagnostics<'src> {
source: &'src str,
messages: Vec<Message>,
}

impl<'src> Diagnostics<'src> {
pub fn new(source: &'src str) -> Self {
Self {
source,
messages: Vec::new(),
}
}

pub fn error(&mut self, diag: Diagnostic, span: Span) {
self.messages.push(Message {
level: Level::Error,
diag,
span,
})
}

pub fn warning(&mut self, diag: Diagnostic, span: Span) {
self.messages.push(Message {
level: Level::Warning,
diag,
span,
})
}

pub fn has_errors(&self) -> bool {
self.messages.iter().any(|msg| msg.level == Level::Error)
}

fn row(&self, col: usize) -> usize {
self.source[..col].chars().filter(|ch| ch == &'\n').count()
}

fn column(&self, row: usize) -> usize {
self.source[..row]
.chars()
.rev()
.enumerate()
.find_map(|(i, ch)| if ch == '\n' { Some(i) } else { None })
.unwrap_or(row)
}

fn lines(&self, span: &Span) -> usize {
self.source[..span.end - span.start]
.chars()
.filter(|ch| ch == &'\n')
.count()
+ 1
}
}

impl std::fmt::Display for Diagnostics<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
const RED_FG: &'static str = "\x1b[1;31m";
const YELLOW_FG: &'static str = "\x1b[1;33m";
const BLUE_FG: &'static str = "\x1b[1;34m";
const RESET: &'static str = "\x1b[0m";

for message in &self.messages {
let color = match message.level {
Level::Error => RED_FG,
Level::Warning => YELLOW_FG,
};

writeln!(f, "{color}{}{RESET}: {}", message.level, message.diag)?;

let col = self.row(message.span.start);
let row = self.column(message.span.start);

writeln!(f, "notarealfilename.rs:{}:{}", col + 1, row + 1)?;

if self.lines(&message.span) > 1 {
todo!("Dunno how to handle multiline diagnostics");
} else {
writeln!(
f,
"{BLUE_FG}{} |{RESET} {}",
col + 1,
self.source.lines().nth(col).unwrap()
)?;
writeln!(
f,
"{}{}",
(0..4).into_iter().map(|_| " ").collect::<String>(),
(0..self.column(message.span.end))
.into_iter()
.map(|i| {
if (self.column(message.span.start)..self.column(message.span.end))
.contains(&i)
{
format!("{color}^{RESET}")
} else {
" ".into()
}
})
.collect::<String>()
)?;
}
}

Ok(())
}
}
7 changes: 0 additions & 7 deletions src/lexer/error.rs

This file was deleted.

56 changes: 40 additions & 16 deletions src/lexer/mod.rs
Original file line number Diff line number Diff line change
@@ -1,31 +1,48 @@
mod error;
mod token;

pub use error::Error;
use span::Span;
pub use token::Token;

pub mod span {
#[derive(Debug)]
pub struct Span {
pub start: usize,
pub end: usize,
}

#[derive(Debug)]
pub struct Spanned<T> {
pub span: Span,
pub inner: T,
}
}

#[derive(Debug)]
pub struct Lexer {
input: String,
pub struct Lexer<'src> {
input: &'src str,
position: usize,
read_position: usize,
ch: char,
start: usize,
}

impl Lexer {
pub fn new(input: String) -> Self {
impl<'src> Lexer<'src> {
pub fn new(input: &'src str) -> Self {
let mut lexer = Self {
input,
ch: '\0',
position: 0,
read_position: 0,
start: 0,
};
lexer.read_char();

lexer
}

fn read_char(&mut self) {
self.start = self.position;

match self.input[self.read_position..].chars().next() {
Some(ch) => {
self.ch = ch;
Expand Down Expand Up @@ -85,10 +102,17 @@ impl Lexer {
self.read_char();
}
}

fn span(&self) -> Span {
Span {
start: self.start,
end: self.position,
}
}
}

impl Iterator for Lexer {
type Item = Result<Token, Error>;
impl<'src> Iterator for Lexer<'src> {
type Item = Result<Token, Span>;

fn next(&mut self) -> Option<Self::Item> {
self.skip_whitespace();
Expand Down Expand Up @@ -218,8 +242,10 @@ impl Iterator for Lexer {
_ => Token::Ident(ident),
}));
}
ch => {
return Some(Err(Error::UnknownCharacter(ch)));
_ => {
self.read_char();

return Some(Err(self.span()));
}
};

Expand All @@ -231,11 +257,11 @@ impl Iterator for Lexer {

#[cfg(test)]
mod test {
use super::{Error, Lexer};
use super::Lexer;
use crate::lexer::Token;

#[test]
fn source_into_tokens() -> Result<(), Error> {
fn source_into_tokens() {
let input = r#"
ident
69
Expand Down Expand Up @@ -369,14 +395,12 @@ mod test {
Token::Null,
];

let mut lexer = Lexer::new(input.to_string());
let mut lexer = Lexer::new(input);

for token in tokens {
let next_token = lexer.next().unwrap()?;
let next_token = lexer.next().unwrap().unwrap();

assert_eq!(token, next_token);
}

Ok(())
}
}
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
pub mod codegen;
pub mod compile;
pub mod diagnostics;
pub mod ir;
pub mod lexer;
pub mod lowering;
Expand Down
4 changes: 1 addition & 3 deletions src/parser/error.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
use super::{OpParseError, Ty};
use crate::lexer::{self, Token};
use crate::lexer::Token;
use thiserror::Error;

#[derive(Error, Debug)]
pub enum Error {
#[error(transparent)]
Lexer(#[from] lexer::Error),
#[error(transparent)]
Type(#[from] TyError),
#[error(transparent)]
Expand Down
Loading

0 comments on commit f644cce

Please sign in to comment.