#[derive(Debug)]
pub struct IdGenerator {
scope_stack: Vec<String>,
child_counter: Vec<u32>,
}
impl IdGenerator {
#[must_use]
pub fn new(file_path: &str) -> Self {
Self {
scope_stack: vec![file_path.to_owned()],
child_counter: vec![0],
}
}
pub fn push_named_scope(&mut self, name: &str) {
self.scope_stack.push(name.to_owned());
self.child_counter.push(0);
}
pub fn push_anonymous_scope(&mut self) -> u32 {
let idx = self.child_counter.last().copied().unwrap_or(0);
if let Some(counter) = self.child_counter.last_mut() {
*counter += 1;
}
self.scope_stack.push(format!("${idx}"));
self.child_counter.push(0);
idx
}
pub fn pop_scope(&mut self) {
if self.scope_stack.len() > 1 {
self.scope_stack.pop();
self.child_counter.pop();
}
}
#[must_use]
pub fn named_id(&self, name: &str) -> String {
if self.scope_stack.len() == 1 {
format!("{}::{name}", self.scope_stack[0])
} else {
format!("{}::{name}", self.current_prefix())
}
}
pub fn anonymous_id(&mut self) -> String {
let idx = self.child_counter.last().copied().unwrap_or(0);
if let Some(counter) = self.child_counter.last_mut() {
*counter += 1;
}
format!("{}::${idx}", self.current_prefix())
}
#[must_use]
pub fn field_id(&self, base_id: &str, field_name: &str) -> String {
format!("{base_id}.{field_name}")
}
#[must_use]
pub fn current_prefix(&self) -> String {
self.scope_stack.join("::")
}
#[must_use]
pub fn depth(&self) -> usize {
self.scope_stack.len()
}
pub fn reset_counter(&mut self) {
if let Some(counter) = self.child_counter.last_mut() {
*counter = 0;
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn basic_named_ids() {
let id_gen = IdGenerator::new("src/main.rs");
assert_eq!(id_gen.named_id("User"), "src/main.rs::User");
}
#[test]
fn nested_scopes() {
let mut id_gen = IdGenerator::new("src/lib.rs");
id_gen.push_named_scope("Parser");
id_gen.push_named_scope("parse");
assert_eq!(
id_gen.named_id("config"),
"src/lib.rs::Parser::parse::config"
);
id_gen.pop_scope();
assert_eq!(id_gen.named_id("new"), "src/lib.rs::Parser::new");
}
#[test]
fn anonymous_ids_increment() {
let mut id_gen = IdGenerator::new("test.ts");
id_gen.push_named_scope("main");
let id0 = id_gen.anonymous_id();
let id1 = id_gen.anonymous_id();
let id2 = id_gen.anonymous_id();
assert_eq!(id0, "test.ts::main::$0");
assert_eq!(id1, "test.ts::main::$1");
assert_eq!(id2, "test.ts::main::$2");
}
#[test]
fn anonymous_scopes() {
let mut id_gen = IdGenerator::new("test.py");
id_gen.push_named_scope("process");
let _stmt_idx = id_gen.push_anonymous_scope(); let inner = id_gen.anonymous_id();
assert_eq!(inner, "test.py::process::$0::$0");
id_gen.pop_scope();
let _stmt_idx2 = id_gen.push_anonymous_scope(); let inner2 = id_gen.anonymous_id();
assert_eq!(inner2, "test.py::process::$1::$0");
}
#[test]
fn field_ids() {
let id_gen = IdGenerator::new("test.rs");
let base = id_gen.named_id("expr");
let left = id_gen.field_id(&base, "left");
let right = id_gen.field_id(&base, "right");
assert_eq!(left, "test.rs::expr.left");
assert_eq!(right, "test.rs::expr.right");
}
#[test]
fn depth_tracking() {
let mut id_gen = IdGenerator::new("f.ts");
assert_eq!(id_gen.depth(), 1);
id_gen.push_named_scope("fn");
assert_eq!(id_gen.depth(), 2);
id_gen.push_anonymous_scope();
assert_eq!(id_gen.depth(), 3);
id_gen.pop_scope();
assert_eq!(id_gen.depth(), 2);
}
}