stackr_rs/interpreter/
stringify.rs1use 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 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 pub fn stringify_program(&self) -> String {
74 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 ":" => {
95 tokens.chomp();
96 tokens.add_space();
97 tokens.chomp();
98 tokens.indent();
99 tokens.add_newline();
100
101 for _ in 0..3 {
103 tokens.chomp();
104 tokens.add_newline();
105 }
106 tokens.add_newline();
107 }
108 ";" => {
110 tokens.dedent();
111 tokens.add_newline();
112 tokens.chomp();
113 tokens.add_newline();
114 tokens.add_newline();
115 }
116 "[" => {
118 tokens.add_newline();
119 tokens.chomp();
120 tokens.indent();
121 tokens.add_newline();
122 }
123 "]" => {
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 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}