titleformat_rs/
program.rs

1use crate::environment::{value_string, Environment, Value};
2use crate::parser;
3use crate::types::Error;
4use crate::types::Expr;
5use crate::types::Expr::*;
6use std::collections::HashMap;
7
8#[derive(Debug, Default)]
9pub struct Program {
10    instr: Vec<Expr>,
11}
12
13impl Program {
14    /// Constructs a new Program
15    ///
16    /// # Examples
17    /// ```
18    /// # use titleformat_rs::program::Program;
19    /// let program = Program::new();
20    /// ```
21    pub fn new() -> Self {
22        Program { instr: vec![] }
23    }
24
25    /// Parses a program string
26    ///
27    /// # Examples
28    /// ```
29    /// # use titleformat_rs::program::Program;
30    /// let mut program = Program::new();
31    /// assert_eq!(program.parse("[%artist%]").unwrap(), ());
32    /// ```
33    pub fn parse(&mut self, instr: &str) -> Result<(), Error> {
34        self.instr = parser::parse(instr)?;
35        Ok(())
36    }
37
38    /// Executes a program without any metadata
39    ///
40    /// # Examples
41    /// ```
42    /// # use titleformat_rs::program::Program;
43    /// let mut program = Program::new();
44    /// assert_eq!(program.parse("[%artist%]").unwrap(), ());
45    /// assert_eq!(program.run().unwrap(), String::from(""));
46    /// ```
47    pub fn run(&mut self) -> Result<String, Error> {
48        self.run_with_meta(HashMap::new())
49    }
50
51    /// Executes a program with associated metadata
52    ///
53    /// # Examples
54    /// ```
55    /// # use titleformat_rs::program::Program;
56    /// # use std::collections::HashMap;
57    /// let mut program = Program::new();
58    /// assert_eq!(program.parse("[%artist%]").unwrap(), ());
59    /// let mut metadata = HashMap::new();
60    /// metadata.insert("artist".into(), vec!["Happy".into()]);
61    /// assert_eq!(program.run_with_meta(metadata).unwrap(), String::from("Happy"));
62    /// ```
63    pub fn run_with_meta(&self, metadata: HashMap<String, Vec<String>>) -> Result<String, Error> {
64        let mut env = Environment::new(metadata);
65        let result = match self.resolve_arg_vec(&mut env, &self.instr)? {
66            ExprValue(v) => v.val.clone(),
67            _ => unreachable!(),
68        };
69        Ok(result)
70    }
71
72    /* resolves a set of expressions into a single resolved value
73     * e.g. '%artist%literal' with artist=best would resolve to 'bestliteral' */
74    fn resolve_arg_vec(&self, env: &mut Environment, args: &Vec<Expr>) -> Result<Expr, Error> {
75        let mut new_arg = value_string("", false);
76
77        for arg in args {
78            let tmp = self.eval(env, arg)?;
79            new_arg.val = new_arg.val + &tmp.val;
80            /* picard does an or here */
81            new_arg.cond = new_arg.cond || tmp.cond;
82        }
83
84        Ok(ExprValue(new_arg))
85    }
86
87    fn eval(&self, env: &mut Environment, expr: &Expr) -> Result<Value, Error> {
88        match expr {
89            ExprValue(v) => Ok(v.clone()),
90            /* literals are always true for conditionals */
91            Literal(v) => Ok(value_string(v, true)),
92            Variable(var) => Ok(env.get_variable(var)),
93            Conditional(args) => {
94                let arg = self.resolve_arg_vec(env, args)?;
95                match self.eval(env, &arg)? {
96                    Value { val: v, cond: true } => Ok(value_string(&v, true)),
97                    _ => Ok(value_string("", false)),
98                }
99            }
100            FuncCall(name, args) => {
101                let mut evaluated_args = Vec::new();
102                for unresolved in args {
103                    let resolved = self.resolve_arg_vec(env, unresolved)?;
104                    let new_arg = self.eval(env, &resolved)?;
105                    evaluated_args.push(new_arg);
106                }
107                Ok(env.call(name, evaluated_args)?)
108            }
109        }
110    }
111}
112
113#[cfg(test)]
114mod tests {
115    use super::*;
116
117    #[test]
118    fn test_parse() {
119        let mut prog = Program::new();
120        prog.parse("%a%").unwrap();
121    }
122
123    #[test]
124    fn test_multi_parse() {
125        let mut prog = Program::new();
126        prog.parse("%a%").unwrap();
127        prog.parse("%b%").unwrap();
128    }
129
130    #[test]
131    fn test_run_empty() {
132        let mut prog = Program::new();
133        prog.parse("").unwrap();
134        assert_eq!(prog.run().unwrap(), String::from(""));
135    }
136
137    #[test]
138    fn test_run() {
139        let mut prog = Program::new();
140        prog.parse("%a%").unwrap();
141        let mut m = HashMap::new();
142        m.insert(String::from("a"), vec![String::from("val")]);
143        assert_eq!(prog.run_with_meta(m).unwrap(), String::from("val"));
144    }
145
146    #[test]
147    fn test_run_unknown_variable() {
148        let mut prog = Program::new();
149        prog.parse("%unknown%").unwrap();
150        assert_eq!(prog.run().unwrap(), String::from("?"));
151    }
152
153    #[test]
154    fn test_run_func() {
155        let mut prog = Program::new();
156        prog.parse("$add(2,2)").unwrap();
157        assert_eq!(prog.run().unwrap(), String::from("4"));
158    }
159
160    #[test]
161    fn test_run_func_variable() {
162        let mut prog = Program::new();
163        prog.parse("$add(%a%,2)").unwrap();
164        let mut m = HashMap::new();
165        m.insert(String::from("a"), vec![String::from("2")]);
166        assert_eq!(prog.run_with_meta(m).unwrap(), String::from("4"));
167    }
168
169    #[test]
170    fn test_run_func_func() {
171        let mut prog = Program::new();
172        prog.parse("$add($add(1,1),2)").unwrap();
173        assert_eq!(prog.run().unwrap(), String::from("4"));
174    }
175
176    #[test]
177    fn test_multi_run() {
178        let mut prog = Program::new();
179        prog.parse("$add(%a%,2)").unwrap();
180        let mut m = HashMap::new();
181        m.insert(String::from("a"), vec![String::from("2")]);
182        assert_eq!(prog.run_with_meta(m.clone()).unwrap(), String::from("4"));
183        assert_eq!(prog.run_with_meta(m).unwrap(), String::from("4"));
184    }
185
186    #[test]
187    fn test_run_conditional_variable_exists() {
188        let mut prog = Program::new();
189        prog.parse("[%a%]").unwrap();
190        let mut m = HashMap::new();
191        m.insert(String::from("a"), vec![String::from("val")]);
192        assert_eq!(prog.run_with_meta(m).unwrap(), String::from("val"));
193    }
194
195    #[test]
196    fn test_run_conditional_variable_nonexistent() {
197        let mut prog = Program::new();
198        prog.parse("[%a%]").unwrap();
199        assert_eq!(prog.run().unwrap(), String::from(""));
200    }
201
202    #[test]
203    fn test_run_conditional_function_variable_exists() {
204        let mut prog = Program::new();
205        prog.parse("[$add(%a%,2)]").unwrap();
206        let mut m = HashMap::new();
207        m.insert(String::from("a"), vec![String::from("2")]);
208        assert_eq!(prog.run_with_meta(m).unwrap(), String::from("4"));
209    }
210
211    #[test]
212    fn test_run_conditional_function_variable_nonexistent() {
213        let mut prog = Program::new();
214        prog.parse("[$add(%a%,2)]").unwrap();
215        assert_eq!(prog.run().unwrap(), String::from(""));
216    }
217
218    #[test]
219    fn test_conditional_variable_literal() {
220        let mut prog = Program::new();
221        prog.parse("[%a%b]").unwrap();
222        let mut m = HashMap::new();
223        m.insert(String::from("a"), vec![String::from("2")]);
224        assert_eq!(prog.run_with_meta(m).unwrap(), String::from("2b"));
225    }
226}