Skip to main content

tokel_engine/
expand.rs

1use std::borrow::Cow;
2use std::iter;
3
4use proc_macro2::{Group, TokenStream, TokenTree};
5use syn::Ident;
6
7use crate::session::Session;
8
9use crate::syntax::{Block, Element, Pipe, Pipeline, TokelGroup, TokelStream, TokelTree};
10
11/// A trait for evaluating Tokel AST nodes into standard token streams.
12///
13/// This trait allows any individual part of the Tokel grammar (from a full `TokelStream`
14/// down to a single `Pipeline`) to be evaluated dynamically using the state held
15/// within a [`Session`].
16pub trait Expand {
17    /// Additional context or data required to expand this specific node.
18    ///
19    /// For self-contained nodes like `TokelStream` or `Element`, this is simply `()`.
20    /// For modifier nodes like `Pipeline`, this is the `TokenStream` being transformed.
21    type Context;
22
23    /// Expands the AST node into a fully resolved token stream using explicit context.
24    ///
25    /// This is the core evaluation method. It traverses the node, looking up and
26    /// applying any transformers from the provided [`Session`], and utilizing the
27    /// provided `context` to complete its evaluation.
28    ///
29    /// # Errors
30    ///
31    /// The failure mode of this associated function is implementation-dependent.
32    fn expand_with(
33        self,
34        session: &mut Session,
35        context: Self::Context,
36    ) -> Result<TokenStream, syn::Error>;
37
38    /// Expands the AST node into a fully resolved token stream.
39    ///
40    /// This is a convenience method available for nodes that do not require complex
41    /// external context (i.e., where `Context` implements `Default`, such as `()`).
42    /// It automatically calls [`expand_with`](Self::expand_with) using the default context.
43    ///
44    /// # Errors
45    ///
46    /// The failure mode of this associated function is implementation-dependent.
47    fn expand(self, session: &mut Session) -> Result<TokenStream, syn::Error>
48    where
49        Self::Context: Default,
50        Self: Sized,
51    {
52        Self::expand_with(self, session, Self::Context::default())
53    }
54}
55
56impl Expand for TokelStream {
57    type Context = ();
58
59    fn expand_with(
60        self,
61        session: &mut Session,
62        (): Self::Context,
63    ) -> Result<TokenStream, syn::Error> {
64        let Self(input_vector) = self;
65
66        let mut output_stream = TokenStream::new();
67
68        for input_element in input_vector {
69            output_stream.extend(input_element.expand(session)?);
70        }
71
72        Ok(output_stream)
73    }
74}
75
76impl Expand for Element {
77    type Context = ();
78
79    fn expand_with(
80        self,
81        session: &mut Session,
82        (): Self::Context,
83    ) -> Result<TokenStream, syn::Error> {
84        match self {
85            Self::Block {
86                block: Block { stream, .. },
87                pipeline: Some(pipeline),
88            } => pipeline.expand_with(session, stream),
89            Self::Block {
90                pipeline: None,
91                block: Block { stream, .. },
92            } => stream.expand(session),
93            Self::Tree(tokel_tree) => tokel_tree.expand(session),
94        }
95    }
96}
97
98impl Expand for TokelTree {
99    type Context = ();
100
101    fn expand_with(
102        self,
103        session: &mut Session,
104        (): Self::Context,
105    ) -> Result<TokenStream, syn::Error> {
106        match self {
107            Self::Group(tokel_group) => tokel_group.expand(session),
108            tree => {
109                let mut stream = TokenStream::new();
110
111                stream.extend(iter::once(match tree {
112                    Self::Ident(ident) => TokenTree::Ident(ident),
113                    Self::Literal(literal) => TokenTree::Literal(literal),
114                    Self::Punct(punct) => TokenTree::Punct(punct),
115                    Self::Group(..) => unreachable!(),
116                }));
117
118                Ok(stream)
119            }
120        }
121    }
122}
123
124impl Expand for TokelGroup {
125    type Context = ();
126
127    fn expand_with(
128        self,
129        session: &mut Session,
130        (): Self::Context,
131    ) -> Result<TokenStream, syn::Error> {
132        let Self {
133            delimiter,
134            span,
135            stream,
136        } = self;
137
138        let mut output_stream = TokenStream::new();
139
140        let mut group = Group::new(delimiter, stream.expand(session)?);
141
142        group.set_span(span);
143
144        output_stream.extend(iter::once(TokenTree::Group(group)));
145
146        Ok(output_stream)
147    }
148}
149
150impl Expand for Pipeline {
151    type Context = TokelStream;
152
153    fn expand_with(
154        self,
155        session: &mut Session,
156        body: Self::Context,
157    ) -> Result<TokenStream, syn::Error> {
158        let mut input = body.expand(session)?;
159
160        let Self(pipe_list) = self;
161
162        for Pipe {
163            ref name, argument, ..
164        } in pipe_list
165        {
166            let target_name = &Cow::Owned(Ident::to_string(name));
167
168            let argument = if let Some((.., argument)) = argument {
169                argument.expand(session)
170            } else {
171                Ok(TokenStream::new())
172            };
173
174            if let Some(transformer) = session.registry_mut().get_mut(target_name) {
175                input = transformer.transform(input, argument?)?;
176            } else {
177                return Err(syn::Error::new(
178                    name.span(),
179                    format!("no such transformer `{target_name}`"),
180                ));
181            }
182        }
183
184        Ok(input)
185    }
186}