| 15 |
15 |
|
use syn::{LitStr, Result};
|
| 16 |
16 |
|
|
| 17 |
17 |
|
use crate::ast::{
|
| 18 |
|
- |
Action, Arg, Declaration, Emission, Hole, HoleRoot, Interpolated, Item, Pattern, Source, Step,
|
| 19 |
|
- |
StrPart,
|
|
18 |
+ |
Action, Arg, Declaration, Emission, Guard, Hole, HoleRoot, Interpolated, Item, Pattern,
|
|
19 |
+ |
Predicate, Source, Step, StrPart,
|
| 20 |
20 |
|
};
|
|
21 |
+ |
use crate::parse::{COMPARISONS, VOCABULARY};
|
| 21 |
22 |
|
|
| 22 |
23 |
|
/// What the items of a body accrete onto.
|
| 23 |
|
- |
#[derive(Clone, Copy)]
|
|
24 |
+ |
#[derive(Clone, Copy, PartialEq, Eq)]
|
| 24 |
25 |
|
enum Container {
|
| 25 |
26 |
|
/// A region: emissions accrete onto a `Slot`.
|
| 26 |
27 |
|
Slot,
|
| 27 |
28 |
|
/// A row: every emission is `beside` and carries a priority.
|
| 28 |
29 |
|
Run,
|
|
30 |
+ |
/// One control: its body sets, and emits nothing.
|
|
31 |
+ |
Act,
|
| 29 |
32 |
|
}
|
| 30 |
33 |
|
|
| 31 |
34 |
|
pub fn declaration(declaration: &Declaration) -> Result<TokenStream> {
|
| 32 |
35 |
|
let Declaration {
|
| 33 |
36 |
|
docs,
|
|
37 |
+ |
flags,
|
| 34 |
38 |
|
vis,
|
| 35 |
39 |
|
name,
|
| 36 |
40 |
|
params,
|
| 38 |
42 |
|
items,
|
| 39 |
43 |
|
} = declaration;
|
| 40 |
44 |
|
|
| 41 |
|
- |
let shaped = shaped_name(returns)?;
|
| 42 |
|
- |
if shaped != "Node" {
|
| 43 |
|
- |
return Err(syn::Error::new_spanned(
|
| 44 |
|
- |
returns,
|
| 45 |
|
- |
format!(
|
| 46 |
|
- |
"`-> {shaped}` is not a shape this form can build yet. \
|
| 47 |
|
- |
Add the R2 case, and name the screen that demanded it in the commit."
|
| 48 |
|
- |
),
|
| 49 |
|
- |
));
|
| 50 |
|
- |
}
|
|
45 |
+ |
let (shaped, optional) = shaped_name(returns)?;
|
| 51 |
46 |
|
|
| 52 |
47 |
|
let docs = docs.iter().map(|line| {
|
| 53 |
48 |
|
let line = LitStr::new(line, Span::call_site());
|
| 54 |
49 |
|
quote!(#[doc = #line])
|
| 55 |
50 |
|
});
|
|
51 |
+ |
let flags = flags.iter().map(|flag| quote!(#[#flag]));
|
| 56 |
52 |
|
let params = params.iter().map(|param| {
|
| 57 |
53 |
|
let name = ¶m.name;
|
| 58 |
54 |
|
let ty = ¶m.ty;
|
| 59 |
55 |
|
quote!(#name: #ty)
|
| 60 |
56 |
|
});
|
| 61 |
|
- |
let body = value_body(items, name.span())?;
|
|
57 |
+ |
let body = value_body(items, &shaped, optional, name.span())?;
|
| 62 |
58 |
|
|
| 63 |
59 |
|
Ok(quote! {
|
| 64 |
60 |
|
#(#docs)*
|
|
61 |
+ |
#(#flags)*
|
| 65 |
62 |
|
#vis fn #name(#(#params),*) -> #returns #body
|
| 66 |
63 |
|
})
|
| 67 |
64 |
|
}
|
| 68 |
65 |
|
|
| 69 |
|
- |
/// The last segment of the return type, which is what R2 dispatches on.
|
| 70 |
|
- |
fn shaped_name(returns: &syn::Type) -> Result<String> {
|
|
66 |
+ |
/// The vocabulary type a shape returns, and whether omission is allowed.
|
|
67 |
+ |
///
|
|
68 |
+ |
/// `Option<T>` is the grammar's `single "?"`, and rule R10 is what makes it
|
|
69 |
+ |
/// usable: a body whose emission is guarded away yields `None`.
|
|
70 |
+ |
fn shaped_name(returns: &syn::Type) -> Result<(String, bool)> {
|
| 71 |
71 |
|
let syn::Type::Path(path) = returns else {
|
| 72 |
72 |
|
return Err(syn::Error::new_spanned(
|
| 73 |
73 |
|
returns,
|
| 74 |
74 |
|
"a shape returns one of the vocabulary types",
|
| 75 |
75 |
|
));
|
| 76 |
76 |
|
};
|
| 77 |
|
- |
path.path
|
| 78 |
|
- |
.segments
|
| 79 |
|
- |
.last()
|
| 80 |
|
- |
.map(|segment| segment.ident.to_string())
|
| 81 |
|
- |
.ok_or_else(|| syn::Error::new_spanned(returns, "an empty return type"))
|
|
77 |
+ |
let Some(last) = path.path.segments.last() else {
|
|
78 |
+ |
return Err(syn::Error::new_spanned(returns, "an empty return type"));
|
|
79 |
+ |
};
|
|
80 |
+ |
if last.ident == "Option" {
|
|
81 |
+ |
let syn::PathArguments::AngleBracketed(arguments) = &last.arguments else {
|
|
82 |
+ |
return Err(syn::Error::new_spanned(returns, "`Option` of what?"));
|
|
83 |
+ |
};
|
|
84 |
+ |
let Some(syn::GenericArgument::Type(inner)) = arguments.args.first() else {
|
|
85 |
+ |
return Err(syn::Error::new_spanned(returns, "`Option` of what?"));
|
|
86 |
+ |
};
|
|
87 |
+ |
let (shaped, nested) = shaped_name(inner)?;
|
|
88 |
+ |
if nested {
|
|
89 |
+ |
return Err(syn::Error::new_spanned(
|
|
90 |
+ |
returns,
|
|
91 |
+ |
"a shape omits its result or does not; there is no second omission",
|
|
92 |
+ |
));
|
|
93 |
+ |
}
|
|
94 |
+ |
return Ok((shaped, true));
|
|
95 |
+ |
}
|
|
96 |
+ |
Ok((last.ident.to_string(), false))
|
| 82 |
97 |
|
}
|
| 83 |
98 |
|
|
| 84 |
|
- |
/// A `-> Node` body: its bindings, then the single emission that is the value.
|
|
99 |
+ |
/// A shape's body: its bindings, then the single emission that is the value.
|
|
100 |
+ |
///
|
|
101 |
+ |
/// Rule R2 for the two shapes converted so far. A `-> Node` shape's single
|
|
102 |
+ |
/// emission IS the result and a `-> Act` shape's single `act` member is, so
|
|
103 |
+ |
/// neither fabricates a container: what a caller gets is what the body said.
|
| 85 |
104 |
|
///
|
| 86 |
105 |
|
/// Bindings are hoisted above the emissions of their own body. Nothing is
|
| 87 |
106 |
|
/// reordered by that: a binding can only name bindings written before it, and
|
| 88 |
107 |
|
/// an emission produces a value rather than an effect, so the two are
|
| 89 |
108 |
|
/// independent within one block.
|
| 90 |
|
- |
fn value_body(items: &[Item], span: Span) -> Result<TokenStream> {
|
|
109 |
+ |
fn value_body(items: &[Item], shaped: &str, optional: bool, span: Span) -> Result<TokenStream> {
|
| 91 |
110 |
|
let mut bindings = Vec::new();
|
| 92 |
111 |
|
let mut emissions = Vec::new();
|
| 93 |
112 |
|
for item in items {
|
| 96 |
115 |
|
let value = source_value(source)?;
|
| 97 |
116 |
|
bindings.push(quote!(let #name = #value;));
|
| 98 |
117 |
|
}
|
|
118 |
+ |
Item::Attribute { name, .. } => {
|
|
119 |
+ |
return Err(syn::Error::new(
|
|
120 |
+ |
name.span(),
|
|
121 |
+ |
"a setting needs something to set: put it in the member's body",
|
|
122 |
+ |
));
|
|
123 |
+ |
}
|
| 99 |
124 |
|
Item::Emit(emission) => emissions.push(emission),
|
| 100 |
125 |
|
}
|
| 101 |
126 |
|
}
|
| 104 |
129 |
|
return Err(syn::Error::new(
|
| 105 |
130 |
|
span,
|
| 106 |
131 |
|
format!(
|
| 107 |
|
- |
"a `-> Node` shape is its single emission, and this one has {}",
|
|
132 |
+ |
"a `-> {shaped}` shape is its single emission, and this one has {}",
|
| 108 |
133 |
|
emissions.len()
|
| 109 |
134 |
|
),
|
| 110 |
135 |
|
));
|
| 111 |
136 |
|
};
|
| 112 |
|
- |
let built = node(only)?;
|
|
137 |
+ |
|
|
138 |
+ |
let (guard, only) = match only {
|
|
139 |
+ |
Emission::Guarded { guard, inner } => (Some(guard), &**inner),
|
|
140 |
+ |
other => (None, *other),
|
|
141 |
+ |
};
|
|
142 |
+ |
if guard.is_some() && !optional {
|
|
143 |
+ |
return Err(syn::Error::new(
|
|
144 |
+ |
span,
|
|
145 |
+ |
"a guard on the whole result needs a shape that may omit it: `-> Option<_>`",
|
|
146 |
+ |
));
|
|
147 |
+ |
}
|
|
148 |
+ |
|
|
149 |
+ |
let built = match shaped {
|
|
150 |
+ |
"Node" => node(only)?,
|
|
151 |
+ |
"Act" => match only {
|
|
152 |
+ |
Emission::Act { .. } => act(only)?,
|
|
153 |
+ |
other => {
|
|
154 |
+ |
return Err(syn::Error::new(
|
|
155 |
+ |
emission_span(other),
|
|
156 |
+ |
"a `-> Act` shape is its single `act` member",
|
|
157 |
+ |
));
|
|
158 |
+ |
}
|
|
159 |
+ |
},
|
|
160 |
+ |
other => {
|
|
161 |
+ |
return Err(syn::Error::new(
|
|
162 |
+ |
span,
|
|
163 |
+ |
format!(
|
|
164 |
+ |
"`-> {other}` is not a shape this form can build yet. \
|
|
165 |
+ |
Add the R2 case, and name the screen that demanded it in the commit."
|
|
166 |
+ |
),
|
|
167 |
+ |
));
|
|
168 |
+ |
}
|
|
169 |
+ |
};
|
|
170 |
+ |
|
|
171 |
+ |
let built = if optional {
|
|
172 |
+ |
let test = match guard {
|
|
173 |
+ |
Some(guard) => predicate(guard)?,
|
|
174 |
+ |
None => quote!(true),
|
|
175 |
+ |
};
|
|
176 |
+ |
quote!(if #test { ::std::option::Option::Some(#built) } else { ::std::option::Option::None })
|
|
177 |
+ |
} else {
|
|
178 |
+ |
built
|
|
179 |
+ |
};
|
| 113 |
180 |
|
|
| 114 |
181 |
|
Ok(quote!({ #(#bindings)* #built }))
|
| 115 |
182 |
|
}
|
| 129 |
196 |
|
let value = source_value(source)?;
|
| 130 |
197 |
|
bindings.push(quote!(let #name = #value;));
|
| 131 |
198 |
|
}
|
| 132 |
|
- |
Item::Emit(emission) => steps.push(step(emission, container)?),
|
|
199 |
+ |
Item::Attribute { name, args } => steps.push(attribute(name, args)?),
|
|
200 |
+ |
Item::Emit(emission) => {
|
|
201 |
+ |
// A member is evaluated into its own binding before the
|
|
202 |
+ |
// container is built, because a container's own name is often
|
|
203 |
+ |
// the same value one of its members borrows: follow.rs names
|
|
204 |
+ |
// the region and then aims the press at it. Built inline, the
|
|
205 |
+ |
// container would move the name before the member read it.
|
|
206 |
+ |
let held = format_ident!("member_{}", bindings.len(), span = Span::call_site());
|
|
207 |
+ |
let (value, call) = step(emission, container, &held)?;
|
|
208 |
+ |
bindings.push(quote!(let #held = #value;));
|
|
209 |
+ |
steps.push(call);
|
|
210 |
+ |
}
|
| 133 |
211 |
|
}
|
| 134 |
212 |
|
}
|
| 135 |
213 |
|
Ok(quote!({ #(#bindings)* #base #(#steps)* }))
|
| 136 |
214 |
|
}
|
| 137 |
215 |
|
|
| 138 |
|
- |
/// One emission, as a step onto the container it lands in.
|
| 139 |
|
- |
fn step(emission: &Emission, container: Container) -> Result<TokenStream> {
|
|
216 |
+ |
/// One setting, as the builder call ATTRIBUTE NAMING says it is.
|
|
217 |
+ |
fn attribute(name: &syn::Ident, args: &[Arg]) -> Result<TokenStream> {
|
|
218 |
+ |
let slot = name.to_string();
|
|
219 |
+ |
let enumeration = VOCABULARY
|
|
220 |
+ |
.iter()
|
|
221 |
+ |
.find(|(attribute, _)| *attribute == slot)
|
|
222 |
+ |
.map(|(_, enumeration)| *enumeration);
|
|
223 |
+ |
let args = args
|
|
224 |
+ |
.iter()
|
|
225 |
+ |
.map(|value| match enumeration {
|
|
226 |
+ |
Some(enumeration) => variant(value, enumeration),
|
|
227 |
+ |
None => arg(value),
|
|
228 |
+ |
})
|
|
229 |
+ |
.collect::<Result<Vec<_>>>()?;
|
|
230 |
+ |
Ok(quote!(.#name(#(#args),*)))
|
|
231 |
+ |
}
|
|
232 |
+ |
|
|
233 |
+ |
/// Rule R1(4): a bare uppercase ident in a vocabulary slot is that enum's
|
|
234 |
+ |
/// variant. A `SCREAMING_CASE` ident never is, because 19 of 19 real uses of
|
|
235 |
+ |
/// the `measured` slot pass a module const rather than a variant.
|
|
236 |
+ |
fn variant(value: &Arg, enumeration: &str) -> Result<TokenStream> {
|
|
237 |
+ |
let Arg::Hole(hole) = value else {
|
|
238 |
+ |
return arg(value);
|
|
239 |
+ |
};
|
|
240 |
+ |
let HoleRoot::Path(path) = &hole.root else {
|
|
241 |
+ |
return arg(value);
|
|
242 |
+ |
};
|
|
243 |
+ |
if !hole.steps.is_empty() || path.leading_colon.is_some() || path.segments.len() != 1 {
|
|
244 |
+ |
return arg(value);
|
|
245 |
+ |
}
|
|
246 |
+ |
let name = path.segments[0].ident.to_string();
|
|
247 |
+ |
if name.chars().all(|letter| !letter.is_lowercase()) {
|
|
248 |
+ |
return arg(value);
|
|
249 |
+ |
}
|
|
250 |
+ |
let enumeration = format_ident!("{}", enumeration);
|
|
251 |
+ |
let name = &path.segments[0].ident;
|
|
252 |
+ |
Ok(quote!(::quasi_router::layout::#enumeration::#name))
|
|
253 |
+ |
}
|
|
254 |
+ |
|
|
255 |
+ |
/// One emission, as the value it builds and the call that places it.
|
|
256 |
+ |
fn step(
|
|
257 |
+ |
emission: &Emission,
|
|
258 |
+ |
container: Container,
|
|
259 |
+ |
held: &proc_macro2::Ident,
|
|
260 |
+ |
) -> Result<(TokenStream, TokenStream)> {
|
| 140 |
261 |
|
match (container, emission) {
|
| 141 |
262 |
|
(Container::Slot, Emission::Across { fallback, body }) => {
|
| 142 |
263 |
|
let run = accrete(
|
| 144 |
265 |
|
Container::Run,
|
| 145 |
266 |
|
"e!(::quasi_router::Run::new(::quasi_router::layout::Fallback::#fallback)),
|
| 146 |
267 |
|
)?;
|
| 147 |
|
- |
Ok(quote!(.across(#run)))
|
| 148 |
|
- |
}
|
| 149 |
|
- |
(Container::Slot, other) => {
|
| 150 |
|
- |
let node = node(other)?;
|
| 151 |
|
- |
Ok(quote!(.with(#node)))
|
| 152 |
|
- |
}
|
| 153 |
|
- |
(Container::Run, Emission::Beside { priority, inner }) => {
|
| 154 |
|
- |
let node = node(inner)?;
|
| 155 |
|
- |
Ok(quote!(.beside(#node, ::quasi_router::layout::Priority::#priority)))
|
|
268 |
+ |
Ok((run, quote!(.across(#held))))
|
| 156 |
269 |
|
}
|
|
270 |
+ |
(Container::Slot, other) => Ok((node(other)?, quote!(.with(#held)))),
|
|
271 |
+ |
(Container::Run, Emission::Beside { priority, inner }) => Ok((
|
|
272 |
+ |
node(inner)?,
|
|
273 |
+ |
quote!(.beside(#held, ::quasi_router::layout::Priority::#priority)),
|
|
274 |
+ |
)),
|
| 157 |
275 |
|
(Container::Run, other) => Err(syn::Error::new(
|
| 158 |
276 |
|
emission_span(other),
|
| 159 |
277 |
|
"a row ranks what it holds: write `beside <priority> <emission>`",
|
| 160 |
278 |
|
)),
|
|
279 |
+ |
(Container::Act, other) => Err(syn::Error::new(
|
|
280 |
+ |
emission_span(other),
|
|
281 |
+ |
"a control holds no members: its body says what it is like",
|
|
282 |
+ |
)),
|
| 161 |
283 |
|
}
|
| 162 |
284 |
|
}
|
| 163 |
285 |
|
|
|
286 |
+ |
/// One guard, as the `bool` it tests.
|
|
287 |
+ |
fn predicate(guard: &Guard) -> Result<TokenStream> {
|
|
288 |
+ |
let test = match &guard.predicate {
|
|
289 |
+ |
Predicate::Truth(hole) => self::hole(hole)?,
|
|
290 |
+ |
Predicate::Comparison {
|
|
291 |
+ |
left,
|
|
292 |
+ |
compare,
|
|
293 |
+ |
right,
|
|
294 |
+ |
} => {
|
|
295 |
+ |
let spelling = compare.to_string();
|
|
296 |
+ |
let operator = COMPARISONS
|
|
297 |
+ |
.iter()
|
|
298 |
+ |
.find(|(known, _)| *known == spelling)
|
|
299 |
+ |
.map(|(_, operator)| *operator)
|
|
300 |
+ |
.ok_or_else(|| {
|
|
301 |
+ |
syn::Error::new(compare.span(), format!("`{spelling}` is not a comparison"))
|
|
302 |
+ |
})?;
|
|
303 |
+ |
let operator: TokenStream = operator.parse().expect("a comparison operator");
|
|
304 |
+ |
let left = self::hole(left)?;
|
|
305 |
+ |
let right = arg(right)?;
|
|
306 |
+ |
quote!(#left #operator #right)
|
|
307 |
+ |
}
|
|
308 |
+ |
};
|
|
309 |
+ |
Ok(if guard.negated {
|
|
310 |
+ |
quote!(!(#test))
|
|
311 |
+ |
} else {
|
|
312 |
+ |
test
|
|
313 |
+ |
})
|
|
314 |
+ |
}
|
|
315 |
+ |
|
| 164 |
316 |
|
/// One emission as a `Node` value.
|
| 165 |
317 |
|
fn node(emission: &Emission) -> Result<TokenStream> {
|
| 166 |
318 |
|
match emission {
|
| 178 |
330 |
|
}
|
| 179 |
331 |
|
})
|
| 180 |
332 |
|
}
|
|
333 |
+ |
Emission::Act { .. } => {
|
|
334 |
+ |
let control = act(emission)?;
|
|
335 |
+ |
Ok(quote!(::quasi_router::Node::Act(#control)))
|
|
336 |
+ |
}
|
|
337 |
+ |
Emission::Include(supplier) => {
|
|
338 |
+ |
let supplier = hole(supplier)?;
|
|
339 |
+ |
Ok(quote!(::std::convert::Into::into(#supplier)))
|
|
340 |
+ |
}
|
| 181 |
341 |
|
Emission::Region { name, kind, body } => {
|
| 182 |
342 |
|
let name = arg(name)?;
|
| 183 |
343 |
|
let slot = accrete(
|
| 187 |
347 |
|
)?;
|
| 188 |
348 |
|
Ok(quote!(::quasi_router::Node::Region(#slot)))
|
| 189 |
349 |
|
}
|
|
350 |
+ |
Emission::Given {
|
|
351 |
+ |
scrutinee,
|
|
352 |
+ |
arms,
|
|
353 |
+ |
otherwise,
|
|
354 |
+ |
} => {
|
|
355 |
+ |
let scrutinee = hole(scrutinee)?;
|
|
356 |
+ |
let arms = arms
|
|
357 |
+ |
.iter()
|
|
358 |
+ |
.map(|(pattern, arm)| {
|
|
359 |
+ |
let pattern = self::pattern(pattern);
|
|
360 |
+ |
let arm = node(arm)?;
|
|
361 |
+ |
Ok(quote!(#pattern => #arm,))
|
|
362 |
+ |
})
|
|
363 |
+ |
.collect::<Result<Vec<_>>>()?;
|
|
364 |
+ |
let otherwise = match otherwise {
|
|
365 |
+ |
Some(otherwise) => {
|
|
366 |
+ |
let otherwise = node(otherwise)?;
|
|
367 |
+ |
quote!(_ => #otherwise,)
|
|
368 |
+ |
}
|
|
369 |
+ |
None => quote!(),
|
|
370 |
+ |
};
|
|
371 |
+ |
Ok(quote!(match #scrutinee { #(#arms)* #otherwise }))
|
|
372 |
+ |
}
|
|
373 |
+ |
Emission::Guarded { guard, .. } => Err(syn::Error::new(
|
|
374 |
+ |
guard.span,
|
|
375 |
+ |
"a guarded member needs a container that may hold nothing; \
|
|
376 |
+ |
only a `-> Option<_>` shape can drop its whole result so far",
|
|
377 |
+ |
)),
|
| 190 |
378 |
|
Emission::Across { fallback, .. } => Err(syn::Error::new(
|
| 191 |
379 |
|
fallback.span(),
|
| 192 |
380 |
|
"a row is not a node: `across` belongs in a region",
|
| 198 |
386 |
|
}
|
| 199 |
387 |
|
}
|
| 200 |
388 |
|
|
|
389 |
+ |
/// One control: what it is called, what it does, and what it is like.
|
|
390 |
+ |
fn act(emission: &Emission) -> Result<TokenStream> {
|
|
391 |
+ |
let Emission::Act {
|
|
392 |
+ |
label,
|
|
393 |
+ |
action,
|
|
394 |
+ |
body,
|
|
395 |
+ |
} = emission
|
|
396 |
+ |
else {
|
|
397 |
+ |
return Err(syn::Error::new(
|
|
398 |
+ |
emission_span(emission),
|
|
399 |
+ |
"expected a control here",
|
|
400 |
+ |
));
|
|
401 |
+ |
};
|
|
402 |
+ |
let label = arg(label)?;
|
|
403 |
+ |
let called = self::action(action)?;
|
|
404 |
+ |
accrete(
|
|
405 |
+ |
body,
|
|
406 |
+ |
Container::Act,
|
|
407 |
+ |
"e!(::quasi_router::Act::new(#label, #called)),
|
|
408 |
+ |
)
|
|
409 |
+ |
}
|
|
410 |
+ |
|
| 201 |
411 |
|
fn action(action: &Action) -> Result<TokenStream> {
|
| 202 |
412 |
|
let Action {
|
| 203 |
413 |
|
verb,
|
| 212 |
422 |
|
}
|
| 213 |
423 |
|
None => quote!(),
|
| 214 |
424 |
|
};
|
| 215 |
|
- |
let modifiers = modifiers.iter().map(|modifier| quote!(.#modifier()));
|
|
425 |
+ |
let modifiers = modifiers
|
|
426 |
+ |
.iter()
|
|
427 |
+ |
.map(|modifier| {
|
|
428 |
+ |
let name = &modifier.name;
|
|
429 |
+ |
let args = modifier.args.iter().map(arg).collect::<Result<Vec<_>>>()?;
|
|
430 |
+ |
Ok(quote!(.#name(#(#args),*)))
|
|
431 |
+ |
})
|
|
432 |
+ |
.collect::<Result<Vec<_>>>()?;
|
| 216 |
433 |
|
Ok(quote!(::quasi_router::Action::#verb(#target) #(#modifiers)*))
|
| 217 |
434 |
|
}
|
| 218 |
435 |
|
|
| 369 |
586 |
|
|
| 370 |
587 |
|
fn emission_span(emission: &Emission) -> Span {
|
| 371 |
588 |
|
match emission {
|
| 372 |
|
- |
Emission::Text(_) | Emission::Link { .. } => Span::call_site(),
|
|
589 |
+ |
Emission::Text(_) | Emission::Link { .. } | Emission::Include(_) => Span::call_site(),
|
|
590 |
+ |
Emission::Act { action, .. } => action.verb.span(),
|
|
591 |
+ |
Emission::Guarded { guard, .. } => guard.span,
|
|
592 |
+ |
Emission::Given { .. } => Span::call_site(),
|
| 373 |
593 |
|
Emission::Region { kind, .. } => kind.span(),
|
| 374 |
594 |
|
Emission::Across { fallback, .. } => fallback.span(),
|
| 375 |
595 |
|
Emission::Beside { priority, .. } => priority.span(),
|