yarn_spool/
engine.rs

1use parse;
2use std::cmp::PartialEq;
3use std::collections::HashMap;
4use std::ops::{Add, Sub, Mul, Div};
5
6//TODO: dialogue options inside conditionals
7
8#[derive(Clone, Debug, PartialEq, Eq, Hash)]
9pub struct NodeName(pub String);
10#[derive(Clone, Debug, PartialEq, Eq, Hash)]
11pub struct VariableName(pub String);
12
13struct Variables(HashMap<VariableName, Value>);
14impl Variables {
15    fn set(&mut self, name: VariableName, value: Value) {
16        self.0.insert(name, value);
17    }
18}
19
20#[derive(Debug, PartialEq)]
21pub(crate) struct Choice {
22    text: String,
23    kind: ChoiceKind,
24}
25
26impl Choice {
27    pub(crate) fn external(text: String, name: NodeName) -> Choice {
28        Choice {
29            text: text,
30            kind: ChoiceKind::External(name),
31        }
32    }
33
34    pub(crate) fn inline(text: String, steps: Vec<Step>, condition: Option<Expr>) -> Choice {
35        Choice {
36            text: text,
37            kind: ChoiceKind::Inline(steps, condition),
38        }
39    }
40}
41
42#[derive(Debug, PartialEq)]
43enum ChoiceKind {
44    External(NodeName),
45    Inline(Vec<Step>, Option<Expr>),
46}
47
48#[derive(Debug, PartialEq)]
49pub(crate) enum Step {
50    Dialogue(String, Vec<Choice>),
51    Command(String),
52    Assign(VariableName, Expr),
53    Conditional(Expr, Vec<Step>, Vec<(Expr, Vec<Step>)>, Vec<Step>),
54    Jump(NodeName),
55}
56
57#[derive(Debug, PartialEq)]
58pub(crate) enum Expr {
59    Unary(UnaryOp, Box<Expr>),
60    Binary(BinaryOp, Box<Expr>, Box<Expr>),
61    Term(Term),
62    Parentheses(Box<Expr>),
63}
64
65#[derive(Debug, PartialEq)]
66pub(crate) enum UnaryOp {
67    Not,
68    Negate,
69}
70
71#[derive(Debug, PartialEq)]
72pub(crate) enum BinaryOp {
73    And,
74    Or,
75    Plus,
76    Minus,
77    Multiply,
78    Divide,
79    Equals,
80    NotEquals,
81    GreaterThan,
82    LessThan,
83    GreaterThanEqual,
84    LessThanEqual,
85}
86
87#[derive(Debug, PartialEq)]
88pub(crate) enum Term {
89    Number(f32),
90    Boolean(bool),
91    String(String),
92    Variable(VariableName),
93    Function(String, Vec<Expr>),
94}
95
96#[derive(Debug, PartialEq)]
97pub(crate) struct Node {
98    pub title: NodeName,
99    pub extra: HashMap<String, String>,
100    pub steps: Vec<Step>,
101    pub visited: bool,
102}
103
104struct Conversation {
105    node: NodeName,
106    base_index: usize,
107    indexes: Vec<StepIndex>,
108}
109
110impl Conversation {
111    fn new(node: NodeName) -> Conversation {
112        Conversation {
113            node,
114            base_index: 0,
115            indexes: vec![],
116        }
117    }
118
119    fn reset(&mut self, name: NodeName) {
120        *self = Conversation::new(name);
121    }
122}
123
124#[derive(Copy, Clone)]
125enum StepIndex {
126    Dialogue(usize, usize),
127    If(usize),
128    ElseIf(usize, usize),
129    Else(usize),
130}
131
132impl StepIndex {
133    fn advance(&mut self) {
134        let idx = match *self {
135            StepIndex::Dialogue(_, ref mut idx) |
136            StepIndex::If(ref mut idx) |
137            StepIndex::ElseIf(_, ref mut idx) |
138            StepIndex::Else(ref mut idx) => idx,
139        };
140        *idx += 1;
141    }
142}
143
144#[derive(PartialEq)]
145enum ExecutionStatus {
146    Continue,
147    Halt,
148}
149
150/// A primitive value .
151#[derive(Clone)]
152pub enum Value {
153    /// A string value.
154    String(String),
155    /// A floating point value.
156    Number(f32),
157    /// A boolean value.
158    Boolean(bool),
159    //TODO: null
160}
161
162impl PartialEq for Value {
163    fn eq(&self, other: &Value) -> bool {
164        match (self, other) {
165            (&Value::String(ref s1), ref v) => s1 == &v.as_string(),
166            (ref v, &Value::String(ref s2)) => s2 == &v.as_string(),
167            (&Value::Number(f1), ref v) => f1 == v.as_num(),
168            (ref v, &Value::Number(f2)) => f2 == v.as_num(),
169            (&Value::Boolean(b1), &Value::Boolean(b2)) => b1 == b2,
170        }
171    }
172}
173
174impl Add for Value {
175    type Output = Value;
176    fn add(self, other: Value) -> Value {
177        match (self, other) {
178            (Value::String(s1), v) =>
179                Value::String(format!("{}{}", s1, v.as_string())),
180            (v, Value::String(s2)) =>
181                Value::String(format!("{}{}", v.as_string(), s2)),
182            (Value::Number(f1), v) =>
183                Value::Number(f1 + v.as_num()),
184            (v, Value::Number(f2)) =>
185                Value::Number(v.as_num() + f2),
186            (v1, v2) =>
187                Value::Number(v1.as_num() + v2.as_num()),
188        }
189    }
190}
191
192impl Sub for Value {
193    type Output = Value;
194    fn sub(self, other: Value) -> Value {
195        Value::Number(self.as_num() - other.as_num())
196    }
197}
198
199impl Mul for Value {
200    type Output = Value;
201    fn mul(self, other: Value) -> Value {
202        Value::Number(self.as_num() * other.as_num())
203    }
204}
205
206impl Div for Value {
207    type Output = Value;
208    fn div(self, other: Value) -> Value {
209        Value::Number(self.as_num() / other.as_num())
210    }
211}
212
213impl Value {
214    /// The contained value represented as a string.
215    pub fn as_string(&self) -> String {
216        match *self {
217            Value::Boolean(b) => b.to_string(),
218            Value::String(ref s) => (*s).clone(),
219            Value::Number(f) => f.to_string(),
220        }
221    }
222
223    /// The contained value represented as a boolean.
224    /// If not already a boolean, true if a non-empty string or non-zero number, false otherwise.
225    fn as_bool(&self) -> bool {
226        match *self {
227            Value::Boolean(b) => b,
228            Value::String(ref s) => !s.is_empty(),
229            Value::Number(f) => f != 0.0,
230        }
231    }
232
233    /// The contained value represented as a floating point number.
234    /// If not already a number, 0 if a string, 0 or 1 if a boolean.
235    fn as_num(&self) -> f32 {
236        match *self {
237            Value::Boolean(b) => b as isize as f32,
238            Value::String(ref _s) => 0.,
239            Value::Number(f) => f,
240        }
241    }
242}
243
244struct Function {
245    num_args: usize,
246    callback: Box<FunctionCallback>,
247}
248
249/// A closure that will be invoked when a particular function is called in a Yarn expression.
250pub type FunctionCallback = Fn(Vec<Value>, &Nodes) -> Result<Value, ()>;
251
252/// The engine that stores all conversation-related state.
253pub struct YarnEngine {
254    handler: Box<YarnHandler>,
255    state: NodeState,
256    engine_state: EngineState,
257}
258
259struct EngineState {
260    variables: Variables,
261    functions: HashMap<String, Function>,
262}
263
264impl EngineState {
265    fn evaluate(&self, expr: &Expr, state: &Nodes) -> Result<Value, ()> {
266        match expr {
267            Expr::Parentheses(expr) => self.evaluate(expr, state),
268            Expr::Term(Term::Number(f)) => Ok(Value::Number(*f)),
269            Expr::Term(Term::Boolean(b)) => Ok(Value::Boolean(*b)),
270            Expr::Term(Term::String(ref s)) => Ok(Value::String((*s).clone())),
271            Expr::Term(Term::Variable(ref n)) => {
272                self.variables.0.get(n).cloned().ok_or(())
273            }
274            Expr::Term(Term::Function(ref name, ref args)) => {
275                let mut eval_args = vec![];
276                for arg in args {
277                    let v = self.evaluate(arg, state)?;
278                    eval_args.push(v);
279                }
280                let f = self.functions.get(name).ok_or(())?;
281                if f.num_args != args.len() {
282                    return Err(());
283                }
284                (f.callback)(eval_args, state)
285            }
286
287            Expr::Unary(UnaryOp::Not, expr) =>
288                self.evaluate(expr, state).map(|v| Value::Boolean(!v.as_bool())),
289            Expr::Unary(UnaryOp::Negate, expr) =>
290                self.evaluate(expr, state).map(|v| Value::Number(-v.as_num())),
291
292            Expr::Binary(BinaryOp::And, left, right) => {
293                let left = self.evaluate(left, state)?.as_bool();
294                let right = self.evaluate(right, state)?.as_bool();
295                Ok(Value::Boolean(left && right))
296            }
297            Expr::Binary(BinaryOp::Or, left, right) => {
298                let left = self.evaluate(left, state)?.as_bool();
299                let right = self.evaluate(right, state)?.as_bool();
300                Ok(Value::Boolean(left || right))
301            }
302
303            Expr::Binary(BinaryOp::Plus, left, right) => {
304                let left = self.evaluate(left, state)?;
305                let right = self.evaluate(right, state)?;
306                Ok(left + right)
307            }
308            Expr::Binary(BinaryOp::Minus, left, right) => {
309                let left = self.evaluate(left, state)?;
310                let right = self.evaluate(right, state)?;
311                Ok(left - right)
312            }
313            Expr::Binary(BinaryOp::Multiply, left, right) => {
314                let left = self.evaluate(left, state)?;
315                let right = self.evaluate(right, state)?;
316                Ok(left * right)
317            }
318            Expr::Binary(BinaryOp::Divide, left, right) => {
319                let left = self.evaluate(left, state)?;
320                let right = self.evaluate(right, state)?;
321                Ok(left / right)
322            }
323
324            Expr::Binary(BinaryOp::Equals, left, right) => {
325                let left = self.evaluate(left, state)?;
326                let right = self.evaluate(right, state)?;
327                Ok(Value::Boolean(left == right))
328            }
329            Expr::Binary(BinaryOp::NotEquals, left, right) => {
330                let left = self.evaluate(left, state)?;
331                let right = self.evaluate(right, state)?;
332                Ok(Value::Boolean(!(left == right)))
333            }
334
335            Expr::Binary(BinaryOp::GreaterThan, left, right) => {
336                let left = self.evaluate(left, state)?;
337                let right = self.evaluate(right, state)?;
338                Ok(Value::Boolean(left.as_num() > right.as_num()))
339            }
340            Expr::Binary(BinaryOp::GreaterThanEqual, left, right) => {
341                let left = self.evaluate(left, state)?;
342                let right = self.evaluate(right, state)?;
343                Ok(Value::Boolean(left.as_num() >= right.as_num()))
344            }
345            Expr::Binary(BinaryOp::LessThan, left, right) => {
346                let left = self.evaluate(left, state)?;
347                let right = self.evaluate(right, state)?;
348                Ok(Value::Boolean(left.as_num() < right.as_num()))
349            }
350            Expr::Binary(BinaryOp::LessThanEqual, left, right) => {
351                let left = self.evaluate(left, state)?;
352                let right = self.evaluate(right, state)?;
353                Ok(Value::Boolean(left.as_num() <= right.as_num()))
354            }
355        }
356    }
357}
358
359/// A collection of Yarn nodes.
360pub struct Nodes(HashMap<NodeName, Node>);
361
362struct NodeState {
363    nodes: Nodes,
364    conversation: Option<Conversation>,
365}
366
367impl NodeState {
368    fn take_conversation(&mut self) -> Conversation {
369        self.conversation.take().expect("missing conversation")
370    }
371
372    fn get_current_step(&self, conversation: &Conversation) -> (&Vec<Step>, usize) {
373        let mut steps = {
374            let current = self.nodes.0.get(&conversation.node).expect("missing node");
375            &current.steps
376        };
377        let mut current_step_index = conversation.base_index;
378
379        for index in conversation.indexes.iter() {
380            match (&steps[current_step_index], *index) {
381                (&Step::Dialogue(_, ref choices), StepIndex::Dialogue(choice, step_index)) => {
382                    let choice = &choices[choice];
383                    match choice.kind {
384                        ChoiceKind::Inline(ref choice_steps, _) => {
385                            steps = choice_steps;
386                            current_step_index = step_index;
387                        }
388                        ChoiceKind::External(..) => unreachable!(),
389                    }
390                }
391                (&Step::Conditional(_, ref if_steps, ..), StepIndex::If(step_index)) => {
392                    steps = if_steps;
393                    current_step_index = step_index;
394                }
395                (&Step::Conditional(_, _, ref else_ifs, ..), StepIndex::ElseIf(index, step_index)) => {
396                    steps = &else_ifs[index].1;
397                    current_step_index = step_index;
398                }
399                (&Step::Conditional(_, _, _, ref else_steps), StepIndex::Else(step_index)) => {
400                    steps = else_steps;
401                    current_step_index = step_index;
402                }
403                _ => unreachable!(),
404            }
405        }
406
407        (steps, current_step_index)
408    }
409}
410
411impl YarnEngine {
412    /// Create a new YarnEngine instance associated with the given handler.
413    pub fn new(handler: Box<YarnHandler>) -> YarnEngine {
414        let mut engine = YarnEngine {
415            state: NodeState {
416                nodes: Nodes(HashMap::new()),
417                conversation: None,
418            },
419            engine_state: EngineState {
420                variables: Variables(HashMap::new()),
421                functions: HashMap::new(),
422            },
423            handler,
424        };
425
426        // Define built-in functions.
427        engine.register_function("visited".to_string(), 1, Box::new(|args, state| {
428            match args[0] {
429                Value::String(ref s) => {
430                        state
431                        .0
432                        .get(&NodeName(s.to_string()))
433                        .map(|node| Value::Boolean(node.visited)).ok_or(())
434                }
435                _ => return Err(())
436            }
437        }));
438
439        engine
440    }
441
442    /// Parse the provided string as a series of Yarn nodes, appending the results to
443    /// the internal node storage. Returns Ok if parsing succeeded, Err otherwise.
444    pub fn load_from_string(&mut self, s: &str) -> Result<(), ()> {
445        let nodes = parse::parse_nodes_from_string(s)?;
446        for node in nodes {
447            self.state.nodes.0.insert(node.title.clone(), node);
448        }
449        Ok(())
450    }
451
452    /// Register a native function for use in Yarn expressions.
453    pub fn register_function(
454        &mut self,
455        name: String,
456        num_args: usize,
457        callback: Box<FunctionCallback>,
458    ) {
459        self.engine_state.functions.insert(name, Function {
460            num_args: num_args,
461            callback,
462        });
463    }
464
465    /// Set a given variable to the provided value. Any Yarn expressions evaluated
466    /// after this call will observe the new value when using the variable.
467    pub fn set_variable(
468        &mut self,
469        name: VariableName,
470        value: Value
471    ) {
472        self.engine_state.variables.set(name, value);
473    }
474
475    /// Begin evaluating the provided Yarn node.
476    pub fn activate(&mut self, node: NodeName) {
477        //TODO: mark visited
478        self.state.conversation = Some(Conversation::new(node));
479        self.proceed();
480    }
481
482    /// Make a choice between a series of options for the current Yarn node's active step.
483    /// Execution will resume immediately based on the choice provided.
484    pub fn choose(&mut self, choice: usize) {
485        let conversation = {
486            let mut conversation = self.state.take_conversation();
487            let (steps, current_step_index) = self.state.get_current_step(&conversation);
488            match &steps[current_step_index] {
489                &Step::Dialogue(_, ref choices) => {
490                    match choices[choice].kind {
491                        ChoiceKind::External(ref node) => conversation.reset((*node).clone()),
492                        ChoiceKind::Inline(..) => {
493                            conversation.indexes.push(StepIndex::Dialogue(choice, 0));
494                        }
495                    }
496                }
497                &Step::Command(..) | &Step::Assign(..) | &Step::Conditional(..) | &Step::Jump(..) =>
498                    unreachable!(),
499            }
500            conversation
501        };
502        self.state.conversation = Some(conversation);
503        self.proceed();
504    }
505
506    /// Resume execution of the current Yarn node.
507    pub fn proceed(&mut self) {
508        while self.proceed_one_step() == ExecutionStatus::Continue {
509        }
510    }
511
512    fn do_proceed_one_step(&mut self) -> (Conversation, ExecutionStatus) {
513        let mut conversation = self.state.take_conversation();
514        let (steps, current_step_index) = self.state.get_current_step(&conversation);
515
516        if current_step_index >= steps.len() {
517            self.handler.end_conversation();
518            return (conversation, ExecutionStatus::Halt);
519        }
520
521        let (advance, execution_status) = match steps[current_step_index] {
522            Step::Dialogue(ref text, ref choices) => {
523                if choices.is_empty() {
524                    self.handler.say(text.clone());
525                    (true, ExecutionStatus::Halt)
526                } else {
527                    //TODO: conditional options
528                    self.handler.choose(text.clone(), choices.iter().map(|c| c.text.clone()).collect());
529                    (false, ExecutionStatus::Halt)
530                }
531            }
532            Step::Command(ref command) => {
533                self.handler.command(command.clone()).unwrap();
534                (true, ExecutionStatus::Continue)
535            }
536            Step::Assign(ref name, ref expr) => {
537                let value = self.engine_state.evaluate(expr, &self.state.nodes).unwrap();
538                self.engine_state.variables.set((*name).clone(), value);
539                (true, ExecutionStatus::Continue)
540            }
541            Step::Conditional(ref expr, ref _if_steps, ref else_ifs, ref _else_steps) => {
542                let value = self.engine_state.evaluate(expr, &self.state.nodes).unwrap();
543                if value.as_bool() {
544                    conversation.indexes.push(StepIndex::If(0));
545                } else {
546                    let mut matched = false;
547                    for (else_if_index, else_ifs) in else_ifs.iter().enumerate() {
548                        let value = self.engine_state.evaluate(&else_ifs.0, &self.state.nodes).unwrap();
549                        if value.as_bool() {
550                            conversation.indexes.push(StepIndex::ElseIf(else_if_index, 0));
551                            matched = true;
552                            break;
553                        }
554                    }
555                    if !matched {
556                        conversation.indexes.push(StepIndex::Else(0));
557                    }
558                }
559                (false, ExecutionStatus::Continue)
560            }
561            Step::Jump(ref name) => {
562                //TODO: mark visited
563                conversation.reset((*name).clone());
564                (false, ExecutionStatus::Continue)
565            }
566        };
567
568        if advance {
569            match conversation.indexes.last_mut() {
570                Some(index) => index.advance(),
571                None => conversation.base_index += 1,
572            }
573        }
574
575        (conversation, execution_status)
576    }
577
578    fn proceed_one_step(&mut self) -> ExecutionStatus {
579        let (conversation, execution_status) = self.do_proceed_one_step();
580
581        self.state.conversation = Some(conversation);
582
583        execution_status
584    }
585}
586
587/// A handler for Yarn actions that require integration with the embedder.
588/// Invoked synchronously during Yarn execution when matching steps are
589/// evaluated.
590pub trait YarnHandler {
591    /// Present a line of dialogue without any choices. Execution will not
592    /// resume until `YarnEngine::proceed` is invoked.
593    fn say(&mut self, text: String);
594    /// Present a line of dialogue with subsequent choices. Execution will not
595    /// resume until `YarnEngine::choose` is invoked.
596    fn choose(&mut self, text: String, choices: Vec<String>);
597    /// Instruct the embedder to perform some kind of action. The given action
598    /// string is passed unmodified from the node source.
599    fn command(&mut self, action: String) -> Result<(), ()>;
600    /// End the current conversation. Execution will not resume until a new
601    /// node is made active with `YarnEngine::activate`.
602    fn end_conversation(&mut self);
603}