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 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, pub indent_instruction: u8, pub indent_label: u8, pub indent_min_comment_from_block: u8, pub space_block_to_comment: u8, pub space_comment_stick_to_body: u8, pub space_from_label_block: u8, pub space_from_start_end_block: u8, 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 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 if self.style.space_block_to_comment != 0 {
116 if let FormatterProgramItem::Directive(_, directive, ..) = current {
118 if matches!(directive.directive_type(), DirectiveType::ORIG(..)) {
119 } 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 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 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}