use crate::{
ast::*,
parser::*,
span::{SourceSpan, Spanned}
};
use pretty_assertions::assert_eq;
#[test]
fn test_function()
{
for (input, expected_string, expected_ast) in [
(
"{x}: 42",
"{x}: 42",
Function {
parameters: Some(vec![Parameter {
name: "x".into(),
span: SourceSpan::default()
}]),
body: Expression::Constant(Constant {
value: 42,
span: SourceSpan::default()
}),
span: SourceSpan::default()
}
),
(
"3d6",
"3D6",
Function {
parameters: None,
body: Expression::Dice(DiceExpression::Standard(
StandardDice {
count: Box::new(Expression::Constant(Constant {
value: 3,
span: SourceSpan::default()
})),
faces: Box::new(Expression::Constant(Constant {
value: 6,
span: SourceSpan::default()
})),
span: SourceSpan::default()
}
)),
span: SourceSpan::default()
}
),
(
"{x}, {y}: {x} + {y}",
"{x}, {y}: {x} + {y}",
Function {
parameters: Some(vec![
Parameter {
name: "x".into(),
span: SourceSpan::default()
},
Parameter {
name: "y".into(),
span: SourceSpan::default()
},
]),
body: Expression::Arithmetic(ArithmeticExpression::Add(Add {
left: Box::new(Expression::Variable(Variable {
name: "x".into(),
span: SourceSpan::default()
})),
right: Box::new(Expression::Variable(Variable {
name: "y".into(),
span: SourceSpan::default()
})),
span: SourceSpan::default()
})),
span: SourceSpan::default()
}
),
(
"2d20 + 5",
"2D20 + 5",
Function {
parameters: None,
body: Expression::Arithmetic(ArithmeticExpression::Add(Add {
left: Box::new(Expression::Dice(DiceExpression::Standard(
StandardDice {
count: Box::new(Expression::Constant(Constant {
value: 2,
span: SourceSpan::default()
})),
faces: Box::new(Expression::Constant(Constant {
value: 20,
span: SourceSpan::default()
})),
span: SourceSpan::default()
}
))),
right: Box::new(Expression::Constant(Constant {
value: 5,
span: SourceSpan::default()
})),
span: SourceSpan::default()
})),
span: SourceSpan::default()
}
),
(
"{a}, {b}, {c}: ({a} + {b}) * {c}",
"{a}, {b}, {c}: ({a} + {b}) * {c}",
Function {
parameters: Some(vec![
Parameter {
name: "a".into(),
span: SourceSpan::default()
},
Parameter {
name: "b".into(),
span: SourceSpan::default()
},
Parameter {
name: "c".into(),
span: SourceSpan::default()
},
]),
body: Expression::Arithmetic(ArithmeticExpression::Mul(Mul {
left: Box::new(Expression::Group(Group {
expression: Box::new(Expression::Arithmetic(
ArithmeticExpression::Add(Add {
left: Box::new(Expression::Variable(
Variable {
name: "a".into(),
span: SourceSpan::default()
}
)),
right: Box::new(Expression::Variable(
Variable {
name: "b".into(),
span: SourceSpan::default()
}
)),
span: SourceSpan::default()
})
)),
span: SourceSpan::default()
})),
right: Box::new(Expression::Variable(Variable {
name: "c".into(),
span: SourceSpan::default()
})),
span: SourceSpan::default()
})),
span: SourceSpan::default()
}
),
(
"4d6 drop lowest",
"4D6 drop lowest",
Function {
parameters: None,
body: Expression::Dice(DiceExpression::DropLowest(
DropLowest {
dice: Box::new(DiceExpression::Standard(
StandardDice {
count: Box::new(Expression::Constant(
Constant {
value: 4,
span: SourceSpan::default()
}
)),
faces: Box::new(Expression::Constant(
Constant {
value: 6,
span: SourceSpan::default()
}
)),
span: SourceSpan::default()
}
)),
drop: None,
span: SourceSpan::default()
}
)),
span: SourceSpan::default()
}
),
(
"4d6 drop highest {z}",
"4D6 drop highest {z}",
Function {
parameters: None,
body: Expression::Dice(DiceExpression::DropHighest(
DropHighest {
dice: Box::new(DiceExpression::Standard(
StandardDice {
count: Box::new(Expression::Constant(
Constant {
value: 4,
span: SourceSpan::default()
}
)),
faces: Box::new(Expression::Constant(
Constant {
value: 6,
span: SourceSpan::default()
}
)),
span: SourceSpan::default()
}
)),
drop: Some(Box::new(Expression::Variable(Variable {
name: "z".into(),
span: SourceSpan::default()
}))),
span: SourceSpan::default()
}
)),
span: SourceSpan::default()
}
),
(
"{x}: {x} * 2 + 1",
"{x}: {x} * 2 + 1",
Function {
parameters: Some(vec![Parameter {
name: "x".into(),
span: SourceSpan::default()
}]),
body: Expression::Arithmetic(ArithmeticExpression::Add(Add {
left: Box::new(Expression::Arithmetic(
ArithmeticExpression::Mul(Mul {
left: Box::new(Expression::Variable(Variable {
name: "x".into(),
span: SourceSpan::default()
})),
right: Box::new(Expression::Constant(Constant {
value: 2,
span: SourceSpan::default()
})),
span: SourceSpan::default()
})
)),
right: Box::new(Expression::Constant(Constant {
value: 1,
span: SourceSpan::default()
})),
span: SourceSpan::default()
})),
span: SourceSpan::default()
}
),
(
"{a}, {b}: [{a}:{b}]",
"{a}, {b}: [{a}:{b}]",
Function {
parameters: Some(vec![
Parameter {
name: "a".into(),
span: SourceSpan::default()
},
Parameter {
name: "b".into(),
span: SourceSpan::default()
},
]),
body: Expression::Range(Range {
start: Box::new(Expression::Variable(Variable {
name: "a".into(),
span: SourceSpan::default()
})),
end: Box::new(Expression::Variable(Variable {
name: "b".into(),
span: SourceSpan::default()
})),
span: SourceSpan::default()
}),
span: SourceSpan::default()
}
),
(
"3D[1,2,3] + 5",
"3D[1, 2, 3] + 5",
Function {
parameters: None,
body: Expression::Arithmetic(ArithmeticExpression::Add(Add {
left: Box::new(Expression::Dice(DiceExpression::Custom(
CustomDice {
count: Box::new(Expression::Constant(Constant {
value: 3,
span: SourceSpan::default()
})),
faces: vec![1, 2, 3],
span: SourceSpan::default()
}
))),
right: Box::new(Expression::Constant(Constant {
value: 5,
span: SourceSpan::default()
})),
span: SourceSpan::default()
})),
span: SourceSpan::default()
}
)
]
{
let span = Span::new(input);
match function(span)
{
Ok((residue, result)) =>
{
assert!(
residue.fragment().is_empty(),
"Residue not empty for input: {}",
input
);
assert_eq!(
result.to_string(),
expected_string,
"Failed for input: {}",
input
);
assert_eq!(
result.untethered(),
expected_ast.untethered(),
"AST mismatch for input: {}",
input
);
},
Err(e) => panic!("Parsing failed for input: {}: {}", input, e)
}
}
for input in ["", " ", ":", "x:", "x: ", "x, y", "x, y:", "1 + "]
{
let span = Span::new(input);
let result = function(span);
assert!(
result.is_err() || !result.unwrap().0.fragment().is_empty(),
"Failed to reject invalid input: {}",
input
);
}
}
#[test]
fn test_parameters()
{
fn param(name: &str) -> Parameter<'_>
{
Parameter {
name: name.into(),
span: SourceSpan::default()
}
}
for (input, expected_names, expected_ast) in [
("{x}:", vec!["x"], vec![param("x")]),
("{x}, {y}:", vec!["x", "y"], vec![param("x"), param("y")]),
(
"{x}, {y}, {z}:",
vec!["x", "y", "z"],
vec![param("x"), param("y"), param("z")]
),
(
"{long_variable_name}:",
vec!["long_variable_name"],
vec![param("long_variable_name")]
),
(
"{x1}, {x2}, {x3}:",
vec!["x1", "x2", "x3"],
vec![param("x1"), param("x2"), param("x3")]
),
(
"{hello-world}:",
vec!["hello-world"],
vec![param("hello-world")]
),
(
"{x}, {y}, {z}:",
vec!["x", "y", "z"],
vec![param("x"), param("y"), param("z")]
),
(
" {x} , { y } , {z} :",
vec!["x", "y", "z"],
vec![param("x"), param("y"), param("z")]
)
]
{
let span = Span::new(input);
match parameters(span)
{
Ok((residue, result)) =>
{
assert!(
residue.fragment().is_empty(),
"Residue not empty for input: {}",
input
);
let result = result.unwrap();
let names: Vec<&str> =
result.iter().map(|p| &*p.name).collect();
assert_eq!(
names, expected_names,
"Failed for input: {}",
input
);
let result_untethered: Vec<Parameter<'_>> =
result.iter().map(Spanned::untethered).collect();
let expected_untethered: Vec<Parameter<'_>> =
expected_ast.iter().map(Spanned::untethered).collect();
assert_eq!(
result_untethered, expected_untethered,
"AST mismatch for input: {}",
input
);
},
Err(e) => panic!("Parsing failed for input: {}: {}", input, e)
}
}
for input in [
"{x},",
"{x}, ",
"{x}, {y}",
"{x}, {y},",
":{x}",
"{x}:{y}",
"1, 2, 3:",
"{x}, 1, {y}:",
"x:",
"x, y:"
]
{
let span = Span::new(input);
let result = parameters(span);
assert!(
result.is_err() || !result.unwrap().0.fragment().is_empty(),
"Failed to reject invalid input: {}",
input
);
}
for input in ["{x}", "{x}@(1)", "{x} + 1", " {x} d6"]
{
let (residue, result) = parameters(Span::new(input)).unwrap();
assert_eq!(result, None, "Parameters found for input: {}", input);
assert_eq!(
*residue.fragment(),
input,
"Input consumed for input: {}",
input
);
}
}
#[test]
fn test_parameter()
{
for (input, expected) in [
("{x}", "x"),
("{ x }", "x"),
("{variable}", "variable"),
("{long_variable_name}", "long_variable_name"),
("{hello-world}", "hello-world"),
("{an argument}", "an argument"),
("{こんにちは}", "こんにちは"),
("{Здравствуй-мир}", "Здравствуй-мир"),
("{Γειά-σου-κόσμε}", "Γειά-σου-κόσμε"),
("{x1}", "x1"),
("{x_1}", "x_1"),
("{x-1}", "x-1"),
("{1x}", "1x"),
("{a b}", "a b"),
("{a\tb}", "a\tb"),
("{\u{00A0}a\n b\u{00A0}}", "a\n b"),
("{weapon: 2/3}", "weapon: 2/3"),
("{x, y}", "x, y")
]
{
let span = Span::new(input);
match parameter(span)
{
Ok((residue, result)) =>
{
assert!(
residue.fragment().is_empty(),
"Residue not empty for input: {}",
input
);
assert_eq!(
result.to_string(),
expected,
"Failed for input: {}",
input
);
},
Err(e) => panic!("Parsing failed for input: {}: {}", input, e)
}
}
for input in [
"",
" ",
"x",
"{",
"{}",
"{ }",
"{\t}",
"{x",
"x}",
"{x}:",
"{x},",
"{x}(",
"{a\u{0}b}",
"{a\u{200B}b}"
]
{
let span = Span::new(input);
let result = parameter(span);
assert!(
result.is_err() || !result.unwrap().0.fragment().is_empty(),
"Failed to reject invalid input: {}",
input
);
}
for input in ["{", "{}", "{a\u{0}b}", "{x"]
{
assert!(
matches!(parameter(Span::new(input)), Err(nom::Err::Error(_))),
"Failed unrecoverably for input: {}",
input
);
}
}