#[macro_use]
extern crate pest_derive;
use pest::{iterators::Pair, Parser};
use std::{
collections::HashMap,
fmt,
fs::File,
io::{self, BufRead, BufReader, Read, Write},
};
fn trim_whitespace(s: &str) -> String {
let mut ret = String::new();
for c in s.chars() {
if c != '\r' && c != '\n' {
ret.push(c);
}
}
ret
}
type NodeId = usize;
static TERMINATING_NODE: NodeId = 9999;
static DEFAULT_INPUT_FILE: &str = "input.txt";
type BranchOption = (String, NodeId);
#[derive(Debug, PartialEq)]
enum NodeType {
Branching(String, Vec<BranchOption>),
Question(NodeId, NodeId, Vec<String>),
Terminating(String),
}
#[derive(Debug, PartialEq)]
struct Node {
node_type: NodeType,
variable: Option<String>,
}
impl Node {
fn new(node_type: NodeType, variable: Option<String>) -> Self {
Self {
node_type,
variable,
}
}
}
#[derive(Debug, PartialEq)]
pub struct Env(HashMap<String, String>);
impl Env {
pub fn get_variable(&self, variable_name: &str) -> String {
match self.0.get(variable_name) {
Some(v) => v.into(),
None => variable_name.into(),
}
}
pub fn set_variable(&mut self, variable_name: &str, value: String) {
let current = self
.0
.entry(variable_name.into())
.or_insert_with(|| variable_name.into());
*current = value;
}
pub fn resolve_template(&self, template: &str) -> String {
let mut ret = String::new();
let vec = template.as_bytes().to_owned();
let mut i = 0;
while i < vec.len() {
if vec[i] == b'$' {
let mut var_name = String::new();
i += 1;
while vec[i] >= b'A' && vec[i] <= b'Z' && i < vec.len() - 1 {
var_name.push(vec[i] as char);
i += 1;
}
ret.push_str(&self.get_variable(&var_name));
} else {
ret.push(vec[i] as char);
i += 1;
}
}
ret
}
}
impl Default for Env {
fn default() -> Self {
Self(HashMap::new())
}
}
#[derive(Parser)]
#[grammar = "nodes.pest"]
pub struct NodesParser;
fn parse_branch_option_line(parsed: Pair<Rule>) -> BranchOption {
match parsed.as_rule() {
Rule::branch_option => {
let mut inner = parsed.into_inner();
let prompt = inner.next().unwrap();
inner.next();
let destination = inner.next().unwrap();
(
prompt.as_str().into(),
destination
.as_str()
.parse::<usize>()
.expect(&format!("Fail to parse {}", destination.as_str())),
)
}
_ => panic!("Called parse_branch_option_line on the wrong rule"),
}
}
fn parse_string_line(parsed: Pair<Rule>) -> String {
match parsed.as_rule() {
Rule::string_line => trim_whitespace(parsed.as_str()),
_ => panic!("Called parse_string_line on the wrong rule"),
}
}
fn parse_int_line(parsed: Pair<Rule>) -> usize {
match parsed.as_rule() {
Rule::int_line => parsed
.into_inner()
.next()
.unwrap()
.as_str()
.parse::<usize>()
.unwrap(),
_ => panic!("Called parse_int_line on the wrong rule"),
}
}
#[derive(Debug, Default, PartialEq)]
pub struct Nodes {
current_node: NodeId,
env: Env,
internal_state: NodeId,
nodes: Vec<Node>,
}
impl Nodes {
pub fn new(specified_input: Option<String>) -> Self {
let input_file = specified_input.unwrap_or_else(|| DEFAULT_INPUT_FILE.into());
println!("Input file: {}\n", input_file);
let mut ret = Nodes::default();
let mut file_str = String::new();
let f = File::open(input_file).expect("Should open input file");
let mut bfr = BufReader::new(f);
bfr.read_to_string(&mut file_str)
.expect("Should read input file");
let mut parsed = match NodesParser::parse(Rule::nodes, &file_str) {
Ok(parse_tree) => parse_tree,
Err(e) => panic!(format!("{}", e)),
};
ret.read_and_register(parsed.next().unwrap());
ret
}
pub fn run(&mut self) {
while self.current_node != TERMINATING_NODE {
use NodeType::*;
let stdin = io::stdin();
let mut stdout = io::stdout();
match &self.nodes[self.current_node].node_type {
Question(success, fail, qs) => {
if self.internal_state < qs.len() {
print!("{}", self);
stdout.flush().expect("Should flush stdout");
let mut line = String::new();
stdin.lock().read_line(&mut line).unwrap();
line = trim_whitespace(&line);
match line.len() {
0 => {
self.internal_state += 1;
}
_ => {
self.env.set_variable(
&self.nodes[self.current_node].variable.as_ref().unwrap(),
line,
);
let destination = *success;
self.state_transition(destination);
}
}
} else {
let destination = *fail;
self.state_transition(destination);
}
}
Branching(_, options) => {
print!("{}", self);
stdout.flush().expect("Should flush stdout");
let mut line = String::new();
stdin.lock().read_line(&mut line).unwrap();
line = trim_whitespace(&line);
match line.len() {
0 => {
eprintln!("TODO - The graphical option won't allow empty input, so just comply please")
},
_ => {
let choice = line.as_str().parse::<usize>();
match choice {
Ok(n) => {
if n > options.len() {
eprintln!("Not a valid option!");
} else {
self.env.set_variable(&self.nodes[self.current_node].variable.as_ref().unwrap(), options[n - 1].0.clone());
let destination = options[n - 1].1;
self.state_transition(destination);
}
}
Err(e) => eprintln!("Unrecognized input: {}", e),
}
},
}
}
Terminating(_) => {
println!("{}", self);
let _ = stdin.lock().lines().next();
self.state_transition(TERMINATING_NODE);
}
}
println!();
}
}
fn register_question_node(
&mut self,
if_answered: NodeId,
if_terminate: NodeId,
variable_name: &str,
questions: Vec<String>,
) {
self.nodes.push(Node::new(
NodeType::Question(if_answered, if_terminate, questions),
Some(variable_name.into()),
));
}
fn register_branching_node(
&mut self,
variable_name: &str,
question: &str,
options: Vec<BranchOption>,
) {
self.nodes.push(Node::new(
NodeType::Branching(question.into(), options),
Some(variable_name.into()),
));
}
fn register_terminating_node(&mut self, text: &str) {
self.nodes
.push(Node::new(NodeType::Terminating(text.into()), None))
}
fn read_and_register(&mut self, parsed: Pair<Rule>) {
match parsed.as_rule() {
Rule::nodes => {
for child in parsed.into_inner() {
self.read_and_register(child);
}
}
Rule::node => self.read_and_register(parsed.into_inner().next().unwrap()),
Rule::question => {
let mut inner = parsed.into_inner().skip(1);
let t1 = parse_int_line(inner.next().unwrap());
let t2 = parse_int_line(inner.next().unwrap());
let var_name = parse_string_line(inner.next().unwrap());
let mut questions = Vec::new();
for qline in inner {
questions.push(parse_string_line(qline));
}
self.register_question_node(t1, t2, &var_name, questions);
}
Rule::branching => {
let mut inner = parsed.into_inner().skip(1);
let var_name = parse_string_line(inner.next().unwrap());
let question = parse_string_line(inner.next().unwrap());
let mut options = Vec::new();
for oline in inner {
options.push(parse_branch_option_line(oline));
}
self.register_branching_node(&var_name, &question, options);
}
Rule::terminating => {
let mut inner = parsed.into_inner().skip(1);
let message = parse_string_line(inner.next().unwrap());
self.register_terminating_node(&message);
}
Rule::EOI => {}
_ => panic!(format!("Cannot handle {:?}", parsed.as_rule())),
}
}
fn state_transition(&mut self, new_state: NodeId) {
self.current_node = new_state;
self.internal_state = 0;
}
}
impl fmt::Display for Nodes {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use NodeType::*;
match &self.nodes[self.current_node].node_type {
Branching(question, options) => {
let mut option_string = String::new();
for (i, option) in options.iter().enumerate() {
option_string.push_str(&format!("{}. {}\n", i + 1, option.0));
}
write!(
f,
"{}\n{}\nEnter choice> ",
self.env.resolve_template(question),
option_string
)
}
Question(_, _, qs) => {
if self.internal_state > qs.len() {
unreachable!()
}
write!(
f,
"{}\nEnter string> ",
self.env.resolve_template(&qs[self.internal_state])
)
}
Terminating(message) => write!(
f,
"{}\nGoodbye (enter anything to exit)> ",
self.env.resolve_template(message)
),
}
}
}
#[cfg(test)]
mod test {
#[test]
fn test_parse_input() {
use super::Nodes;
use pretty_assertions::assert_eq;
let mut test = Nodes::default();
test.register_question_node(
1,
3,
"NAME",
vec![
"What is your name?".into(),
"Please tell me your name".into(),
"You better tell me your name".into(),
],
);
test.register_branching_node(
"QUEST",
"$NAME, what is your quest?",
vec![
("The Holy Grail".into(), 2),
("Some Other Grail".into(), 2),
("Run and Hide".into(), 3),
],
);
test.register_branching_node(
"COLOR",
"$NAME, who seeks $QUEST, what is your favorite color?",
vec![("Red".into(), 4), ("I mean blue".into(), 5)],
);
test.register_terminating_node(
"Since you have REFUSED to answer, customer service has been called",
);
test.register_terminating_node(
"You may pass, $NAME who loves $COLOR, on your noble quest for the $QUEST.",
);
test.register_terminating_node("AAAARRRRGGGGGHHHHH");
assert_eq!(Nodes::new(None), test);
}
}