use alloc::string::String;
use crate::NodeId;
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
pub enum ScopeType {
Block,
Class,
Declaration,
File,
Function,
Namespace,
}
#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
pub struct StackScope {
pub is_exported: bool,
pub scope_type: Option<ScopeType>,
}
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct SymbolPush {
pub symbol: String,
pub precedence: i64,
pub is_reference: bool,
}
#[derive(Clone, PartialEq, Eq, Debug, Default)]
pub struct Definition {
pub is_private: bool,
pub visibility_guard: Option<String>,
}
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct SymbolPop {
pub symbol: String,
pub precedence: i64,
pub definition: Option<Definition>,
}
#[derive(Clone, PartialEq, Eq, Debug)]
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)]
pub enum StackNode {
DropScopes,
JumpTo,
PopScopedSymbol {
pop: SymbolPop,
import: Option<ImportBinding>,
},
PopSymbol(SymbolPop),
PushScopedSymbol {
push: SymbolPush,
scope: NodeId,
},
PushSymbol(SymbolPush),
Root,
Scope(StackScope),
}
impl StackNode {
#[must_use]
pub fn symbol(&self) -> Option<&str> {
match self {
Self::PushScopedSymbol { push, .. } | Self::PushSymbol(push) => Some(&push.symbol),
Self::PopScopedSymbol { pop, .. } | Self::PopSymbol(pop) => Some(&pop.symbol),
Self::DropScopes | Self::JumpTo | Self::Root | Self::Scope(_) => None,
}
}
#[must_use]
pub const fn precedence(&self) -> i64 {
match self {
Self::PushScopedSymbol { push, .. } | Self::PushSymbol(push) => push.precedence,
Self::PopScopedSymbol { pop, .. } | Self::PopSymbol(pop) => pop.precedence,
Self::DropScopes | Self::JumpTo | Self::Root | Self::Scope(_) => 0,
}
}
#[must_use]
pub const fn is_reference(&self) -> bool {
match self {
Self::PushScopedSymbol { push, .. } | Self::PushSymbol(push) => push.is_reference,
Self::DropScopes
| Self::JumpTo
| Self::PopScopedSymbol { .. }
| Self::PopSymbol(_)
| Self::Root
| Self::Scope(_) => false,
}
}
#[must_use]
pub const fn is_definition(&self) -> bool {
match self {
Self::PopScopedSymbol { pop, .. } | Self::PopSymbol(pop) => pop.definition.is_some(),
Self::DropScopes
| Self::JumpTo
| Self::PushScopedSymbol { .. }
| Self::PushSymbol(_)
| Self::Root
| Self::Scope(_) => false,
}
}
#[must_use]
pub const fn import_binding(&self) -> Option<&ImportBinding> {
match self {
Self::PopScopedSymbol { import, .. } => import.as_ref(),
Self::DropScopes
| Self::JumpTo
| Self::PopSymbol(_)
| Self::PushScopedSymbol { .. }
| Self::PushSymbol(_)
| Self::Root
| Self::Scope(_) => None,
}
}
}
#[cfg(test)]
mod tests {
use super::{
Definition, ImportBinding, ScopeType, StackNode, StackScope, SymbolPop, SymbolPush,
};
use crate::NodeId;
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()),
}
}
fn scoped_pop(symbol: &str) -> StackNode {
StackNode::PopScopedSymbol {
pop: pop(symbol),
import: None,
}
}
fn file_scope() -> StackNode {
StackNode::Scope(StackScope {
is_exported: true,
scope_type: Some(ScopeType::File),
})
}
#[test]
fn symbol_nodes_carry_their_symbol() {
let scoped_push = StackNode::PushScopedSymbol {
push: push("send"),
scope: NodeId(4),
};
assert_eq!(StackNode::PushSymbol(push("send")).symbol(), Some("send"));
assert_eq!(scoped_push.symbol(), Some("send"));
assert_eq!(StackNode::PopSymbol(pop("utils")).symbol(), Some("utils"));
assert_eq!(scoped_pop("send").symbol(), Some("send"));
assert_eq!(StackNode::Root.symbol(), None);
assert_eq!(file_scope().symbol(), None);
}
#[test]
fn nodes_without_a_symbol_take_no_precedence() {
assert_eq!(scoped_pop("send").precedence(), 1);
assert_eq!(StackNode::PushSymbol(push("send")).precedence(), 0);
assert_eq!(StackNode::JumpTo.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::PushScopedSymbol {
push: push("send"),
scope: NodeId(4),
}
.is_reference());
assert!(!StackNode::PushSymbol(chain_link).is_reference());
assert!(!scoped_pop("send").is_reference());
assert!(!StackNode::DropScopes.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!(scoped_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_scoped_pops_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::PopScopedSymbol {
pop: pop("send"),
import: Some(binding.clone()),
};
assert_eq!(import.import_binding(), Some(&binding));
assert!(import.is_definition());
assert_eq!(scoped_pop("send").import_binding(), None);
assert_eq!(StackNode::PopSymbol(pop("send")).import_binding(), None);
assert_eq!(StackNode::PushSymbol(push("send")).import_binding(), None);
}
}