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}