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}
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: Program) {
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(&mut self, current: &ProgramItem, next: Option<&ProgramItem>) -> usize {
98        let mut paddings = 0usize;
99
100        // space_comment_stick_to_body
101        if self.style.space_comment_stick_to_body != 0 {
102            if current.is_comment()
103                && next.is_some()
104                && (next.unwrap().is_directive() || next.unwrap().is_instruction())
105            {
106                paddings += self.style.space_comment_stick_to_body as usize;
107            }
108        }
109
110        // space_block_between
111        if self.style.space_block_to_comment != 0 {
112            // solve conflict with padding_start_end_directive_block
113            if let ProgramItem::Directive(_, directive, ..) = current {
114                if matches!(directive.directive_type(), DirectiveType::ORIG(..)) {
115                    // balabala
116                } else {
117                    if (current.is_directive() || current.is_instruction())
118                        && next.is_some()
119                        && next.unwrap().is_comment()
120                    {
121                        paddings += self.style.space_block_to_comment as usize;
122                    }
123                }
124            } else {
125                if (current.is_directive() || current.is_instruction())
126                    && next.is_some()
127                    && next.unwrap().is_comment()
128                {
129                    paddings += self.style.space_block_to_comment as usize;
130                }
131            }
132        }
133
134        // space_from_label_block
135        if self.style.space_from_label_block != 0 {
136            let space: u8 = match current {
137                ProgramItem::Instruction(curr_label, ..)
138                | ProgramItem::Directive(curr_label, ..) => match next {
139                    None => 0,
140                    Some(next) => match next {
141                        ProgramItem::Instruction(next_label, ..)
142                        | ProgramItem::Directive(next_label, ..) => {
143                            if curr_label.is_empty() && (!next_label.is_empty()) {
144                                self.style.space_from_label_block
145                            } else {
146                                0
147                            }
148                        }
149                        ProgramItem::EOL(..) | ProgramItem::Comment(..) => 0,
150                    },
151                },
152                ProgramItem::EOL(..) | ProgramItem::Comment(..) => 0,
153            };
154            paddings += space as usize;
155        }
156
157        // padding_start_end_directive_block
158        if self.style.space_from_start_end_block != 0 {
159            let space: u8 = match current {
160                ProgramItem::Directive(_, directive, ..) => {
161                    if matches!(directive.directive_type(), DirectiveType::ORIG(..)) {
162                        self.style.space_from_start_end_block
163                    } else if next.is_some() {
164                        match next.unwrap() {
165                            ProgramItem::Directive(_, directive, _, _) => {
166                                if matches!(directive.directive_type(), DirectiveType::END) {
167                                    self.style.space_from_start_end_block
168                                } else {
169                                    0
170                                }
171                            }
172                            _ => 0,
173                        }
174                    } else {
175                        0
176                    }
177                }
178                _ => 0,
179            };
180            paddings += space as usize;
181        }
182        paddings
183    }
184}
185
186impl FormattedDisplay for ProgramItem {
187    fn formatted_display(&self, style: &FormatStyle) -> (Vec<String>, String, Option<String>) {
188        match self {
189            ProgramItem::Comment(comment, _) => (vec![], print_comment(comment), None),
190            ProgramItem::Instruction(labels, instruction, comment, _) => {
191                let mut label_indent = "".to_owned();
192                add_indent(
193                    &mut label_indent,
194                    labels.is_empty().then_some(0).unwrap_or(style.indent_label),
195                );
196                let labels = labels
197                    .into_iter()
198                    .map(|l| format!("{label_indent}{}", print_label(style, l)))
199                    .collect();
200                let mut instruction_indent = "".to_owned();
201                add_indent(&mut instruction_indent, style.indent_instruction);
202                let comment = comment.as_ref().map_or(None, |c| Some(print_comment(c)));
203                (
204                    labels,
205                    format!("{instruction_indent}{}", print_instruction(instruction)),
206                    comment,
207                )
208            }
209            ProgramItem::Directive(labels, directive, comment, _) => {
210                let mut label_indent = "".to_owned();
211                add_indent(
212                    &mut label_indent,
213                    labels.is_empty().then_some(0).unwrap_or(style.indent_label),
214                );
215                let labels = labels
216                    .into_iter()
217                    .map(|l| format!("{label_indent}{}", print_label(style, l)))
218                    .collect();
219                let mut directive_indent = "".to_owned();
220                add_indent(
221                    &mut directive_indent,
222                    (matches!(directive.directive_type(), DirectiveType::END)
223                        || matches!(directive.directive_type(), DirectiveType::ORIG(..)))
224                    .then_some(0)
225                    .unwrap_or(style.indent_directive),
226                );
227                let comment = comment.as_ref().map_or(None, |c| Some(print_comment(c)));
228                (
229                    labels,
230                    format!("{directive_indent}{}", print_directive(directive)),
231                    comment,
232                )
233            }
234            ProgramItem::EOL(labels) => {
235                let mut label_indent = "".to_owned();
236                add_indent(
237                    &mut label_indent,
238                    labels.is_empty().then_some(0).unwrap_or(style.indent_label),
239                );
240                let labels = labels
241                    .into_iter()
242                    .map(|l| format!("{label_indent}{}", print_label(style, l)))
243                    .collect();
244                (labels, "".to_owned(), None)
245            }
246        }
247    }
248}
249
250fn print_instruction(instruction: &Instruction) -> String {
251    let operands: String = match instruction.instruction_type() {
252        InstructionType::Add(register1, register2, register_or_immediate)
253        | InstructionType::And(register1, register2, register_or_immediate) => {
254            format!(
255                "{}, {}, {}",
256                register1.content(),
257                register2.content(),
258                print_register_or_immediate(register_or_immediate)
259            )
260        }
261        InstructionType::Not(register1, register2) => {
262            format!("{}, {}", register1.content(), register2.content(),)
263        }
264        InstructionType::Ldr(register1, register2, immediate)
265        | InstructionType::Str(register1, register2, immediate) => {
266            format!(
267                "{}, {}, {}",
268                register1.content(),
269                register2.content(),
270                immediate.content()
271            )
272        }
273        InstructionType::Ld(register1, label_ref)
274        | InstructionType::Ldi(register1, label_ref)
275        | InstructionType::Lea(register1, label_ref)
276        | InstructionType::St(register1, label_ref)
277        | InstructionType::Sti(register1, label_ref) => {
278            format!("{}, {}", register1.content(), label_ref.content())
279        }
280        InstructionType::Br(_, label_ref) => label_ref.content().to_owned(),
281        InstructionType::Jmp(register) | InstructionType::Jsrr(register) => {
282            register.content().to_owned()
283        }
284        InstructionType::Jsr(label_ref) => label_ref.content().to_owned(),
285        InstructionType::Nop
286        | InstructionType::Ret
287        | InstructionType::Halt
288        | InstructionType::Puts
289        | InstructionType::Getc
290        | InstructionType::Out
291        | InstructionType::In => "".to_owned(),
292        InstructionType::Trap(hex_address) => hex_address.content().to_owned(),
293    };
294    if operands.is_empty() {
295        format!("{}", instruction.content())
296    } else {
297        format!("{} {}", instruction.content(), operands)
298    }
299}
300
301fn print_comment(comment: &Comment) -> String {
302    match comment.content().strip_prefix(";") {
303        None => {
304            unreachable!()
305        }
306        Some(comment) => {
307            let comment = comment.trim();
308            format!(";{comment}")
309        }
310    }
311}
312
313fn print_label(style: &FormatStyle, label: &Label) -> String {
314    if style.colon_after_label {
315        match label.content().ends_with(":") {
316            false => {
317                if style.colon_after_label {
318                    format!("{}:", label.content())
319                } else {
320                    label.content().into()
321                }
322            }
323            true => label.content().to_owned(),
324        }
325    } else {
326        match label.content().strip_suffix(":") {
327            None => label.content().into(),
328            Some(label) => label.to_owned(),
329        }
330    }
331}
332
333fn print_register_or_immediate(either: &Either<Register, Immediate>) -> String {
334    match either {
335        Either::Left(r) => r.content(),
336        Either::Right(im) => im.content(),
337    }
338    .to_owned()
339}
340
341fn print_directive(directive: &Directive) -> String {
342    let operands: String = match directive.directive_type() {
343        DirectiveType::ORIG(address) => address.content(),
344        DirectiveType::END => "",
345        DirectiveType::BLKW(immediate) | DirectiveType::FILL(immediate) => immediate.content(),
346        DirectiveType::STRINGZ(string) => string.content(),
347    }
348    .to_owned();
349    if operands.is_empty() {
350        format!("{}", directive.content())
351    } else {
352        format!("{} {}", directive.content(), operands)
353    }
354}
355
356fn add_indent(string: &mut String, indent: u8) {
357    for _ in 0..indent {
358        string.push(' ');
359    }
360}