#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SourcePositionEntry {
pub bytecode_offset: u32,
pub line: u32,
pub column: u32,
pub is_statement: bool,
}
impl SourcePositionEntry {
pub fn new(bytecode_offset: u32, line: u32, column: u32, is_statement: bool) -> Self {
Self {
bytecode_offset,
line,
column,
is_statement,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct SourcePositionTable {
entries: Vec<SourcePositionEntry>,
}
impl SourcePositionTable {
pub fn new() -> Self {
Self {
entries: Vec::new(),
}
}
pub fn add_entry(&mut self, entry: SourcePositionEntry) {
debug_assert!(
self.entries
.last()
.is_none_or(|last| last.bytecode_offset <= entry.bytecode_offset),
"entries must be added in non-decreasing bytecode_offset order"
);
self.entries.push(entry);
}
pub fn len(&self) -> usize {
self.entries.len()
}
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
pub fn entries(&self) -> &[SourcePositionEntry] {
&self.entries
}
pub fn lookup(&self, bytecode_offset: u32) -> Option<&SourcePositionEntry> {
let idx = self
.entries
.partition_point(|e| e.bytecode_offset <= bytecode_offset);
idx.checked_sub(1).map(|i| &self.entries[i])
}
}
#[cfg(test)]
mod tests {
use super::*;
fn make_table() -> SourcePositionTable {
let mut t = SourcePositionTable::new();
t.add_entry(SourcePositionEntry::new(0, 1, 1, true));
t.add_entry(SourcePositionEntry::new(4, 1, 5, false));
t.add_entry(SourcePositionEntry::new(8, 2, 1, true));
t
}
#[test]
fn test_build_table_len() {
let t = make_table();
assert_eq!(t.len(), 3);
assert!(!t.is_empty());
}
#[test]
fn test_build_empty_table() {
let t = SourcePositionTable::new();
assert_eq!(t.len(), 0);
assert!(t.is_empty());
}
#[test]
fn test_entries_slice() {
let t = make_table();
let entries = t.entries();
assert_eq!(entries.len(), 3);
assert_eq!(entries[0], SourcePositionEntry::new(0, 1, 1, true));
assert_eq!(entries[1], SourcePositionEntry::new(4, 1, 5, false));
assert_eq!(entries[2], SourcePositionEntry::new(8, 2, 1, true));
}
#[test]
fn test_lookup_exact_first_entry() {
let t = make_table();
let e = t.lookup(0).unwrap();
assert_eq!(e.bytecode_offset, 0);
assert_eq!(e.line, 1);
assert_eq!(e.column, 1);
assert!(e.is_statement);
}
#[test]
fn test_lookup_exact_middle_entry() {
let t = make_table();
let e = t.lookup(4).unwrap();
assert_eq!(e.bytecode_offset, 4);
assert_eq!(e.line, 1);
assert_eq!(e.column, 5);
assert!(!e.is_statement);
}
#[test]
fn test_lookup_exact_last_entry() {
let t = make_table();
let e = t.lookup(8).unwrap();
assert_eq!(e.bytecode_offset, 8);
assert_eq!(e.line, 2);
assert_eq!(e.column, 1);
assert!(e.is_statement);
}
#[test]
fn test_lookup_between_entries_returns_preceding() {
let t = make_table();
let e = t.lookup(2).unwrap();
assert_eq!(e.bytecode_offset, 0);
let e = t.lookup(6).unwrap();
assert_eq!(e.bytecode_offset, 4);
}
#[test]
fn test_lookup_beyond_last_entry() {
let t = make_table();
let e = t.lookup(100).unwrap();
assert_eq!(e.bytecode_offset, 8);
}
#[test]
fn test_lookup_empty_table_returns_none() {
let t = SourcePositionTable::new();
assert!(t.lookup(0).is_none());
assert!(t.lookup(42).is_none());
}
#[test]
fn test_lookup_before_first_entry_returns_none() {
let mut t = SourcePositionTable::new();
t.add_entry(SourcePositionEntry::new(4, 1, 1, true));
assert!(t.lookup(0).is_none());
assert!(t.lookup(3).is_none());
assert!(t.lookup(4).is_some());
}
#[test]
fn test_is_statement_field() {
let mut t = SourcePositionTable::new();
t.add_entry(SourcePositionEntry::new(0, 1, 1, true));
t.add_entry(SourcePositionEntry::new(2, 1, 3, false));
assert!(t.lookup(0).unwrap().is_statement);
assert!(!t.lookup(2).unwrap().is_statement);
}
#[test]
fn test_single_entry_table() {
let mut t = SourcePositionTable::new();
t.add_entry(SourcePositionEntry::new(10, 5, 3, true));
assert!(t.lookup(9).is_none());
let e = t.lookup(10).unwrap();
assert_eq!(e.line, 5);
assert_eq!(e.column, 3);
assert!(t.lookup(99).is_some());
}
#[test]
fn test_default_is_empty() {
let t = SourcePositionTable::default();
assert!(t.is_empty());
}
}