use alloc::{
borrow::ToOwned,
boxed::Box,
format,
vec,
vec::Vec,
};
use anyhow::{
Error,
Result,
};
use battler_data::Fraction;
use crate::{
effect::fxlang::tree,
error::{
WrapOptionError,
WrapResultError,
general_error,
},
};
#[derive(Clone, Copy, PartialEq, Eq)]
pub(crate) enum Token {
Identifier,
String,
Integer,
VariableStart,
Colon,
Comma,
Dot,
LeftParenthesis,
RightParenthesis,
LeftBracket,
RightBracket,
LineCommentStart,
Assignment,
Equal,
NotEqual,
LessThan,
LessThanOrEqual,
GreaterThan,
GreaterThanOrEqual,
Plus,
Minus,
Asterisk,
ForwardSlash,
Percent,
Caret,
Exclamation,
TrueKeyword,
FalseKeyword,
UndefinedKeyword,
ExprKeyword,
FuncCallKeyword,
IfKeyword,
ElseKeyword,
ForEachKeyword,
InKeyword,
ReturnKeyword,
OrKeyword,
AndKeyword,
HasKeyword,
HasAnyKeyword,
StrKeyword,
ContinueKeyword,
BreakKeyword,
RequireKeyword,
AssignKeyword,
}
mod byte {
pub fn valid_identifier_start(b: u8) -> bool {
(b >= b'a' && b <= b'z') || (b >= b'A' && b <= b'Z')
}
pub fn valid_identifier(b: u8) -> bool {
valid_identifier_start(b) || is_digit(b) || b == b'-' || b == b'_'
}
pub fn is_digit(b: u8) -> bool {
b >= b'0' && b <= b'9'
}
pub fn is_whitespace(b: u8) -> bool {
b == b' ' || b == b'\t' || b == b'\r' || b == b'\n' || b == 11 || b == 12
}
}
pub(crate) struct NextTokenContext {}
impl NextTokenContext {
pub fn new() -> Self {
Self {}
}
}
mod token {
use alloc::{
borrow::ToOwned,
format,
string::String,
vec::Vec,
};
use anyhow::Result;
use super::{
NextTokenContext,
Token,
byte,
};
use crate::error::{
WrapOptionError,
general_error,
};
pub(crate) struct TokenParser<'s> {
input: &'s [u8],
input_index: usize,
buffer_index: usize,
next_token: Option<Token>,
string: Option<String>,
}
impl<'s> TokenParser<'s> {
pub fn new(input: &'s str) -> Self {
Self {
input: input.as_bytes(),
input_index: 0,
buffer_index: 0,
next_token: None,
string: None,
}
}
fn peek_next_byte(&self) -> Option<u8> {
self.input.get(self.buffer_index).cloned()
}
fn next_byte(&mut self) {
self.buffer_index += 1;
}
#[allow(unused)]
fn put_back_byte(&mut self) -> bool {
if self.buffer_index <= self.input_index {
return false;
}
self.buffer_index -= 1;
true
}
fn lexeme_buffer_empty(&self) -> bool {
self.buffer_index == self.input_index
}
fn lexeme_buffer_range(&self) -> (usize, usize) {
(self.input_index, self.buffer_index)
}
fn lexeme_buffer_str_from_range(&self, (start, end): (usize, usize)) -> Option<&str> {
unsafe { Some(core::str::from_utf8_unchecked(self.input.get(start..end)?)) }
}
fn lexeme_buffer_str_from_range_or_default(&self, range: (usize, usize)) -> &str {
self.lexeme_buffer_str_from_range(range).unwrap_or_default()
}
fn lexeme_buffer_str(&self) -> Option<&str> {
self.lexeme_buffer_str_from_range(self.lexeme_buffer_range())
}
fn consume_buffer(&mut self) {
self.input_index = self.buffer_index;
self.string = None;
self.next_token = None;
}
fn reset_buffer(&mut self) {
self.buffer_index = self.input_index;
}
fn skip_whitespace_bytes(&mut self) {
while let Some(byte) = self.peek_next_byte() {
if !byte::is_whitespace(byte) {
break;
}
self.next_byte();
}
self.consume_buffer();
}
fn try_read_identifier(&mut self) -> bool {
match self.peek_next_byte() {
Some(next) if byte::valid_identifier_start(next) => (),
_ => return false,
}
self.next_byte();
while let Some(next) = self.peek_next_byte() {
if !byte::valid_identifier(next) {
break;
}
self.next_byte();
}
true
}
fn try_read_string(&mut self) -> Result<Option<String>> {
match self.peek_next_byte() {
Some(b'\'') => (),
_ => return Ok(None),
}
self.next_byte();
let mut string = Vec::new();
let mut escape = false;
let mut terminated = false;
while !terminated {
match self.peek_next_byte() {
None => break,
Some(next) if escape => {
escape = false;
match next {
b'\'' | b'\\' => string.push(next),
b'n' => string.push(b'\n'),
_ => {
return Err(general_error(format!(
"invalid escape character: \\{}",
next as char,
)));
}
}
}
Some(b'\'') => {
terminated = true;
}
Some(b'\\') => {
if escape {
escape = false;
string.push(b'\\');
} else {
escape = true;
}
}
Some(next) => {
string.push(next);
}
}
self.next_byte();
}
if !terminated {
Err(general_error("unterminated string"))
} else {
Ok(Some(unsafe {
core::str::from_utf8_unchecked(string.as_slice()).to_owned()
}))
}
}
fn try_read_integer(&mut self) -> bool {
match self.peek_next_byte() {
Some(next) if byte::is_digit(next) => (),
_ => return false,
}
self.next_byte();
while let Some(next) = self.peek_next_byte() {
if !byte::is_digit(next) {
break;
}
self.next_byte();
}
true
}
fn try_read_symbol(&mut self) -> Option<Token> {
match self.peek_next_byte() {
Some(b'$') => {
self.next_byte();
Some(Token::VariableStart)
}
Some(b':') => {
self.next_byte();
Some(Token::Colon)
}
Some(b',') => {
self.next_byte();
Some(Token::Comma)
}
Some(b'.') => {
self.next_byte();
Some(Token::Dot)
}
Some(b'(') => {
self.next_byte();
Some(Token::LeftParenthesis)
}
Some(b')') => {
self.next_byte();
Some(Token::RightParenthesis)
}
Some(b'[') => {
self.next_byte();
Some(Token::LeftBracket)
}
Some(b']') => {
self.next_byte();
Some(Token::RightBracket)
}
Some(b'#') => {
self.next_byte();
Some(Token::LineCommentStart)
}
Some(b'=') => {
self.next_byte();
match self.peek_next_byte() {
Some(b'=') => {
self.next_byte();
Some(Token::Equal)
}
_ => Some(Token::Assignment),
}
}
Some(b'!') => {
self.next_byte();
match self.peek_next_byte() {
Some(b'=') => {
self.next_byte();
Some(Token::NotEqual)
}
_ => Some(Token::Exclamation),
}
}
Some(b'<') => {
self.next_byte();
match self.peek_next_byte() {
Some(b'=') => {
self.next_byte();
Some(Token::LessThanOrEqual)
}
_ => Some(Token::LessThan),
}
}
Some(b'>') => {
self.next_byte();
match self.peek_next_byte() {
Some(b'=') => {
self.next_byte();
Some(Token::GreaterThanOrEqual)
}
_ => Some(Token::GreaterThan),
}
}
Some(b'+') => {
self.next_byte();
Some(Token::Plus)
}
Some(b'-') => {
self.next_byte();
Some(Token::Minus)
}
Some(b'*') => {
self.next_byte();
Some(Token::Asterisk)
}
Some(b'/') => {
self.next_byte();
Some(Token::ForwardSlash)
}
Some(b'%') => {
self.next_byte();
Some(Token::Percent)
}
Some(b'^') => {
self.next_byte();
Some(Token::Caret)
}
_ => None,
}
}
fn identifier_to_token(&self, identifier: &str) -> Token {
match identifier {
"true" => Token::TrueKeyword,
"false" => Token::FalseKeyword,
"undefined" => Token::UndefinedKeyword,
"expr" => Token::ExprKeyword,
"func_call" => Token::FuncCallKeyword,
"if" => Token::IfKeyword,
"else" => Token::ElseKeyword,
"foreach" => Token::ForEachKeyword,
"in" => Token::InKeyword,
"return" => Token::ReturnKeyword,
"or" => Token::OrKeyword,
"and" => Token::AndKeyword,
"has" => Token::HasKeyword,
"hasany" => Token::HasAnyKeyword,
"str" => Token::StrKeyword,
"continue" => Token::ContinueKeyword,
"break" => Token::BreakKeyword,
"require" => Token::RequireKeyword,
"assign" => Token::AssignKeyword,
_ => Token::Identifier,
}
}
pub fn token_index(&self) -> usize {
self.input_index
}
pub fn token(&self) -> Option<Token> {
self.next_token
}
pub fn lexeme(&self) -> &str {
self.lexeme_buffer_str().unwrap_or_default()
}
pub fn consume_lexeme(&mut self) -> &str {
let range = self.lexeme_buffer_range();
self.consume_buffer();
self.lexeme_buffer_str_from_range_or_default(range)
}
pub fn consume_string(&mut self) -> Option<String> {
let string = self.string.clone();
self.consume_buffer();
string
}
fn parse_next_token(&mut self, _: NextTokenContext) -> Result<Option<Token>> {
self.skip_whitespace_bytes();
if self.peek_next_byte().is_none() {
return Ok(None);
}
if self.try_read_identifier() {
let identifier = self
.lexeme_buffer_str()
.wrap_expectation("parsed empty identifier")?;
return Ok(Some(self.identifier_to_token(identifier)));
}
self.reset_buffer();
if self.try_read_integer() {
return Ok(Some(Token::Integer));
}
self.reset_buffer();
if let Some(token) = self.try_read_symbol() {
return Ok(Some(token));
}
self.reset_buffer();
if let Some(string) = self.try_read_string()? {
self.string = Some(string);
return Ok(Some(Token::String));
}
self.reset_buffer();
match self.peek_next_byte() {
None => return Err(general_error("unexpected end of line")),
Some(next) => Err(general_error(format!(
"unexpected character: {}",
next as char
))),
}
}
pub fn next_token(&mut self, context: NextTokenContext) -> Result<Option<Token>> {
if !self.lexeme_buffer_empty() {
return Ok(self.next_token);
}
self.next_token = self.parse_next_token(context)?;
Ok(self.next_token)
}
}
}
pub struct StatementParser<'s> {
token_parser: token::TokenParser<'s>,
depth: u8,
}
impl<'s> StatementParser<'s> {
const MAX_DEPTH: u8 = 5;
pub fn new(input: &'s str) -> Self {
Self {
token_parser: token::TokenParser::new(input),
depth: 0,
}
}
fn unexpected_token_error(&self) -> Error {
match self.token_parser.token() {
None => general_error("unexpected end of line"),
_ => general_error(format!(
"unexpected token at index {}: {}",
self.token_parser.token_index(),
self.token_parser.lexeme(),
)),
}
}
fn unexpected_token_error_with_expected_hint(&self, expected: &str) -> Error {
match self.token_parser.token() {
None => general_error(format!("unexpected end of line (expected {expected})")),
_ => general_error(format!(
"unexpected token at index {}: {} (expected {expected})",
self.token_parser.token_index(),
self.token_parser.lexeme(),
)),
}
}
fn down_one_level(&mut self) -> Result<()> {
if self.depth == Self::MAX_DEPTH {
Err(general_error(format!(
"stack overflow: exceeded maximum depth of {}",
Self::MAX_DEPTH,
)))
} else {
self.depth += 1;
Ok(())
}
}
fn up_one_level(&mut self) {
if self.depth > 0 {
self.depth -= 1;
}
}
pub fn parse(&mut self) -> Result<tree::Statement> {
self.down_one_level()?;
let statement = self.parse_statement()?;
self.up_one_level();
if self.token_parser.token().is_some() && statement != tree::Statement::Empty {
return Err(self.unexpected_token_error());
}
Ok(statement)
}
fn parse_statement(&mut self) -> Result<tree::Statement> {
match self.token_parser.next_token(NextTokenContext::new())? {
None => Ok(tree::Statement::Empty),
Some(Token::LineCommentStart) => Ok(tree::Statement::Empty),
Some(Token::Identifier) => {
Ok(tree::Statement::FunctionCall(self.parse_function_call()?))
}
Some(Token::VariableStart) => Ok(tree::Statement::Assignment(self.parse_assignment()?)),
Some(Token::IfKeyword) => Ok(tree::Statement::IfStatement(self.parse_if_statement()?)),
Some(Token::ElseKeyword) => Ok(tree::Statement::ElseIfStatement(
self.parse_else_if_statement()?,
)),
Some(Token::ForEachKeyword) => Ok(tree::Statement::ForEachStatement(
self.parse_for_each_statement()?,
)),
Some(Token::ReturnKeyword) => Ok(tree::Statement::ReturnStatement(
self.parse_return_statement()?,
)),
Some(Token::ContinueKeyword) => {
Ok(tree::Statement::Continue(self.parse_continue_statement()?))
}
Some(Token::BreakKeyword) => Ok(tree::Statement::Break(self.parse_break_statement()?)),
Some(Token::RequireKeyword) => Ok(tree::Statement::RequireStatement(
self.parse_require_statement()?,
)),
_ => Err(self.unexpected_token_error()),
}
}
fn parse_assignment(&mut self) -> Result<tree::Assignment> {
let lhs = self.parse_var()?;
match self.token_parser.next_token(NextTokenContext::new())? {
Some(Token::Assignment) => self.token_parser.consume_lexeme(),
_ => return Err(self.unexpected_token_error_with_expected_hint("=")),
};
let rhs = self.parse_expr()?;
Ok(tree::Assignment { lhs, rhs })
}
fn parse_if_statement(&mut self) -> Result<tree::IfStatement> {
match self.token_parser.next_token(NextTokenContext::new())? {
Some(Token::IfKeyword) => self.token_parser.consume_lexeme(),
_ => return Err(self.unexpected_token_error_with_expected_hint("if")),
};
let expr = self.parse_expr()?;
match self.token_parser.next_token(NextTokenContext::new())? {
Some(Token::Colon) => self.token_parser.consume_lexeme(),
_ => return Err(self.unexpected_token_error_with_expected_hint(":")),
};
Ok(tree::IfStatement(expr))
}
fn parse_else_if_statement(&mut self) -> Result<tree::ElseIfStatement> {
match self.token_parser.next_token(NextTokenContext::new())? {
Some(Token::ElseKeyword) => self.token_parser.consume_lexeme(),
_ => return Err(self.unexpected_token_error_with_expected_hint("else")),
};
let if_statement = match self.token_parser.next_token(NextTokenContext::new())? {
Some(Token::IfKeyword) => Some(self.parse_if_statement()?),
Some(Token::Colon) => {
self.token_parser.consume_lexeme();
None
}
_ => return Err(self.unexpected_token_error_with_expected_hint(":")),
};
Ok(tree::ElseIfStatement(if_statement))
}
fn parse_for_each_statement(&mut self) -> Result<tree::ForEachStatement> {
match self.token_parser.next_token(NextTokenContext::new())? {
Some(Token::ForEachKeyword) => self.token_parser.consume_lexeme(),
_ => return Err(self.unexpected_token_error_with_expected_hint("foreach")),
};
let var = self.parse_var()?;
match self.token_parser.next_token(NextTokenContext::new())? {
Some(Token::InKeyword) => self.token_parser.consume_lexeme(),
_ => return Err(self.unexpected_token_error_with_expected_hint("in")),
};
let range = match self.parse_value()? {
None => return Err(self.unexpected_token_error_with_expected_hint("value")),
Some(value) => value,
};
match self.token_parser.next_token(NextTokenContext::new())? {
Some(Token::Colon) => self.token_parser.consume_lexeme(),
_ => return Err(self.unexpected_token_error_with_expected_hint(":")),
};
Ok(tree::ForEachStatement { var, range })
}
fn parse_return_statement(&mut self) -> Result<tree::ReturnStatement> {
match self.token_parser.next_token(NextTokenContext::new())? {
Some(Token::ReturnKeyword) => self.token_parser.consume_lexeme(),
_ => return Err(self.unexpected_token_error_with_expected_hint("return")),
};
let expr = match self.token_parser.next_token(NextTokenContext::new())? {
Some(_) => Some(self.parse_expr()?),
None => None,
};
Ok(tree::ReturnStatement(expr))
}
fn parse_continue_statement(&mut self) -> Result<tree::ContinueStatement> {
self.token_parser.consume_lexeme();
Ok(tree::ContinueStatement)
}
fn parse_break_statement(&mut self) -> Result<tree::BreakStatement> {
self.token_parser.consume_lexeme();
Ok(tree::BreakStatement)
}
fn parse_require_statement(&mut self) -> Result<tree::RequireStatement> {
match self.token_parser.next_token(NextTokenContext::new())? {
Some(Token::RequireKeyword) => self.token_parser.consume_lexeme(),
_ => return Err(self.unexpected_token_error_with_expected_hint("require")),
};
let condition = self.parse_expr()?;
let mut else_return = None;
if let Some(Token::ElseKeyword) = self.token_parser.next_token(NextTokenContext::new())? {
self.token_parser.consume_lexeme();
match self.token_parser.next_token(NextTokenContext::new())? {
Some(Token::ReturnKeyword) => self.token_parser.consume_lexeme(),
_ => return Err(self.unexpected_token_error_with_expected_hint("return")),
};
let ret_expr = match self.token_parser.next_token(NextTokenContext::new())? {
Some(_) => Some(self.parse_expr()?),
None => None,
};
else_return = Some(ret_expr);
}
Ok(tree::RequireStatement {
condition,
else_return,
})
}
fn parse_function_call(&mut self) -> Result<tree::FunctionCall> {
let identifier = self.parse_identifier()?;
match self.token_parser.next_token(NextTokenContext::new())? {
Some(Token::Colon) => self.token_parser.consume_lexeme(),
_ => {
return Ok(tree::FunctionCall {
function: identifier,
args: tree::Values(vec![]),
});
}
};
let values = self.parse_values(false)?;
Ok(tree::FunctionCall {
function: identifier,
args: values,
})
}
fn parse_identifier(&mut self) -> Result<tree::Identifier> {
match self.token_parser.next_token(NextTokenContext::new())? {
Some(Token::Identifier) => Ok(tree::Identifier(
self.token_parser.consume_lexeme().to_owned(),
)),
_ => Err(self.unexpected_token_error_with_expected_hint("identifier")),
}
}
fn parse_values(&mut self, none_allowed: bool) -> Result<tree::Values> {
let mut values = Vec::new();
loop {
match self.token_parser.next_token(NextTokenContext::new())? {
None => break,
Some(Token::Comma) => {
self.token_parser.consume_lexeme();
}
_ => match self.parse_value()? {
Some(value) => values.push(value),
None => break,
},
}
}
if values.is_empty() && !none_allowed {
Err(general_error(format!(
"expected at least one value at index {}, found 0",
self.token_parser.token_index(),
)))
} else {
Ok(tree::Values(values))
}
}
fn parse_value(&mut self) -> Result<Option<tree::Value>> {
match self.token_parser.next_token(NextTokenContext::new())? {
Some(Token::FalseKeyword | Token::TrueKeyword) => {
Ok(Some(tree::Value::BoolLiteral(self.parse_bool_literal()?)))
}
Some(Token::UndefinedKeyword) => {
self.parse_undefined_literal()?;
Ok(Some(tree::Value::UndefinedLiteral))
}
Some(Token::Integer | Token::Plus | Token::Minus) => Ok(Some(
tree::Value::NumberLiteral(self.parse_number_literal()?),
)),
Some(Token::String | Token::Identifier) => Ok(Some(tree::Value::StringLiteral(
self.parse_string_literal()?,
))),
Some(Token::LeftBracket) => Ok(Some(tree::Value::List(self.parse_list()?))),
Some(Token::VariableStart) => Ok(Some(tree::Value::Var(self.parse_var()?))),
Some(Token::ExprKeyword) => Ok(Some(tree::Value::ValueExpr(self.parse_value_expr()?))),
Some(Token::FuncCallKeyword) => Ok(Some(tree::Value::ValueFunctionCall(
self.parse_value_func_call()?,
))),
Some(Token::AssignKeyword) => Ok(Some(tree::Value::ValueAssignment(
self.parse_value_assignment()?,
))),
Some(Token::StrKeyword) => Ok(Some(tree::Value::FormattedString(
self.parse_formatted_string()?,
))),
_ => Ok(None),
}
}
fn parse_bool_literal(&mut self) -> Result<tree::BoolLiteral> {
let result = match self.token_parser.next_token(NextTokenContext::new())? {
Some(Token::FalseKeyword) => {
self.token_parser.consume_lexeme();
tree::BoolLiteral(false)
}
Some(Token::TrueKeyword) => {
self.token_parser.consume_lexeme();
tree::BoolLiteral(true)
}
_ => return Err(self.unexpected_token_error_with_expected_hint("bool")),
};
Ok(result)
}
fn parse_undefined_literal(&mut self) -> Result<()> {
match self.token_parser.next_token(NextTokenContext::new())? {
Some(Token::UndefinedKeyword) => {
self.token_parser.consume_lexeme();
}
_ => return Err(self.unexpected_token_error_with_expected_hint("undefined")),
};
Ok(())
}
fn parse_number_literal(&mut self) -> Result<tree::NumberLiteral> {
let (signed, negative) = match self.token_parser.next_token(NextTokenContext::new())? {
Some(Token::Plus) => {
self.token_parser.consume_lexeme();
(true, false)
}
Some(Token::Minus) => {
self.token_parser.consume_lexeme();
(true, true)
}
_ => (false, false),
};
let mut numerator = match self.token_parser.next_token(NextTokenContext::new())? {
Some(Token::Integer) => self
.token_parser
.consume_lexeme()
.parse::<i64>()
.wrap_error_with_format(format_args!(
"number token \"{}\" could not parsed to integer",
self.token_parser.lexeme()
))?,
_ => return Err(self.unexpected_token_error_with_expected_hint("integer")),
};
let denominator = match self.token_parser.next_token(NextTokenContext::new())? {
Some(Token::ForwardSlash) => {
self.token_parser.consume_lexeme();
match self.token_parser.next_token(NextTokenContext::new())? {
Some(Token::Integer) => self
.token_parser
.consume_lexeme()
.parse::<i64>()
.wrap_error_with_format(format_args!(
"number token \"{}\" could not parsed to integer",
self.token_parser.lexeme()
))?,
_ => return Err(self.unexpected_token_error_with_expected_hint("integer")),
}
}
_ => 1,
};
if negative {
numerator = -numerator;
}
if signed || numerator < 0 {
Ok(tree::NumberLiteral::Signed(
Fraction::new(numerator, denominator).simplify(),
))
} else {
Ok(tree::NumberLiteral::Unsigned(
Fraction::new(numerator as u64, denominator as u64).simplify(),
))
}
}
fn parse_string_literal(&mut self) -> Result<tree::StringLiteral> {
match self.token_parser.next_token(NextTokenContext::new())? {
Some(Token::Identifier) => Ok(tree::StringLiteral(
self.token_parser.consume_lexeme().to_owned(),
)),
Some(Token::String) => Ok(tree::StringLiteral(
self.token_parser
.consume_string()
.wrap_expectation("string token did not produce a string")?,
)),
_ => return Err(self.unexpected_token_error_with_expected_hint("string")),
}
}
fn parse_list(&mut self) -> Result<tree::List> {
match self.token_parser.next_token(NextTokenContext::new())? {
Some(Token::LeftBracket) => self.token_parser.consume_lexeme(),
_ => return Err(self.unexpected_token_error_with_expected_hint("[")),
};
let values = self.parse_values(true)?;
match self.token_parser.next_token(NextTokenContext::new())? {
Some(Token::RightBracket) => self.token_parser.consume_lexeme(),
_ => return Err(self.unexpected_token_error_with_expected_hint("]")),
};
Ok(tree::List(values))
}
fn parse_var(&mut self) -> Result<tree::Var> {
match self.token_parser.next_token(NextTokenContext::new())? {
Some(Token::VariableStart) => self.token_parser.consume_lexeme(),
_ => return Err(self.unexpected_token_error_with_expected_hint("$")),
};
let name = self.parse_identifier()?;
let mut member_access = Vec::new();
while let Some(Token::Dot) = self.token_parser.next_token(NextTokenContext::new())? {
self.token_parser.consume_lexeme();
member_access.push(self.parse_identifier()?);
}
Ok(tree::Var {
name,
member_access,
})
}
fn parse_value_expr(&mut self) -> Result<tree::ValueExpr> {
match self.token_parser.next_token(NextTokenContext::new())? {
Some(Token::ExprKeyword) => self.token_parser.consume_lexeme(),
_ => return Err(self.unexpected_token_error_with_expected_hint("expr")),
};
match self.token_parser.next_token(NextTokenContext::new())? {
Some(Token::LeftParenthesis) => self.token_parser.consume_lexeme(),
_ => return Err(self.unexpected_token_error_with_expected_hint("(")),
};
self.down_one_level()?;
let expr = self.parse_expr()?;
self.up_one_level();
match self.token_parser.next_token(NextTokenContext::new())? {
Some(Token::RightParenthesis) => self.token_parser.consume_lexeme(),
_ => return Err(self.unexpected_token_error_with_expected_hint(")")),
};
Ok(tree::ValueExpr(Box::new(expr)))
}
fn parse_value_func_call(&mut self) -> Result<tree::ValueFunctionCall> {
match self.token_parser.next_token(NextTokenContext::new())? {
Some(Token::FuncCallKeyword) => self.token_parser.consume_lexeme(),
_ => return Err(self.unexpected_token_error_with_expected_hint("func_call")),
};
match self.token_parser.next_token(NextTokenContext::new())? {
Some(Token::LeftParenthesis) => self.token_parser.consume_lexeme(),
_ => return Err(self.unexpected_token_error_with_expected_hint("(")),
};
self.down_one_level()?;
let function_call = self.parse_function_call()?;
self.up_one_level();
match self.token_parser.next_token(NextTokenContext::new())? {
Some(Token::RightParenthesis) => self.token_parser.consume_lexeme(),
_ => return Err(self.unexpected_token_error_with_expected_hint(")")),
};
Ok(tree::ValueFunctionCall(function_call))
}
fn parse_value_assignment(&mut self) -> Result<tree::ValueAssignment> {
match self.token_parser.next_token(NextTokenContext::new())? {
Some(Token::AssignKeyword) => self.token_parser.consume_lexeme(),
_ => return Err(self.unexpected_token_error_with_expected_hint("assign")),
};
match self.token_parser.next_token(NextTokenContext::new())? {
Some(Token::LeftParenthesis) => self.token_parser.consume_lexeme(),
_ => return Err(self.unexpected_token_error_with_expected_hint("(")),
};
self.down_one_level()?;
let assignment = self.parse_assignment()?;
self.up_one_level();
match self.token_parser.next_token(NextTokenContext::new())? {
Some(Token::RightParenthesis) => self.token_parser.consume_lexeme(),
_ => return Err(self.unexpected_token_error_with_expected_hint(")")),
};
Ok(tree::ValueAssignment(Box::new(assignment)))
}
fn parse_formatted_string(&mut self) -> Result<tree::FormattedString> {
match self.token_parser.next_token(NextTokenContext::new())? {
Some(Token::StrKeyword) => self.token_parser.consume_lexeme(),
_ => return Err(self.unexpected_token_error_with_expected_hint("str")),
};
match self.token_parser.next_token(NextTokenContext::new())? {
Some(Token::LeftParenthesis) => self.token_parser.consume_lexeme(),
_ => return Err(self.unexpected_token_error_with_expected_hint("(")),
};
let template = self.parse_string_literal()?;
let args = match self.token_parser.next_token(NextTokenContext::new())? {
Some(Token::Comma) => {
self.token_parser.consume_lexeme();
self.parse_values(false)?
}
_ => tree::Values(vec![]),
};
match self.token_parser.next_token(NextTokenContext::new())? {
Some(Token::RightParenthesis) => self.token_parser.consume_lexeme(),
_ => return Err(self.unexpected_token_error_with_expected_hint(")")),
};
Ok(tree::FormattedString { template, args })
}
fn parse_expr(&mut self) -> Result<tree::Expr> {
self.parse_expr_prec_9()
}
fn parse_expr_prec_9(&mut self) -> Result<tree::Expr> {
let lhs = self.parse_expr_prec_8()?;
let mut rhs = Vec::new();
loop {
let op = match self.token_parser.next_token(NextTokenContext::new())? {
Some(Token::OrKeyword) => {
self.token_parser.consume_lexeme();
tree::Operator::Or
}
_ => break,
};
let expr = self.parse_expr_prec_8()?;
rhs.push(tree::BinaryExprRhs {
op,
expr: Box::new(expr),
});
}
if rhs.is_empty() {
Ok(lhs)
} else {
Ok(tree::Expr::BinaryExpr(tree::BinaryExpr {
lhs: Box::new(lhs),
rhs,
}))
}
}
fn parse_expr_prec_8(&mut self) -> Result<tree::Expr> {
let lhs = self.parse_expr_prec_7()?;
let mut rhs = Vec::new();
loop {
let op = match self.token_parser.next_token(NextTokenContext::new())? {
Some(Token::AndKeyword) => {
self.token_parser.consume_lexeme();
tree::Operator::And
}
_ => break,
};
let expr = self.parse_expr_prec_7()?;
rhs.push(tree::BinaryExprRhs {
op,
expr: Box::new(expr),
});
}
if rhs.is_empty() {
Ok(lhs)
} else {
Ok(tree::Expr::BinaryExpr(tree::BinaryExpr {
lhs: Box::new(lhs),
rhs,
}))
}
}
fn parse_expr_prec_7(&mut self) -> Result<tree::Expr> {
let lhs = self.parse_expr_prec_6()?;
let mut rhs = Vec::new();
loop {
let op = match self.token_parser.next_token(NextTokenContext::new())? {
Some(Token::Equal) => {
self.token_parser.consume_lexeme();
tree::Operator::Equal
}
Some(Token::NotEqual) => {
self.token_parser.consume_lexeme();
tree::Operator::NotEqual
}
_ => break,
};
let expr = self.parse_expr_prec_6()?;
rhs.push(tree::BinaryExprRhs {
op,
expr: Box::new(expr),
});
}
if rhs.is_empty() {
Ok(lhs)
} else {
Ok(tree::Expr::BinaryExpr(tree::BinaryExpr {
lhs: Box::new(lhs),
rhs,
}))
}
}
fn parse_expr_prec_6(&mut self) -> Result<tree::Expr> {
let lhs = self.parse_expr_prec_5()?;
let mut rhs = Vec::new();
loop {
let op = match self.token_parser.next_token(NextTokenContext::new())? {
Some(Token::LessThan) => {
self.token_parser.consume_lexeme();
tree::Operator::LessThan
}
Some(Token::LessThanOrEqual) => {
self.token_parser.consume_lexeme();
tree::Operator::LessThanOrEqual
}
Some(Token::GreaterThan) => {
self.token_parser.consume_lexeme();
tree::Operator::GreaterThan
}
Some(Token::GreaterThanOrEqual) => {
self.token_parser.consume_lexeme();
tree::Operator::GreaterThanOrEqual
}
Some(Token::HasKeyword) => {
self.token_parser.consume_lexeme();
tree::Operator::Has
}
Some(Token::HasAnyKeyword) => {
self.token_parser.consume_lexeme();
tree::Operator::HasAny
}
_ => break,
};
let expr = self.parse_expr_prec_5()?;
rhs.push(tree::BinaryExprRhs {
op,
expr: Box::new(expr),
});
}
if rhs.is_empty() {
Ok(lhs)
} else {
Ok(tree::Expr::BinaryExpr(tree::BinaryExpr {
lhs: Box::new(lhs),
rhs,
}))
}
}
fn parse_expr_prec_5(&mut self) -> Result<tree::Expr> {
let lhs = self.parse_expr_prec_4()?;
let mut rhs = Vec::new();
loop {
let op = match self.token_parser.next_token(NextTokenContext::new())? {
Some(Token::Plus) => {
self.token_parser.consume_lexeme();
tree::Operator::Add
}
Some(Token::Minus) => {
self.token_parser.consume_lexeme();
tree::Operator::Subtract
}
_ => break,
};
let expr = self.parse_expr_prec_4()?;
rhs.push(tree::BinaryExprRhs {
op,
expr: Box::new(expr),
});
}
if rhs.is_empty() {
Ok(lhs)
} else {
Ok(tree::Expr::BinaryExpr(tree::BinaryExpr {
lhs: Box::new(lhs),
rhs,
}))
}
}
fn parse_expr_prec_4(&mut self) -> Result<tree::Expr> {
let lhs = self.parse_expr_prec_3()?;
let mut rhs = Vec::new();
loop {
let op = match self.token_parser.next_token(NextTokenContext::new())? {
Some(Token::Asterisk) => {
self.token_parser.consume_lexeme();
tree::Operator::Multiply
}
Some(Token::ForwardSlash) => {
self.token_parser.consume_lexeme();
tree::Operator::Divide
}
Some(Token::Percent) => {
self.token_parser.consume_lexeme();
tree::Operator::Modulo
}
_ => break,
};
let expr = self.parse_expr_prec_3()?;
rhs.push(tree::BinaryExprRhs {
op,
expr: Box::new(expr),
});
}
if rhs.is_empty() {
Ok(lhs)
} else {
Ok(tree::Expr::BinaryExpr(tree::BinaryExpr {
lhs: Box::new(lhs),
rhs,
}))
}
}
fn parse_expr_prec_3(&mut self) -> Result<tree::Expr> {
let lhs = self.parse_expr_prec_2()?;
let mut rhs = Vec::new();
loop {
let op = match self.token_parser.next_token(NextTokenContext::new())? {
Some(Token::Caret) => {
self.token_parser.consume_lexeme();
tree::Operator::Exponent
}
_ => break,
};
let expr = self.parse_expr_prec_2()?;
rhs.push(tree::BinaryExprRhs {
op,
expr: Box::new(expr),
});
}
if rhs.is_empty() {
Ok(lhs)
} else {
Ok(tree::Expr::BinaryExpr(tree::BinaryExpr {
lhs: Box::new(lhs),
rhs,
}))
}
}
fn parse_expr_prec_2(&mut self) -> Result<tree::Expr> {
let mut ops = Vec::new();
loop {
match self.token_parser.next_token(NextTokenContext::new())? {
Some(Token::Exclamation) => {
self.token_parser.consume_lexeme();
ops.push(tree::Operator::Not);
}
Some(Token::Plus) => {
self.token_parser.consume_lexeme();
ops.push(tree::Operator::UnaryPlus);
}
_ => break,
}
}
if ops.is_empty() {
self.parse_expr_prec_1()
} else {
let expr = self.parse_expr_prec_1()?;
Ok(tree::Expr::PrefixUnaryExpr(tree::PrefixUnaryExpr {
ops,
expr: Box::new(expr),
}))
}
}
fn parse_expr_prec_1(&mut self) -> Result<tree::Expr> {
match self.token_parser.next_token(NextTokenContext::new())? {
Some(Token::LeftParenthesis) => self.parse_parenthesis_expr(),
_ => match self.parse_value()? {
None => Err(self.unexpected_token_error_with_expected_hint("value")),
Some(value) => Ok(tree::Expr::Value(value)),
},
}
}
fn parse_parenthesis_expr(&mut self) -> Result<tree::Expr> {
match self.token_parser.next_token(NextTokenContext::new())? {
Some(Token::LeftParenthesis) => self.token_parser.consume_lexeme(),
_ => return Err(self.unexpected_token_error_with_expected_hint("(")),
};
let expr = self.parse_expr()?;
match self.token_parser.next_token(NextTokenContext::new())? {
Some(Token::RightParenthesis) => self.token_parser.consume_lexeme(),
_ => return Err(self.unexpected_token_error_with_expected_hint(")")),
};
Ok(expr)
}
}
#[cfg(test)]
mod statement_parser_test {
use alloc::{
borrow::ToOwned,
boxed::Box,
format,
vec,
};
use battler_data::Fraction;
use pretty_assertions::assert_eq;
use super::StatementParser;
use crate::effect::fxlang::tree::{
self,
BinaryExprRhs,
};
#[test]
fn parses_empty_statement() {
assert_eq!(
StatementParser::new("").parse().unwrap(),
tree::Statement::Empty
);
}
#[test]
fn parses_line_comment() {
assert_eq!(
StatementParser::new("# This is a comment! ### 12345 'testffff")
.parse()
.unwrap(),
tree::Statement::Empty
);
}
#[test]
fn fails_invalid_function_identifier() {
assert_matches::assert_matches!(
StatementParser::new("23456").parse(),
Err(err) => assert_eq!(format!("{err:#}"), "unexpected token at index 0: 23456")
);
}
#[test]
fn parses_basic_function_call() {
assert_eq!(
StatementParser::new("display").parse().unwrap(),
tree::Statement::FunctionCall(tree::FunctionCall {
function: tree::Identifier("display".to_owned()),
args: tree::Values(vec![]),
})
);
}
#[test]
fn parses_bool_literals() {
assert_eq!(
StatementParser::new("test: true false true")
.parse()
.unwrap(),
tree::Statement::FunctionCall(tree::FunctionCall {
function: tree::Identifier("test".to_owned()),
args: tree::Values(vec![
tree::Value::BoolLiteral(tree::BoolLiteral(true)),
tree::Value::BoolLiteral(tree::BoolLiteral(false)),
tree::Value::BoolLiteral(tree::BoolLiteral(true)),
])
})
);
}
#[test]
fn parses_number_literals() {
assert_eq!(
StatementParser::new("test: 1 -3 55/100 -1/2 +23456542 - 1 / 3")
.parse()
.unwrap(),
tree::Statement::FunctionCall(tree::FunctionCall {
function: tree::Identifier("test".to_owned()),
args: tree::Values(vec![
tree::Value::NumberLiteral(tree::NumberLiteral::Unsigned(1u64.into())),
tree::Value::NumberLiteral(tree::NumberLiteral::Signed((-3i64).into())),
tree::Value::NumberLiteral(tree::NumberLiteral::Unsigned(Fraction::new(
11, 20
))),
tree::Value::NumberLiteral(tree::NumberLiteral::Signed(Fraction::new(-1, 2))),
tree::Value::NumberLiteral(tree::NumberLiteral::Signed(23456542i64.into())),
tree::Value::NumberLiteral(tree::NumberLiteral::Signed(Fraction::new(-1, 3))),
])
})
);
}
#[test]
fn parses_string_literals() {
assert_eq!(
StatementParser::new("strings: 'hello world!' 'another' 'it\\'s magnitude 7!' 'complex \\\\ backslashing \\\\\\\\ \\n :)'")
.parse().unwrap(),
tree::Statement::FunctionCall(tree::FunctionCall {
function: tree::Identifier("strings".to_owned()),
args: tree::Values(vec![
tree::Value::StringLiteral(tree::StringLiteral("hello world!".to_owned())),
tree::Value::StringLiteral(tree::StringLiteral("another".to_owned())),
tree::Value::StringLiteral(tree::StringLiteral("it's magnitude 7!".to_owned())),
tree::Value::StringLiteral(tree::StringLiteral("complex \\ backslashing \\\\ \n :)".to_owned())),
])
})
);
}
#[test]
fn parses_lists() {
assert_eq!(
StatementParser::new("lists: [1/40] [1, 2, 3] ['a' b false] []")
.parse()
.unwrap(),
tree::Statement::FunctionCall(tree::FunctionCall {
function: tree::Identifier("lists".to_owned()),
args: tree::Values(vec![
tree::Value::List(tree::List(tree::Values(vec![tree::Value::NumberLiteral(
tree::NumberLiteral::Unsigned(Fraction::new(1, 40))
)]))),
tree::Value::List(tree::List(tree::Values(vec![
tree::Value::NumberLiteral(tree::NumberLiteral::Unsigned(1u64.into())),
tree::Value::NumberLiteral(tree::NumberLiteral::Unsigned(2u64.into())),
tree::Value::NumberLiteral(tree::NumberLiteral::Unsigned(3u64.into())),
]))),
tree::Value::List(tree::List(tree::Values(vec![
tree::Value::StringLiteral(tree::StringLiteral("a".to_owned())),
tree::Value::StringLiteral(tree::StringLiteral("b".to_owned())),
tree::Value::BoolLiteral(tree::BoolLiteral(false)),
]))),
tree::Value::List(tree::List(tree::Values(vec![]))),
])
})
);
}
#[test]
fn parses_vars() {
assert_eq!(
StatementParser::new(
"vars: $source $target $source.hp $a.b.c.d9.e $ident_with-000more_chars123 $move.effect_state.infiltrates"
)
.parse().unwrap(),
tree::Statement::FunctionCall(tree::FunctionCall {
function: tree::Identifier("vars".to_owned()),
args: tree::Values(vec![
tree::Value::Var(tree::Var {
name: tree::Identifier("source".to_owned()),
member_access: vec![],
}),
tree::Value::Var(tree::Var {
name: tree::Identifier("target".to_owned()),
member_access: vec![],
}),
tree::Value::Var(tree::Var {
name: tree::Identifier("source".to_owned()),
member_access: vec![tree::Identifier("hp".to_owned())],
}),
tree::Value::Var(tree::Var {
name: tree::Identifier("a".to_owned()),
member_access: vec![
tree::Identifier("b".to_owned()),
tree::Identifier("c".to_owned()),
tree::Identifier("d9".to_owned()),
tree::Identifier("e".to_owned()),
],
}),
tree::Value::Var(tree::Var {
name: tree::Identifier("ident_with-000more_chars123".to_owned()),
member_access: vec![],
}),
tree::Value::Var(tree::Var {
name: tree::Identifier("move".to_owned()),
member_access: vec![
tree::Identifier("effect_state".to_owned()),
tree::Identifier("infiltrates".to_owned()),],
}),
])
})
);
}
#[test]
fn parses_nested_function_calls() {
assert_eq!(
StatementParser::new("fn: $a func_call(rand: 1 5) $b func_call(other)")
.parse()
.unwrap(),
tree::Statement::FunctionCall(tree::FunctionCall {
function: tree::Identifier("fn".to_owned()),
args: tree::Values(vec![
tree::Value::Var(tree::Var {
name: tree::Identifier("a".to_owned()),
member_access: vec![],
}),
tree::Value::ValueFunctionCall(tree::ValueFunctionCall(tree::FunctionCall {
function: tree::Identifier("rand".to_owned()),
args: tree::Values(vec![
tree::Value::NumberLiteral(tree::NumberLiteral::Unsigned(1u64.into())),
tree::Value::NumberLiteral(tree::NumberLiteral::Unsigned(5u64.into())),
]),
})),
tree::Value::Var(tree::Var {
name: tree::Identifier("b".to_owned()),
member_access: vec![],
}),
tree::Value::ValueFunctionCall(tree::ValueFunctionCall(tree::FunctionCall {
function: tree::Identifier("other".to_owned()),
args: tree::Values(vec![]),
})),
])
})
);
}
#[test]
fn fails_on_max_depth_exceeded() {
assert_matches::assert_matches!(
StatementParser::new(
"a:func_call(b:func_call(c:func_call(d:func_call(e:func_call(f)))))",
)
.parse(),
Err(err) => assert_eq!(format!("{err:#}"), "stack overflow: exceeded maximum depth of 5")
);
}
#[test]
fn parses_simple_nested_exprs() {
assert_eq!(
StatementParser::new("exprs: expr(1 + 1) expr($list has ability)")
.parse()
.unwrap(),
tree::Statement::FunctionCall(tree::FunctionCall {
function: tree::Identifier("exprs".to_owned()),
args: tree::Values(vec![
tree::Value::ValueExpr(tree::ValueExpr(Box::new(tree::Expr::BinaryExpr(
tree::BinaryExpr {
lhs: Box::new(tree::Expr::Value(tree::Value::NumberLiteral(
tree::NumberLiteral::Unsigned(1u64.into()),
))),
rhs: vec![tree::BinaryExprRhs {
op: tree::Operator::Add,
expr: Box::new(tree::Expr::Value(tree::Value::NumberLiteral(
tree::NumberLiteral::Unsigned(1u64.into()),
))),
}],
}
)))),
tree::Value::ValueExpr(tree::ValueExpr(Box::new(tree::Expr::BinaryExpr(
tree::BinaryExpr {
lhs: Box::new(tree::Expr::Value(tree::Value::Var(tree::Var {
name: tree::Identifier("list".to_owned()),
member_access: vec![],
}))),
rhs: vec![tree::BinaryExprRhs {
op: tree::Operator::Has,
expr: Box::new(tree::Expr::Value(tree::Value::StringLiteral(
tree::StringLiteral("ability".to_owned()),
))),
}],
}
)))),
])
})
);
}
fn string_value_expr(s: &str) -> tree::Expr {
tree::Expr::Value(tree::Value::StringLiteral(tree::StringLiteral(
s.to_owned(),
)))
}
#[test]
#[rustfmt::skip]
fn parses_exprs_with_operator_precedence() {
assert_eq!(
StatementParser::new(
"exprs: expr(!a ^ af * b / c % d + e - f < g <= h > i >= j has k hasany l == m != n and o or p or q and r != s == t hasany u has v >= w > x <= y < z - aa + ab % ac / ad * ag ^ !ae)"
)
.parse().unwrap(),
tree::Statement::FunctionCall(tree::FunctionCall {
function: tree::Identifier("exprs".to_owned()),
args: tree::Values(vec![tree::Value::ValueExpr(tree::ValueExpr(
Box::new(tree::Expr::BinaryExpr(tree::BinaryExpr {
lhs: Box::new(tree::Expr::BinaryExpr(tree::BinaryExpr {
lhs: Box::new(tree::Expr::BinaryExpr(tree::BinaryExpr {
lhs: Box::new(tree::Expr::BinaryExpr(tree::BinaryExpr {
lhs: Box::new(tree::Expr::BinaryExpr(tree::BinaryExpr {
lhs: Box::new(tree::Expr::BinaryExpr(tree::BinaryExpr {
lhs: Box::new(tree::Expr::BinaryExpr(tree::BinaryExpr {
lhs: Box::new(tree::Expr::PrefixUnaryExpr(
tree::PrefixUnaryExpr {
ops: vec![tree::Operator::Not],
expr: Box::new(string_value_expr("a")),
}
)),
rhs: vec![
tree::BinaryExprRhs {
op: tree::Operator::Exponent,
expr: Box::new(string_value_expr("af"))
}
],
})),
rhs: vec![
tree::BinaryExprRhs {
op: tree::Operator::Multiply,
expr: Box::new(string_value_expr("b")),
},
tree::BinaryExprRhs {
op: tree::Operator::Divide,
expr: Box::new(string_value_expr("c")),
},
tree::BinaryExprRhs {
op: tree::Operator::Modulo,
expr: Box::new(string_value_expr("d")),
},
],
})),
rhs: vec![
tree::BinaryExprRhs {
op: tree::Operator::Add,
expr: Box::new(string_value_expr("e")),
},
tree::BinaryExprRhs {
op: tree::Operator::Subtract,
expr: Box::new(string_value_expr("f")),
},
],
})),
rhs: vec![
tree::BinaryExprRhs {
op: tree::Operator::LessThan,
expr: Box::new(string_value_expr("g")),
},
tree::BinaryExprRhs {
op: tree::Operator::LessThanOrEqual,
expr: Box::new(string_value_expr("h")),
},
tree::BinaryExprRhs {
op: tree::Operator::GreaterThan,
expr: Box::new(string_value_expr("i")),
},
tree::BinaryExprRhs {
op: tree::Operator::GreaterThanOrEqual,
expr: Box::new(string_value_expr("j")),
},
tree::BinaryExprRhs {
op: tree::Operator::Has,
expr: Box::new(string_value_expr("k")),
},
tree::BinaryExprRhs {
op: tree::Operator::HasAny,
expr: Box::new(string_value_expr("l")),
},
],
})),
rhs: vec![
tree::BinaryExprRhs {
op: tree::Operator::Equal,
expr: Box::new(string_value_expr("m")),
},
tree::BinaryExprRhs {
op: tree::Operator::NotEqual,
expr: Box::new(string_value_expr("n")),
},
],
})),
rhs: vec![
tree::BinaryExprRhs {
op: tree::Operator::And,
expr: Box::new(string_value_expr("o")),
},
],
})),
rhs: vec![
tree::BinaryExprRhs {
op: tree::Operator::Or,
expr: Box::new(string_value_expr("p")),
},
tree::BinaryExprRhs {
op: tree::Operator::Or,
expr: Box::new(tree::Expr::BinaryExpr(tree::BinaryExpr {
lhs: Box::new(string_value_expr("q")),
rhs: vec![
tree::BinaryExprRhs {
op: tree::Operator::And,
expr: Box::new(tree::Expr::BinaryExpr(tree::BinaryExpr {
lhs: Box::new(string_value_expr("r")),
rhs: vec![
tree::BinaryExprRhs {
op: tree::Operator::NotEqual,
expr: Box::new(string_value_expr("s")),
},
tree::BinaryExprRhs {
op: tree::Operator::Equal,
expr: Box::new(tree::Expr::BinaryExpr(tree::BinaryExpr {
lhs: Box::new(string_value_expr("t")),
rhs: vec![
tree::BinaryExprRhs {
op: tree::Operator::HasAny,
expr: Box::new(string_value_expr("u")),
},
tree::BinaryExprRhs {
op: tree::Operator::Has,
expr: Box::new(string_value_expr("v")),
},
tree::BinaryExprRhs {
op: tree::Operator::GreaterThanOrEqual,
expr: Box::new(string_value_expr("w")),
},
tree::BinaryExprRhs {
op: tree::Operator::GreaterThan,
expr: Box::new(string_value_expr("x")),
},
tree::BinaryExprRhs {
op: tree::Operator::LessThanOrEqual,
expr: Box::new(string_value_expr("y")),
},
tree::BinaryExprRhs {
op: tree::Operator::LessThan,
expr: Box::new(tree::Expr::BinaryExpr(tree::BinaryExpr {
lhs: Box::new(string_value_expr("z")),
rhs: vec![
tree::BinaryExprRhs {
op: tree::Operator::Subtract,
expr: Box::new(string_value_expr("aa")),
},
tree::BinaryExprRhs {
op: tree::Operator::Add,
expr: Box::new(tree::Expr::BinaryExpr(tree::BinaryExpr {
lhs: Box::new(string_value_expr("ab")),
rhs: vec![
tree::BinaryExprRhs {
op: tree::Operator::Modulo,
expr: Box::new(string_value_expr("ac")),
},
tree::BinaryExprRhs {
op: tree::Operator::Divide,
expr: Box::new(string_value_expr("ad")),
},
tree::BinaryExprRhs {
op: tree::Operator::Multiply,
expr: Box::new(tree::Expr::BinaryExpr(tree::BinaryExpr {
lhs: Box::new(string_value_expr("ag")),
rhs: vec![
tree::BinaryExprRhs {
op: tree::Operator::Exponent,
expr: Box::new(tree::Expr::PrefixUnaryExpr(tree::PrefixUnaryExpr {
ops: vec![tree::Operator::Not],
expr: Box::new(string_value_expr("ae")),
})),
}
]
})),
},
],
})),
},
],
})),
},
],
})),
},
],
})),
},
],
})),
},
],
})),
))])
})
);
}
#[test]
fn parenthesis_override_operator_precedence() {
assert_eq!(
StatementParser::new("exprs: expr((a + b) * c)")
.parse()
.unwrap(),
tree::Statement::FunctionCall(tree::FunctionCall {
function: tree::Identifier("exprs".to_owned()),
args: tree::Values(vec![tree::Value::ValueExpr(tree::ValueExpr(Box::new(
tree::Expr::BinaryExpr(tree::BinaryExpr {
lhs: Box::new(tree::Expr::BinaryExpr(tree::BinaryExpr {
lhs: Box::new(string_value_expr("a")),
rhs: vec![tree::BinaryExprRhs {
op: tree::Operator::Add,
expr: Box::new(string_value_expr("b")),
}],
})),
rhs: vec![BinaryExprRhs {
op: tree::Operator::Multiply,
expr: Box::new(string_value_expr("c")),
}],
})
)))])
})
);
}
#[test]
fn parses_value_assignment() {
assert_eq!(
StatementParser::new("$var = value").parse().unwrap(),
tree::Statement::Assignment(tree::Assignment {
lhs: tree::Var {
name: tree::Identifier("var".to_owned()),
member_access: vec![],
},
rhs: tree::Expr::Value(tree::Value::StringLiteral(tree::StringLiteral(
"value".to_owned()
))),
})
);
}
#[test]
fn parses_copy_assignment() {
assert_eq!(
StatementParser::new("$var = $other.prop").parse().unwrap(),
tree::Statement::Assignment(tree::Assignment {
lhs: tree::Var {
name: tree::Identifier("var".to_owned()),
member_access: vec![],
},
rhs: tree::Expr::Value(tree::Value::Var(tree::Var {
name: tree::Identifier("other".to_owned()),
member_access: vec![tree::Identifier("prop".to_owned())]
})),
})
);
}
#[test]
fn parses_expr_assignment() {
assert_eq!(
StatementParser::new("$var = 2 * func_call(rand: 1 5)")
.parse()
.unwrap(),
tree::Statement::Assignment(tree::Assignment {
lhs: tree::Var {
name: tree::Identifier("var".to_owned()),
member_access: vec![],
},
rhs: tree::Expr::BinaryExpr(tree::BinaryExpr {
lhs: Box::new(tree::Expr::Value(tree::Value::NumberLiteral(
tree::NumberLiteral::Unsigned(2u64.into())
))),
rhs: vec![tree::BinaryExprRhs {
op: tree::Operator::Multiply,
expr: Box::new(tree::Expr::Value(tree::Value::ValueFunctionCall(
tree::ValueFunctionCall(tree::FunctionCall {
function: tree::Identifier("rand".to_owned()),
args: tree::Values(vec![
tree::Value::NumberLiteral(tree::NumberLiteral::Unsigned(
1u64.into()
)),
tree::Value::NumberLiteral(tree::NumberLiteral::Unsigned(
5u64.into()
)),
])
})
)))
}]
})
})
);
}
#[test]
fn parses_if_statement() {
assert_eq!(
StatementParser::new("if $var == 2:").parse().unwrap(),
tree::Statement::IfStatement(tree::IfStatement(tree::Expr::BinaryExpr(
tree::BinaryExpr {
lhs: Box::new(tree::Expr::Value(tree::Value::Var(tree::Var {
name: tree::Identifier("var".to_owned()),
member_access: vec![],
}))),
rhs: vec![tree::BinaryExprRhs {
op: tree::Operator::Equal,
expr: Box::new(tree::Expr::Value(tree::Value::NumberLiteral(
tree::NumberLiteral::Unsigned(2u64.into())
)))
}]
}
)))
);
}
#[test]
fn parses_else_statement() {
assert_eq!(
StatementParser::new("else:").parse().unwrap(),
tree::Statement::ElseIfStatement(tree::ElseIfStatement(None))
);
}
#[test]
fn parses_else_if_statement() {
assert_eq!(
StatementParser::new("else if $val < 10:").parse().unwrap(),
tree::Statement::ElseIfStatement(tree::ElseIfStatement(Some(tree::IfStatement(
tree::Expr::BinaryExpr(tree::BinaryExpr {
lhs: Box::new(tree::Expr::Value(tree::Value::Var(tree::Var {
name: tree::Identifier("val".to_owned()),
member_access: vec![],
}))),
rhs: vec![tree::BinaryExprRhs {
op: tree::Operator::LessThan,
expr: Box::new(tree::Expr::Value(tree::Value::NumberLiteral(
tree::NumberLiteral::Unsigned(10u64.into())
)))
}]
})
))))
);
}
#[test]
fn parses_foreach_statement_with_var() {
assert_eq!(
StatementParser::new("foreach $item in $list:")
.parse()
.unwrap(),
tree::Statement::ForEachStatement(tree::ForEachStatement {
var: tree::Var {
name: tree::Identifier("item".to_owned()),
member_access: vec![],
},
range: tree::Value::Var(tree::Var {
name: tree::Identifier("list".to_owned()),
member_access: vec![],
}),
})
);
}
#[test]
fn parses_foreach_statement_with_list() {
assert_eq!(
StatementParser::new("foreach $mon in [bulbasaur, charmander, squirtle]:")
.parse()
.unwrap(),
tree::Statement::ForEachStatement(tree::ForEachStatement {
var: tree::Var {
name: tree::Identifier("mon".to_owned()),
member_access: vec![],
},
range: tree::Value::List(tree::List(tree::Values(vec![
tree::Value::StringLiteral(tree::StringLiteral("bulbasaur".to_owned())),
tree::Value::StringLiteral(tree::StringLiteral("charmander".to_owned())),
tree::Value::StringLiteral(tree::StringLiteral("squirtle".to_owned())),
]))),
})
);
}
#[test]
fn parses_foreach_statement_with_generator() {
assert_eq!(
StatementParser::new("foreach $mon in func_call(range: 0 10 2):")
.parse()
.unwrap(),
tree::Statement::ForEachStatement(tree::ForEachStatement {
var: tree::Var {
name: tree::Identifier("mon".to_owned()),
member_access: vec![],
},
range: tree::Value::ValueFunctionCall(tree::ValueFunctionCall(
tree::FunctionCall {
function: tree::Identifier("range".to_owned()),
args: tree::Values(vec![
tree::Value::NumberLiteral(tree::NumberLiteral::Unsigned(0u64.into())),
tree::Value::NumberLiteral(tree::NumberLiteral::Unsigned(10u64.into())),
tree::Value::NumberLiteral(tree::NumberLiteral::Unsigned(2u64.into())),
])
}
)),
})
);
}
#[test]
fn parses_return_statement_with_value() {
assert_eq!(
StatementParser::new("return false").parse().unwrap(),
tree::Statement::ReturnStatement(tree::ReturnStatement(Some(tree::Expr::Value(
tree::Value::BoolLiteral(tree::BoolLiteral(false))
))))
);
}
#[test]
fn parses_return_statement_with_expr() {
assert_eq!(
StatementParser::new("return 4 * 16").parse().unwrap(),
tree::Statement::ReturnStatement(tree::ReturnStatement(Some(tree::Expr::BinaryExpr(
tree::BinaryExpr {
lhs: Box::new(tree::Expr::Value(tree::Value::NumberLiteral(
tree::NumberLiteral::Unsigned(4u64.into())
))),
rhs: vec![tree::BinaryExprRhs {
op: tree::Operator::Multiply,
expr: Box::new(tree::Expr::Value(tree::Value::NumberLiteral(
tree::NumberLiteral::Unsigned(16u64.into())
)))
}]
}
))))
);
}
#[test]
fn parses_formatted_string() {
assert_eq!(
StatementParser::new("print: str('test = {}', 1/10, 5)")
.parse()
.unwrap(),
tree::Statement::FunctionCall(tree::FunctionCall {
function: tree::Identifier("print".to_owned()),
args: tree::Values(vec![tree::Value::FormattedString(tree::FormattedString {
template: tree::StringLiteral("test = {}".to_owned()),
args: tree::Values(vec![
tree::Value::NumberLiteral(tree::NumberLiteral::Unsigned(Fraction::new(
1, 10
))),
tree::Value::NumberLiteral(tree::NumberLiteral::Unsigned(5u64.into())),
])
})])
})
);
}
#[test]
fn parses_return_missing_value() {
assert_eq!(
StatementParser::new("return").parse().unwrap(),
tree::Statement::ReturnStatement(tree::ReturnStatement(None)),
)
}
#[test]
fn parses_continue() {
assert_eq!(
StatementParser::new("continue").parse().unwrap(),
tree::Statement::Continue(tree::ContinueStatement)
);
}
#[test]
fn parses_unary_plus_expr() {
assert_eq!(
StatementParser::new("$test = +1 + +$test").parse().unwrap(),
tree::Statement::Assignment(tree::Assignment {
lhs: tree::Var {
name: tree::Identifier("test".to_owned()),
member_access: vec![],
},
rhs: tree::Expr::BinaryExpr(tree::BinaryExpr {
lhs: Box::new(tree::Expr::PrefixUnaryExpr(tree::PrefixUnaryExpr {
ops: vec![tree::Operator::UnaryPlus],
expr: Box::new(tree::Expr::Value(tree::Value::NumberLiteral(
tree::NumberLiteral::Unsigned(Fraction::from(1u64))
))),
})),
rhs: vec![tree::BinaryExprRhs {
op: tree::Operator::Add,
expr: Box::new(tree::Expr::PrefixUnaryExpr(tree::PrefixUnaryExpr {
ops: vec![tree::Operator::UnaryPlus],
expr: Box::new(tree::Expr::Value(tree::Value::Var(tree::Var {
name: tree::Identifier("test".to_owned()),
member_access: vec![],
})))
}))
}]
})
})
);
}
#[test]
fn fails_if_statement_missing_colon() {
assert_matches::assert_matches!(
StatementParser::new("if $test == expr($other / 3)").parse(),
Err(err) => assert_eq!(format!("{err:#}"), "unexpected end of line (expected :)")
);
}
#[test]
fn fails_else_statement_missing_colon() {
assert_matches::assert_matches!(
StatementParser::new("else").parse(),
Err(err) => assert_eq!(format!("{err:#}"), "unexpected end of line (expected :)")
);
}
#[test]
fn fails_foreach_missing_in() {
assert_matches::assert_matches!(
StatementParser::new("foreach $var").parse(),
Err(err) => assert_eq!(format!("{err:#}"), "unexpected end of line (expected in)")
);
}
#[test]
fn fails_foreach_wrong_keyword() {
assert_matches::assert_matches!(
StatementParser::new("foreach $var of $list:").parse(),
Err(err) => assert_eq!(format!("{err:#}"), "unexpected token at index 13: of (expected in)")
);
}
#[test]
fn parses_require() {
assert_eq!(
StatementParser::new("require $test").parse().unwrap(),
tree::Statement::RequireStatement(tree::RequireStatement {
condition: tree::Expr::Value(tree::Value::Var(tree::Var {
name: tree::Identifier("test".to_owned()),
member_access: vec![],
})),
else_return: None,
})
);
assert_eq!(
StatementParser::new("require !$test").parse().unwrap(),
tree::Statement::RequireStatement(tree::RequireStatement {
condition: tree::Expr::PrefixUnaryExpr(tree::PrefixUnaryExpr {
ops: vec![tree::Operator::Not],
expr: Box::new(tree::Expr::Value(tree::Value::Var(tree::Var {
name: tree::Identifier("test".to_owned()),
member_access: vec![],
})))
}),
else_return: None,
})
);
assert_eq!(
StatementParser::new("require $test else return")
.parse()
.unwrap(),
tree::Statement::RequireStatement(tree::RequireStatement {
condition: tree::Expr::Value(tree::Value::Var(tree::Var {
name: tree::Identifier("test".to_owned()),
member_access: vec![],
})),
else_return: Some(None),
})
);
assert_eq!(
StatementParser::new("require $test else return stop")
.parse()
.unwrap(),
tree::Statement::RequireStatement(tree::RequireStatement {
condition: tree::Expr::Value(tree::Value::Var(tree::Var {
name: tree::Identifier("test".to_owned()),
member_access: vec![],
})),
else_return: Some(Some(tree::Expr::Value(tree::Value::StringLiteral(
tree::StringLiteral("stop".to_owned())
)))),
})
);
assert_eq!(
StatementParser::new("require $test else return $other + 1")
.parse()
.unwrap(),
tree::Statement::RequireStatement(tree::RequireStatement {
condition: tree::Expr::Value(tree::Value::Var(tree::Var {
name: tree::Identifier("test".to_owned()),
member_access: vec![],
})),
else_return: Some(Some(tree::Expr::BinaryExpr(tree::BinaryExpr {
lhs: Box::new(tree::Expr::Value(tree::Value::Var(tree::Var {
name: tree::Identifier("other".to_owned()),
member_access: vec![],
}))),
rhs: vec![tree::BinaryExprRhs {
op: tree::Operator::Add,
expr: Box::new(tree::Expr::Value(tree::Value::NumberLiteral(
tree::NumberLiteral::Unsigned(1u64.into())
))),
}]
}))),
})
);
assert_eq!(
StatementParser::new("require $test.a + $test.b == 3 else return stop")
.parse()
.unwrap(),
tree::Statement::RequireStatement(tree::RequireStatement {
condition: tree::Expr::BinaryExpr(tree::BinaryExpr {
lhs: Box::new(tree::Expr::BinaryExpr(tree::BinaryExpr {
lhs: Box::new(tree::Expr::Value(tree::Value::Var(tree::Var {
name: tree::Identifier("test".to_owned()),
member_access: vec![tree::Identifier("a".to_owned())],
}))),
rhs: vec![tree::BinaryExprRhs {
op: tree::Operator::Add,
expr: Box::new(tree::Expr::Value(tree::Value::Var(tree::Var {
name: tree::Identifier("test".to_owned()),
member_access: vec![tree::Identifier("b".to_owned())],
}))),
}]
})),
rhs: vec![tree::BinaryExprRhs {
op: tree::Operator::Equal,
expr: Box::new(tree::Expr::Value(tree::Value::NumberLiteral(
tree::NumberLiteral::Unsigned(3u64.into())
))),
}]
}),
else_return: Some(Some(tree::Expr::Value(tree::Value::StringLiteral(
tree::StringLiteral("stop".to_owned())
)))),
})
);
}
#[test]
fn fails_require_missing_expr() {
assert_matches::assert_matches!(
StatementParser::new("require").parse(),
Err(err) => assert_eq!(format!("{err:#}"), "unexpected end of line (expected value)")
);
}
#[test]
fn parses_assign_rule() {
assert_eq!(
StatementParser::new("$test = assign($a = 5)")
.parse()
.unwrap(),
tree::Statement::Assignment(tree::Assignment {
lhs: tree::Var {
name: tree::Identifier("test".to_owned()),
member_access: vec![],
},
rhs: tree::Expr::Value(tree::Value::ValueAssignment(tree::ValueAssignment(
Box::new(tree::Assignment {
lhs: tree::Var {
name: tree::Identifier("a".to_owned()),
member_access: vec![],
},
rhs: tree::Expr::Value(tree::Value::NumberLiteral(
tree::NumberLiteral::Unsigned(5u64.into())
))
})
)))
})
);
}
}