scriptful/core/
machine.rs

1use crate::core::ScriptRef;
2use crate::prelude::*;
3use core::marker::PhantomData;
4
5/// A convenient wrapper around [`Stack`][Stack] providing multiple operation methods, i.e.
6/// xecuting scripts by evaluating operators and pushing values into the stack.
7///
8/// This is the preferred way to interact with [`Stack`s][Stack], as they do not support operators,
9/// [`Item`s][Item], and other abstractions.
10///
11/// [Stack]: ../stack/struct.Stack.html
12/// [Item]: ../item/enum.Item.html
13pub struct Machine<Op, Val, F, E>
14where
15    Val: core::fmt::Debug + core::cmp::PartialEq,
16    F: FnMut(&mut Stack<Val>, &Op, &mut ConditionStack) -> Result<(), E>,
17{
18    op_sys: F,
19    stack: Stack<Val>,
20    if_stack: ConditionStack,
21    phantom_op: PhantomData<fn(&Op)>,
22}
23
24impl<Op, Val, F, E> Machine<Op, Val, F, E>
25where
26    Op: core::fmt::Debug + core::cmp::Eq,
27    Val: core::fmt::Debug + core::cmp::PartialEq + core::clone::Clone,
28    F: FnMut(&mut Stack<Val>, &Op, &mut ConditionStack) -> Result<(), E>,
29{
30    /// A simple factory that helps constructing a `Machine` around a existing operator system, be
31    /// it user defined or any of the ones in the [`op_systems`][op_systems] module.
32    ///
33    /// This method initializes the internal stack to be empty.
34    ///
35    /// [op_systems]: ../../op_systems/
36    ///
37    /// # Examples
38    ///
39    /// ```rust
40    /// use scriptful::prelude::*;
41    /// use scriptful::op_systems::simple_math::simple_math_op_sys;
42    ///
43    /// // Instantiate the machine with a reference to your operator system, or any of the ones in
44    /// // the `op_systems` module.
45    /// let machine = Machine::new(&simple_math_op_sys);
46    ///
47    /// // Make sure the stack is initialized to be empty.
48    /// assert_eq!(machine.stack_length(), 0);
49    /// ```
50    pub fn new(op_sys: F) -> Self {
51        Self {
52            op_sys,
53            stack: Stack::<Val>::default(),
54            if_stack: ConditionStack::default(),
55            phantom_op: PhantomData,
56        }
57    }
58
59    /// The simplest way to make a `Machine` evaluate a single [`Item`][Item], be it a `Value` or
60    /// `Operator`.
61    ///
62    /// Note that the preferred way to evaluate multiple [`Item`s][Item] at once is through the
63    /// [`run_script`][run_script] method, which instead of single [`Item`s][Item] takes a
64    /// [`Script`][Script], i.e. an array of [`Item`s][Item].
65    ///
66    /// # Panics
67    ///
68    /// Operating on a `Machine` that has an empty [`Stack`][Stack] can cause a panic if the
69    /// [`Item`][Item] is an operator that tries to pop from it.
70    ///
71    /// # Examples
72    ///
73    /// ```rust
74    /// use scriptful::prelude::*;
75    /// use scriptful::core::value::Value::*;
76    /// use scriptful::op_systems::simple_math::*;
77    ///
78    /// // Instantiate the machine with a reference to your operator system, or any of the ones in
79    /// // the `op_systems` module.
80    /// let mut machine = Machine::new(&simple_math_op_sys);
81    ///
82    /// // Operating a `Value::Integer(1)` should simply push it into the stack.
83    /// let result = machine.operate(&Item::Value(Integer(1)));
84    /// // Make sure the value gets pushed.
85    /// assert_eq!(result, Some(&Integer(1)));
86    /// // The length of the stack should be 1.
87    /// assert_eq!(machine.stack_length(), 1);
88    ///
89    /// // Operating a `Value::Integer(2)` should simply push it into the stack.
90    /// let result = machine.operate(&Item::Value(Integer(2)));
91    /// // Make sure the value gets pushed.
92    /// assert_eq!(result, Some(&Integer(2)));
93    /// // The length of the stack should be 2.
94    /// assert_eq!(machine.stack_length(), 2);
95    ///
96    /// // Operating an `MathOperator::Add` should pop the two topmost values in the stack, add them
97    /// // together, and push the result back into the stack.
98    /// let result = machine.operate(&Item::Operator(MathOperator::Add));
99    /// // Make sure the result is 3.
100    /// assert_eq!(result, Some(&Integer(3)));
101    /// // The final length of the stack should be 1 again.
102    /// assert_eq!(machine.stack_length(), 1);
103    /// ```
104    ///
105    /// [Item]: ../item/enum.Item.html
106    /// [run_script]: #method.run_script
107    /// [Script]: ../type.Script.html
108    /// [Stack]: ../stack/struct.Stack.html
109    pub fn operate(&mut self, item: &Item<Op, Val>) -> Result<Option<&Val>, E> {
110        match item {
111            Item::Operator(operator) => {
112                (self.op_sys)(&mut self.stack, operator, &mut self.if_stack)
113            }
114            Item::Value(value) => {
115                if self.if_stack.all_true() {
116                    self.stack.push((*value).clone());
117                }
118
119                Ok(())
120            }
121        }
122        .map(|()| self.stack.topmost())
123    }
124
125    /// Evaluates a [`Script`][Script] in the context of a `Machine`.
126    ///
127    /// # Panics
128    ///
129    /// Operating on a `Machine` that has an empty [`Stack`][Stack] can cause a panic if any of the
130    /// [`Item`s][Item] in the [`Script`][Script] is an operator that tries to pop from it.
131    ///
132    /// # Examples
133    ///
134    /// ```rust
135    /// use scriptful::prelude::*;
136    /// use scriptful::core::value::Value::*;
137    /// use scriptful::op_systems::simple_math::*;
138    ///
139    /// // Instantiate the machine with a reference to your operator system, or any of the ones in
140    /// // the `op_systems` module.
141    /// let mut machine = Machine::new(&simple_math_op_sys);
142    ///
143    /// // Run a script that simply adds 1 and 2.
144    /// let result = machine.run_script(&Vec::from([
145    ///    Item::Value(Integer(1)),
146    ///    Item::Value(Integer(2)),
147    ///    Item::Operator(MathOperator::Add),
148    /// ]));
149    ///
150    /// // The result should unsurprisingly be 3.
151    /// assert_eq!(result, Some(&Integer(3)));
152    /// // The final length of the stack should be 1.
153    /// assert_eq!(machine.stack_length(), 1);
154    /// ```
155    ///
156    /// [Script]: ../type.Script.html
157    /// [Stack]: ../stack/struct.Stack.html
158    /// [Item]: ../item/enum.Item.html
159    pub fn run_script(&mut self, script: ScriptRef<Op, Val>) -> Result<Option<&Val>, E> {
160        for item in script {
161            self.operate(item)?;
162        }
163
164        Ok(self.stack.topmost())
165    }
166
167    /// Returns the number of [`Value`s][Value] currently in the [`Stack`][Stack].
168    ///
169    /// # Examples
170    ///
171    /// ```rust
172    /// use scriptful::prelude::*;
173    /// use scriptful::core::value::Value::*;
174    /// use scriptful::op_systems::simple_math::*;
175    ///
176    /// // Instantiate the machine with a reference to your operator system, or any of the ones in
177    /// // the `op_systems` module.
178    /// let mut machine = Machine::new(&simple_math_op_sys);
179    ///
180    /// // Run a script that simply pushes 4 values into the stack.
181    /// machine.run_script(&Vec::from([
182    ///     Item::Value(Boolean(true)),
183    ///     Item::Value(Float(3.141592)),
184    ///     Item::Value(Integer(1337)),
185    ///     Item::Value(String("foo".into()))
186    /// ]));
187    ///
188    /// // The final length of the stack should be 4.
189    /// assert_eq!(machine.stack_length(), 4);
190    /// ```
191    ///
192    /// [Value]: ../value/enum.Value.html
193    /// [Stack]: ../stack/struct.Stack.html
194    pub fn stack_length(&self) -> usize {
195        self.stack.length()
196    }
197}
198
199/// Debugging of `Machine` only shows the internal [`Stack`][Stack], but not the operator system.
200///
201/// The explanation for this is straightforward: how do you print a dynamic reference to a function?
202///
203/// [Stack]: ../stack/struct.Stack.html
204impl<Op, Val, F, E> core::fmt::Debug for Machine<Op, Val, F, E>
205where
206    Op: core::fmt::Debug + core::cmp::Eq,
207    Val: core::fmt::Debug + core::cmp::PartialEq + core::clone::Clone,
208    F: FnMut(&mut Stack<Val>, &Op, &mut ConditionStack) -> Result<(), E>,
209{
210    fn fmt(&self, f: &mut core::fmt::Formatter) -> Result<(), core::fmt::Error> {
211        f.debug_struct("Machine")
212            .field("stack", &self.stack)
213            .field("if_stack", &self.if_stack)
214            .finish()
215    }
216}