lc3_toolchain/fmt/
formatter.rs

1use crate::ast::processed_ast::{Program, ProgramItem};
2use crate::ast::raw_ast::{
3    Comment, Directive, DirectiveType, Immediate, Instruction, InstructionType, Label, Register,
4};
5use either::Either;
6use serde::{Deserialize, Serialize};
7
8trait FormattedDisplay {
9    // label body comment
10    fn formatted_display(&self, style: &FormatStyle) -> (Vec<String>, String, Option<String>);
11}
12
13#[derive(Debug, Default, Copy, Clone, Serialize, Deserialize)]
14#[serde(rename_all = "kebab-case")]
15pub struct FormatStyle {
16    pub indent_directive: u8,              // horizontal // done
17    pub indent_instruction: u8,            // horizontal // done
18    pub indent_label: u8,                  // horizontal // done
19    pub indent_min_comment_from_block: u8, // horizontal // done
20    pub space_block_to_comment: u8,        // vertical //done
21    pub space_comment_stick_to_body: u8,   // vertical //done
22    pub space_from_label_block: u8,        // vertical //done
23    pub space_from_start_end_block: u8,    // vertical  // done
24    pub colon_after_label: bool,
25    pub fixed_body_comment_indent: bool,
26    pub directive_label_wrap: bool,
27}
28
29pub struct Formatter<'a> {
30    style: &'a FormatStyle,
31    buffer: Vec<u8>,
32}
33
34impl<'a> Formatter<'a> {
35    pub fn new(style: &'a FormatStyle) -> Self {
36        Self {
37            style,
38            buffer: Vec::new(),
39        }
40    }
41
42    pub fn format(&mut self, program: Program) {
43        self.buffer.reserve(program.items().len() * 10);
44        let mut lines: Vec<(Vec<String>, String, Option<String>, usize)> = vec![];
45        for (index, line) in program.items().iter().enumerate() {
46            let (labels, mut body, comments) = line.formatted_display(&self.style);
47            if self.style.directive_label_wrap == false && (!labels.is_empty()) {
48                body = body.trim().to_string();
49            }
50            lines.push((
51                labels,
52                body,
53                comments,
54                self.control_padding(line, program.items().get(index + 1)) + 1,
55            ));
56        }
57
58        let comment_start_column = lines.iter().map(|e| e.1.len()).max().unwrap_or(0)
59            + (self.style.indent_min_comment_from_block as usize);
60
61        for (labels, body, comment, space) in lines.into_iter() {
62            let missing_indent = comment_start_column - body.len();
63            let mut label = "".to_owned();
64            let is_label_empty = labels.is_empty();
65            labels
66                .into_iter()
67                .map(|mut l| {
68                    l.push('\n');
69                    l
70                })
71                .for_each(|e| label.push_str(e.as_str()));
72            if self.style.directive_label_wrap == false && !is_label_empty {
73                label.pop();
74                label.push(' ');
75            }
76            self.buffer.append(&mut label.into_bytes());
77            self.buffer.append(&mut body.into_bytes());
78            self.add_indent(
79                self.style
80                    .fixed_body_comment_indent
81                    .then_some(1)
82                    .unwrap_or(missing_indent), // default indent between block and comment
83            );
84            match comment {
85                None => {}
86                Some(comment) => {
87                    self.buffer.append(&mut comment.into_bytes());
88                }
89            }
90            self.add_newline(space);
91        }
92    }
93
94    pub fn contents(&self) -> &Vec<u8> {
95        &self.buffer
96    }
97
98    #[inline]
99    fn add_newline(&mut self, lines: usize) {
100        for _ in 0..lines {
101            self.buffer.push(b'\n');
102        }
103    }
104
105    #[inline]
106    fn add_indent(&mut self, indent: usize) {
107        for _ in 0..indent {
108            self.buffer.push(b' ');
109        }
110    }
111
112    fn control_padding(&mut self, current: &ProgramItem, next: Option<&ProgramItem>) -> usize {
113        let mut paddings = 0usize;
114
115        // space_comment_stick_to_body
116        if self.style.space_comment_stick_to_body != 0 {
117            if current.is_comment()
118                && next.is_some()
119                && (next.unwrap().is_directive() || next.unwrap().is_instruction())
120            {
121                paddings += self.style.space_comment_stick_to_body as usize;
122            }
123        }
124
125        // space_block_between
126        if self.style.space_block_to_comment != 0 {
127            // solve conflict with padding_start_end_directive_block
128            if let ProgramItem::Directive(_, directive, ..) = current {
129                if matches!(directive.directive_type(), DirectiveType::ORIG(..)) {
130                    // balabala
131                } else {
132                    if (current.is_directive() || current.is_instruction())
133                        && next.is_some()
134                        && next.unwrap().is_comment()
135                    {
136                        paddings += self.style.space_block_to_comment as usize;
137                    }
138                }
139            } else {
140                if (current.is_directive() || current.is_instruction())
141                    && next.is_some()
142                    && next.unwrap().is_comment()
143                {
144                    paddings += self.style.space_block_to_comment as usize;
145                }
146            }
147        }
148
149        // space_from_label_block
150        if self.style.space_from_label_block != 0 {
151            let space: u8 = match current {
152                ProgramItem::Instruction(curr_label, ..)
153                | ProgramItem::Directive(curr_label, ..) => match next {
154                    None => 0,
155                    Some(next) => match next {
156                        ProgramItem::Instruction(next_label, ..)
157                        | ProgramItem::Directive(next_label, ..) => {
158                            if curr_label.is_empty() && (!next_label.is_empty()) {
159                                self.style.space_from_label_block
160                            } else {
161                                0
162                            }
163                        }
164                        ProgramItem::EOL(..) | ProgramItem::Comment(..) => 0,
165                    },
166                },
167                ProgramItem::EOL(..) | ProgramItem::Comment(..) => 0,
168            };
169            paddings += space as usize;
170        }
171
172        // padding_start_end_directive_block
173        if self.style.space_from_start_end_block != 0 {
174            let space: u8 = match current {
175                ProgramItem::Directive(_, directive, ..) => {
176                    if matches!(directive.directive_type(), DirectiveType::ORIG(..)) {
177                        self.style.space_from_start_end_block
178                    } else if next.is_some() {
179                        match next.unwrap() {
180                            ProgramItem::Directive(_, directive, _, _) => {
181                                if matches!(directive.directive_type(), DirectiveType::END) {
182                                    self.style.space_from_start_end_block
183                                } else {
184                                    0
185                                }
186                            }
187                            _ => 0,
188                        }
189                    } else {
190                        0
191                    }
192                }
193                _ => 0,
194            };
195            paddings += space as usize;
196        }
197        paddings
198    }
199}
200
201impl FormattedDisplay for ProgramItem {
202    fn formatted_display(&self, style: &FormatStyle) -> (Vec<String>, String, Option<String>) {
203        match self {
204            ProgramItem::Comment(comment, _) => (vec![], print_comment(comment), None),
205            ProgramItem::Instruction(labels, instruction, comment, _) => {
206                let mut label_indent = "".to_owned();
207                add_indent(
208                    &mut label_indent,
209                    labels.is_empty().then_some(0).unwrap_or(style.indent_label),
210                );
211                let labels = labels
212                    .into_iter()
213                    .map(|l| format!("{label_indent}{}", print_label(style, l)))
214                    .collect();
215                let mut instruction_indent = "".to_owned();
216                add_indent(&mut instruction_indent, style.indent_instruction);
217                let comment = comment.as_ref().map_or(None, |c| Some(print_comment(c)));
218                (
219                    labels,
220                    format!("{instruction_indent}{}", print_instruction(instruction)),
221                    comment,
222                )
223            }
224            ProgramItem::Directive(labels, directive, comment, _) => {
225                let mut label_indent = "".to_owned();
226                add_indent(
227                    &mut label_indent,
228                    labels.is_empty().then_some(0).unwrap_or(style.indent_label),
229                );
230                let labels = labels
231                    .into_iter()
232                    .map(|l| format!("{label_indent}{}", print_label(style, l)))
233                    .collect();
234                let mut directive_indent = "".to_owned();
235                add_indent(
236                    &mut directive_indent,
237                    (matches!(directive.directive_type(), DirectiveType::END)
238                        || matches!(directive.directive_type(), DirectiveType::ORIG(..)))
239                    .then_some(0)
240                    .unwrap_or(style.indent_directive),
241                );
242                let comment = comment.as_ref().map_or(None, |c| Some(print_comment(c)));
243                (
244                    labels,
245                    format!("{directive_indent}{}", print_directive(directive)),
246                    comment,
247                )
248            }
249            ProgramItem::EOL(labels) => {
250                let mut label_indent = "".to_owned();
251                add_indent(
252                    &mut label_indent,
253                    labels.is_empty().then_some(0).unwrap_or(style.indent_label),
254                );
255                let labels = labels
256                    .into_iter()
257                    .map(|l| format!("{label_indent}{}", print_label(style, l)))
258                    .collect();
259                (labels, "".to_owned(), None)
260            }
261        }
262    }
263}
264
265fn print_instruction(instruction: &Instruction) -> String {
266    let operands: String = match instruction.instruction_type() {
267        InstructionType::Add(register1, register2, register_or_immediate)
268        | InstructionType::And(register1, register2, register_or_immediate) => {
269            format!(
270                "{}, {}, {}",
271                register1.content(),
272                register2.content(),
273                print_register_or_immediate(register_or_immediate)
274            )
275        }
276        InstructionType::Not(register1, register2) => {
277            format!("{}, {}", register1.content(), register2.content(),)
278        }
279        InstructionType::Ldr(register1, register2, immediate)
280        | InstructionType::Str(register1, register2, immediate) => {
281            format!(
282                "{}, {}, {}",
283                register1.content(),
284                register2.content(),
285                immediate.content()
286            )
287        }
288        InstructionType::Ld(register1, label_ref)
289        | InstructionType::Ldi(register1, label_ref)
290        | InstructionType::Lea(register1, label_ref)
291        | InstructionType::St(register1, label_ref)
292        | InstructionType::Sti(register1, label_ref) => {
293            format!("{}, {}", register1.content(), label_ref.content())
294        }
295        InstructionType::Br(_, label_ref) => label_ref.content().to_owned(),
296        InstructionType::Jmp(register) | InstructionType::Jsrr(register) => {
297            register.content().to_owned()
298        }
299        InstructionType::Jsr(label_ref) => label_ref.content().to_owned(),
300        InstructionType::Nop
301        | InstructionType::Ret
302        | InstructionType::Halt
303        | InstructionType::Puts
304        | InstructionType::Getc
305        | InstructionType::Out
306        | InstructionType::In => "".to_owned(),
307        InstructionType::Trap(hex_address) => hex_address.content().to_owned(),
308    };
309    if operands.is_empty() {
310        format!("{}", instruction.content())
311    } else {
312        format!("{} {}", instruction.content(), operands)
313    }
314}
315
316fn print_comment(comment: &Comment) -> String {
317    match comment.content().strip_prefix(";") {
318        None => {
319            unreachable!()
320        }
321        Some(comment) => {
322            let comment = comment.trim();
323            format!(";{comment}")
324        }
325    }
326}
327
328fn print_label(style: &FormatStyle, label: &Label) -> String {
329    if style.colon_after_label {
330        match label.content().ends_with(":") {
331            false => {
332                if style.colon_after_label {
333                    format!("{}:", label.content())
334                } else {
335                    label.content().into()
336                }
337            }
338            true => label.content().to_owned(),
339        }
340    } else {
341        match label.content().strip_suffix(":") {
342            None => label.content().into(),
343            Some(label) => label.to_owned(),
344        }
345    }
346}
347
348fn print_register_or_immediate(either: &Either<Register, Immediate>) -> String {
349    match either {
350        Either::Left(r) => r.content(),
351        Either::Right(im) => im.content(),
352    }
353    .to_owned()
354}
355
356fn print_directive(directive: &Directive) -> String {
357    let operands: String = match directive.directive_type() {
358        DirectiveType::ORIG(address) => address.content(),
359        DirectiveType::END => "",
360        DirectiveType::BLKW(immediate) | DirectiveType::FILL(immediate) => immediate.content(),
361        DirectiveType::STRINGZ(string) => string.content(),
362    }
363    .to_owned();
364    if operands.is_empty() {
365        format!("{}", directive.content())
366    } else {
367        format!("{} {}", directive.content(), operands)
368    }
369}
370
371fn add_indent(string: &mut String, indent: u8) {
372    for _ in 0..indent {
373        string.push(' ');
374    }
375}