lc3_toolchain/fmt/
formatter.rs

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