diff --git a/Cargo.lock b/Cargo.lock index 674573b..66a4e1e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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", diff --git a/Cargo.toml b/Cargo.toml index 8c5f47d..1a1ffc4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,6 +3,7 @@ members = [ "crates/main", "crates/lexer", "crates/parser", + "crates/diagnostic", "crates/hir", "crates/codegen", ] \ No newline at end of file diff --git a/crates/diagnostic/Cargo.toml b/crates/diagnostic/Cargo.toml new file mode 100644 index 0000000..8dfae66 --- /dev/null +++ b/crates/diagnostic/Cargo.toml @@ -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" } \ No newline at end of file diff --git a/crates/diagnostic/src/lib.rs b/crates/diagnostic/src/lib.rs new file mode 100644 index 0000000..f41330c --- /dev/null +++ b/crates/diagnostic/src/lib.rs @@ -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, +} + +#[derive(Debug)] +pub enum Kind { + LexError(Simple), + ParseError(Simple), + 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) { + self.errors.push(Kind::LexError(error)); + } + + pub fn add_parse_error(&mut self, error: Simple) { + 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::>() + .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 + } +} \ No newline at end of file diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs index 915849e..5f6ac83 100644 --- a/crates/hir/src/lib.rs +++ b/crates/hir/src/lib.rs @@ -22,54 +22,108 @@ pub struct IR { pub span: Range } +#[derive(Debug)] +pub struct LoweringError { + pub span: Range, + pub message: String +} + impl IR { pub fn new(kind: IRKind, span: Range) -> Self { Self { kind, span } } } -pub fn ast_to_ir(ast: Vec<(Expr, Range)>) -> Vec { +pub fn ast_to_ir(ast: Vec<(Expr, Range)>) -> (Vec, Vec) { 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, Option) { 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::>(); - 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::>(); 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::>(); - 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!() } } } diff --git a/crates/main/Cargo.toml b/crates/main/Cargo.toml index 08c693f..5c9ea72 100644 --- a/crates/main/Cargo.toml +++ b/crates/main/Cargo.toml @@ -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" diff --git a/crates/main/src/main.rs b/crates/main/src/main.rs index c23efe9..6360f01 100644 --- a/crates/main/src/main.rs +++ b/crates/main/src/main.rs @@ -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::>() - .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!(); } } } diff --git a/example/err.hades b/example/err.hades new file mode 100644 index 0000000..0de1a37 --- /dev/null +++ b/example/err.hades @@ -0,0 +1,2 @@ +-- Can't use Int as identifier +let 123 = 123; \ No newline at end of file