turing_lib/
lib.rs

1mod instruction;
2mod output;
3mod turing;
4mod warnings;
5
6use std::{borrow::Cow, collections::HashMap};
7
8pub use instruction::{Movement, TuringInstruction};
9pub use output::TuringOutput;
10use pest::Parser;
11use serde::{Deserialize, Serialize};
12pub use turing::{Rule, TuringMachine, TuringParser};
13pub use warnings::{CompilerError, CompilerWarning, ErrorPosition};
14
15#[derive(Debug, Clone, Serialize, Deserialize)]
16pub struct Library {
17    pub name: Cow<'static, str>,
18    pub description: Cow<'static, str>,
19    pub initial_state: Cow<'static, str>,
20    pub final_state: Cow<'static, str>,
21    pub used_states: Cow<'static, [Cow<'static, str>]>,
22    pub code: Cow<'static, str>,
23}
24
25impl Library {
26    pub fn get_instructions(
27        &self,
28    ) -> Result<HashMap<(String, bool), TuringInstruction>, CompilerError> {
29        let mut instructions: HashMap<(String, bool), TuringInstruction> = HashMap::new();
30
31        let file = match TuringParser::parse(Rule::instructions, self.code.as_ref()) {
32            Ok(mut f) => f.next().unwrap(),
33            Err(e) => panic!("{}", e),
34        };
35
36        for record in file.into_inner() {
37            let tmp = match TuringInstruction::from(record.into_inner()) {
38                Ok(i) => i,
39                Err(e) => return Err(e),
40            };
41            instructions.insert((tmp.from_state.clone(), tmp.from_value), tmp.clone());
42        }
43
44        Ok(instructions)
45    }
46}
47
48/// Array of all the libraries that are included in the compiler.
49/// # List of Libraries
50///
51/// ## sum
52/// Adds two numbers together.
53///
54/// ## x2
55/// Duplicates a number.
56///
57/// ## mod
58/// Calculates the modulo of two numbers.
59///
60/// ## div2
61/// Divides a number by two.
62///
63/// ## bound_diff
64/// Calculates the difference between two numbers, but the result is always positive.
65pub const LIBRARIES: [Library; 5] = [
66    Library {
67        name: Cow::Borrowed("sum"),
68        description: Cow::Borrowed("x + y"),
69        initial_state: Cow::Borrowed("q0"),
70        final_state: Cow::Borrowed("q2"),
71        used_states: Cow::Borrowed(&[
72            Cow::Borrowed("q0"),
73            Cow::Borrowed("q1"),
74            Cow::Borrowed("q2"),
75        ]),
76        code: Cow::Borrowed(include_str!("./composition/sum.tm")),
77    },
78    Library {
79        name: Cow::Borrowed("x2"),
80        description: Cow::Borrowed("x * 2"),
81        initial_state: Cow::Borrowed("q0"),
82        final_state: Cow::Borrowed("qf"),
83        used_states: Cow::Borrowed(&[
84            Cow::Borrowed("q0"),
85            Cow::Borrowed("q1"),
86            Cow::Borrowed("q2"),
87            Cow::Borrowed("q3"),
88            Cow::Borrowed("q4"),
89            Cow::Borrowed("q5"),
90            Cow::Borrowed("qf"),
91        ]),
92        code: Cow::Borrowed(include_str!("./composition/duplicate.tm")),
93    },
94    Library {
95        name: Cow::Borrowed("mod"),
96        description: Cow::Borrowed("x mod y"),
97        initial_state: Cow::Borrowed("q0"),
98        final_state: Cow::Borrowed("qf"),
99        used_states: Cow::Borrowed(&[
100            Cow::Borrowed("q0"),
101            Cow::Borrowed("q1"),
102            Cow::Borrowed("q2"),
103            Cow::Borrowed("q2"),
104            Cow::Borrowed("q4"),
105            Cow::Borrowed("q5"),
106            Cow::Borrowed("q5"),
107            Cow::Borrowed("q6"),
108            Cow::Borrowed("q7"),
109            Cow::Borrowed("q8"),
110            Cow::Borrowed("q9"),
111            Cow::Borrowed("q10"),
112            Cow::Borrowed("q11"),
113            Cow::Borrowed("qf"),
114        ]),
115        code: Cow::Borrowed(include_str!("./composition/mod.tm")),
116    },
117    Library {
118        name: Cow::Borrowed("div2"),
119        description: Cow::Borrowed("x div 2"),
120        initial_state: Cow::Borrowed("q0"),
121        final_state: Cow::Borrowed("qf"),
122        used_states: Cow::Borrowed(&[
123            Cow::Borrowed("q0"),
124            Cow::Borrowed("q1"),
125            Cow::Borrowed("q2"),
126            Cow::Borrowed("qf"),
127        ]),
128        code: Cow::Borrowed(include_str!("./composition/div2.tm")),
129    },
130    Library {
131        name: Cow::Borrowed("bound_diff"),
132        description: Cow::Borrowed("x ∸ y"),
133        initial_state: Cow::Borrowed("q0"),
134        final_state: Cow::Borrowed("qf"),
135        used_states: Cow::Borrowed(&[
136            Cow::Borrowed("q0"),
137            Cow::Borrowed("q1"),
138            Cow::Borrowed("q2"),
139            Cow::Borrowed("q3"),
140            Cow::Borrowed("q4"),
141            Cow::Borrowed("q5"),
142            Cow::Borrowed("q6"),
143            Cow::Borrowed("qf"),
144        ]),
145        code: Cow::Borrowed(include_str!("./composition/bound_diff.tm")),
146    },
147];
148
149#[cfg(test)]
150mod test_parsing {
151    use std::fs;
152
153    use crate::warnings::ErrorPosition;
154    use crate::CompilerError;
155    use crate::Rule;
156    use crate::TuringMachine;
157    use crate::TuringParser;
158    use pest::{consumes_to, parses_to};
159
160    #[test]
161    fn parse_description() {
162        let test = "/// a + b\r\n";
163
164        parses_to! {
165            parser: TuringParser,
166            input: test,
167            rule: Rule::description,
168            tokens: [
169                description(0, 11),
170            ]
171        }
172    }
173
174    #[test]
175    fn parse_tape_valid() {
176        let test = "{111011};";
177
178        parses_to! {
179            parser: TuringParser,
180            input: test,
181            rule: Rule::tape,
182            tokens: [
183                tape(0, 9, [
184                    value(1, 2),
185                    value(2, 3),
186                    value(3, 4),
187                    value(4, 5),
188                    value(5, 6),
189                    value(6, 7),
190                ]),
191            ]
192        }
193    }
194
195    #[test]
196    // Test that the parser fails when the tape does not contain a 1
197    fn parse_tape_zeros() {
198        let test = "
199        {000};
200        I = {q0};
201        F = {q2};
202        
203        (q0, 1, 0, R, q1);
204        
205        (q1, 1, 1, R, q1);
206        (q1, 0, 0, R, q2);
207        
208        (q2, 1, 0, H, q2);
209        (q2, 0, 0, H, q2);
210        ";
211
212        let tm_error = TuringMachine::new(test);
213
214        let expected: CompilerError = CompilerError::SyntaxError {
215            position: ErrorPosition::new((1, 9), None), // FIXME: Positions are not correct
216            message: String::from("Expected at least a 1 in the tape"),
217            code: String::from("000"),
218            expected: Rule::tape,
219            found: None,
220        };
221
222        assert_eq!(tm_error.unwrap_err(), expected);
223    }
224
225    #[test]
226    fn parse_initial_state() {
227        let test = "I = {q0};";
228
229        parses_to! {
230            parser: TuringParser,
231            input: test,
232            rule: Rule::initial_state,
233            tokens: [
234                initial_state(0, 9, [
235                    state(5, 7)
236                ])
237            ]
238        }
239    }
240
241    #[test]
242    fn parse_final_state() {
243        let test = "F = {q2};";
244
245        parses_to! {
246            parser: TuringParser,
247            input: test,
248            rule: Rule::final_state,
249            tokens: [
250                final_state(0, 9, [
251                    state(5, 7)
252                ])
253            ]
254        }
255    }
256
257    #[test]
258    fn parse_instruction() {
259        let test = "(q0, 1, 0, R, q1);";
260
261        parses_to! {
262            parser: TuringParser,
263            input: test,
264            rule: Rule::instruction,
265            tokens: [
266                instruction(0, 18, [
267                    state(1, 3),
268                    value(5, 6),
269                    value(8, 9),
270                    movement(11, 12),
271                    state(14, 16)
272                ]),
273            ]
274        }
275    }
276
277    #[test]
278    fn parse_file() {
279        let unparsed_file = fs::read_to_string("Examples/Example1.tm").expect("cannot read file");
280        let (tm, _) = match TuringMachine::new(&unparsed_file) {
281            Ok(t) => t,
282            Err(e) => {
283                TuringMachine::handle_error(e);
284                std::process::exit(1);
285            }
286        };
287
288        assert_eq!(
289            tm.to_string(),
290            "0 0 0 1 1 1 1 1 0 1 1 \n      ^               "
291        )
292    }
293}
294
295#[cfg(test)]
296mod test_composition {
297    use crate::Rule;
298    use crate::TuringMachine;
299    use crate::TuringOutput;
300    use crate::TuringParser;
301    use crate::LIBRARIES;
302    use pest::{consumes_to, parses_to};
303
304    #[test]
305    fn parse_composition_function_name_valid() {
306        let test = "sum_test";
307
308        parses_to! {
309            parser: TuringParser,
310            input: test,
311            rule: Rule::function_name,
312            tokens: [
313                function_name(0, 8)
314            ]
315        }
316    }
317
318    #[test]
319    fn parse_composition_valid() {
320        let test = "compose = { sum_test };";
321
322        parses_to! {
323            parser: TuringParser,
324            input: test,
325            rule: Rule::composition,
326            tokens: [
327                composition(0, 23, [
328                    function_name(12, 20)
329                ])
330            ]
331        }
332    }
333
334    #[test]
335    fn parse_multiple_compositions() {
336        let test = "compose = {sum, diff};";
337
338        parses_to! {
339            parser: TuringParser,
340            input: test,
341            rule: Rule::composition,
342            tokens: [
343                composition(0, 22, [
344                    function_name(11, 14),
345                    function_name(16, 20)
346                ])
347            ]
348        }
349    }
350
351    #[test]
352    /// Test that all the libraries are correctly parsed
353    /// (should not panic)
354    fn libraries() {
355        for lib in LIBRARIES {
356            let _ = lib.get_instructions();
357        }
358    }
359
360    #[test]
361    /// Test compiling a program that uses composition and nothing else (no extra code)
362    /// Also tests that you can write the `compose`, tape (`{111011}`), initial state (`I = {q0}`) and final state (`F = {q2}`) in any order
363    fn composition() {
364        let test = "
365        compose = {sum};
366        
367        F = {q2};
368        {111011};
369        I = {q0};
370        ";
371
372        let mut tm = match TuringMachine::new(test) {
373            Ok(t) => t.0,
374            Err(e) => {
375                println!("{:?}", e);
376                std::process::exit(1);
377            }
378        };
379
380        assert_eq!(tm.final_result(), TuringOutput::Defined((5, 3)));
381
382        assert_eq!(
383            tm.to_string(),
384            "0 0 0 0 1 1 0 0 1 0 0 \n              ^       "
385        );
386    }
387}