use std::cell::RefCell;
use std::collections::HashMap;
use std::rc::Rc;
use engine::{Expr, Term, UnaryOp, BinaryOp, VariableName, NodeName, Node, Choice, Step};
use engine::{YarnEngine, YarnHandler, Value};
use parse::{TokenIterator, Token, Line};
use parse::{parse_step, parse_node_contents, parse_node, parse_nodes, parse_expr, parse_line};
#[test]
fn tokenize_number() {
let input = "-1.23";
let mut t = TokenIterator::new(input);
assert_eq!(t.next().unwrap(), Token::Minus);
assert_eq!(t.next().unwrap(), Token::Number(1.23));
assert!(t.next().is_none());
}
#[test]
fn tokenize_numbers() {
let input = "-123.232 55 21.4 0.2";
let mut t = TokenIterator::new(input);
assert_eq!(t.next().unwrap(), Token::Minus);
assert_eq!(t.next().unwrap(), Token::Number(123.232));
assert_eq!(t.next().unwrap(), Token::Number(55.));
assert_eq!(t.next().unwrap(), Token::Number(21.4));
assert_eq!(t.next().unwrap(), Token::Number(0.2));
assert!(t.next().is_none());
}
#[test]
fn tokenize_word() {
let input = "hello";
let mut t = TokenIterator::new(input);
assert_eq!(t.next().unwrap(), Token::Word("hello".to_string()));
assert!(t.next().is_none());
}
#[test]
fn tokenize_words() {
let input = "why hello there";
let mut t = TokenIterator::new(input);
assert_eq!(t.next().unwrap(), Token::Word("why".to_string()));
assert_eq!(t.next().unwrap(), Token::Word("hello".to_string()));
assert_eq!(t.next().unwrap(), Token::Word("there".to_string()));
assert!(t.next().is_none());
}
#[test]
fn tokenize_words_and_numbers_on_lines() {
let input = "why\n5\nhello \n-0.1there";
let mut t = TokenIterator::new(input);
assert_eq!(t.next().unwrap(), Token::Word("why".to_string()));
assert_eq!(t.next().unwrap(), Token::Number(5.));
assert_eq!(t.next().unwrap(), Token::Word("hello".to_string()));
assert_eq!(t.next().unwrap(), Token::Minus);
assert_eq!(t.next().unwrap(), Token::Number(0.1));
assert_eq!(t.next().unwrap(), Token::Word("there".to_string()));
assert!(t.next().is_none());
}
#[test]
fn track_indent() {
let input = " hi there\n bye\n";
let mut t = TokenIterator::new(input);
assert_eq!(t.next().unwrap(), Token::Word("hi".to_string()));
assert_eq!(t.last_indent(), 5);
assert_eq!(t.next().unwrap(), Token::Word("there".to_string()));
assert_eq!(t.last_indent(), 5);
assert_eq!(t.next().unwrap(), Token::Word("bye".to_string()));
assert_eq!(t.last_indent(), 4);
assert!(t.next().is_none());
}
#[test]
fn remainder_of_line() {
let input = " hi there\n bye bye";
let mut t = TokenIterator::new(input);
assert_eq!(t.next().unwrap(), Token::Word("hi".to_string()));
assert_eq!(t.remainder_of_line(), Some(" there".to_string()));
assert_eq!(t.next().unwrap(), Token::Word("bye".to_string()));
assert_eq!(t.remainder_of_line(), Some(" bye".to_string()));
assert!(t.remainder_of_line().is_none());
assert!(t.next().is_none());
}
#[test]
fn parse_dialogue_with_option() {
let input = "this is dialogue\n[[this is a choice|targetnode]]";
let mut t = TokenIterator::new(input);
let step = parse_step(&mut t).unwrap();
assert_eq!(step, Step::Dialogue("this is dialogue".to_string(),
vec![
Choice::external("this is a choice".to_string(),
NodeName("targetnode".to_string())),
]));
}
#[test]
fn parse_dialogue_with_two_options() {
let input = "this is dialogue\n[[this is a choice|targetnode]]\n[[this is another choice|targetnode2]]";
let mut t = TokenIterator::new(input);
let step = parse_step(&mut t).unwrap();
assert_eq!(step, Step::Dialogue("this is dialogue".to_string(),
vec![
Choice::external(
"this is a choice".to_string(),
NodeName("targetnode".to_string()),
),
Choice::external(
"this is another choice".to_string(),
NodeName("targetnode2".to_string()),
)
]));
}
#[test]
fn parse_conditional_dialogue() {
let input = "<<if true>>\nthis is dialogue\n[[this is a choice|targetnode]]\n<<endif>>";
let mut t = TokenIterator::new(input);
let step = parse_step(&mut t).unwrap();
let expected = Step::Conditional(
Expr::Term(Term::Boolean(true)),
vec![Step::Dialogue(
"this is dialogue".to_string(),
vec![
Choice::external(
"this is a choice".to_string(),
NodeName("targetnode".to_string()),
)
])],
vec![],
vec![]
);
assert_eq!(step, expected);
}
#[test]
fn parse_conditional_dialogue_with_else() {
let input = r#"<<if true>>
this is dialogue
[[this is a choice|targetnode]]
<<else>>
this is other dialogue
[[this is another choice|targetnode2]]
<<endif>>"#;
let mut t = TokenIterator::new(input);
let step = parse_step(&mut t).unwrap();
let expected = Step::Conditional(
Expr::Term(Term::Boolean(true)),
vec![Step::Dialogue(
"this is dialogue".to_string(),
vec![Choice::external(
"this is a choice".to_string(),
NodeName("targetnode".to_string()),
)])],
vec![],
vec![Step::Dialogue(
"this is other dialogue".to_string(),
vec![Choice::external(
"this is another choice".to_string(),
NodeName("targetnode2".to_string()),
)])]
);
assert_eq!(step, expected);
}
#[test]
fn parse_conditional_dialogue_with_else_if() {
let input = r#"<<if true>>
this is dialogue
[[this is a choice|targetnode]]
<<elseif false>>
this is other dialogue
[[this is another choice|targetnode2]]
<<elseif true>>
third dialogue!
<<else>>
whatever
[[look a choice|targetnode3]]
<<endif>>"#;
let mut t = TokenIterator::new(input);
let step = parse_step(&mut t).unwrap();
let expected = Step::Conditional(
Expr::Term(Term::Boolean(true)),
vec![Step::Dialogue(
"this is dialogue".to_string(),
vec![Choice::external(
"this is a choice".to_string(),
NodeName("targetnode".to_string()),
)])
],
vec![(Expr::Term(Term::Boolean(false)),
vec![Step::Dialogue(
"this is other dialogue".to_string(),
vec![Choice::external(
"this is another choice".to_string(),
NodeName("targetnode2".to_string()),
)])
]),
(Expr::Term(Term::Boolean(true)),
vec![Step::Dialogue(
"third dialogue!".to_string(),
vec![])
])
],
vec![Step::Dialogue(
"whatever".to_string(),
vec![Choice::external(
"look a choice".to_string(),
NodeName("targetnode3".to_string()),
)])
]
);
assert_eq!(step, expected);
}
#[test]
fn parse_command() {
let input = "<<move doo to wop>>";
let mut t = TokenIterator::new(input);
let step = parse_step(&mut t).unwrap();
assert_eq!(step, Step::Command("move doo to wop".to_string()));
}
#[test]
fn parse_commands() {
let input = "<<move doo to wop>>\n<<hi>>";
let mut t = TokenIterator::new(input);
let step = parse_step(&mut t).unwrap();
assert_eq!(step, Step::Command("move doo to wop".to_string()));
let step = parse_step(&mut t).unwrap();
assert_eq!(step, Step::Command("hi".to_string()));
}
#[test]
fn parse_until_end_of_node() {
let input = r#"dialogue
dialogue2
dialogue3
===
more
"#;
let mut t = TokenIterator::new(input);
let steps = parse_node_contents(&mut t).unwrap();
let expected = vec![
Step::Dialogue("dialogue".to_string(), vec![]),
Step::Dialogue("dialogue2".to_string(), vec![]),
Step::Dialogue("dialogue3".to_string(), vec![]),
];
assert_eq!(steps, expected);
assert_eq!(t.next().unwrap(), Token::Word("more".to_string()));
}
#[test]
fn parse_whole_node() {
let input = r#"title: whee hello
extra: hi there
---
dialogue
dialogue2
dialogue3
==="#;
let mut t = TokenIterator::new(input);
let mut extra = HashMap::new();
extra.insert("extra".to_string(), "hi there".to_string());
let expected = Node {
title: NodeName("whee hello".to_string()),
extra: extra,
steps: vec![
Step::Dialogue("dialogue".to_string(), vec![]),
Step::Dialogue("dialogue2".to_string(), vec![]),
Step::Dialogue("dialogue3".to_string(), vec![]),
],
visited: false,
};
let node = parse_node(&mut t).unwrap();
assert_eq!(node, expected);
}
#[test]
fn parse_multiple_nodes() {
let input = r#"title: whee hello
extra: hi there
---
dialogue
dialogue2
dialogue3
===
extra: foo bar -5
title: title!
---
dialogue
[[option|whee hello]]
[[option2|title!]]
===
"#;
let mut t = TokenIterator::new(input);
let mut extra = HashMap::new();
extra.insert("extra".to_string(), "hi there".to_string());
let mut extra2 = HashMap::new();
extra2.insert("extra".to_string(), "foo bar -5".to_string());
let expected = vec![
Node {
title: NodeName("whee hello".to_string()),
extra: extra,
steps: vec![
Step::Dialogue("dialogue".to_string(), vec![]),
Step::Dialogue("dialogue2".to_string(), vec![]),
Step::Dialogue("dialogue3".to_string(), vec![]),
],
visited: false,
},
Node {
title: NodeName("title!".to_string()),
extra: extra2,
steps: vec![
Step::Dialogue("dialogue".to_string(), vec![
Choice::external(
"option".to_string(),
NodeName("whee hello".to_string()),
),
Choice::external(
"option2".to_string(),
NodeName("title!".to_string()),
),
]),
],
visited: false,
},
];
let nodes = parse_nodes(&mut t).unwrap();
assert_eq!(nodes, expected);
}
#[test]
fn parse_number_expression() {
let input = "5.4";
let mut t = TokenIterator::new(input);
assert_eq!(parse_expr(&mut t).unwrap(), Expr::Term(Term::Number(5.4)));
}
#[test]
fn parse_string_expression() {
let input = "\"hi there\"";
let mut t = TokenIterator::new(input);
assert_eq!(parse_expr(&mut t).unwrap(), Expr::Term(Term::String("hi there".to_string())));
}
#[test]
fn parse_function_expression() {
let input = "visited(\"someNode\")";
let mut t = TokenIterator::new(input);
assert_eq!(parse_expr(&mut t).unwrap(),
Expr::Term(Term::Function("visited".to_string(),
vec![Expr::Term(Term::String("someNode".to_string()))])));
}
#[test]
fn parse_function_expression_no_args() {
let input = "visited()";
let mut t = TokenIterator::new(input);
assert_eq!(parse_expr(&mut t).unwrap(),
Expr::Term(Term::Function("visited".to_string(), vec![])));
}
#[test]
fn parse_function_expression_two_args() {
let input = "visited(\"someNode\", 5.4)";
let mut t = TokenIterator::new(input);
assert_eq!(parse_expr(&mut t).unwrap(),
Expr::Term(Term::Function("visited".to_string(),
vec![Expr::Term(Term::String("someNode".to_string())),
Expr::Term(Term::Number(5.4))])));
}
#[test]
fn parse_negative_number_expression() {
let input = "-5.4";
let mut t = TokenIterator::new(input);
let expected = Expr::Unary(UnaryOp::Negate, Box::new(Expr::Term(Term::Number(5.4))));
assert_eq!(parse_expr(&mut t).unwrap(), expected);
}
#[test]
fn parse_addition() {
let input = "4 + 8";
let mut t = TokenIterator::new(input);
let expected = Expr::Binary(BinaryOp::Plus,
Box::new(Expr::Term(Term::Number(4.0))),
Box::new(Expr::Term(Term::Number(8.0))));
assert_eq!(parse_expr(&mut t).unwrap(), expected);
}
#[test]
fn parse_parentheses() {
let input = "(4 + 8)";
let mut t = TokenIterator::new(input);
let expected = Expr::Parentheses(
Box::new(Expr::Binary(BinaryOp::Plus,
Box::new(Expr::Term(Term::Number(4.0))),
Box::new(Expr::Term(Term::Number(8.0))))));
assert_eq!(parse_expr(&mut t).unwrap(), expected);
}
#[test]
fn parse_jump() {
let input = "[[SomeNode.Walk]]";
let mut t = TokenIterator::new(input);
let step = parse_step(&mut t).unwrap();
assert_eq!(step, Step::Jump(NodeName("SomeNode.Walk".to_string())));
}
#[test]
fn parse_inline_option_with_condition() {
let input = "-> This is some text << if $money >= 5 >>";
let mut t = TokenIterator::new(input);
let (_indent, line) = parse_line(&mut t).unwrap();
assert_eq!(line, Line::InlineOption("This is some text".to_string(),
Some("$money >= 5".to_string())));
}
#[test]
fn parse_inline_option_with_condition2() {
let input = r#"This is dialogue
-> This is some text << if $money >= 5 >>
Some inline dialogue
Some more inline dialogue
-> Another text
Some inline dialogue
"#;
let mut t = TokenIterator::new(input);
let step = parse_step(&mut t).unwrap();
assert_eq!(step,Step::Dialogue("This is dialogue".to_string(),
vec![
Choice::inline("This is some text".to_string(),
vec![
Step::Dialogue("Some inline dialogue".to_string(),
vec![]),
Step::Dialogue("Some more inline dialogue".to_string(),
vec![]),
],
Some(Expr::Binary(BinaryOp::GreaterThanEqual,
Box::new(Expr::Term(Term::Variable(VariableName("money".to_string())))),
Box::new(Expr::Term(Term::Number(5.0)))))),
Choice::inline("Another text".to_string(),
vec![
Step::Dialogue("Some inline dialogue".to_string(),
vec![]),
],
None),
]));
}
#[derive(PartialEq, Debug)]
enum Event {
Say(String),
Choose(String, Vec<String>),
Command(String),
End,
}
#[derive(Default)]
struct TestHandler {
events: Rc<RefCell<Vec<Event>>>,
}
impl YarnHandler for TestHandler {
fn say(&mut self, text: String) {
self.events.borrow_mut().push(Event::Say(text));
}
fn choose(&mut self, text: String, choices: Vec<String>) {
self.events.borrow_mut().push(Event::Choose(text, choices));
}
fn command(&mut self, action: String) -> Result<(), ()> {
self.events.borrow_mut().push(Event::Command(action));
Ok(())
}
fn end_conversation(&mut self) {
self.events.borrow_mut().push(Event::End);
}
}
#[test]
fn test_execution() {
let nodes = r#"
title: 1
---
text1
text2
===
"#;
let handler = Box::new(TestHandler::default());
let events = handler.events.clone();
let mut engine = YarnEngine::new(handler);
engine.load_from_string(&nodes).unwrap();
engine.activate(NodeName("1".to_string()));
assert_eq!(&*events.borrow(), &[Event::Say("text1".to_string())]);
engine.proceed();
assert_eq!(&*events.borrow(), &[Event::Say("text1".to_string()),
Event::Say("text2".to_string())]);
engine.proceed();
assert_eq!(&*events.borrow(), &[Event::Say("text1".to_string()),
Event::Say("text2".to_string()),
Event::End]);
}
#[test]
fn test_execution_choice() {
let nodes = r#"
title: 1
---
some text
[[whee|3]]
[[whee2|2]]
===
title: 2
---
[[1]]
===
title: 3
---
that's all
===
"#;
let handler = Box::new(TestHandler::default());
let events = handler.events.clone();
let mut engine = YarnEngine::new(handler);
engine.load_from_string(&nodes).unwrap();
engine.activate(NodeName("1".to_string()));
assert_eq!(&*events.borrow(), &[Event::Choose("some text".to_string(),
vec!["whee".to_string(),
"whee2".to_string()])]);
events.borrow_mut().clear();
engine.choose(1);
assert_eq!(&*events.borrow(), &[Event::Choose("some text".to_string(),
vec!["whee".to_string(),
"whee2".to_string()])]);
events.borrow_mut().clear();
engine.choose(0);
assert_eq!(&*events.borrow(), &[Event::Say("that's all".to_string())]);
events.borrow_mut().clear();
engine.proceed();
assert_eq!(&*events.borrow(), &[Event::End]);
}
#[test]
fn test_execution_variable() {
let nodes = r#"
title: 1
---
<<if $foo == 5>>
some text
<<else>>
other text
<<endif>>
===
"#;
let handler = Box::new(TestHandler::default());
let events = handler.events.clone();
let mut engine = YarnEngine::new(handler);
engine.set_variable(VariableName("foo".to_string()), Value::Number(5.0));
engine.load_from_string(&nodes).unwrap();
engine.activate(NodeName("1".to_string()));
assert_eq!(&*events.borrow(), &[Event::Say("some text".to_string())]);
events.borrow_mut().clear();
engine.proceed();
assert_eq!(&*events.borrow(), &[Event::End]);
events.borrow_mut().clear();
engine.set_variable(VariableName("foo".to_string()), Value::Number(6.0));
engine.activate(NodeName("1".to_string()));
assert_eq!(&*events.borrow(), &[Event::Say("other text".to_string())]);
events.borrow_mut().clear();
engine.proceed();
assert_eq!(&*events.borrow(), &[Event::End]);
}