use std::collections::HashMap;
#[derive(Debug, Clone, Default)]
pub struct ParseContext {
scope_stack: Vec<ScopeEntry>,
symbols: HashMap<String, SymbolInfo>,
pub in_test: bool,
pub in_macro: bool,
depth: usize,
}
#[derive(Debug, Clone)]
pub enum ScopeEntry {
Module(String),
Function(String),
Impl { type_name: String },
Block,
Test,
}
#[derive(Debug, Clone)]
pub struct SymbolInfo {
pub name: String,
pub value: Option<String>,
pub kind: SymbolKind,
pub line: u32,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SymbolKind {
Constant,
Static,
Function,
Variable,
Local,
Type,
}
impl ParseContext {
pub fn new() -> Self {
Self::default()
}
pub fn push_scope(&mut self, entry: ScopeEntry) {
if matches!(entry, ScopeEntry::Test) {
self.in_test = true;
}
self.scope_stack.push(entry);
self.depth += 1;
}
pub fn pop_scope(&mut self) {
if let Some(entry) = self.scope_stack.pop() {
if matches!(entry, ScopeEntry::Test) {
self.in_test = self.scope_stack.iter().any(|e| matches!(e, ScopeEntry::Test));
}
self.depth = self.depth.saturating_sub(1);
}
}
pub fn scope_path(&self) -> String {
self.scope_stack
.iter()
.filter_map(|e| match e {
ScopeEntry::Module(name) => Some(name.as_str()),
ScopeEntry::Function(name) => Some(name.as_str()),
ScopeEntry::Impl { type_name } => Some(type_name.as_str()),
ScopeEntry::Block | ScopeEntry::Test => None,
})
.collect::<Vec<_>>()
.join("::")
}
pub fn register_symbol(&mut self, name: String, info: SymbolInfo) {
self.symbols.insert(name, info);
}
pub fn lookup_symbol(&self, name: &str) -> Option<&SymbolInfo> {
self.symbols.get(name)
}
pub fn get_constant_value(&self, name: &str) -> Option<&str> {
self.symbols.get(name).and_then(|s| {
if s.kind == SymbolKind::Constant
|| s.kind == SymbolKind::Static
|| s.kind == SymbolKind::Local
{
s.value.as_deref()
} else {
None
}
})
}
pub fn is_test_context(&self) -> bool {
self.in_test
}
pub fn depth(&self) -> usize {
self.depth
}
pub fn in_module(&self, name: &str) -> bool {
self.scope_stack.iter().any(|e| {
if let ScopeEntry::Module(m) = e {
m == name
} else {
false
}
})
}
pub fn in_function(&self, name: &str) -> bool {
self.scope_stack.iter().any(|e| {
if let ScopeEntry::Function(f) = e {
f == name
} else {
false
}
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_scope_path() {
let mut ctx = ParseContext::new();
ctx.push_scope(ScopeEntry::Module("foo".into()));
ctx.push_scope(ScopeEntry::Module("bar".into()));
ctx.push_scope(ScopeEntry::Function("baz".into()));
assert_eq!(ctx.scope_path(), "foo::bar::baz");
}
#[test]
fn test_test_context() {
let mut ctx = ParseContext::new();
assert!(!ctx.is_test_context());
ctx.push_scope(ScopeEntry::Test);
assert!(ctx.is_test_context());
ctx.pop_scope();
assert!(!ctx.is_test_context());
}
#[test]
fn test_symbol_lookup() {
let mut ctx = ParseContext::new();
ctx.register_symbol(
"API_KEY".into(),
SymbolInfo {
name: "API_KEY".into(),
value: Some("secret".into()),
kind: SymbolKind::Constant,
line: 10,
},
);
assert_eq!(ctx.get_constant_value("API_KEY"), Some("secret"));
assert_eq!(ctx.get_constant_value("UNKNOWN"), None);
}
}