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, Parser, 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 /// Construct a parser over a token stream, bounded by the compiler options
131 /// this macro is being expanded under.
132 ///
133 /// The syntax tree this parser produces is walked by recursing over it, so
134 /// how deep it is allowed to get is bounded by the `max-ast-depth` option.
135 /// A parser built with [`Parser::from_token_stream`] instead uses that
136 /// option's default, since it has no way of seeing what the compiler was
137 /// configured with.
138 ///
139 /// `span` is the span to use if the stream is empty - typically
140 /// [`MacroContext::input_span`].
141 ///
142 /// # Examples
143 ///
144 /// ```
145 /// # use rune::support::*;
146 /// use rune::ast;
147 /// use rune::macros::{self, quote};
148 ///
149 /// macros::test(|cx| {
150 /// let stream = quote!(1 + 2).into_token_stream(cx)?;
151 ///
152 /// let mut p = cx.parser(&stream, cx.input_span());
153 /// let expr = p.parse_all::<ast::Expr>()?;
154 /// let value = cx.eval(&expr)?;
155 ///
156 /// let integer = value.as_integer::<u32>().context("Expected integer")?;
157 /// assert_eq!(3, integer);
158 /// Ok(())
159 /// })?;
160 /// # Ok::<_, rune::support::Error>(())
161 /// ```
162 pub fn parser<'s>(&self, token_stream: &'s TokenStream, span: Span) -> Parser<'s> {
163 Parser::from_token_stream(token_stream, span)
164 .with_max_depth(self.idx.q.options.max_ast_depth)
165 }
166
167 /// Evaluate the given target as a constant expression.
168 ///
169 /// # Panics
170 ///
171 /// This will panic if it's called outside of a macro context.
172 ///
173 /// # Examples
174 ///
175 /// ```
176 /// # use rune::support::*;
177 /// use rune::ast;
178 /// use rune::macros::{self, quote};
179 /// use rune::parse::{Parser};
180 ///
181 /// macros::test(|cx| {
182 /// let stream = quote!(1 + 2).into_token_stream(cx)?;
183 ///
184 /// let mut p = Parser::from_token_stream(&stream, cx.input_span());
185 /// let expr = p.parse_all::<ast::Expr>()?;
186 /// let value = cx.eval(&expr)?;
187 ///
188 /// let integer = value.as_integer::<u32>().context("Expected integer")?;
189 /// assert_eq!(3, integer);
190 /// Ok(())
191 /// })?;
192 /// # Ok::<_, rune::support::Error>(())
193 /// ```
194 pub fn eval(&mut self, target: &ast::Expr) -> compile::Result<Value> {
195 crate::compile::const_eval::eval_ast(self, target)
196 }
197
198 /// Construct a new literal from within a macro context.
199 ///
200 /// # Examples
201 ///
202 /// ```
203 /// use rune::ast;
204 /// use rune::macros;
205 ///
206 /// macros::test(|cx| {
207 /// let lit = cx.lit("hello world")?;
208 /// assert!(matches!(lit, ast::Lit::Str(..)));
209 /// Ok(())
210 /// })?;
211 /// # Ok::<_, rune::support::Error>(())
212 /// ```
213 pub fn lit<T>(&mut self, lit: T) -> alloc::Result<ast::Lit>
214 where
215 T: IntoLit,
216 {
217 T::into_lit(lit, self)
218 }
219
220 /// Construct a new identifier from the given string from inside of a macro
221 /// context.
222 ///
223 /// # Examples
224 ///
225 /// ```
226 /// use rune::ast;
227 /// use rune::macros;
228 ///
229 /// macros::test(|cx| {
230 /// let lit = cx.ident("foo")?;
231 /// assert!(matches!(lit, ast::Ident { .. }));
232 /// Ok(())
233 /// })?;
234 /// # Ok::<_, rune::support::Error>(())
235 /// ```
236 pub fn ident(&mut self, ident: &str) -> alloc::Result<ast::Ident> {
237 let span = self.macro_span();
238 let id = self.idx.q.storage.insert_str(ident)?;
239 let source = ast::LitSource::Synthetic(id);
240 Ok(ast::Ident { span, source })
241 }
242
243 /// Construct a new label from the given string. The string should be
244 /// specified *without* the leading `'`, so `"foo"` instead of `"'foo"`.
245 ///
246 /// This constructor does not panic when called outside of a macro context
247 /// but requires access to a `span` and `storage`.
248 ///
249 /// # Examples
250 ///
251 /// ```
252 /// use rune::ast;
253 /// use rune::macros;
254 ///
255 /// macros::test(|cx| {
256 /// let lit = cx.label("foo")?;
257 /// assert!(matches!(lit, ast::Label { .. }));
258 /// Ok(())
259 /// })?;
260 /// # Ok::<_, rune::support::Error>(())
261 /// ```
262 pub fn label(&mut self, label: &str) -> alloc::Result<ast::Label> {
263 let span = self.macro_span();
264 let id = self.idx.q.storage.insert_str(label)?;
265 let source = ast::LitSource::Synthetic(id);
266 Ok(ast::Label { span, source })
267 }
268
269 /// Stringify the token stream.
270 pub fn stringify<T>(&mut self, tokens: &T) -> alloc::Result<Stringify<'_, 'a, 'b, 'arena>>
271 where
272 T: ToTokens,
273 {
274 let mut stream = TokenStream::new();
275 tokens.to_tokens(self, &mut stream)?;
276 Ok(Stringify { cx: self, stream })
277 }
278
279 /// Resolve the value of a token.
280 pub fn resolve<'r, T>(&'r self, item: T) -> compile::Result<T::Output>
281 where
282 T: Resolve<'r>,
283 {
284 item.resolve(resolve_context!(self.idx.q))
285 }
286
287 /// Access a literal source as a string.
288 pub(crate) fn literal_source(&self, source: ast::LitSource, span: Span) -> Option<&str> {
289 match source {
290 ast::LitSource::Text(source_id) => self.idx.q.sources.source(source_id, span),
291 ast::LitSource::Synthetic(id) => self.idx.q.storage.get_string(id),
292 ast::LitSource::BuiltIn(builtin) => Some(builtin.as_str()),
293 }
294 }
295
296 /// Insert the given source so that it has a [SourceId] that can be used in
297 /// combination with parsing functions such as
298 /// [parse_source][MacroContext::parse_source].
299 pub fn insert_source(&mut self, name: &str, source: &str) -> alloc::Result<SourceId> {
300 self.idx.q.sources.insert(Source::new(name, source)?)
301 }
302
303 /// Parse the given input as the given type that implements
304 /// [Parse][crate::parse::Parse].
305 pub fn parse_source<T>(&self, id: SourceId) -> compile::Result<T>
306 where
307 T: Parse,
308 {
309 let source = self.idx.q.sources.get(id).ok_or_else(|| {
310 compile::Error::new(Span::empty(), ErrorKind::MissingSourceId { source_id: id })
311 })?;
312
313 crate::parse::parse_all(source.as_str(), id, false)
314 }
315
316 /// The span of the macro call including the name of the macro.
317 ///
318 /// If the macro call was `stringify!(a + b)` this would refer to the whole
319 /// macro call.
320 pub fn macro_span(&self) -> Span {
321 self.macro_span
322 }
323
324 /// The span of the macro stream (the argument).
325 ///
326 /// If the macro call was `stringify!(a + b)` this would refer to `a + b`.
327 pub fn input_span(&self) -> Span {
328 self.input_span
329 }
330}
331
332pub struct Stringify<'cx, 'a, 'b, 'arena> {
333 cx: &'cx MacroContext<'a, 'b, 'arena>,
334 stream: TokenStream,
335}
336
337impl fmt::Display for Stringify<'_, '_, '_, '_> {
338 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
339 let mut it = self.stream.iter();
340 let last = it.next_back();
341
342 for token in it {
343 token.token_fmt(self.cx, f)?;
344 write!(f, " ")?;
345 }
346
347 if let Some(last) = last {
348 last.token_fmt(self.cx, f)?;
349 }
350
351 Ok(())
352 }
353}