use super::GLOBALS;
use serde::{Deserialize, Serialize};
use std::{
collections::{HashMap, HashSet},
fmt,
};
#[derive(Clone, Copy, PartialEq, Eq, Default, PartialOrd, Ord, Hash, Serialize, Deserialize)]
#[serde(transparent)]
pub struct SyntaxContext(u32);
#[derive(Copy, Clone, Debug)]
struct SyntaxContextData {
outer_mark: Mark,
prev_ctxt: SyntaxContext,
opaque: SyntaxContext,
opaque_and_semitransparent: SyntaxContext,
}
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
pub struct Mark(u32);
#[derive(Clone, Debug)]
struct MarkData {
parent: Mark,
is_builtin: bool,
}
impl Mark {
pub fn fresh(parent: Mark) -> Self {
HygieneData::with(|data| {
data.marks.push(MarkData {
parent,
is_builtin: false,
});
Mark(data.marks.len() as u32 - 1)
})
}
#[inline]
pub fn root() -> Self {
Mark(0)
}
#[inline]
pub fn as_u32(self) -> u32 {
self.0
}
#[inline]
pub fn from_u32(raw: u32) -> Mark {
Mark(raw)
}
#[inline]
pub fn parent(self) -> Mark {
HygieneData::with(|data| data.marks[self.0 as usize].parent)
}
#[inline]
pub fn is_builtin(self) -> bool {
assert_ne!(self, Mark::root());
HygieneData::with(|data| data.marks[self.0 as usize].is_builtin)
}
#[inline]
pub fn set_is_builtin(self, is_builtin: bool) {
assert_ne!(self, Mark::root());
HygieneData::with(|data| data.marks[self.0 as usize].is_builtin = is_builtin)
}
pub fn is_descendant_of(mut self, ancestor: Mark) -> bool {
HygieneData::with(|data| {
while self != ancestor {
if self == Mark::root() {
return false;
}
self = data.marks[self.0 as usize].parent;
}
true
})
}
pub fn least_ancestor(mut a: Mark, mut b: Mark) -> Mark {
HygieneData::with(|data| {
let mut a_path = HashSet::<Mark>::default();
while a != Mark::root() {
a_path.insert(a);
a = data.marks[a.0 as usize].parent;
}
while !a_path.contains(&b) {
b = data.marks[b.0 as usize].parent;
}
b
})
}
}
#[derive(Debug)]
pub(crate) struct HygieneData {
marks: Vec<MarkData>,
syntax_contexts: Vec<SyntaxContextData>,
markings: HashMap<(SyntaxContext, Mark), SyntaxContext>,
}
impl Default for HygieneData {
fn default() -> Self {
Self::new()
}
}
impl HygieneData {
pub(crate) fn new() -> Self {
HygieneData {
marks: vec![MarkData {
parent: Mark::root(),
is_builtin: true,
}],
syntax_contexts: vec![SyntaxContextData {
outer_mark: Mark::root(),
prev_ctxt: SyntaxContext(0),
opaque: SyntaxContext(0),
opaque_and_semitransparent: SyntaxContext(0),
}],
markings: HashMap::default(),
}
}
fn with<T, F: FnOnce(&mut HygieneData) -> T>(f: F) -> T {
GLOBALS.with(|globals| f(&mut *globals.hygiene_data.lock().unwrap()))
}
}
impl SyntaxContext {
pub const fn empty() -> Self {
SyntaxContext(0)
}
pub fn apply_mark(self, mark: Mark) -> SyntaxContext {
assert_ne!(mark, Mark::root());
return self.apply_mark_internal(mark);
}
fn apply_mark_internal(self, mark: Mark) -> SyntaxContext {
HygieneData::with(|data| {
let syntax_contexts = &mut data.syntax_contexts;
let mut opaque = syntax_contexts[self.0 as usize].opaque;
let opaque_and_semitransparent =
syntax_contexts[self.0 as usize].opaque_and_semitransparent;
let prev_ctxt = opaque;
opaque = *data.markings.entry((prev_ctxt, mark)).or_insert_with(|| {
let new_opaque = SyntaxContext(syntax_contexts.len() as u32);
syntax_contexts.push(SyntaxContextData {
outer_mark: mark,
prev_ctxt,
opaque: new_opaque,
opaque_and_semitransparent: new_opaque,
});
new_opaque
});
let prev_ctxt = self;
*data.markings.entry((prev_ctxt, mark)).or_insert_with(|| {
let new_opaque_and_semitransparent_and_transparent =
SyntaxContext(syntax_contexts.len() as u32);
syntax_contexts.push(SyntaxContextData {
outer_mark: mark,
prev_ctxt,
opaque,
opaque_and_semitransparent,
});
new_opaque_and_semitransparent_and_transparent
})
})
}
pub fn remove_mark(&mut self) -> Mark {
HygieneData::with(|data| {
let outer_mark = data.syntax_contexts[self.0 as usize].outer_mark;
*self = data.syntax_contexts[self.0 as usize].prev_ctxt;
outer_mark
})
}
pub fn adjust(&mut self, expansion: Mark) -> Option<Mark> {
let mut scope = None;
while !expansion.is_descendant_of(self.outer()) {
scope = Some(self.remove_mark());
}
scope
}
pub fn glob_adjust(
&mut self,
expansion: Mark,
mut glob_ctxt: SyntaxContext,
) -> Option<Option<Mark>> {
let mut scope = None;
while !expansion.is_descendant_of(glob_ctxt.outer()) {
scope = Some(glob_ctxt.remove_mark());
if self.remove_mark() != scope.unwrap() {
return None;
}
}
if self.adjust(expansion).is_some() {
return None;
}
Some(scope)
}
pub fn reverse_glob_adjust(
&mut self,
expansion: Mark,
mut glob_ctxt: SyntaxContext,
) -> Option<Option<Mark>> {
if self.adjust(expansion).is_some() {
return None;
}
let mut marks = Vec::new();
while !expansion.is_descendant_of(glob_ctxt.outer()) {
marks.push(glob_ctxt.remove_mark());
}
let scope = marks.last().cloned();
while let Some(mark) = marks.pop() {
*self = self.apply_mark(mark);
}
Some(scope)
}
#[inline]
pub fn outer(self) -> Mark {
HygieneData::with(|data| data.syntax_contexts[self.0 as usize].outer_mark)
}
}
impl fmt::Debug for SyntaxContext {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "#{}", self.0)
}
}
impl Default for Mark {
fn default() -> Self {
Mark::root()
}
}