use std::collections::HashMap;
use std::sync::atomic::{AtomicU32, Ordering};
use crate::lexer::Span;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct SyntaxContext(pub u32);
impl SyntaxContext {
pub const ROOT: SyntaxContext = SyntaxContext(0);
pub fn fresh() -> Self {
static COUNTER: AtomicU32 = AtomicU32::new(1);
SyntaxContext(COUNTER.fetch_add(1, Ordering::SeqCst))
}
pub fn is_root(self) -> bool {
self.0 == 0
}
}
impl Default for SyntaxContext {
fn default() -> Self {
Self::ROOT
}
}
#[derive(Debug, Clone)]
pub struct HygieneContext {
contexts: HashMap<SyntaxContext, ContextInfo>,
next_id: u32,
}
#[derive(Debug, Clone)]
pub struct ContextInfo {
pub parent: Option<SyntaxContext>,
pub expansion_span: Span,
pub is_opaque: bool,
}
impl HygieneContext {
pub fn new() -> Self {
let mut contexts = HashMap::new();
contexts.insert(
SyntaxContext::ROOT,
ContextInfo {
parent: None,
expansion_span: Span::dummy(),
is_opaque: false,
},
);
Self {
contexts,
next_id: 1,
}
}
pub fn fresh_context(&mut self, span: Span) -> SyntaxContext {
let id = SyntaxContext(self.next_id);
self.next_id += 1;
self.contexts.insert(
id,
ContextInfo {
parent: Some(SyntaxContext::ROOT),
expansion_span: span,
is_opaque: true,
},
);
id
}
pub fn fresh_with_parent(&mut self, parent: SyntaxContext, span: Span) -> SyntaxContext {
let id = SyntaxContext(self.next_id);
self.next_id += 1;
self.contexts.insert(
id,
ContextInfo {
parent: Some(parent),
expansion_span: span,
is_opaque: true,
},
);
id
}
pub fn context_info(&self, ctx: SyntaxContext) -> Option<&ContextInfo> {
self.contexts.get(&ctx)
}
pub fn contexts_compatible(&self, ctx1: SyntaxContext, ctx2: SyntaxContext) -> bool {
if ctx1 == ctx2 {
return true;
}
self.is_ancestor(ctx1, ctx2) || self.is_ancestor(ctx2, ctx1)
}
pub fn is_ancestor(&self, ctx1: SyntaxContext, ctx2: SyntaxContext) -> bool {
let mut current = ctx2;
while let Some(info) = self.contexts.get(¤t) {
if let Some(parent) = info.parent {
if parent == ctx1 {
return true;
}
current = parent;
} else {
break;
}
}
false
}
pub fn parent_chain(&self, ctx: SyntaxContext) -> Vec<SyntaxContext> {
let mut chain = vec![ctx];
let mut current = ctx;
while let Some(info) = self.contexts.get(¤t) {
if let Some(parent) = info.parent {
chain.push(parent);
current = parent;
} else {
break;
}
}
chain
}
pub fn apply_context(&self, span: Span, _ctx: SyntaxContext) -> Span {
span
}
pub fn mark_cross_boundary(&mut self, ctx: SyntaxContext, name: &str) {
let _ = (ctx, name);
}
}
impl Default for HygieneContext {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct HygienicIdent {
pub name: String,
pub context: SyntaxContext,
}
impl HygienicIdent {
pub fn new(name: impl Into<String>, context: SyntaxContext) -> Self {
Self {
name: name.into(),
context,
}
}
pub fn root(name: impl Into<String>) -> Self {
Self::new(name, SyntaxContext::ROOT)
}
pub fn can_resolve_to(&self, other: &HygienicIdent, hygiene: &HygieneContext) -> bool {
if self.name != other.name {
return false;
}
hygiene.contexts_compatible(self.context, other.context)
}
}
pub fn gensym(prefix: &str) -> String {
static COUNTER: AtomicU32 = AtomicU32::new(0);
format!("{}_{}", prefix, COUNTER.fetch_add(1, Ordering::SeqCst))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_root_context() {
assert!(SyntaxContext::ROOT.is_root());
assert!(!SyntaxContext::fresh().is_root());
}
#[test]
fn test_fresh_contexts() {
let ctx1 = SyntaxContext::fresh();
let ctx2 = SyntaxContext::fresh();
assert_ne!(ctx1, ctx2);
}
#[test]
fn test_context_compatibility() {
let mut hygiene = HygieneContext::new();
let ctx1 = hygiene.fresh_context(Span::dummy());
let ctx2 = hygiene.fresh_with_parent(ctx1, Span::dummy());
assert!(hygiene.contexts_compatible(ctx1, ctx1));
assert!(hygiene.contexts_compatible(ctx1, ctx2));
let ctx3 = hygiene.fresh_context(Span::dummy());
assert!(!hygiene.contexts_compatible(ctx2, ctx3));
}
#[test]
fn test_hygienic_ident_resolution() {
let hygiene = HygieneContext::new();
let ident1 = HygienicIdent::root("x");
let ident2 = HygienicIdent::root("x");
let ident3 = HygienicIdent::root("y");
let ident4 = HygienicIdent::new("x", SyntaxContext::fresh());
assert!(ident1.can_resolve_to(&ident2, &hygiene));
assert!(!ident1.can_resolve_to(&ident3, &hygiene));
}
#[test]
fn test_gensym() {
let name1 = gensym("temp");
let name2 = gensym("temp");
assert_ne!(name1, name2);
assert!(name1.starts_with("temp_"));
}
}