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 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 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), );
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 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 if self.style.space_block_to_comment != 0 {
127 if let ProgramItem::Directive(_, directive, ..) = current {
129 if matches!(directive.directive_type(), DirectiveType::ORIG(..)) {
130 } 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 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 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}