Skip to content
Snippets Groups Projects
forge_script.rs 3.25 KiB
Newer Older
use crate::error::ForgeResult;
use crate::lexer::{script_to_tokens, ScriptTokenType};
use crate::parser::ast::{Expression, Program};
use crate::TokenError;
use peg::Parse;

Louis's avatar
Louis committed
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) => {
Louis's avatar
Louis committed
		pub fn $name(source: &str) -> Result<$output, String> {
			let tokens = script_to_tokens(source)
				.map_err(|e| format!("{}", e))?
				.iter()
				.map(|tok| {
					Ok((
Louis's avatar
Louis committed
						tok.position.location_offset(),
						tok.token_type.clone(),
Louis's avatar
Louis committed
						tok.position.location_offset() + tok.token_type.len(),
					))
				})
				.collect::<Vec<ExprSpan>>();

Louis's avatar
Louis committed
			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, parse_program};
	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")]
Louis's avatar
Louis committed
	#[test_case("if foo {}" => matches Ok(_) ; "Parse conditional")]
	#[test_case("if foo {} else if bar {}" => matches Ok(_) ; "Parse multi-conditional")]
	#[test_case("if foo {} else if bar {} else {}" => matches Ok(_) ; "Parse multi-conditional with else")]
Louis's avatar
Louis committed
	#[test_case("2 * 4 - 3" => matches Ok(_) ; "Parse arithmetic")]
	#[test_case("10 - 2 * 4 + 3" => matches Ok(_) ; "Parse arithmetic reverse")]
	#[test_case("foo.bar" => matches Ok(_) ; "Basic property accessor")]
	#[test_case("foo.bar()" => matches Ok(_) ; "function property accessor")]
	#[test_case("foo.bar().baz" => matches Ok(_) ; "nested property accessor")]
	#[test_case("if something_else {}.bar" => matches Ok(_) ; "expression property accessor")]
	#[test_case("123 + if if foo { bar } else { baz } { 200 } else { 300 }" => matches Ok(_) ; "Parse inline condition")]
	#[test_case("fn add(a, b) { a + b }" => matches Ok(_) ; "Declare basic fn")]
	#[test_case("fn has_no_params() {}" => matches Ok(_) ; "Declare empty params fn")]
	#[test_case("fn do_some_stuff(foo = 123, bar, baz = \"My Default String\") {}" => matches Ok(_) ; "Declare complex fn")]
	#[test_case("let som_value = fn do_some_stuff(foo = 123, bar, baz = \"My Default String\") {}" => matches Ok(_) ; "First class fn")]
Louis's avatar
Louis committed
	fn expression_parsing(prog: &str) -> Result<Expression, String> {
		parse_expression(prog).map(|expr| {
			dbg!(&expr);
			expr
		})
	}

	#[test_case("10 - 2 * 4 + 3; false; 12 + 14" => matches Ok(_) ; "Parse value expr list")]
	#[test_case("10 - 2 * 4 + 3; false; 12 + 14;" => matches Ok(_) ; "Parse void expr list")]
	#[test_case("10 - 2 * 4 + 3;; false; 12 + 14;;;;;" => matches Ok(_) ; "Infinite semicolons")]
	fn program_parsing(prog: &str) -> Result<Program, String> {
		parse_program(prog).map(|expr| {
			dbg!(&expr);
			expr