use alloc::string::String;
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
#[allow(
clippy::exhaustive_enums,
reason = "a fixed ontology: the lexical scope kinds the stack graph model distinguishes"
)]
pub enum ScopeType {
Block,
Class,
Declaration,
File,
Function,
Namespace,
}
#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
#[non_exhaustive]
pub struct StackScope {
pub scope_type: Option<ScopeType>,
}
#[derive(Clone, PartialEq, Eq, Debug)]
#[non_exhaustive]
pub struct SymbolPush {
pub symbol: String,
pub precedence: i64,
pub is_reference: bool,
}
#[derive(Clone, PartialEq, Eq, Debug, Default)]
#[non_exhaustive]
pub struct Definition {
pub is_private: bool,
pub visibility_guard: Option<String>,
}
#[derive(Clone, PartialEq, Eq, Debug)]
#[non_exhaustive]
pub struct SymbolPop {
pub symbol: String,
pub precedence: i64,
pub definition: Option<Definition>,
pub import: Option<ImportBinding>,
}
#[derive(Clone, PartialEq, Eq, Debug)]
#[non_exhaustive]
pub struct ImportBinding {
pub module_stem: String,
pub source_symbol: Option<String>,
pub relative_level: u32,
pub binds_module: bool,
}
#[derive(Clone, PartialEq, Eq, Debug)]
#[allow(
clippy::exhaustive_enums,
reason = "a fixed ontology: the stack-graphs algorithm's node kinds"
)]
pub enum StackNode {
PopSymbol(SymbolPop),
PushSymbol(SymbolPush),
Root,
Scope(StackScope),
}
impl StackNode {
#[must_use]
pub fn symbol(&self) -> Option<&str> {
match self {
Self::PushSymbol(push) => Some(&push.symbol),
Self::PopSymbol(pop) => Some(&pop.symbol),
Self::Root | Self::Scope(_) => None,
}
}
#[must_use]
pub const fn precedence(&self) -> i64 {
match self {
Self::PushSymbol(push) => push.precedence,
Self::PopSymbol(pop) => pop.precedence,
Self::Root | Self::Scope(_) => 0,
}
}
#[must_use]
pub const fn is_reference(&self) -> bool {
match self {
Self::PushSymbol(push) => push.is_reference,
Self::PopSymbol(_) | Self::Root | Self::Scope(_) => false,
}
}
#[must_use]
pub const fn is_definition(&self) -> bool {
match self {
Self::PopSymbol(pop) => pop.definition.is_some(),
Self::PushSymbol(_) | Self::Root | Self::Scope(_) => false,
}
}
#[must_use]
pub const fn import_binding(&self) -> Option<&ImportBinding> {
match self {
Self::PopSymbol(pop) => pop.import.as_ref(),
Self::PushSymbol(_) | Self::Root | Self::Scope(_) => None,
}
}
}
#[cfg(test)]
mod tests {
use super::{
Definition, ImportBinding, ScopeType, StackNode, StackScope, SymbolPop, SymbolPush,
};
use alloc::borrow::ToOwned;
fn push(symbol: &str) -> SymbolPush {
SymbolPush {
symbol: symbol.to_owned(),
precedence: 0,
is_reference: true,
}
}
fn pop(symbol: &str) -> SymbolPop {
SymbolPop {
symbol: symbol.to_owned(),
precedence: 1,
definition: Some(Definition::default()),
import: None,
}
}
fn file_scope() -> StackNode {
StackNode::Scope(StackScope {
scope_type: Some(ScopeType::File),
})
}
#[test]
fn symbol_nodes_carry_their_symbol() {
assert_eq!(StackNode::PushSymbol(push("send")).symbol(), Some("send"));
assert_eq!(StackNode::PopSymbol(pop("utils")).symbol(), Some("utils"));
assert_eq!(StackNode::Root.symbol(), None);
assert_eq!(file_scope().symbol(), None);
}
#[test]
fn nodes_without_a_symbol_take_no_precedence() {
assert_eq!(StackNode::PopSymbol(pop("send")).precedence(), 1);
assert_eq!(StackNode::PushSymbol(push("send")).precedence(), 0);
assert_eq!(StackNode::Root.precedence(), 0);
assert_eq!(file_scope().precedence(), 0);
}
#[test]
fn only_marked_push_nodes_are_references() {
let chain_link = SymbolPush {
is_reference: false,
..push("utils")
};
assert!(StackNode::PushSymbol(push("send")).is_reference());
assert!(!StackNode::PushSymbol(chain_link).is_reference());
assert!(!StackNode::PopSymbol(pop("send")).is_reference());
assert!(!StackNode::Root.is_reference());
}
#[test]
fn only_marked_pop_nodes_are_definitions() {
let path_segment = SymbolPop {
definition: None,
..pop("utils")
};
assert!(StackNode::PopSymbol(pop("send")).is_definition());
assert!(!StackNode::PopSymbol(path_segment).is_definition());
assert!(!StackNode::PushSymbol(push("send")).is_definition());
assert!(!file_scope().is_definition());
}
#[test]
fn only_pops_from_imports_bind_imports() {
let binding = ImportBinding {
module_stem: "utils".to_owned(),
source_symbol: Some("send".to_owned()),
relative_level: 1,
binds_module: false,
};
let import = StackNode::PopSymbol(SymbolPop {
import: Some(binding.clone()),
..pop("send")
});
assert_eq!(import.import_binding(), Some(&binding));
assert!(import.is_definition());
assert_eq!(StackNode::PopSymbol(pop("send")).import_binding(), None);
assert_eq!(StackNode::PushSymbol(push("send")).import_binding(), None);
}
}