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"
checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7"
[[package]]
name = "diagnostic"
version = "0.1.0"
dependencies = [
"ariadne",
"chumsky",
"hir",
"lexer",
]
[[package]]
name = "getrandom"
version = "0.2.5"
@ -148,6 +158,7 @@ dependencies = [
"chumsky",
"clap",
"codegen",
"diagnostic",
"hir",
"lexer",
"parser",

View file

@ -3,6 +3,7 @@ members = [
"crates/main",
"crates/lexer",
"crates/parser",
"crates/diagnostic",
"crates/hir",
"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>
}
#[derive(Debug)]
pub struct LoweringError {
pub span: Range<usize>,
pub message: String
}
impl IR {
pub fn new(kind: IRKind, span: Range<usize>) -> Self {
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 errors = Vec::new();
for expr in ast {
let ir_kind = expr_to_ir(&expr.0);
let ir = IR::new(ir_kind, expr.1);
irs.push(ir);
if let Some(err) = ir_kind.1 {
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 {
Expr::Let { name, type_hint, value } => {
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 } => {
let name = match &name.0 {
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<_>>();
IRKind::Call { name, args }
let mut largs = Vec::new(); // `largs` stand for lowered 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 } => {
// 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 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 } => {
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 } => {
let body = body.iter().map(|expr| expr_to_ir(&expr.0)).collect::<Vec<_>>();
IRKind::Do { body }
let mut lbody = Vec::new();
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) },
Expr::Boolean(value) => IRKind::Value { value: Value::Boolean(*value) },
Expr::String(value) => IRKind::Value { value: Value::String(value.clone()) },
Expr::Identifier(value) => IRKind::Value { value: Value::Ident(value.clone()) },
// TODO: Handle primitive types error (e.g. overflow)
// For now it just leaves the value as is and let the target compiler handle it
Expr::Int(value) => (Some(IRKind::Value { value: Value::Int(*value) }), None),
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!() }
}
}

View file

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

View file

@ -1,10 +1,10 @@
use std::{fs, io::Write};
use clap::Parser as ArgParser;
use ariadne::{Report, ReportKind, Label, Source, Color, Fmt};
use lexer::lex;
use parser::parse;
use diagnostic::Diagnostics;
use hir::ast_to_ir;
use codegen::cpp;
@ -39,84 +39,31 @@ fn main() {
let (tokens, lex_error) = lex(src.clone());
let (ast, parse_error) = parse(tokens.unwrap(), src.chars().count());
// Report errors
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 mut diagnostics = Diagnostics::new();
for err in lex_error { diagnostics.add_lex_error(err); }
for err in parse_error { diagnostics.add_parse_error(err); }
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
logif!(0, format!("Parsing took {}ms", start.elapsed().as_millis()));
if diagnostics.has_error() {
diagnostics.display(src);
logif!(0, "Epic parsing fail");
std::process::exit(1);
} else {
logif!(0, format!("Parsing took {}ms", start.elapsed().as_millis()));
}
match ast {
Some(ast) => {
// Convert the AST to HIR
let ir = ast_to_ir(ast);
logif!(0, "Generated HIR.");
let (ir, lowering_error) = ast_to_ir(ast);
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
let mut codegen = cpp::Codegen::new();
codegen.gen(ir);
@ -137,7 +84,7 @@ fn main() {
logif!(0, format!("Wrote output to `{}`. All done.", output_path.display()));
},
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;