use serde::{Deserialize, Serialize};
pub const WIRE_VERSION: u16 = 1;
pub(crate) const ROOT: u32 = u32::MAX;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct Sym {
pub name: (u32, u16),
pub kind: u8,
pub start_line: u32,
pub end_line: u32,
pub parent: u32,
pub depth: u8,
}
#[derive(Debug, Default, Clone, PartialEq, Eq)]
pub struct SymbolOutline {
pub(crate) syms: Vec<Sym>,
pub(crate) names: String,
pub(crate) kinds: Vec<String>,
}
impl SymbolOutline {
#[must_use]
pub fn len(&self) -> usize {
self.syms.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.syms.is_empty()
}
pub(crate) fn name_of(&self, s: &Sym) -> &str {
let (off, len) = s.name;
&self.names[off as usize..off as usize + len as usize]
}
#[must_use]
pub fn find(&self, name: &str) -> Vec<usize> {
self.syms
.iter()
.enumerate()
.filter(|(_, s)| self.name_of(s) == name)
.map(|(i, _)| i)
.collect()
}
#[must_use]
pub fn name_at(&self, idx: usize) -> Option<&str> {
self.syms.get(idx).map(|s| self.name_of(s))
}
#[must_use]
pub fn lines_of(&self, idx: usize) -> Option<(u32, u32, &str)> {
let s = self.syms.get(idx)?;
Some((
s.start_line,
s.end_line,
self.kinds.get(s.kind as usize).map_or("", String::as_str),
))
}
#[must_use]
pub fn to_wire(&self) -> Vec<u8> {
let w = Wire {
x: WIRE_VERSION,
kinds: self.kinds.clone(),
names: self
.syms
.iter()
.map(|s| self.name_of(s).to_owned())
.collect(),
kind: self.syms.iter().map(|s| s.kind).collect(),
start: self.syms.iter().map(|s| s.start_line).collect(),
end: self.syms.iter().map(|s| s.end_line).collect(),
depth: self.syms.iter().map(|s| s.depth).collect(),
};
serde_json::to_vec(&w).expect("plain data always serializes")
}
}
#[derive(Serialize, Deserialize)]
struct Wire {
x: u16,
kinds: Vec<String>,
names: Vec<String>,
kind: Vec<u8>,
start: Vec<u32>,
end: Vec<u32>,
depth: Vec<u8>,
}
#[cfg(test)]
mod tests {
use super::*;
fn outline(n: usize) -> SymbolOutline {
let mut o = SymbolOutline {
kinds: vec!["function".into(), "class".into()],
..SymbolOutline::default()
};
for i in 0..n {
let name = format!("sym_{i}");
let off = u32::try_from(o.names.len()).unwrap();
o.names.push_str(&name);
o.syms.push(Sym {
name: (off, u16::try_from(name.len()).unwrap()),
kind: u8::try_from(i % 2).unwrap(),
start_line: u32::try_from(i * 10 + 1).unwrap(),
end_line: u32::try_from(i * 10 + 8).unwrap(),
parent: ROOT,
depth: u8::try_from(i % 3).unwrap(),
});
}
o
}
#[test]
fn wire_is_parallel_arrays_with_the_version_pin() {
let o = outline(7);
let v: serde_json::Value = serde_json::from_slice(&o.to_wire()).unwrap();
assert_eq!(v["x"], WIRE_VERSION);
for field in ["names", "kind", "start", "end", "depth"] {
assert_eq!(v[field].as_array().unwrap().len(), 7, "{field}");
}
assert_eq!(v["names"][3], "sym_3");
assert_eq!(v["start"][3], 31);
assert_eq!(v["kinds"][0], "function");
}
#[test]
fn arena_lookups() {
let o = outline(7);
assert_eq!(o.find("sym_3"), vec![3]);
let (start, end, kind) = o.lines_of(3).unwrap();
assert_eq!((start, end), (31, 38));
assert_eq!(kind, "class");
assert!(o.find("nope").is_empty());
}
}