use super::types::{AmxDbg, DbgError, DbgSymbol, Ident, VClass};
impl AmxDbg {
pub fn parse(data: &[u8]) -> Result<Self, DbgError> {
super::parse::parse(data)
}
pub fn from_amx(amx: &[u8]) -> Result<Self, DbgError> {
let size_bytes = amx.get(0..4).ok_or(DbgError::Truncated)?;
let offset =
u32::from_le_bytes([size_bytes[0], size_bytes[1], size_bytes[2], size_bytes[3]])
as usize;
let block = amx.get(offset..).ok_or(DbgError::Truncated)?;
Self::parse(block)
}
#[must_use]
pub fn lookup_line(&self, addr: u32) -> Option<i32> {
self.lines
.iter()
.take_while(|l| l.address <= addr)
.last()
.map(|l| l.line)
}
#[must_use]
pub fn lookup_file(&self, addr: u32) -> Option<&str> {
self.files
.iter()
.take_while(|f| f.address <= addr)
.last()
.map(|f| f.name.as_str())
}
#[must_use]
pub fn lookup_function(&self, addr: u32) -> Option<&str> {
self.symbols
.iter()
.find(|s| {
s.ident == Ident::Function
&& s.codestart <= addr
&& s.codeend > addr
&& !s.name.starts_with('@')
})
.map(|s| s.name.as_str())
}
#[must_use]
pub fn function_address(&self, name: &str) -> Option<u32> {
let func = self
.symbols
.iter()
.find(|s| s.ident == Ident::Function && s.name == name && !s.name.starts_with('@'))?;
let first_line = self
.lines
.iter()
.filter(|l| l.address >= func.codestart && l.address < func.codeend)
.map(|l| l.address)
.min();
Some(first_line.unwrap_or(func.codestart))
}
#[must_use]
pub fn line_to_address(&self, line: i32, file: Option<&str>) -> Option<u32> {
let Some(want) = file else {
return self
.lines
.iter()
.find(|l| l.line >= line)
.map(|l| l.address);
};
for (i, f) in self.files.iter().enumerate() {
if !file_name_matches(&f.name, want) {
continue;
}
let bottom = f.address;
let top = self.files.get(i + 1).map_or(u32::MAX, |n| n.address);
if let Some(l) = self
.lines
.iter()
.filter(|l| l.address >= bottom && l.address < top)
.find(|l| l.line >= line)
{
return Some(l.address);
}
}
None
}
#[must_use]
pub fn symbols_in_scope(&self, addr: u32) -> Vec<&DbgSymbol> {
self.symbols
.iter()
.filter(|s| {
s.ident != Ident::Function
&& match s.vclass {
VClass::Global => true,
_ => s.codestart <= addr && s.codeend > addr,
}
})
.collect()
}
#[must_use]
pub fn tag_name(&self, tag: i16) -> Option<&str> {
self.tags
.iter()
.find(|t| t.tag == tag)
.map(|t| t.name.as_str())
}
}
fn file_name_matches(tbl: &str, want: &str) -> bool {
let norm = |s: &str| s.replace('\\', "/");
let (tbl, want) = (norm(tbl), norm(want));
if tbl == want {
return true;
}
let suffix_at_boundary = |a: &str, b: &str| {
b.strip_suffix(a)
.is_some_and(|head| head.is_empty() || head.ends_with('/'))
};
suffix_at_boundary(&tbl, &want) || suffix_at_boundary(&want, &tbl)
}