1use crate::ast::raw_ast::{Comment, Directive, Instruction, Label, Span};
2use getset::Getters;
3use pest::Stack;
4use std::collections::HashMap;
5
6#[derive(Debug, Getters)]
7pub struct FormatterProgram {
8 #[get = "pub"]
9 items: Vec<FormatterProgramItem>,
10}
11
12#[derive(Debug)]
13pub struct StandardTransform<'a> {
14 label_buffer: Stack<Label>,
15 forward_next_comment: bool,
16 look_table: LineColumnLookTable<'a>,
17 hybrid_inline_comment: bool,
18}
19
20#[allow(dead_code)]
21#[derive(Debug, Copy, Clone)]
22pub struct LineColumn {
23 line: usize,
24 column: usize,
25}
26
27#[derive(Debug, Clone)]
28enum ProgramItem {
29 Comment(Comment),
30 Instruction(Vec<Label>, Instruction, Option<Comment>),
31 Directive(Vec<Label>, Directive, Option<Comment>),
32 EOL(Vec<Label>),
33}
34
35#[derive(Debug, Clone)]
36pub enum FormatterProgramItem {
37 Comment(Comment, LineColumn),
38 Instruction(Vec<Label>, Instruction, Option<Comment>, LineColumn),
39 Directive(Vec<Label>, Directive, Option<Comment>, LineColumn),
40 EOL(Vec<Label>),
41}
42
43impl<'a> StandardTransform<'a> {
44 pub fn new(hybrid_inline_comment: bool, file_content: &'a str) -> Self {
45 Self {
46 label_buffer: Stack::new(),
47 forward_next_comment: true,
48 look_table: LineColumnLookTable::new(file_content),
49 hybrid_inline_comment,
50 }
51 }
52
53 pub fn transform(&mut self, program: crate::ast::raw_ast::Program) -> FormatterProgram {
54 let mut labelled_items: Vec<_> = program
55 .items()
56 .into_iter()
57 .map(|p| self.hybrid_label(p))
58 .collect();
59 if !self.label_buffer.is_empty() {
60 let mut labels = vec![];
61 while let Some(item) = self.label_buffer.pop() {
62 labels.push(item);
63 }
64 labelled_items.push(Some(ProgramItem::EOL(labels)));
65 }
66 let labelled_items: Vec<_> = labelled_items
67 .into_iter()
68 .filter_map(|p| p)
69 .map(|p| self.add_line_info(p))
70 .collect();
71 FormatterProgram {
72 items: if self.hybrid_inline_comment {
73 let mut res = vec![];
74 for (index, current) in labelled_items.iter().enumerate() {
75 let next = labelled_items.get(index + 1);
76 res.push(self.hybrid_comment(current.clone(), next.map(|i| i.clone())));
77 }
78 res.into_iter().filter_map(|i| i).collect()
79 } else {
80 labelled_items
81 },
82 }
83 }
84
85 fn hybrid_label(
86 &mut self,
87 program_item: crate::ast::raw_ast::ProgramItem,
88 ) -> Option<ProgramItem> {
89 match program_item {
90 crate::ast::raw_ast::ProgramItem::Label(label) => {
91 self.label_buffer.push(label);
92 None
93 }
94 crate::ast::raw_ast::ProgramItem::Instruction(instruction) => {
95 let mut labels = vec![];
96 while let Some(item) = self.label_buffer.pop() {
97 labels.push(item);
98 }
99 Some(ProgramItem::Instruction(labels, instruction, None))
100 }
101 crate::ast::raw_ast::ProgramItem::Directive(directive) => {
102 let mut labels = vec![];
103 while let Some(item) = self.label_buffer.pop() {
104 labels.push(item);
105 }
106 Some(ProgramItem::Directive(labels, directive, None))
107 }
108 crate::ast::raw_ast::ProgramItem::Comment(comment) => {
109 Some(ProgramItem::Comment(comment))
110 }
111 }
112 }
113
114 fn add_line_info(&mut self, program_item: ProgramItem) -> FormatterProgramItem {
115 match program_item {
116 ProgramItem::Comment(comment) => {
117 let lc = self.look_table.get_line_and_column(comment.span());
118 FormatterProgramItem::Comment(comment, lc)
119 }
120 ProgramItem::Instruction(label, instruction, comment) => {
121 let lc = self.look_table.get_line_and_column(instruction.span());
122 FormatterProgramItem::Instruction(label, instruction, comment, lc)
123 }
124 ProgramItem::Directive(label, directive, comment) => {
125 let lc = self.look_table.get_line_and_column(directive.span());
126 FormatterProgramItem::Directive(label, directive, comment, lc)
127 }
128 ProgramItem::EOL(label) => FormatterProgramItem::EOL(label),
129 }
130 }
131
132 fn hybrid_comment(
133 &mut self,
134 curr: FormatterProgramItem,
135 next: Option<FormatterProgramItem>,
136 ) -> Option<FormatterProgramItem> {
137 match curr {
138 FormatterProgramItem::Comment(comment, comment_lc) => {
139 if self.forward_next_comment {
140 Some(FormatterProgramItem::Comment(comment, comment_lc))
141 } else {
142 self.forward_next_comment = true;
143 None
144 }
145 }
146 FormatterProgramItem::Instruction(labels, instruction, comment, lc) => {
147 if let Some(FormatterProgramItem::Comment(comment, lc_comment)) = next {
148 if lc_comment.at_the_same_line(&lc) {
149 self.forward_next_comment = false;
150 return Some(FormatterProgramItem::Instruction(
151 labels,
152 instruction,
153 Some(comment),
154 lc,
155 ));
156 }
157 }
158 Some(FormatterProgramItem::Instruction(
159 labels,
160 instruction,
161 comment,
162 lc,
163 ))
164 }
165 FormatterProgramItem::Directive(labels, directive, comment, lc) => {
166 if let Some(FormatterProgramItem::Comment(comment, lc_comment)) = next {
167 if lc_comment.at_the_same_line(&lc) {
168 self.forward_next_comment = false;
169 return Some(FormatterProgramItem::Directive(
170 labels,
171 directive,
172 Some(comment),
173 lc,
174 ));
175 }
176 }
177 Some(FormatterProgramItem::Directive(
178 labels, directive, comment, lc,
179 ))
180 }
181 FormatterProgramItem::EOL(labels) => Some(FormatterProgramItem::EOL(labels)),
182 }
183 }
184}
185
186#[derive(Debug)]
187struct LineColumnLookTable<'a> {
188 line_start_indices: HashMap<usize, (usize, usize)>, lines: Vec<&'a str>,
190}
191
192impl<'a> LineColumnLookTable<'a> {
193 pub fn new(file_content: &'a str) -> Self {
195 let mut line_start_indices = HashMap::new();
196 let mut char_count = 0; let mut line_number = 1; for line in file_content.lines() {
200 line_start_indices.insert(char_count, (line_number, 1));
202 char_count += line.len() + 1; line_number += 1;
204 }
205 let lines: Vec<&str> = file_content.lines().collect();
206
207 LineColumnLookTable {
208 line_start_indices,
209 lines,
210 }
211 }
212
213 pub fn get_line_and_column(&self, span: &Span) -> LineColumn {
215 let start = *span.start();
216 for (start_index, (line_number, _)) in self.line_start_indices.iter() {
218 let line_len = self.lines[*line_number - 1].len();
219 if start >= *start_index && start < *start_index + line_len {
220 let column = start - *start_index + 1;
222 return LineColumn {
223 line: *line_number,
224 column,
225 };
226 }
227 }
228
229 unreachable!()
230 }
231}
232
233impl LineColumn {
234 pub fn at_the_same_line(&self, other: &LineColumn) -> bool {
235 self.line == other.line
236 }
237}
238
239impl FormatterProgramItem {
240 pub fn is_comment(&self) -> bool {
241 matches!(self, FormatterProgramItem::Comment(..))
242 }
243
244 pub fn is_instruction(&self) -> bool {
245 matches!(self, FormatterProgramItem::Instruction(..))
246 }
247
248 pub fn is_directive(&self) -> bool {
249 matches!(self, FormatterProgramItem::Directive(..))
250 }
251
252 pub fn is_eol(&self) -> bool {
253 matches!(self, FormatterProgramItem::EOL(..))
254 }
255}