From 850e7e891984b5044fd8e2ed10ab6ad4300fec61 Mon Sep 17 00:00:00 2001 From: Louis Capitanchik <contact@louiscap.co> Date: Mon, 15 May 2023 23:20:18 +0100 Subject: [PATCH] Start parsing test suite, propogate correct error in wasm --- forge-script-lang/src/error/mod.rs | 68 ++++++++++++++++++++++ forge-script-lang/src/lib.rs | 5 +- forge-script-lang/src/parser/grammar.rs | 1 + forge-script-lang/src/parser/mod.rs | 2 + forge-script-lang/src/parser/test_suite.rs | 30 ++++++++++ forge-script-web/src/lib.rs | 20 ++++++- 6 files changed, 122 insertions(+), 4 deletions(-) create mode 100644 forge-script-lang/src/parser/test_suite.rs diff --git a/forge-script-lang/src/error/mod.rs b/forge-script-lang/src/error/mod.rs index 46b8e80..7019a93 100644 --- a/forge-script-lang/src/error/mod.rs +++ b/forge-script-lang/src/error/mod.rs @@ -1,5 +1,6 @@ use crate::lexer::Span; use crate::parse::ScriptToken; +use peg::error::ExpectedSet; use std::error::Error; use std::fmt::{Display, Formatter}; @@ -133,6 +134,63 @@ pub fn print_unexpected_token<'a>( eprintln!("|\n| Failed To Parse: expected {}", expected); } +pub struct ErrorFormat<'a>(pub &'a str, pub &'a ScriptToken<'a>, pub &'a ExpectedSet); + +impl<'a> Display for ErrorFormat<'a> { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + let ErrorFormat(source, token, expected) = self; + + let line = token.position.location_line() as usize; + let column = token.position.get_column(); + + let previous_line = if line > 1 { + source.lines().nth(line - 2) + } else { + None + }; + let source_line = source.lines().nth(line - 1).expect("Missing line"); + let next_line = source.lines().nth(line); + + let largest_line_num = line.max(line.saturating_sub(1)).max(line.saturating_add(1)); + let number_length = format!("{}", largest_line_num).len(); + + writeln!(f, "| Script error on line {} at \"{}\"\n|", line, token)?; + if let Some(prev) = previous_line { + writeln!( + f, + "| [{:>width$}] {}", + line - 1, + prev, + width = number_length + )?; + } + writeln!( + f, + "| [{:>width$}] {}", + line, + source_line, + width = number_length + )?; + writeln!( + f, + "| {} {}{}", + vec![" "; number_length + 2].join(""), + vec![" "; column - 1].join(""), + vec!["^"; token.token_type.len()].join(""), + )?; + if let Some(next) = next_line { + writeln!( + f, + "| [{:>width$}] {}", + line + 1, + next, + width = number_length + )?; + } + writeln!(f, "|\n| Failed To Parse: expected {}", expected) + } +} + pub fn print_forge_error<'a>(source: &'a str, fe: &'a ForgeError) { match &fe.kind { ForgeErrorKind::IncompleteInput => eprintln!("| Unexpected end of file"), @@ -142,3 +200,13 @@ pub fn print_forge_error<'a>(source: &'a str, fe: &'a ForgeError) { } } } + +pub fn format_forge_error<'a>(source: &'a str, fe: &'a ForgeError) -> String { + match &fe.kind { + ForgeErrorKind::IncompleteInput => String::from("| Unexpected end of file"), + ForgeErrorKind::LexerError(err) => format!("| {}", err), + ForgeErrorKind::UnexpectedToken { found, expected } => { + format!("{}", ErrorFormat(source, found, expected)) + } + } +} diff --git a/forge-script-lang/src/lib.rs b/forge-script-lang/src/lib.rs index 91ddc9b..24498c6 100644 --- a/forge-script-lang/src/lib.rs +++ b/forge-script-lang/src/lib.rs @@ -3,7 +3,10 @@ mod lexer; mod parser; pub mod runtime; -pub use error::{print_forge_error, ParseError, ParseErrorKind, TokenError, TokenErrorKind}; +pub use error::{ + format_forge_error, print_forge_error, ForgeError, ForgeErrorKind, ParseError, ParseErrorKind, + TokenError, TokenErrorKind, +}; pub mod parse { pub use super::lexer::{script_to_tokens, ScriptToken, ScriptTokenType}; pub use super::parser::ast; diff --git a/forge-script-lang/src/parser/grammar.rs b/forge-script-lang/src/parser/grammar.rs index e292608..9e5fdba 100644 --- a/forge-script-lang/src/parser/grammar.rs +++ b/forge-script-lang/src/parser/grammar.rs @@ -62,6 +62,7 @@ peg::parser! { / "-" { BinaryOp::Subtract } / "*" { BinaryOp::Multiply } / "/" { BinaryOp::Divide } + / "%" { BinaryOp::Modulo } rule unary_operator() -> UnaryOp = "!" { UnaryOp::Not } diff --git a/forge-script-lang/src/parser/mod.rs b/forge-script-lang/src/parser/mod.rs index 454b676..acbf4a7 100644 --- a/forge-script-lang/src/parser/mod.rs +++ b/forge-script-lang/src/parser/mod.rs @@ -1,6 +1,8 @@ pub mod ast; mod atoms; mod grammar; +#[cfg(test)] +mod test_suite; use crate::error::{ForgeError, ForgeErrorKind, ForgeResult}; use crate::print_forge_error; diff --git a/forge-script-lang/src/parser/test_suite.rs b/forge-script-lang/src/parser/test_suite.rs new file mode 100644 index 0000000..19f8c1a --- /dev/null +++ b/forge-script-lang/src/parser/test_suite.rs @@ -0,0 +1,30 @@ +use crate::parse::parse_program; + +#[test] +fn binary_ops() { + parse_program("1+1").expect("Failed binary add"); + parse_program("1-1").expect("Failed binary sub"); + parse_program("1*1").expect("Failed binary mul"); + parse_program("1/1").expect("Failed binary div"); + parse_program("1%1").expect("Failed binary mod"); +} + +#[test] +fn unary_ops() { + parse_program("-1").expect("Failed binary inv"); + parse_program("!1").expect("Failed binary not"); +} + +#[test] +fn indentifiers() { + parse_program("let my_ident").expect("Failed declare"); + parse_program("let my_ident = 123").expect("Failed declare/assign"); +} + +#[test] +fn conditional() { + parse_program("if false { }").expect("Failed conditional with literal"); + parse_program("if some_ident { }").expect("Failed conditional with identifier"); + parse_program("if 1 + 1 { }").expect("Failed conditional with expr"); + parse_program("if (let ident = false) { }").expect("Failed conditional with decl"); +} diff --git a/forge-script-web/src/lib.rs b/forge-script-web/src/lib.rs index 11e449e..7d8985d 100644 --- a/forge-script-web/src/lib.rs +++ b/forge-script-web/src/lib.rs @@ -1,3 +1,4 @@ +use forge_script_lang::format_forge_error; use forge_script_lang::parse::parse_program; use forge_script_lang::runtime::executor::{TreePrinter, Visitor}; use wasm_bindgen::prelude::*; @@ -9,13 +10,26 @@ pub fn init() { #[wasm_bindgen] pub fn compile_ast(program: &str) -> Result<JsValue, serde_wasm_bindgen::Error> { - serde_wasm_bindgen::to_value(&parse_program(program).expect("Failed to parse")) + match parse_program(program) { + Ok(val) => serde_wasm_bindgen::to_value(&val), + Err(e) => Err(serde_wasm_bindgen::Error::new(format_forge_error( + program, &e, + ))), + } } #[wasm_bindgen] pub fn format_script(program: &str) -> Result<String, serde_wasm_bindgen::Error> { - let ast = &parse_program(program).expect("Failed to parse"); + let ast = match parse_program(program) { + Ok(val) => val, + Err(e) => { + return Err(serde_wasm_bindgen::Error::new(format_forge_error( + program, &e, + ))); + } + }; + let mut writer = TreePrinter::new(); - writer.evaluate_program(ast); + writer.evaluate_program(&ast); Ok(writer.take_value()) } -- GitLab