lc3_toolchain/ast/
processed_ast.rs

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)>, // Key: start index, Value: (line number, column start)
189    lines: Vec<&'a str>,
190}
191
192impl<'a> LineColumnLookTable<'a> {
193    // Build a new lookup table based on the file content
194    pub fn new(file_content: &'a str) -> Self {
195        let mut line_start_indices = HashMap::new();
196        let mut char_count = 0; // Keeps track of the starting character index of the line
197        let mut line_number = 1; // Line numbers are 1-based
198
199        for line in file_content.lines() {
200            // Insert the line start index and its corresponding line number and column start
201            line_start_indices.insert(char_count, (line_number, 1));
202            char_count += line.len() + 1; // Increment by the length of the line + 1 for newline character
203            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    // Function to get the line and column for a given span
214    pub fn get_line_and_column(&self, span: &Span) -> LineColumn {
215        let start = *span.start();
216        // Find the line where the span starts
217        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                // Find the column number
221                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}