1
1
Fork 0
mirror of https://github.com/azur1s/bobbylisp.git synced 2024-10-16 02:37:40 -05:00

diagnostic crate

This commit is contained in:
Natapat Samutpong 2022-03-12 06:35:14 +07:00
parent 0743717ce5
commit 682178bec9
8 changed files with 240 additions and 92 deletions

11
Cargo.lock generated
View file

@ -123,6 +123,16 @@ version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7"
[[package]]
name = "diagnostic"
version = "0.1.0"
dependencies = [
"ariadne",
"chumsky",
"hir",
"lexer",
]
[[package]] [[package]]
name = "getrandom" name = "getrandom"
version = "0.2.5" version = "0.2.5"
@ -148,6 +158,7 @@ dependencies = [
"chumsky", "chumsky",
"clap", "clap",
"codegen", "codegen",
"diagnostic",
"hir", "hir",
"lexer", "lexer",
"parser", "parser",

View file

@ -3,6 +3,7 @@ members = [
"crates/main", "crates/main",
"crates/lexer", "crates/lexer",
"crates/parser", "crates/parser",
"crates/diagnostic",
"crates/hir", "crates/hir",
"crates/codegen", "crates/codegen",
] ]

View file

@ -0,0 +1,12 @@
[package]
name = "diagnostic"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
chumsky = "0.8.0"
ariadne = "0.1.5"
lexer = { path = "../lexer" }
hir = { path = "../hir" }

View file

@ -0,0 +1,120 @@
use lexer::Token;
use chumsky::prelude::Simple;
use ariadne::{Report, ReportKind, Label, Source, Color, Fmt};
#[derive(Debug)]
pub struct Diagnostics {
pub errors: Vec<Kind>,
}
#[derive(Debug)]
pub enum Kind {
LexError(Simple<char>),
ParseError(Simple<Token>),
LoweringError(hir::LoweringError),
}
impl Diagnostics {
pub fn new() -> Self {
Self {
errors: Vec::new(),
}
}
pub fn has_error(&self) -> bool {
!self.errors.is_empty()
}
pub fn add_lex_error(&mut self, error: Simple<char>) {
self.errors.push(Kind::LexError(error));
}
pub fn add_parse_error(&mut self, error: Simple<Token>) {
self.errors.push(Kind::ParseError(error));
}
pub fn add_lowering_error(&mut self, error: hir::LoweringError) {
self.errors.push(Kind::LoweringError(error));
}
pub fn display(&self, src: String) {
let lex_error = self.errors.iter().filter_map(|kind| match kind {
Kind::LexError(error) => Some(error.clone()), // Using clone() to remove reference
_ => None,
});
let parse_error = self.errors.iter().filter_map(|kind| match kind {
Kind::ParseError(error) => Some(error.clone()), // Same case as above
_ => None,
});
// TODO: Lowering error
lex_error.into_iter()
.map(|e| e.map(|e| e.to_string()))
.chain(parse_error.into_iter().map(|e| e.map(|tok| tok.to_string())))
.for_each(|e| {
let report = Report::build(ReportKind::Error, (), e.span().start);
let report = match e.reason() {
chumsky::error::SimpleReason::Unclosed { span, delimiter } => report
.with_message(format!(
"Unclosed delimiter {}",
delimiter.fg(Color::Yellow)
))
.with_label(
Label::new(span.clone())
.with_message(format!(
"Expected closing delimiter {}",
delimiter.fg(Color::Yellow)
))
.with_color(Color::Yellow)
)
.with_label(
Label::new(e.span())
.with_message(format!(
"Must be closed before this {}",
e.found()
.unwrap_or(&"end of file".to_string())
.fg(Color::Red)
))
.with_color(Color::Red)
),
chumsky::error::SimpleReason::Unexpected => report
.with_message(format!(
"{}, expected {}",
if e.found().is_some() { "Unexpected token in input" }
else { "Unexpected end of input" },
if e.expected().len() == 0 { "something else".to_string().fg(Color::Green) }
else {
e.expected()
.map(|expected| match expected {
Some(expected) => expected.to_string(),
None => "end of input".to_string()
})
.collect::<Vec<_>>()
.join(", ")
.fg(Color::Green)
}
))
.with_label(
Label::new(e.span())
.with_message(format!(
"Unexpected token {}",
e.found()
.unwrap_or(&"EOF".to_string())
.fg(Color::Red)
))
.with_color(Color::Red)
),
_ => {
println!("{:?}", e);
todo!();
}
};
report.finish().print(Source::from(&src)).unwrap();
}); // End errors reporting
}
}

View file

@ -22,54 +22,108 @@ pub struct IR {
pub span: Range<usize> pub span: Range<usize>
} }
#[derive(Debug)]
pub struct LoweringError {
pub span: Range<usize>,
pub message: String
}
impl IR { impl IR {
pub fn new(kind: IRKind, span: Range<usize>) -> Self { pub fn new(kind: IRKind, span: Range<usize>) -> Self {
Self { kind, span } Self { kind, span }
} }
} }
pub fn ast_to_ir(ast: Vec<(Expr, Range<usize>)>) -> Vec<IR> { pub fn ast_to_ir(ast: Vec<(Expr, Range<usize>)>) -> (Vec<IR>, Vec<LoweringError>) {
let mut irs = Vec::new(); let mut irs = Vec::new();
let mut errors = Vec::new();
for expr in ast { for expr in ast {
let ir_kind = expr_to_ir(&expr.0); let ir_kind = expr_to_ir(&expr.0);
let ir = IR::new(ir_kind, expr.1); if let Some(err) = ir_kind.1 {
irs.push(ir); errors.push(err);
} else {
irs.push(IR::new(ir_kind.0.unwrap(), expr.1));
}
} }
irs (irs, errors)
} }
pub fn expr_to_ir(expr: &Expr) -> IRKind { pub fn expr_to_ir(expr: &Expr) -> (Option<IRKind>, Option<LoweringError>) {
match expr { match expr {
Expr::Let { name, type_hint, value } => { Expr::Let { name, type_hint, value } => {
let value = expr_to_ir(&value.0); let value = expr_to_ir(&value.0);
IRKind::Define { name: name.clone(), type_hint: gen_type_hint(type_hint), value: Box::new(value) } if let Some(err) = value.1 {
// Return error
return (None, Some(err));
} else {
let value = value.0.unwrap(); // Unwrapping because it should always be Some
let ir_kind = IRKind::Define { name: name.clone(), type_hint: type_hint.clone(), value: Box::new(value) };
return (Some(ir_kind), None);
}
}, },
Expr::Call { name, args } => { Expr::Call { name, args } => {
let name = match &name.0 { let name = match &name.0 {
Expr::Identifier(s) => s.clone(), Expr::Identifier(s) => s.clone(),
_ => panic!("Expected identifier") // TODO: Remove panic and use error handling // Should never happen because the parser should have caught this
_ => return (None, Some(LoweringError { span: name.1.clone(), message: "Expected identifier".to_string() }))
}; };
let args = args.0.iter().map(|arg| expr_to_ir(&arg.0)).collect::<Vec<_>>(); let mut largs = Vec::new(); // `largs` stand for lowered args
IRKind::Call { name, args } // Iterate over args
for arg in &args.0 {
// Lower each argument, if there is an error then return early
let arg = expr_to_ir(&arg.0);
if let Some(err) = arg.1 {
return (None, Some(err));
} else {
// Else push the lowered argument
largs.push(arg.0.unwrap());
}
}
let ir_kind = IRKind::Call { name, args: largs };
return (Some(ir_kind), None);
}, },
Expr::Fun { name, type_hint, args, body } => { Expr::Fun { name, type_hint, args, body } => {
// Iterate each argument and give it a type hint
let args = args.0.iter().map(|arg| (arg.0.0.clone(), gen_type_hint(&arg.1.0))).collect::<Vec<_>>(); let args = args.0.iter().map(|arg| (arg.0.0.clone(), gen_type_hint(&arg.1.0))).collect::<Vec<_>>();
let body = expr_to_ir(&body.0); let body = expr_to_ir(&body.0);
IRKind::Fun { name: name.to_string(), return_type_hint: gen_type_hint(type_hint), args, body: Box::new(body) } if let Some(err) = body.1 {
return (None, Some(err));
} else {
let body = body.0.unwrap();
let ir_kind = IRKind::Fun { name: name.clone(), return_type_hint: gen_type_hint(type_hint), args, body: Box::new(body) };
return (Some(ir_kind), None);
}
}, },
Expr::Return { expr } => { Expr::Return { expr } => {
let expr = expr_to_ir(&expr.0); let expr = expr_to_ir(&expr.0);
IRKind::Return { value: Box::new(expr) } if let Some(err) = expr.1 {
return (None, Some(err));
} else {
let expr = expr.0.unwrap();
let ir_kind = IRKind::Return { value: Box::new(expr) };
return (Some(ir_kind), None);
}
}, },
Expr::Do { body } => { Expr::Do { body } => {
let body = body.iter().map(|expr| expr_to_ir(&expr.0)).collect::<Vec<_>>(); let mut lbody = Vec::new();
IRKind::Do { body } for expr in body {
let expr = expr_to_ir(&expr.0);
if let Some(err) = expr.1 {
return (None, Some(err));
} else {
lbody.push(expr.0.unwrap());
}
}
let ir_kind = IRKind::Do { body: lbody };
return (Some(ir_kind), None);
}, },
Expr::Int(value) => IRKind::Value { value: Value::Int(*value) }, // TODO: Handle primitive types error (e.g. overflow)
Expr::Boolean(value) => IRKind::Value { value: Value::Boolean(*value) }, // For now it just leaves the value as is and let the target compiler handle it
Expr::String(value) => IRKind::Value { value: Value::String(value.clone()) }, Expr::Int(value) => (Some(IRKind::Value { value: Value::Int(*value) }), None),
Expr::Identifier(value) => IRKind::Value { value: Value::Ident(value.clone()) }, Expr::Boolean(value) => (Some(IRKind::Value { value: Value::Boolean(*value) }), None),
Expr::String(value) => (Some(IRKind::Value { value: Value::String(value.clone()) }), None),
Expr::Identifier(value) => (Some(IRKind::Value { value: Value::Ident(value.clone()) }), None),
_ => { dbg!(expr); todo!() } _ => { dbg!(expr); todo!() }
} }
} }

View file

@ -8,6 +8,7 @@ edition = "2021"
clap = { version = "3.0.14", features = ["derive"] } clap = { version = "3.0.14", features = ["derive"] }
lexer = { path = "../lexer" } lexer = { path = "../lexer" }
parser = { path = "../parser" } parser = { path = "../parser" }
diagnostic = { path = "../diagnostic" }
hir = { path = "../hir" } hir = { path = "../hir" }
codegen = { path = "../codegen" } codegen = { path = "../codegen" }
chumsky = "0.8.0" chumsky = "0.8.0"

View file

@ -1,10 +1,10 @@
use std::{fs, io::Write}; use std::{fs, io::Write};
use clap::Parser as ArgParser; use clap::Parser as ArgParser;
use ariadne::{Report, ReportKind, Label, Source, Color, Fmt};
use lexer::lex; use lexer::lex;
use parser::parse; use parser::parse;
use diagnostic::Diagnostics;
use hir::ast_to_ir; use hir::ast_to_ir;
use codegen::cpp; use codegen::cpp;
@ -39,84 +39,31 @@ fn main() {
let (tokens, lex_error) = lex(src.clone()); let (tokens, lex_error) = lex(src.clone());
let (ast, parse_error) = parse(tokens.unwrap(), src.chars().count()); let (ast, parse_error) = parse(tokens.unwrap(), src.chars().count());
// Report errors let mut diagnostics = Diagnostics::new();
lex_error.into_iter() for err in lex_error { diagnostics.add_lex_error(err); }
.map(|e| e.map(|e| e.to_string())) for err in parse_error { diagnostics.add_parse_error(err); }
.chain(parse_error.into_iter().map(|e| e.map(|tok| tok.to_string())))
.for_each(|e| {
let report = Report::build(ReportKind::Error, (), e.span().start);
let report = match e.reason() { if diagnostics.has_error() {
chumsky::error::SimpleReason::Unclosed { span, delimiter } => report diagnostics.display(src);
.with_message(format!( logif!(0, "Epic parsing fail");
"Unclosed delimiter {}", std::process::exit(1);
delimiter.fg(Color::Yellow) } else {
)) logif!(0, format!("Parsing took {}ms", start.elapsed().as_millis()));
.with_label( }
Label::new(span.clone())
.with_message(format!(
"Expected closing delimiter {}",
delimiter.fg(Color::Yellow)
))
.with_color(Color::Yellow)
)
.with_label(
Label::new(e.span())
.with_message(format!(
"Must be closed before this {}",
e.found()
.unwrap_or(&"end of file".to_string())
.fg(Color::Red)
))
.with_color(Color::Red)
),
chumsky::error::SimpleReason::Unexpected => report
.with_message(format!(
"{}, expected {}",
if e.found().is_some() { "Unexpected token in input" }
else { "Unexpected end of input" },
if e.expected().len() == 0 { "something else".to_string().fg(Color::Green) }
else {
e.expected()
.map(|expected| match expected {
Some(expected) => expected.to_string(),
None => "end of input".to_string()
})
.collect::<Vec<_>>()
.join(", ")
.fg(Color::Green)
}
))
.with_label(
Label::new(e.span())
.with_message(format!(
"Unexpected token {}",
e.found()
.unwrap_or(&"EOF".to_string())
.fg(Color::Red)
))
.with_color(Color::Red)
),
_ => {
println!("{:?}", e);
todo!();
}
};
report.finish().print(Source::from(&src)).unwrap();
}
); // End errors reporting
logif!(0, format!("Parsing took {}ms", start.elapsed().as_millis()));
match ast { match ast {
Some(ast) => { Some(ast) => {
// Convert the AST to HIR // Convert the AST to HIR
let ir = ast_to_ir(ast); let (ir, lowering_error) = ast_to_ir(ast);
logif!(0, "Generated HIR."); for err in lowering_error { diagnostics.add_lowering_error(err); }
if diagnostics.has_error() {
diagnostics.display(src);
logif!(0, "Epic Lowering(HIR) fail");
std::process::exit(1);
} else {
logif!(0, format!("Lowering took {}ms", start.elapsed().as_millis()));
}
// Generate code // Generate code
let mut codegen = cpp::Codegen::new(); let mut codegen = cpp::Codegen::new();
codegen.gen(ir); codegen.gen(ir);
@ -137,7 +84,7 @@ fn main() {
logif!(0, format!("Wrote output to `{}`. All done.", output_path.display())); logif!(0, format!("Wrote output to `{}`. All done.", output_path.display()));
}, },
None => { None => {
logif!(2, "Failed to parse."); unreachable!();
} }
} }
} }

2
example/err.hades Normal file
View file

@ -0,0 +1,2 @@
-- Can't use Int as identifier
let 123 = 123;