Skip to content

Commit

Permalink
parser(feat): add error reporting
Browse files Browse the repository at this point in the history
  • Loading branch information
chanbengz committed Dec 5, 2024
1 parent 6a9564d commit a71e1ec
Show file tree
Hide file tree
Showing 5 changed files with 95 additions and 40 deletions.
6 changes: 2 additions & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[package]
name = "spl"
name = "incredibuild"
version = "0.1.0"
edition = "2021"

Expand All @@ -13,8 +13,6 @@ members = [
]

[dependencies]
logos = "0.14.2"
clap = "4.5.22"
lalrpop = { version = "0.22.0", default-features = false }
llvm-ir = { version = "0.11.1", features = ["llvm-17"] }
spl_parser = { path = "src/parser" }
spl_parser = { path = "src/parser" }
22 changes: 10 additions & 12 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
extern crate llvm_ir as llvm;

use std::fs::File;
use std::io::Read;
use spl_parser::parse;
use clap::{arg, Arg, Command};
use spl_parser::{parse_from_file};
use clap::{Arg, Command};
// use std::ffi::CString;
// use std::ptr;

Expand All @@ -17,16 +15,16 @@ fn main() {
Arg::new("output").short('o').long("output").required(false)
)
.get_matches();
let mut input = String::new();
File::open(args.get_one::<String>("input").unwrap())
.unwrap().read_to_string(&mut input)
.expect(format!("error: could not read file {}",
args.get_one::<String>("input").unwrap()).as_str()
);

let source_path = args.get_one::<String>("input").unwrap();

let parsed_input = parse(&input).unwrap();
println!("{:?}", parsed_input);
let parsed_input = parse_from_file(&source_path);
match parsed_input {
Ok(_) => println!("[32mParsed successfully[0m"),
Err(e) => {
println!("{}", e);
}
}

// unsafe {
// codegen(parsed_input);
Expand Down
1 change: 1 addition & 0 deletions src/parser/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ license = "GPL-3.0"
spl_lexer = {version = "0.0.1", path = "../lexer"}
spl_ast = {version = "0.0.1", path = "../ast"}
lalrpop-util = { version = "0.21.0", features = ["unicode"] }
colored = "2"

[build-dependencies]
lalrpop = { version = "0.22.0", default-features = false }
79 changes: 59 additions & 20 deletions src/parser/src/error.rs
Original file line number Diff line number Diff line change
@@ -1,33 +1,72 @@
use colored::Colorize;
use spl_lexer::tokens::{Token, LexicalError};
use lalrpop_util::ErrorRecovery;


pub fn display_error(errors: &Vec<ErrorRecovery<usize, Token, LexicalError>>, input: &str) {
let error_str = format_errors(errors, input);
eprintln!("{}", error_str);
pub fn display_error(errors: &Vec<ErrorRecovery<usize, Token, LexicalError>>, input: &str, source_path: &str) {
let lines = input.lines().collect::<Vec<&str>>();
for error in errors {
let error = &error.error;
if let lalrpop_util::ParseError::User { error } = error { match error {
LexicalError::MissingLexeme(l, token, r) => {
let lineno = input[..*l].lines().count();
let lineno = if lineno == 0 { 1 } else { lineno };
let begin = input[..*l].rfind('\n').unwrap_or(0);
line_error(
(*l - begin, *r - begin), lineno,
lines[lineno - 1],
&format!("{} missing {} [{}]",
"error:".red(), token.as_str(), "B".red()
),
source_path
);
},
LexicalError::UnknownLexeme(l, r) => {
let lineno = input[..*l].lines().count();
let lineno = if lineno == 0 { 1 } else { lineno };
let begin = input[..*l].rfind('\n').unwrap_or(0);
line_error(
(*l - begin, *r - begin), lineno,
lines[lineno - 1],
&format!("{} unknown lexeme [{}]",
"error:".red(), "A".red()
),
source_path,
);
}, _ => {} }
}
}
}

fn line_error( span: (usize, usize), lineno: usize, line_str: &str, error_msg: &str, source_path: &str) {
let lineno = lineno.to_string();
let padding = " ".repeat(lineno.len() + 1);
let padding_msg = " ".repeat(span.0);
let mut indicator = "^".to_string();
indicator.push_str(&"~".repeat(span.1 - span.0 - 1));
println!("{} {}:{lineno}:{}: {error_msg}\n{padding}{}\n{} {} {line_str}\n\
{padding}{}{padding_msg}{}",
"-->".purple(), source_path, span.0, "|".purple(), lineno.purple(), "|".purple(),
"|".purple(), indicator.red()
)
}

pub fn format_errors(errors: &Vec<ErrorRecovery<usize, Token, LexicalError>>, input: &str) -> String {
let mut error_str = Vec::new();
for error in errors {
match &error.error {
lalrpop_util::ParseError::User { error } => {
match error {
LexicalError::MissingLexeme(l, token, _) => {
let lineno = input[..*l].lines().count();
let lineno = if lineno == 0 { 1 } else { lineno };
error_str.push((lineno, format!("Error type B at Line {}: Missing {}\n",
lineno, token.as_str()).to_owned()));
},
LexicalError::UnknownLexeme(l, r) => {
let lineno = input[..*l].lines().count();
error_str.push((lineno, format!("Error type A at Line {}: unknown lexeme {}\n",
lineno, input[*l..*r].to_string()).to_owned()));
},
_ => {}
}
let error = &error.error;
if let lalrpop_util::ParseError::User { error } = error { match error {
LexicalError::MissingLexeme(l, token, _) => {
let lineno = input[..*l].lines().count();
let lineno = if lineno == 0 { 1 } else { lineno };
error_str.push((lineno, format!("Error type B at Line {}: Missing {}\n",
lineno, token.as_str()).to_owned()));
},
_ => {}
LexicalError::UnknownLexeme(l, r) => {
let lineno = input[..*l].lines().count();
error_str.push((lineno, format!("Error type A at Line {}: unknown lexeme {}\n",
lineno, input[*l..*r].to_string()).to_owned()));
}, _ => {} }
}
}

Expand Down
27 changes: 23 additions & 4 deletions src/parser/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,24 +1,43 @@
use lalrpop_util::lalrpop_mod;
use std::fs::File;
use lalrpop_util::{lalrpop_mod, ErrorRecovery};
use spl_lexer::tokens::{Token, LexicalError};

lalrpop_mod!(pub grammar); // synthesized by LALRPOP
use spl_ast::tree;
pub use crate::error::display_error;
use crate::grammar::ProgramParser;

pub mod error;
use std::io::Read;

pub fn parse(source: &str) -> Result<tree::Program, String> {
pub fn parse(source: &str) -> Result<tree::Program, Vec<ErrorRecovery<usize, Token, LexicalError>>> {
let mut errors = Vec::new();
let lexer = spl_lexer::lexer::Lexer::new(&source);
let result = ProgramParser::new().parse(&mut errors, lexer).unwrap();
if errors.len() > 0 {
display_error(&errors, &source);
Err(format!("Syntax Error: {}", errors.len()))
Err(errors.to_owned())
} else {
Ok(result)
}
}

pub fn parse_from_file(source_path: &str) -> Result<tree::Program, String> {
let mut source = String::new();
File::open(source_path)
.expect("File not found")
.read_to_string(&mut source)
.expect("Failed to read file");

let result = parse(&source);
match result {
Ok(ast) => Ok(ast),
Err(errors) => {
display_error(&errors, &source, source_path);
Err(format!("\n{} syntax error(s) found", errors.len()))
}
}
}

#[cfg(test)]
mod tests {
use std::fs::File;
Expand Down

0 comments on commit a71e1ec

Please sign in to comment.