use super::*;
pub(super) struct ScopeMemo {
pub(super) nodes: std::rc::Rc<Vec<NodeId>>,
pub(super) referrers:
std::cell::RefCell<HashMap<(String, Option<String>), std::rc::Rc<Referrers>>>,
pub(super) values: std::cell::RefCell<HashMap<String, std::rc::Rc<ValueKeys>>>,
pub(super) declared: std::cell::RefCell<HashMap<(bool, u64), bool>>,
}
pub(super) type Referrers = HashMap<NodeId, Vec<usize>>;
pub(super) struct ValueKeys {
pub(super) keyed: HashMap<String, Vec<usize>>,
pub(super) unkeyed: Vec<usize>,
}
thread_local! {
static SCOPES: std::cell::RefCell<HashMap<(usize, String), std::rc::Rc<ScopeMemo>>> =
std::cell::RefCell::new(HashMap::new());
}
pub(super) type LastLinks = Option<((usize, NodeId), std::rc::Rc<Vec<(String, NodeId)>>)>;
pub(super) fn links_of(
adapter: &impl AstAdapter,
node: NodeId,
) -> std::rc::Rc<Vec<(String, NodeId)>> {
let key = (std::ptr::from_ref(adapter).cast::<u8>() as usize, node);
if let Some(hit) = with_run(|s| match &s.last_out {
Some((k, v)) if *k == key => Some(v.clone()),
_ => None,
}) {
return hit;
}
let v = std::rc::Rc::new(adapter.links(node));
with_run(|s| s.last_out = Some((key, v.clone())));
v
}
pub(super) fn backlinks_of(
adapter: &impl AstAdapter,
node: NodeId,
) -> std::rc::Rc<Vec<(String, NodeId)>> {
let key = (std::ptr::from_ref(adapter).cast::<u8>() as usize, node);
if let Some(hit) = with_run(|s| match &s.last_in {
Some((k, v)) if *k == key => Some(v.clone()),
_ => None,
}) {
return hit;
}
let v = std::rc::Rc::new(adapter.backlinks(node));
with_run(|s| s.last_in = Some((key, v.clone())));
v
}
pub(super) fn scope_is_static(text: &str) -> bool {
if text.contains(['$', '@', '%', '&']) || text.contains("((") {
return false;
}
let b: Vec<char> = text.chars().collect();
let word = |c: char| c.is_alphanumeric() || c == '_' || c == '-';
for (i, &c) in b.iter().enumerate() {
if c == '_' && !(i > 0 && word(b[i - 1])) && !(i + 1 < b.len() && word(b[i + 1])) {
return false;
}
if c == '(' && b.get(i + 1).is_some_and(|d| d.is_ascii_digit()) {
let mut j = i + 1;
while b.get(j).is_some_and(|d| d.is_ascii_digit()) {
j += 1;
}
if b.get(j) == Some(&')') {
return false;
}
}
}
true
}
pub(super) fn scope_memo(
adapter: &impl AstAdapter,
key: &str,
nodes: impl FnOnce() -> Vec<NodeId>,
) -> std::rc::Rc<ScopeMemo> {
let k = (
std::ptr::from_ref(adapter).cast::<u8>() as usize,
key.to_string(),
);
if let Some(m) = with_run(|s| s.scopes.get(&k).cloned()) {
return m;
}
let memo = std::rc::Rc::new(ScopeMemo {
nodes: std::rc::Rc::new(nodes()),
referrers: std::cell::RefCell::new(HashMap::new()),
values: std::cell::RefCell::new(HashMap::new()),
declared: std::cell::RefCell::new(HashMap::new()),
});
with_run(|s| s.scopes.insert(k, memo.clone()));
memo
}
pub(super) fn whole_arbor(adapter: &impl AstAdapter) -> Vec<NodeId> {
let mut all = Vec::new();
collect_subtree(adapter, adapter.root(), &mut all);
all.extend(adapter.non_tree_nodes());
all
}
pub(super) fn memo_referrers(
adapter: &impl AstAdapter,
memo: &ScopeMemo,
property: &str,
properties_of: impl Fn(NodeId) -> Vec<String>,
label: Option<&str>,
implied: Option<&str>,
target: NodeId,
) -> Vec<NodeId> {
let mut labels = vec![label.map(str::to_string)];
if label.is_none()
&& let Some(i) = implied
{
labels.push(Some(i.to_string()));
}
let mut at: Vec<usize> = Vec::new();
for l in labels {
let key = (property.to_string(), l);
let known = memo.referrers.borrow().get(&key).cloned();
let map = match known {
Some(m) => m,
None => {
let nodes = memo.nodes.clone();
let mut map: Referrers = HashMap::new();
for (pos, &u) in nodes.iter().enumerate() {
for p in properties_of(u) {
if let Some(t) = adapter.resolve(u, &p, key.1.as_deref()) {
let at = map.entry(t).or_default();
if at.last() != Some(&pos) {
at.push(pos);
}
}
}
}
let map = std::rc::Rc::new(map);
memo.referrers.borrow_mut().insert(key, map.clone());
map
}
};
if let Some(hits) = map.get(&target) {
at.extend(hits);
}
}
at.sort_unstable();
at.dedup();
at.into_iter().map(|pos| memo.nodes[pos]).collect()
}
pub(super) fn memo_holders(
adapter: &impl AstAdapter,
memo: &ScopeMemo,
property: &str,
value: &Value,
) -> Vec<NodeId> {
let Some(key) = value.equality_key() else {
return memo.nodes.to_vec();
};
let known = memo.values.borrow().get(property).cloned();
let keys = match known {
Some(k) => k,
None => {
let nodes = memo.nodes.clone();
let mut keyed: HashMap<String, Vec<usize>> = HashMap::new();
let mut unkeyed = Vec::new();
for (pos, &u) in nodes.iter().enumerate() {
match adapter.property(u, property) {
Some(v) => match v.equality_key() {
Some(k) => keyed.entry(k).or_default().push(pos),
None => unkeyed.push(pos),
},
None => {}
}
}
let keys = std::rc::Rc::new(ValueKeys { keyed, unkeyed });
memo.values
.borrow_mut()
.insert(property.to_string(), keys.clone());
keys
}
};
let mut at: Vec<usize> = keys.keyed.get(&key).cloned().unwrap_or_default();
at.extend(&keys.unkeyed);
at.sort_unstable();
at.dedup();
at.into_iter().map(|pos| memo.nodes[pos]).collect()
}