use crate::error::ForgeResult; use crate::lexer::{script_to_tokens, ScriptTokenType}; use crate::parser::ast::{Expression, Program}; use crate::TokenError; use peg::Parse; pub type InputSpan<'a, Loc, Tok> = Result<(Loc, Tok, Loc), String>; type ExprSpan<'a> = InputSpan<'a, usize, ScriptTokenType>; macro_rules! export_grammar_fn { ($name:ident = $output:ty => $part: tt) => { pub fn $name(source: &str) -> Result<$output, String> { let tokens = script_to_tokens(source) .map_err(|e| format!("{}", e))? .iter() .map(|tok| { Ok(( tok.position.location_offset(), tok.token_type.clone(), tok.position.location_offset() + tok.token_type.len(), )) }) .collect::<Vec<ExprSpan>>(); let value = super::forge_grammar::$part::new() .parse::<ExprSpan, Vec<ExprSpan>>(tokens) .map_err(|e| format!("{}", e))?; Ok(value) } }; } export_grammar_fn!(parse_program = Program => ProgramParser); export_grammar_fn!(parse_expression = Expression => ExpressionParser); #[cfg(test)] mod grammar_test { use super::parse_expression; use crate::error::ForgeResult; use crate::parse::ast::Expression; use test_case::test_case; #[test_case("123" => matches Ok(_) ; "Parse literal number")] #[test_case(r#""Basic String""# => matches Ok(_) ; "Parse literal string")] #[test_case("false" => matches Ok(_) ; "Parse literal false")] #[test_case("true" => matches Ok(_) ; "Parse literal true")] #[test_case("null" => matches Ok(_) ; "Parse literal null")] #[test_case("if foo {}" => matches Ok(_) ; "Parse conditional")] #[test_case("2 * 4 - 3" => matches Ok(_) ; "Parse arithmetic")] fn expression_parsing(prog: &str) -> Result<Expression, String> { parse_expression(prog) } }