mirror of
https://github.com/azur1s/bobbylisp.git
synced 2024-10-16 02:37:40 -05:00
diagnostic crate
This commit is contained in:
parent
0743717ce5
commit
682178bec9
11
Cargo.lock
generated
11
Cargo.lock
generated
|
@ -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",
|
||||
|
|
|
@ -3,6 +3,7 @@ members = [
|
|||
"crates/main",
|
||||
"crates/lexer",
|
||||
"crates/parser",
|
||||
"crates/diagnostic",
|
||||
"crates/hir",
|
||||
"crates/codegen",
|
||||
]
|
12
crates/diagnostic/Cargo.toml
Normal file
12
crates/diagnostic/Cargo.toml
Normal 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" }
|
120
crates/diagnostic/src/lib.rs
Normal file
120
crates/diagnostic/src/lib.rs
Normal 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
|
||||
}
|
||||
}
|
|
@ -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!() }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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,83 +39,30 @@ 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();
|
||||
|
@ -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
2
example/err.hades
Normal file
|
@ -0,0 +1,2 @@
|
|||
-- Can't use Int as identifier
|
||||
let 123 = 123;
|
Loading…
Reference in a new issue