unsynn/
dynamic.rs

1//! This module contains the types for dynamic transformations after parsing.
2
3#[allow(clippy::wildcard_imports)]
4use crate::*;
5
6use std::any::Any;
7use std::cell::{Ref, RefCell};
8use std::marker::PhantomData;
9use std::rc::Rc;
10
11// Planned: SharedDedup: -> global hashmap of all entities with the same content. allows replacing variables etc
12
13/// Trait alias for any type that can be used in dynamic `ToTokens` contexts.
14pub trait DynamicTokens: Any + ToTokens + std::fmt::Debug {
15    /// Upcasts `&DynamicTokens` to `&dyn Any`. This allows us to stay backward compatible with older rust.
16    /// Rust 1.86 implements upcast coercion.
17    fn as_any(&self) -> &dyn Any;
18}
19impl<T: Any + ToTokens + std::fmt::Debug> DynamicTokens for T {
20    fn as_any(&self) -> &dyn Any {
21        self
22    }
23}
24
25/// Parses a `T` (default: `Nothing`). Allows one to replace it at runtime, after parsing with
26/// anything else implementing `ToTokens`. This is backed by a `Rc`. One can replace any
27/// cloned occurrences or only the current one.
28///
29///
30/// # Example
31///
32/// ```
33/// # use unsynn::*;
34/// let mut token_iter = "foo".to_token_iter();
35///
36/// let parsed = <DynNode<Ident>>::parser(&mut token_iter).unwrap();
37/// assert_tokens_eq!(parsed, "foo");
38///
39/// let _test: Ident = parsed.downcast_ref::<Ident>().unwrap().clone();
40///
41/// // Global replacement of all cloned locations (parsed & other)
42/// let mut other = parsed.clone();
43/// other.replace_all_with(<Cons<ConstInteger<123>, Comma>>::default());
44/// assert_tokens_eq!(parsed, "123,");
45///
46/// // Local replacement (only other)
47/// other.replace_here_with(Bang::default());
48/// assert_tokens_eq!(other, "!");
49/// assert_tokens_eq!(parsed, "123,");
50/// ```
51#[derive(Clone, Debug)]
52pub struct DynNode<T = Nothing>(pub Rc<RefCell<Box<dyn DynamicTokens>>>, PhantomData<T>);
53
54impl<T> DynNode<T> {
55    /// Replaces the interior of a `DynNode` at all locations that cloned this, returns the
56    /// old content.
57    pub fn replace_all_with<U: DynamicTokens>(&self, this: U) -> Box<dyn DynamicTokens> {
58        std::mem::replace(&mut self.0.borrow_mut(), Box::new(this))
59    }
60
61    /// Detaches this `DynNode`, insert new content, returns `Self` with the old content.
62    #[allow(clippy::return_self_not_must_use)]
63    pub fn replace_here_with<U: DynamicTokens>(&mut self, this: U) -> Self {
64        Self(
65            std::mem::replace(&mut self.0, Rc::new(RefCell::new(Box::new(this)))),
66            PhantomData,
67        )
68    }
69
70    /// Casts a `DynNode` to a reference to a concrete type. Will return `None` on type error.
71    #[must_use]
72    pub fn downcast_ref<U: DynamicTokens>(&self) -> Option<Ref<'_, U>> {
73        Ref::filter_map(self.0.borrow(), |boxed| {
74            boxed.as_any().downcast_ref::<U>()
75            // for rust 1.86+ we could use (boxed.as_ref() as &dyn Any).downcast_ref::<U>()
76        })
77        .ok()
78    }
79}
80
81impl<T> Parser for DynNode<T>
82where
83    T: Parse + DynamicTokens,
84{
85    fn parser(tokens: &mut TokenIter) -> Result<Self> {
86        let t = tokens.parse::<T>().refine_err::<Self>()?;
87        Ok(Self(Rc::new(RefCell::new(Box::new(t))), PhantomData))
88    }
89}
90
91impl<T> ToTokens for DynNode<T> {
92    #[inline]
93    fn to_tokens(&self, tokens: &mut TokenStream) {
94        self.0.borrow().to_tokens(tokens);
95    }
96}