stackr_rs/interpreter/
stringify.rs

1use super::*;
2
3struct ProgramTokens {
4    tokens: Vec<String>,
5    indent_count: usize,
6    buffer: String,
7}
8impl ProgramTokens {
9    pub fn new() -> Self {
10        Self {
11            buffer: String::new(),
12            tokens: vec![],
13            indent_count: 0,
14        }
15    }
16
17    pub fn push(&mut self, token: String) {
18        self.tokens.push(token);
19    }
20
21    fn last_char(&self) -> Option<char> {
22        if self.buffer.is_empty() {
23            return None;
24        }
25        Some(self.buffer.chars().last().unwrap())
26    }
27
28    pub fn indent(&mut self) {
29        self.indent_count += 1;
30    }
31
32    pub fn dedent(&mut self) {
33        if self.indent_count > 0 {
34            self.indent_count -= 1;
35        }
36    }
37
38    pub fn add_space(&mut self) {
39        self.buffer.push(' ');
40    }
41
42    pub fn chomp(&mut self) {
43        if self.tokens.is_empty() {
44            return;
45        }
46        self.buffer.push_str(&self.tokens.remove(0));
47    }
48
49    pub fn peek(&self) -> Option<String> {
50        if self.tokens.is_empty() {
51            return None;
52        }
53        Some(self.tokens[0].clone())
54    }
55
56    pub fn add_newline(&mut self) {
57        self.buffer.push('\n');
58        for _ in 0..self.indent_count {
59            self.buffer.push('\t');
60        }
61    }
62}
63
64impl<State> Interpreter<State> {
65    /// Format a code file.
66    pub fn format_code(code: &str, path: Option<PathBuf>) -> Result<String, Err> {
67        let mut interpreter = Interpreter::new(());
68        interpreter.load_program(code, path)?;
69        Ok(interpreter.stringify_program())
70    }
71
72    /// Returns the program as a formatted string.
73    pub fn stringify_program(&self) -> String {
74        // Tokenize the program
75        let mut tokens = ProgramTokens::new();
76        let mut idx = 0;
77        while idx < self.program.len() {
78            let instruction = self.program[idx].clone();
79
80            match instruction {
81                Instruction::PushNumber(n) => tokens.push(format!("{}", n)),
82                Instruction::PushString(s) => tokens.push(format!("\"{}\"", s)),
83                Instruction::Address(address) => {
84                    tokens.push(self.get_name(address));
85                }
86            }
87
88            idx += 1;
89        }
90
91        while let Some(token) = tokens.peek() {
92            match token.as_str() {
93                // Function definition
94                ":" => {
95                    tokens.chomp();
96                    tokens.add_space();
97                    tokens.chomp();
98                    tokens.indent();
99                    tokens.add_newline();
100
101                    // Documentation stuff
102                    for _ in 0..3 {
103                        tokens.chomp();
104                        tokens.add_newline();
105                    }
106                    tokens.add_newline();
107                }
108                // Function definition end
109                ";" => {
110                    tokens.dedent();
111                    tokens.add_newline();
112                    tokens.chomp();
113                    tokens.add_newline();
114                    tokens.add_newline();
115                }
116                // Read mode start
117                "[" => {
118                    tokens.add_newline();
119                    tokens.chomp();
120                    tokens.indent();
121                    tokens.add_newline();
122                }
123                // Read mode end
124                "]" => {
125                    tokens.dedent();
126                    tokens.add_newline();
127                    tokens.chomp();
128
129                    let mut add_newline = true;
130                    if let Some(token) = tokens.peek() {
131                        if token == ";" {
132                            add_newline = false;
133                        }
134                    }
135
136                    if add_newline {
137                        tokens.add_newline();
138                    }
139                }
140                "begin" | "if" => {
141                    tokens.add_newline();
142                    tokens.chomp();
143                    tokens.indent();
144                    tokens.add_newline();
145                }
146                "else" => {
147                    tokens.dedent();
148                    tokens.add_newline();
149                    tokens.chomp();
150                    tokens.indent();
151                    tokens.add_newline();
152                }
153                "loop" | "end" => {
154                    tokens.dedent();
155                    tokens.add_newline();
156                    tokens.chomp();
157
158                    if Some("loop".to_string()) == tokens.peek() {
159                    } else {
160                        tokens.add_newline();
161                        tokens.add_newline();
162                    }
163                }
164                "break" => {
165                    tokens.add_newline();
166                    tokens.chomp();
167                }
168                "." => {
169                    tokens.add_space();
170                    tokens.chomp();
171                    tokens.add_newline();
172
173                    if let Some(token) = tokens.peek() {
174                        match token.as_str() {
175                            ":" | "begin" | "if" => {
176                                tokens.add_newline();
177                            }
178                            _ => {}
179                        }
180                    }
181                }
182                _ => {
183                    if Some("begin".to_string()) == tokens.peek()
184                        || Some("if".to_string()) == tokens.peek()
185                    {
186                        tokens.add_newline();
187                    } else if Some('\n') != tokens.last_char() && Some('\t') != tokens.last_char() {
188                        tokens.add_space();
189                    }
190                    tokens.chomp();
191                }
192            }
193        }
194
195        let mut buffer = tokens.buffer.trim().to_string();
196        buffer.push('\n');
197        buffer
198    }
199}
200
201#[cfg(test)]
202mod tests {
203    use super::*;
204
205    /// Helper function to assert that two strings are equal.
206    /// Provides debugging information if the assertion fails.
207    fn assert_equal(expected: &str, actual: &str) {
208        println!("EXPECTED START");
209        println!("{}", expected);
210        println!("EXPECTED END\n");
211        println!("ACTUAL START");
212        println!("{}", actual);
213        println!("ACTUAL END\n");
214        assert_eq!(expected, actual);
215    }
216
217    #[test]
218    fn stringify_with_noop_formats_nicely() {
219        let code = r#"
220        var 
221        stuff 
222        .
223    
224        1 stuff 
225        set .
226    
227        stuff
228        get 
229        .
230    
231    
232        : debug "" "" "" print-stack drop ;
233        0 begin 1 + dup 2 == if 
234                "hello" .  stuff get drop .  break
235            end
236    
237            dup
238            2 == if 
239                "hello"
240            else
241                "world" drop end loop "world""#;
242
243        let actual = Interpreter::<()>::format_code(code, None).unwrap();
244        let expected = "var stuff .\n1 stuff set .\nstuff get .\n\n: debug\n\t\"\"\n\t\"\"\n\t\"\"\n\t\n\tprint-stack drop\n;\n\n0\nbegin\n\t1 + dup 2 ==\n\tif\n\t\t\"hello\" .\n\t\tstuff get drop .\n\t\t\n\t\tbreak\n\tend\n\t\n\tdup 2 ==\n\tif\n\t\t\"hello\"\n\telse\n\t\t\"world\" drop\n\tend\nloop\n\n\"world\"\n";
245
246        assert_equal(expected, &actual);
247    }
248
249    #[test]
250    fn stringify_program_returns_program_as_string() {
251        let mut interpreter = Interpreter::new(());
252        interpreter.evaluate("1 2 +", None).unwrap();
253        let program = interpreter.stringify_program();
254
255        assert_equal("1 2 +\n", &program);
256    }
257
258    #[test]
259    fn stringify_complex_program_returns_program_as_string() {
260        let mut interpreter = Interpreter::new(());
261        let code = r#"
262        : square 
263            "stack modification" 
264            "documentation"  "example" dup * ; : complextro "stack modification" "documentation" "example" square 3 [ dup * square ] ; 2 square
265        "#;
266        interpreter.evaluate(code, None).unwrap();
267        let program = interpreter.stringify_program();
268        let expected = ": square\n\t\"stack modification\"\n\t\"documentation\"\n\t\"example\"\n\t\n\tdup *\n;\n\n: complextro\n\t\"stack modification\"\n\t\"documentation\"\n\t\"example\"\n\t\n\tsquare 3\n\t[\n\t\tdup * square\n\t]\n;\n\n2 square\n";
269
270        assert_equal(expected, &program);
271    }
272
273    #[test]
274    fn stringify_single_loop_and_if_returns_program_as_string() {
275        let mut interpreter = Interpreter::new(());
276
277        let code = r#"
278        : debug "" "" "" print-stack drop ;
279        0
280        begin
281            1 + dup
282
283            2 == if 
284                "hello" 
285                break
286            end
287
288            dup
289            2 == if 
290                "hello"
291            else
292                "world"
293                drop
294            end
295        loop
296
297        "world"
298        
299        "#;
300        interpreter.evaluate(code, None).unwrap();
301        let program = interpreter.stringify_program();
302        let expected = ": debug\n\t\"\"\n\t\"\"\n\t\"\"\n\t\n\tprint-stack drop\n;\n\n0\nbegin\n\t1 + dup 2 ==\n\tif\n\t\t\"hello\"\n\t\tbreak\n\tend\n\t\n\tdup 2 ==\n\tif\n\t\t\"hello\"\n\telse\n\t\t\"world\" drop\n\tend\nloop\n\n\"world\"\n";
303
304        assert_equal(expected, &program);
305    }
306}