rune/macros/
macro_context.rs

1//! Context for a macro.
2
3use core::fmt;
4
5use crate::alloc;
6use crate::ast;
7use crate::ast::Span;
8use crate::compile::{self, ErrorKind, ItemMeta};
9use crate::indexing::Indexer;
10use crate::internal_macros::resolve_context;
11use crate::macros::{IntoLit, ToTokens, TokenStream};
12use crate::parse::{Parse, Resolve};
13use crate::runtime::Value;
14use crate::{Source, SourceId};
15
16/// Construct an empty macro context which can be used for testing.
17///
18/// # Examples
19///
20/// ```
21/// use rune::ast;
22/// use rune::macros;
23///
24/// macros::test(|cx| {
25///     let lit = cx.lit("hello world")?;
26///     assert!(matches!(lit, ast::Lit::Str(..)));
27///     Ok(())
28/// })?;
29/// # Ok::<_, rune::support::Error>(())
30/// ```
31#[cfg(feature = "std")]
32#[cfg_attr(rune_docsrs, doc(cfg(feature = "std")))]
33pub fn test<F, O>(f: F) -> crate::support::Result<O>
34where
35    F: FnOnce(&mut MacroContext<'_, '_, '_>) -> crate::support::Result<O>,
36{
37    use rust_alloc::rc::Rc;
38
39    use crate::compile::{NoopCompileVisitor, NoopSourceLoader, Pool, Prelude, UnitBuilder};
40    use crate::hir;
41    use crate::indexing::{IndexItem, Items, Scopes};
42    use crate::macros::Storage;
43    use crate::query::Query;
44    use crate::shared::{Consts, Gen};
45    use crate::support::Context as _;
46    use crate::{Context, Diagnostics, Item, Options, Sources};
47
48    let mut unit = UnitBuilder::default();
49    let prelude = Prelude::default();
50    let gen = Gen::default();
51    let const_arena = hir::Arena::new();
52    let mut consts = Consts::default();
53    let mut storage = Storage::default();
54    let mut sources = Sources::default();
55    let mut pool = Pool::new().context("Failed to allocate pool")?;
56    let mut visitor = NoopCompileVisitor::new();
57    let mut diagnostics = Diagnostics::default();
58    let mut source_loader = NoopSourceLoader::default();
59    let options = Options::from_default_env()?;
60    let context = Context::default();
61    let mut inner = Default::default();
62
63    let mut query = Query::new(
64        &mut unit,
65        &prelude,
66        &const_arena,
67        &mut consts,
68        &mut storage,
69        &mut sources,
70        &mut pool,
71        &mut visitor,
72        &mut diagnostics,
73        &mut source_loader,
74        &options,
75        &[],
76        &gen,
77        &context,
78        &mut inner,
79    );
80
81    let source_id = SourceId::empty();
82
83    let (root_id, root_mod_id) = query
84        .insert_root_mod(source_id, Span::empty())
85        .context("Failed to inserted root module")?;
86
87    let item_meta = query
88        .item_for("root item", root_id)
89        .context("Just inserted item meta does not exist")?;
90
91    let tree = Rc::default();
92
93    let mut idx = Indexer {
94        q: query.borrow(),
95        source_id,
96        items: Items::new(Item::new()).context("Failed to construct items")?,
97        scopes: Scopes::new().context("Failed to build indexer scopes")?,
98        item: IndexItem::new(root_mod_id, root_id),
99        nested_item: None,
100        macro_depth: 0,
101        root: None,
102        queue: None,
103        loaded: None,
104        tree: &tree,
105    };
106
107    let mut cx = MacroContext {
108        macro_span: Span::empty(),
109        input_span: Span::empty(),
110        item_meta,
111        idx: &mut idx,
112    };
113
114    f(&mut cx)
115}
116
117/// Context for a running macro.
118pub struct MacroContext<'a, 'b, 'arena> {
119    /// Macro span of the full macro call.
120    pub(crate) macro_span: Span,
121    /// Macro span of the input.
122    pub(crate) input_span: Span,
123    /// The item where the macro is being evaluated.
124    pub(crate) item_meta: ItemMeta,
125    /// Indexer.
126    pub(crate) idx: &'a mut Indexer<'b, 'arena>,
127}
128
129impl<'a, 'b, 'arena> MacroContext<'a, 'b, 'arena> {
130    /// Evaluate the given target as a constant expression.
131    ///
132    /// # Panics
133    ///
134    /// This will panic if it's called outside of a macro context.
135    ///
136    /// # Examples
137    ///
138    /// ```
139    /// # use rune::support::*;
140    /// use rune::ast;
141    /// use rune::macros::{self, quote};
142    /// use rune::parse::{Parser};
143    ///
144    /// macros::test(|cx| {
145    ///     let stream = quote!(1 + 2).into_token_stream(cx)?;
146    ///
147    ///     let mut p = Parser::from_token_stream(&stream, cx.input_span());
148    ///     let expr = p.parse_all::<ast::Expr>()?;
149    ///     let value = cx.eval(&expr)?;
150    ///
151    ///     let integer = value.as_integer::<u32>().context("Expected integer")?;
152    ///     assert_eq!(3, integer);
153    ///     Ok(())
154    /// })?;
155    /// # Ok::<_, rune::support::Error>(())
156    /// ```
157    pub fn eval(&mut self, target: &ast::Expr) -> compile::Result<Value> {
158        target.eval(self)
159    }
160
161    /// Construct a new literal from within a macro context.
162    ///
163    /// # Examples
164    ///
165    /// ```
166    /// use rune::ast;
167    /// use rune::macros;
168    ///
169    /// macros::test(|cx| {
170    ///     let lit = cx.lit("hello world")?;
171    ///     assert!(matches!(lit, ast::Lit::Str(..)));
172    ///     Ok(())
173    /// })?;
174    /// # Ok::<_, rune::support::Error>(())
175    /// ```
176    pub fn lit<T>(&mut self, lit: T) -> alloc::Result<ast::Lit>
177    where
178        T: IntoLit,
179    {
180        T::into_lit(lit, self)
181    }
182
183    /// Construct a new identifier from the given string from inside of a macro
184    /// context.
185    ///
186    /// # Examples
187    ///
188    /// ```
189    /// use rune::ast;
190    /// use rune::macros;
191    ///
192    /// macros::test(|cx| {
193    ///     let lit = cx.ident("foo")?;
194    ///     assert!(matches!(lit, ast::Ident { .. }));
195    ///     Ok(())
196    /// })?;
197    /// # Ok::<_, rune::support::Error>(())
198    /// ```
199    pub fn ident(&mut self, ident: &str) -> alloc::Result<ast::Ident> {
200        let span = self.macro_span();
201        let id = self.idx.q.storage.insert_str(ident)?;
202        let source = ast::LitSource::Synthetic(id);
203        Ok(ast::Ident { span, source })
204    }
205
206    /// Construct a new label from the given string. The string should be
207    /// specified *without* the leading `'`, so `"foo"` instead of `"'foo"`.
208    ///
209    /// This constructor does not panic when called outside of a macro context
210    /// but requires access to a `span` and `storage`.
211    ///
212    /// # Examples
213    ///
214    /// ```
215    /// use rune::ast;
216    /// use rune::macros;
217    ///
218    /// macros::test(|cx| {
219    ///     let lit = cx.label("foo")?;
220    ///     assert!(matches!(lit, ast::Label { .. }));
221    ///     Ok(())
222    /// })?;
223    /// # Ok::<_, rune::support::Error>(())
224    /// ```
225    pub fn label(&mut self, label: &str) -> alloc::Result<ast::Label> {
226        let span = self.macro_span();
227        let id = self.idx.q.storage.insert_str(label)?;
228        let source = ast::LitSource::Synthetic(id);
229        Ok(ast::Label { span, source })
230    }
231
232    /// Stringify the token stream.
233    pub fn stringify<T>(&mut self, tokens: &T) -> alloc::Result<Stringify<'_, 'a, 'b, 'arena>>
234    where
235        T: ToTokens,
236    {
237        let mut stream = TokenStream::new();
238        tokens.to_tokens(self, &mut stream)?;
239        Ok(Stringify { cx: self, stream })
240    }
241
242    /// Resolve the value of a token.
243    pub fn resolve<'r, T>(&'r self, item: T) -> compile::Result<T::Output>
244    where
245        T: Resolve<'r>,
246    {
247        item.resolve(resolve_context!(self.idx.q))
248    }
249
250    /// Access a literal source as a string.
251    pub(crate) fn literal_source(&self, source: ast::LitSource, span: Span) -> Option<&str> {
252        match source {
253            ast::LitSource::Text(source_id) => self.idx.q.sources.source(source_id, span),
254            ast::LitSource::Synthetic(id) => self.idx.q.storage.get_string(id),
255            ast::LitSource::BuiltIn(builtin) => Some(builtin.as_str()),
256        }
257    }
258
259    /// Insert the given source so that it has a [SourceId] that can be used in
260    /// combination with parsing functions such as
261    /// [parse_source][MacroContext::parse_source].
262    pub fn insert_source(&mut self, name: &str, source: &str) -> alloc::Result<SourceId> {
263        self.idx.q.sources.insert(Source::new(name, source)?)
264    }
265
266    /// Parse the given input as the given type that implements
267    /// [Parse][crate::parse::Parse].
268    pub fn parse_source<T>(&self, id: SourceId) -> compile::Result<T>
269    where
270        T: Parse,
271    {
272        let source = self.idx.q.sources.get(id).ok_or_else(|| {
273            compile::Error::new(Span::empty(), ErrorKind::MissingSourceId { source_id: id })
274        })?;
275
276        crate::parse::parse_all(source.as_str(), id, false)
277    }
278
279    /// The span of the macro call including the name of the macro.
280    ///
281    /// If the macro call was `stringify!(a + b)` this would refer to the whole
282    /// macro call.
283    pub fn macro_span(&self) -> Span {
284        self.macro_span
285    }
286
287    /// The span of the macro stream (the argument).
288    ///
289    /// If the macro call was `stringify!(a + b)` this would refer to `a + b`.
290    pub fn input_span(&self) -> Span {
291        self.input_span
292    }
293}
294
295pub struct Stringify<'cx, 'a, 'b, 'arena> {
296    cx: &'cx MacroContext<'a, 'b, 'arena>,
297    stream: TokenStream,
298}
299
300impl fmt::Display for Stringify<'_, '_, '_, '_> {
301    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
302        let mut it = self.stream.iter();
303        let last = it.next_back();
304
305        for token in it {
306            token.token_fmt(self.cx, f)?;
307            write!(f, " ")?;
308        }
309
310        if let Some(last) = last {
311            last.token_fmt(self.cx, f)?;
312        }
313
314        Ok(())
315    }
316}