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