calyx_utils/
position.rs

1//! Definitions for tracking source position information of Calyx programs
2
3use itertools::Itertools;
4use std::{cmp, fmt::Write, mem, sync};
5
6#[derive(Clone, Copy, PartialEq, Eq, Debug)]
7/// Handle to a position in a [PositionTable]
8/// The index refers to the index in the [PositionTable::indices] vector.
9pub struct PosIdx(u32);
10
11#[derive(Clone, Copy, PartialEq, Eq)]
12/// Handle to a file in a [PositionTable]
13/// The index refers to the index in the [PositionTable::files] vector.
14pub struct FileIdx(u32);
15
16/// A source program file
17struct File {
18    /// Name of the file
19    name: String,
20    /// The source code of the file
21    source: String,
22}
23
24struct PosData {
25    /// The file in the program. The index refers to the index in the
26    /// [PositionTable::files] vector.
27    file: FileIdx,
28    /// Start of the span
29    start: usize,
30    /// End of the span
31    end: usize,
32}
33
34/// Source position information for a Calyx program.
35pub struct PositionTable {
36    /// The source files of the program
37    files: Vec<File>,
38    /// Mapping from indexes to position data
39    indices: Vec<PosData>,
40}
41
42impl Default for PositionTable {
43    fn default() -> Self {
44        Self::new()
45    }
46}
47
48impl PositionTable {
49    /// The unknown position
50    pub const UNKNOWN: PosIdx = PosIdx(0);
51
52    /// Create a new position table where the first file and first position are unknown
53    pub fn new() -> Self {
54        let mut table = PositionTable {
55            files: Vec::new(),
56            indices: Vec::new(),
57        };
58        table.add_file("unknown".to_string(), "".to_string());
59        let pos = table.add_pos(FileIdx(0), 0, 0);
60        debug_assert!(pos == Self::UNKNOWN);
61        table
62    }
63
64    /// Add a new file to the position table
65    pub fn add_file(&mut self, name: String, source: String) -> FileIdx {
66        let file = File { name, source };
67        let file_idx = self.files.len();
68        self.files.push(file);
69        FileIdx(file_idx as u32)
70    }
71
72    /// Return a reference to the file with the given index
73    fn get_file_data(&self, file: FileIdx) -> &File {
74        &self.files[file.0 as usize]
75    }
76
77    pub fn get_source(&self, file: FileIdx) -> &str {
78        &self.get_file_data(file).source
79    }
80
81    /// Add a new position to the position table
82    pub fn add_pos(
83        &mut self,
84        file: FileIdx,
85        start: usize,
86        end: usize,
87    ) -> PosIdx {
88        let pos = PosData { file, start, end };
89        let pos_idx = self.indices.len();
90        self.indices.push(pos);
91        PosIdx(pos_idx as u32)
92    }
93
94    fn get_pos(&self, pos: PosIdx) -> &PosData {
95        &self.indices[pos.0 as usize]
96    }
97}
98
99/// The global position table
100pub struct GlobalPositionTable;
101
102impl GlobalPositionTable {
103    /// Return reference to a global [PositionTable]
104    pub fn as_mut() -> &'static mut PositionTable {
105        static mut SINGLETON: mem::MaybeUninit<PositionTable> =
106            mem::MaybeUninit::uninit();
107        static ONCE: sync::Once = sync::Once::new();
108
109        // SAFETY:
110        // - writing to the singleton is OK because we only do it one time
111        // - the ONCE guarantees that SINGLETON is init'ed before assume_init_ref
112        unsafe {
113            ONCE.call_once(|| {
114                SINGLETON.write(PositionTable::new());
115                assert!(PositionTable::UNKNOWN == GPosIdx::UNKNOWN.0)
116            });
117            SINGLETON.assume_init_mut()
118        }
119    }
120
121    /// Return an immutable reference to the global position table
122    pub fn as_ref() -> &'static PositionTable {
123        Self::as_mut()
124    }
125}
126
127#[derive(Clone, Copy, PartialEq, Eq, Debug)]
128/// A position index backed by a global [PositionTable]
129pub struct GPosIdx(pub PosIdx);
130
131impl Default for GPosIdx {
132    fn default() -> Self {
133        Self::UNKNOWN
134    }
135}
136
137impl GPosIdx {
138    /// Symbol for the unknown position
139    pub const UNKNOWN: GPosIdx = GPosIdx(PosIdx(0));
140
141    /// Convert the position into an optional.
142    /// Returns `None` if the position is the unknown position.
143    pub fn into_option(self) -> Option<Self> {
144        if self == Self::UNKNOWN {
145            None
146        } else {
147            Some(self)
148        }
149    }
150
151    /// Returns the
152    /// 1. lines associated with this span
153    /// 2. start position of the first line in span
154    /// 3. line number of the span
155    fn get_lines(&self) -> (Vec<&str>, usize, usize) {
156        let table = GlobalPositionTable::as_ref();
157        let pos_d = table.get_pos(self.0);
158        let file = &table.get_file_data(pos_d.file).source;
159
160        let lines = file.split('\n').collect_vec();
161        let mut pos: usize = 0;
162        let mut linum: usize = 1;
163        let mut collect_lines = false;
164        let mut buf = Vec::new();
165
166        let mut out_line: usize = 0;
167        let mut out_idx: usize = 0;
168        for l in lines {
169            let next_pos = pos + l.len();
170            if pos_d.start >= pos && pos_d.start <= next_pos {
171                out_line = linum;
172                out_idx = pos;
173                collect_lines = true;
174            }
175            if collect_lines && pos_d.end >= pos {
176                buf.push(l)
177            }
178            if pos_d.end <= next_pos {
179                break;
180            }
181            pos = next_pos + 1;
182            linum += 1;
183        }
184        (buf, out_idx, out_line)
185    }
186
187    /// Format this position with a the error message `err_msg`
188    pub fn format<S: AsRef<str>>(&self, err_msg: S) -> String {
189        let table = GlobalPositionTable::as_ref();
190        let pos_d = table.get_pos(self.0);
191        let name = &table.get_file_data(pos_d.file).name;
192
193        let (lines, pos, linum) = self.get_lines();
194        let mut buf = name.to_string();
195
196        let l = lines[0];
197        let linum_text = format!("{} ", linum);
198        let linum_space: String = " ".repeat(linum_text.len());
199        let mark: String = "^".repeat(cmp::min(
200            pos_d.end - pos_d.start,
201            l.len() - (pos_d.start - pos),
202        ));
203        let space: String = " ".repeat(pos_d.start - pos);
204        writeln!(buf).unwrap();
205        writeln!(buf, "{}|{}", linum_text, l).unwrap();
206        write!(
207            buf,
208            "{}|{}{} {}",
209            linum_space,
210            space,
211            mark,
212            err_msg.as_ref()
213        )
214        .unwrap();
215        buf
216    }
217
218    pub fn get_location(&self) -> (&str, usize, usize) {
219        let table = GlobalPositionTable::as_ref();
220        let pos_d = table.get_pos(self.0);
221        let name = &table.get_file_data(pos_d.file).name;
222        (name, pos_d.start, pos_d.end)
223    }
224
225    /// Visualizes the span without any message or mkaring
226    pub fn show(&self) -> String {
227        let (lines, _, linum) = self.get_lines();
228        let l = lines[0];
229        let linum_text = format!("{} ", linum);
230        format!("{}|{}\n", linum_text, l)
231    }
232}
233
234/// An IR node that may contain position information.
235pub trait WithPos {
236    /// Copy the span associated with this node.
237    fn copy_span(&self) -> GPosIdx;
238}