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 Program {
8    #[get = "pub"]
9    items: Vec<ProgramItem>,
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 RawProgramItem {
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 ProgramItem {
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) -> Program {
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(RawProgramItem::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        Program {
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<RawProgramItem> {
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(RawProgramItem::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(RawProgramItem::Directive(labels, directive, None))
107            }
108            crate::ast::raw_ast::ProgramItem::Comment(comment) => {
109                Some(RawProgramItem::Comment(comment))
110            }
111        }
112    }
113
114    fn add_line_info(&mut self, program_item: RawProgramItem) -> ProgramItem {
115        match program_item {
116            RawProgramItem::Comment(comment) => {
117                let lc = self.look_table.get_line_and_column(comment.span());
118                ProgramItem::Comment(comment, lc)
119            }
120            RawProgramItem::Instruction(label, instruction, comment) => {
121                let lc = self.look_table.get_line_and_column(instruction.span());
122                ProgramItem::Instruction(label, instruction, comment, lc)
123            }
124            RawProgramItem::Directive(label, directive, comment) => {
125                let lc = self.look_table.get_line_and_column(directive.span());
126                ProgramItem::Directive(label, directive, comment, lc)
127            }
128            RawProgramItem::EOL(label) => ProgramItem::EOL(label),
129        }
130    }
131
132    fn hybrid_comment(
133        &mut self,
134        curr: ProgramItem,
135        next: Option<ProgramItem>,
136    ) -> Option<ProgramItem> {
137        match curr {
138            ProgramItem::Comment(comment, comment_lc) => {
139                if self.forward_next_comment {
140                    Some(ProgramItem::Comment(comment, comment_lc))
141                } else {
142                    self.forward_next_comment = true;
143                    None
144                }
145            }
146            ProgramItem::Instruction(labels, instruction, comment, lc) => {
147                if let Some(ProgramItem::Comment(comment, lc_comment)) = next {
148                    if lc_comment.at_the_same_line(&lc) {
149                        self.forward_next_comment = false;
150                        return Some(ProgramItem::Instruction(
151                            labels,
152                            instruction,
153                            Some(comment),
154                            lc,
155                        ));
156                    }
157                }
158                Some(ProgramItem::Instruction(labels, instruction, comment, lc))
159            }
160            ProgramItem::Directive(labels, directive, comment, lc) => {
161                if let Some(ProgramItem::Comment(comment, lc_comment)) = next {
162                    if lc_comment.at_the_same_line(&lc) {
163                        self.forward_next_comment = false;
164                        return Some(ProgramItem::Directive(labels, directive, Some(comment), lc));
165                    }
166                }
167                Some(ProgramItem::Directive(labels, directive, comment, lc))
168            }
169            ProgramItem::EOL(labels) => Some(ProgramItem::EOL(labels)),
170        }
171    }
172}
173
174#[derive(Debug)]
175struct LineColumnLookTable<'a> {
176    line_start_indices: HashMap<usize, (usize, usize)>, // Key: start index, Value: (line number, column start)
177    lines: Vec<&'a str>,
178}
179
180impl<'a> LineColumnLookTable<'a> {
181    // Build a new lookup table based on the file content
182    pub fn new(file_content: &'a str) -> Self {
183        let mut line_start_indices = HashMap::new();
184        let mut char_count = 0; // Keeps track of the starting character index of the line
185        let mut line_number = 1; // Line numbers are 1-based
186
187        for line in file_content.lines() {
188            // Insert the line start index and its corresponding line number and column start
189            line_start_indices.insert(char_count, (line_number, 1));
190            char_count += line.len() + 1; // Increment by the length of the line + 1 for newline character
191            line_number += 1;
192        }
193        let lines: Vec<&str> = file_content.lines().collect();
194
195        LineColumnLookTable {
196            line_start_indices,
197            lines,
198        }
199    }
200
201    // Function to get the line and column for a given span
202    pub fn get_line_and_column(&self, span: &Span) -> LineColumn {
203        let start = *span.start();
204        // Find the line where the span starts
205        for (start_index, (line_number, _)) in self.line_start_indices.iter() {
206            let line_len = self.lines[*line_number - 1].len();
207            if start >= *start_index && start < *start_index + line_len {
208                // Find the column number
209                let column = start - *start_index + 1;
210                return LineColumn {
211                    line: *line_number,
212                    column,
213                };
214            }
215        }
216
217        unreachable!()
218    }
219}
220
221impl LineColumn {
222    pub fn at_the_same_line(&self, other: &LineColumn) -> bool {
223        self.line == other.line
224    }
225}
226
227impl ProgramItem {
228    pub fn is_comment(&self) -> bool {
229        matches!(self, ProgramItem::Comment(..))
230    }
231
232    pub fn is_instruction(&self) -> bool {
233        matches!(self, ProgramItem::Instruction(..))
234    }
235
236    pub fn is_directive(&self) -> bool {
237        matches!(self, ProgramItem::Directive(..))
238    }
239
240    pub fn is_eol(&self) -> bool {
241        matches!(self, ProgramItem::EOL(..))
242    }
243}