Skip to main content

rune/macros/
macro_context.rs

1//! Context for a macro.
2
3use core::fmt;
4
5use crate::alloc;
6use crate::alloc::Vec;
7use crate::ast;
8use crate::ast::{OptionSpanned, Span};
9use crate::compile::{self, ErrorKind, ItemMeta};
10use crate::grammar::ws;
11use crate::indexing::Indexer;
12use crate::internal_macros::resolve_context;
13use crate::macros::{IntoLit, ToTokens, TokenStream};
14use crate::parse::{Parse, Parser, Resolve};
15use crate::runtime::Value;
16use crate::{Source, SourceId};
17
18/// Construct an empty macro context which can be used for testing.
19///
20/// # Examples
21///
22/// ```
23/// use rune::ast;
24/// use rune::macros;
25///
26/// macros::test(|cx| {
27///     let lit = cx.lit("hello world")?;
28///     assert!(matches!(lit, ast::Lit::Str(..)));
29///     Ok(())
30/// })?;
31/// # Ok::<_, rune::support::Error>(())
32/// ```
33#[cfg(feature = "std")]
34#[cfg_attr(rune_docsrs, doc(cfg(feature = "std")))]
35pub fn test<F, O>(f: F) -> crate::support::Result<O>
36where
37    F: FnOnce(&mut MacroContext<'_, '_, '_>) -> crate::support::Result<O>,
38{
39    use rust_alloc::rc::Rc;
40
41    use crate::compile::{NoopCompileVisitor, NoopSourceLoader, Pool, Prelude, UnitBuilder};
42    use crate::hir;
43    use crate::indexing::{IndexItem, Items, Scopes};
44    use crate::macros::Storage;
45    use crate::query::Query;
46    use crate::shared::{Consts, Gen};
47    use crate::support::Context as _;
48    use crate::{Context, Diagnostics, Item, Options, Sources};
49
50    let mut unit = UnitBuilder::default();
51    let prelude = Prelude::default();
52    let gen = Gen::default();
53    let const_arena = hir::Arena::new();
54    let mut consts = Consts::default();
55    let mut storage = Storage::default();
56    let mut sources = Sources::default();
57    let mut pool = Pool::new().context("Failed to allocate pool")?;
58    let mut visitor = NoopCompileVisitor::new();
59    let mut diagnostics = Diagnostics::default();
60    let mut source_loader = NoopSourceLoader::default();
61    let options = Options::from_default_env()?;
62    let context = Context::default();
63    let mut inner = Default::default();
64
65    let mut query = Query::new(
66        &mut unit,
67        &prelude,
68        &const_arena,
69        &mut consts,
70        &mut storage,
71        &mut sources,
72        &mut pool,
73        &mut visitor,
74        &mut diagnostics,
75        &mut source_loader,
76        &options,
77        &[],
78        &gen,
79        &context,
80        &mut inner,
81    );
82
83    let source_id = SourceId::empty();
84
85    let (root_id, root_mod_id) = query
86        .insert_root_mod(source_id, Span::empty())
87        .context("Failed to inserted root module")?;
88
89    let item_meta = query
90        .item_for("root item", root_id)
91        .context("Just inserted item meta does not exist")?;
92
93    let tree = Rc::default();
94
95    let mut idx = Indexer {
96        q: query.borrow(),
97        source_id,
98        items: Items::new(Item::new()).context("Failed to construct items")?,
99        scopes: Scopes::new().context("Failed to build indexer scopes")?,
100        item: IndexItem::new(root_mod_id, root_id),
101        nested_item: None,
102        macro_depth: 0,
103        root: None,
104        queue: None,
105        loaded: None,
106        tree: &tree,
107    };
108
109    let mut cx = MacroContext {
110        macro_span: Span::empty(),
111        input_span: Span::empty(),
112        item_meta,
113        idx: &mut idx,
114    };
115
116    f(&mut cx)
117}
118
119/// Context for a running macro.
120pub struct MacroContext<'a, 'b, 'arena> {
121    /// Macro span of the full macro call.
122    pub(crate) macro_span: Span,
123    /// Macro span of the input.
124    pub(crate) input_span: Span,
125    /// The item where the macro is being evaluated.
126    pub(crate) item_meta: ItemMeta,
127    /// Indexer.
128    pub(crate) idx: &'a mut Indexer<'b, 'arena>,
129}
130
131impl<'a, 'b, 'arena> MacroContext<'a, 'b, 'arena> {
132    /// Construct a parser over a token stream, bounded by the compiler options
133    /// this macro is being expanded under.
134    ///
135    /// The syntax tree this parser produces is walked by recursing over it, so
136    /// how deep it is allowed to get is bounded by the `max-ast-depth` option.
137    /// A parser built with [`Parser::from_token_stream`] instead uses that
138    /// option's default, since it has no way of seeing what the compiler was
139    /// configured with.
140    ///
141    /// A macro which only needs to know where each of its arguments ends does
142    /// not need a tree at all - see [`MacroContext::exprs`], which splits an
143    /// input without either recursing over it or holding it to that bound.
144    ///
145    /// `span` is the span to use if the stream is empty - typically
146    /// [`MacroContext::input_span`].
147    ///
148    /// # Examples
149    ///
150    /// ```
151    /// # use rune::support::*;
152    /// use rune::ast;
153    /// use rune::macros::{self, quote};
154    ///
155    /// macros::test(|cx| {
156    ///     let stream = quote!(1 + 2).into_token_stream(cx)?;
157    ///
158    ///     let mut p = cx.parser(&stream, cx.input_span());
159    ///     let expr = p.parse_all::<ast::Expr>()?;
160    ///     let value = cx.eval(&expr)?;
161    ///
162    ///     let integer = value.as_integer::<u32>().context("Expected integer")?;
163    ///     assert_eq!(3, integer);
164    ///     Ok(())
165    /// })?;
166    /// # Ok::<_, rune::support::Error>(())
167    /// ```
168    pub fn parser<'s>(&self, token_stream: &'s TokenStream, span: Span) -> Parser<'s> {
169        Parser::from_token_stream(token_stream, span)
170            .with_max_depth(self.idx.q.options.max_ast_depth)
171    }
172
173    /// Split a token stream into the comma separated expressions it is made
174    /// of, each one being the tokens it was written as.
175    ///
176    /// This is what a macro which takes a list of arguments uses to find where
177    /// each one ends. The split is done by the same parser the compiler uses,
178    /// which walks its input over an explicit stack, and each expression is
179    /// handed back as tokens rather than as a syntax tree - so a macro built
180    /// out of this neither recurses over its own input nor holds it to the
181    /// much smaller `max-ast-depth` which bounds [`MacroContext::parser`].
182    ///
183    /// What comes back is where each argument ends rather than what it means:
184    /// the tokens are handed on as they were written, and whatever they turn
185    /// out to say is reported where the macro puts them, since that is where
186    /// they are lowered.
187    ///
188    /// # Examples
189    ///
190    /// ```
191    /// # use rune::support::*;
192    /// use rune::macros::{self, quote};
193    ///
194    /// macros::test(|cx| {
195    ///     let stream = quote!("Hello {}", 1 + 2).into_token_stream(cx)?;
196    ///
197    ///     let exprs = cx.exprs(&stream)?;
198    ///     assert_eq!(exprs.len(), 2);
199    ///
200    ///     let value = cx.eval_stream(&exprs[1])?;
201    ///     assert_eq!(value.as_integer::<u32>()?, 3);
202    ///     Ok(())
203    /// })?;
204    /// # Ok::<_, rune::support::Error>(())
205    /// ```
206    pub fn exprs(&mut self, stream: &TokenStream) -> compile::Result<Vec<TokenStream>> {
207        let span = self.stream_span(stream);
208
209        let tree = crate::grammar::token_stream(stream)
210            .max_nesting(self.idx.q.options.max_depth)
211            .exprs(ast::Kind::Comma)?;
212
213        let Some([root]) = tree.nodes() else {
214            return Err(compile::Error::msg(span, "expected a single root"));
215        };
216
217        let mut exprs = Vec::new();
218
219        // Whether the expression read most recently is still waiting for the
220        // separator which ends it, which is what tells a missing separator
221        // apart from a missing expression.
222        let mut separated = true;
223
224        for node in root.children() {
225            if node.is_empty() {
226                match node.kind() {
227                    ws!() => {}
228                    ast::Kind::Comma if !separated => {
229                        separated = true;
230                    }
231                    _ => {
232                        return Err(compile::Error::msg(node.span(), "expected an expression"));
233                    }
234                }
235
236                continue;
237            }
238
239            if !separated {
240                return Err(compile::Error::msg(node.span(), "expected `,`"));
241            }
242
243            let mut expr = TokenStream::new();
244
245            for token in node.walk_tokens() {
246                expr.push(token)?;
247            }
248
249            exprs.try_push(expr)?;
250            separated = false;
251        }
252
253        Ok(exprs)
254    }
255
256    /// Evaluate the tokens of an expression as a constant.
257    ///
258    /// This is [`MacroContext::eval`] over the tokens an expression was
259    /// written as, which is what a macro that split its input with
260    /// [`MacroContext::exprs`] holds.
261    ///
262    /// # Examples
263    ///
264    /// ```
265    /// # use rune::support::*;
266    /// use rune::macros::{self, quote};
267    ///
268    /// macros::test(|cx| {
269    ///     let stream = quote!(1 + 2).into_token_stream(cx)?;
270    ///
271    ///     let value = cx.eval_stream(&stream)?;
272    ///     assert_eq!(value.as_integer::<u32>()?, 3);
273    ///     Ok(())
274    /// })?;
275    /// # Ok::<_, rune::support::Error>(())
276    /// ```
277    pub fn eval_stream(&mut self, stream: &TokenStream) -> compile::Result<Value> {
278        let span = self.stream_span(stream);
279        crate::compile::const_eval::eval_stream(self, stream, span)
280    }
281
282    /// The span of a token stream, which is the span of the input the macro
283    /// was called with if the stream is empty.
284    pub(crate) fn stream_span(&self, stream: &TokenStream) -> Span {
285        stream.option_span().unwrap_or(self.input_span)
286    }
287
288    /// Evaluate the given target as a constant expression.
289    ///
290    /// # Panics
291    ///
292    /// This will panic if it's called outside of a macro context.
293    ///
294    /// # Examples
295    ///
296    /// ```
297    /// # use rune::support::*;
298    /// use rune::ast;
299    /// use rune::macros::{self, quote};
300    /// use rune::parse::{Parser};
301    ///
302    /// macros::test(|cx| {
303    ///     let stream = quote!(1 + 2).into_token_stream(cx)?;
304    ///
305    ///     let mut p = Parser::from_token_stream(&stream, cx.input_span());
306    ///     let expr = p.parse_all::<ast::Expr>()?;
307    ///     let value = cx.eval(&expr)?;
308    ///
309    ///     let integer = value.as_integer::<u32>().context("Expected integer")?;
310    ///     assert_eq!(3, integer);
311    ///     Ok(())
312    /// })?;
313    /// # Ok::<_, rune::support::Error>(())
314    /// ```
315    pub fn eval(&mut self, target: &ast::Expr) -> compile::Result<Value> {
316        crate::compile::const_eval::eval_ast(self, target)
317    }
318
319    /// Construct a new literal from within a macro context.
320    ///
321    /// # Examples
322    ///
323    /// ```
324    /// use rune::ast;
325    /// use rune::macros;
326    ///
327    /// macros::test(|cx| {
328    ///     let lit = cx.lit("hello world")?;
329    ///     assert!(matches!(lit, ast::Lit::Str(..)));
330    ///     Ok(())
331    /// })?;
332    /// # Ok::<_, rune::support::Error>(())
333    /// ```
334    pub fn lit<T>(&mut self, lit: T) -> alloc::Result<ast::Lit>
335    where
336        T: IntoLit,
337    {
338        T::into_lit(lit, self)
339    }
340
341    /// Construct a new identifier from the given string from inside of a macro
342    /// context.
343    ///
344    /// # Examples
345    ///
346    /// ```
347    /// use rune::ast;
348    /// use rune::macros;
349    ///
350    /// macros::test(|cx| {
351    ///     let lit = cx.ident("foo")?;
352    ///     assert!(matches!(lit, ast::Ident { .. }));
353    ///     Ok(())
354    /// })?;
355    /// # Ok::<_, rune::support::Error>(())
356    /// ```
357    pub fn ident(&mut self, ident: &str) -> alloc::Result<ast::Ident> {
358        let span = self.macro_span();
359        let id = self.idx.q.storage.insert_str(ident)?;
360        let source = ast::LitSource::Synthetic(id);
361        Ok(ast::Ident { span, source })
362    }
363
364    /// Construct a new label from the given string. The string should be
365    /// specified *without* the leading `'`, so `"foo"` instead of `"'foo"`.
366    ///
367    /// This constructor does not panic when called outside of a macro context
368    /// but requires access to a `span` and `storage`.
369    ///
370    /// # Examples
371    ///
372    /// ```
373    /// use rune::ast;
374    /// use rune::macros;
375    ///
376    /// macros::test(|cx| {
377    ///     let lit = cx.label("foo")?;
378    ///     assert!(matches!(lit, ast::Label { .. }));
379    ///     Ok(())
380    /// })?;
381    /// # Ok::<_, rune::support::Error>(())
382    /// ```
383    pub fn label(&mut self, label: &str) -> alloc::Result<ast::Label> {
384        let span = self.macro_span();
385        let id = self.idx.q.storage.insert_str(label)?;
386        let source = ast::LitSource::Synthetic(id);
387        Ok(ast::Label { span, source })
388    }
389
390    /// Stringify the token stream.
391    pub fn stringify<T>(&mut self, tokens: &T) -> alloc::Result<Stringify<'_, 'a, 'b, 'arena>>
392    where
393        T: ToTokens,
394    {
395        let mut stream = TokenStream::new();
396        tokens.to_tokens(self, &mut stream)?;
397        Ok(Stringify { cx: self, stream })
398    }
399
400    /// Resolve the value of a token.
401    pub fn resolve<'r, T>(&'r self, item: T) -> compile::Result<T::Output>
402    where
403        T: Resolve<'r>,
404    {
405        item.resolve(resolve_context!(self.idx.q))
406    }
407
408    /// Access a literal source as a string.
409    pub(crate) fn literal_source(&self, source: ast::LitSource, span: Span) -> Option<&str> {
410        match source {
411            ast::LitSource::Text(source_id) => self.idx.q.sources.source(source_id, span),
412            ast::LitSource::Synthetic(id) => self.idx.q.storage.get_string(id),
413            ast::LitSource::BuiltIn(builtin) => Some(builtin.as_str()),
414        }
415    }
416
417    /// Insert the given source so that it has a [SourceId] that can be used in
418    /// combination with parsing functions such as
419    /// [parse_source][MacroContext::parse_source].
420    pub fn insert_source(&mut self, name: &str, source: &str) -> alloc::Result<SourceId> {
421        self.idx.q.sources.insert(Source::new(name, source)?)
422    }
423
424    /// Parse the given input as the given type that implements
425    /// [Parse][crate::parse::Parse].
426    pub fn parse_source<T>(&self, id: SourceId) -> compile::Result<T>
427    where
428        T: Parse,
429    {
430        let source = self.idx.q.sources.get(id).ok_or_else(|| {
431            compile::Error::new(Span::empty(), ErrorKind::MissingSourceId { source_id: id })
432        })?;
433
434        crate::parse::parse_all(source.as_str(), id, false)
435    }
436
437    /// The span of the macro call including the name of the macro.
438    ///
439    /// If the macro call was `stringify!(a + b)` this would refer to the whole
440    /// macro call.
441    pub fn macro_span(&self) -> Span {
442        self.macro_span
443    }
444
445    /// The span of the macro stream (the argument).
446    ///
447    /// If the macro call was `stringify!(a + b)` this would refer to `a + b`.
448    pub fn input_span(&self) -> Span {
449        self.input_span
450    }
451}
452
453pub struct Stringify<'cx, 'a, 'b, 'arena> {
454    cx: &'cx MacroContext<'a, 'b, 'arena>,
455    stream: TokenStream,
456}
457
458impl fmt::Display for Stringify<'_, '_, '_, '_> {
459    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
460        let mut it = self.stream.iter();
461        let last = it.next_back();
462
463        for token in it {
464            token.token_fmt(self.cx, f)?;
465            write!(f, " ")?;
466        }
467
468        if let Some(last) = last {
469            last.token_fmt(self.cx, f)?;
470        }
471
472        Ok(())
473    }
474}