Skip to content
Snippets Groups Projects
forge_script.rs 1.7 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;
	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")]
Louis's avatar
Louis committed
	#[test_case("if foo {}" => matches Ok(_) ; "Parse conditional")]
	#[test_case("2 * 4 - 3" => matches Ok(_) ; "Parse arithmetic")]
Louis's avatar
Louis committed
	fn expression_parsing(prog: &str) -> Result<Expression, String> {
		parse_expression(prog)
	}
}