1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
use std::collections::HashMap;
use thiserror::Error;

mod ast;
use ast::intern::Interner;
use ast::NodeLabel;

pub mod event;
use event::Event;

pub trait Context {
    fn get_var(&self, var: NodeLabel) -> Option<ast::Value>;
    fn set_var(&mut self, var: NodeLabel, val: ast::Value);
}

impl Context for HashMap<NodeLabel, ast::Value> {
    fn get_var(&self, var: NodeLabel) -> Option<ast::Value> { 
        self.get(&var).map(|v| v.clone())   
    }
    fn set_var(&mut self, var: NodeLabel, val: ast::Value) {
        self.insert(var, val);
    }
}

enum Wait {
    // Input,
    // Deadline( std::time::Instant),
    Nothing,
}

pub struct DialogRunner<C: Context = HashMap<NodeLabel, ast::Value>> {
    string_table: Interner,
    
    nodes: HashMap<NodeLabel, ast::Node>,
    functions: HashMap<NodeLabel, (usize, Box<dyn FnMut(&mut C, &[&str])>)>, 
    _ctx: C,

    curr_node: Option<NodeLabel>,
    curr_line: usize,
    _waiting: Wait,
}

impl DialogRunner {
    pub fn new() -> Self {
        Self::with_context(HashMap::new())
    }
}

impl<C: Context> DialogRunner<C> {
    pub fn with_context(_ctx: C) -> Self {
        DialogRunner {
            string_table: Interner::with_capacity(128),
            nodes: HashMap::new(),
            functions: HashMap::new(),
            _ctx,

            curr_node: None,
            curr_line: 0,
            _waiting: Wait::Nothing,
        }
    }

    pub fn add_function<F>(&mut self, name: &str, args: usize, func: F) 
        where F: FnMut(&mut C, &[&str]) + 'static
    {
        let name = self.string_table.intern(name);
        self.functions.insert(name, (args, Box::new(func)));
    }

    pub fn load_yarn_file<'src>(&mut self, text: &'src str) -> Result<(), Error<'src>> {
        let mut start = 0;
        
        loop {
            let node = ast::node(&text[start..], &mut self.string_table);
            match node {
                Ok((node, pos)) => {
                    start += pos;
                    let title = node.title();
                    self.nodes.insert(title, node);
                    if self.curr_node.is_none() {
                        self.curr_node = Some(title);
                    }
                }
                Err(e) => {
                    if e.1 == "empty" {
                        break;
                    }

                    let line = text[start..].lines().nth(e.0 as usize);
                    return Err(Error::Syntax(e.0, line.unwrap(), e.1));
                }
            }

        }

        Ok(())
    }

    pub fn next_event(&mut self) -> Option<Event> {
        if let Some(s) = self.curr_node {
            self.nodes.get(&s).map(|node|{
                let line = &node.lines[self.curr_line];

                let author = line.label.map(|s| {
                    self.string_table.lookup(s).to_string()
                });

                Event::Line(author, line.line.clone())
            })
        } else {
            None
        }
    }
}

#[derive(Error, Debug)]
pub enum Error<'a> {
    #[error("Syntax Error:\n\n{0} | {1}\n\t-- {2}")]
    Syntax(u32, &'a str, &'static str),
}




#[cfg(test)]
mod tests {
    #[test]
    fn it_works() {
        assert_eq!(2 + 2, 4);
    }
}