| 12 |
12 |
|
|
| 13 |
13 |
|
use proc_macro2::{Span, TokenStream};
|
| 14 |
14 |
|
use quote::{format_ident, quote};
|
|
15 |
+ |
use syn::spanned::Spanned as _;
|
| 15 |
16 |
|
use syn::{LitStr, Result};
|
| 16 |
17 |
|
|
| 17 |
18 |
|
use crate::ast::{
|
| 29 |
30 |
|
Run,
|
| 30 |
31 |
|
/// One control: its body sets, and emits nothing.
|
| 31 |
32 |
|
Act,
|
|
33 |
+ |
/// A document: its members are regions, held as slots rather than nodes.
|
|
34 |
+ |
Screen,
|
|
35 |
+ |
/// One row of a list: its members are the controls it offers.
|
|
36 |
+ |
Row,
|
| 32 |
37 |
|
}
|
| 33 |
38 |
|
|
| 34 |
39 |
|
pub fn declaration(declaration: &Declaration) -> Result<TokenStream> {
|
| 56 |
61 |
|
});
|
| 57 |
62 |
|
let body = value_body(items, &shaped, optional, name.span())?;
|
| 58 |
63 |
|
|
|
64 |
+ |
// The vocabulary type, not the path the file happened to import it under:
|
|
65 |
+ |
// team.rs takes `Screen as Described` because it has a function called
|
|
66 |
+ |
// `screen`, and a declaration names what it returns rather than what the
|
|
67 |
+ |
// module around it calls that.
|
|
68 |
+ |
let shaped_type = format_ident!("{}", shaped, span = returns.span());
|
|
69 |
+ |
let returns = if optional {
|
|
70 |
+ |
quote!(::std::option::Option<::quasi_router::#shaped_type>)
|
|
71 |
+ |
} else {
|
|
72 |
+ |
quote!(::quasi_router::#shaped_type)
|
|
73 |
+ |
};
|
|
74 |
+ |
|
| 59 |
75 |
|
Ok(quote! {
|
| 60 |
76 |
|
#(#docs)*
|
| 61 |
77 |
|
#(#flags)*
|
| 121 |
137 |
|
"a setting needs something to set: put it in the member's body",
|
| 122 |
138 |
|
));
|
| 123 |
139 |
|
}
|
|
140 |
+ |
Item::For { binder, .. } => {
|
|
141 |
+ |
return Err(syn::Error::new(
|
|
142 |
+ |
binder.span(),
|
|
143 |
+ |
"a loop emits many members, and this shape is its single one",
|
|
144 |
+ |
));
|
|
145 |
+ |
}
|
| 124 |
146 |
|
Item::Emit(emission) => emissions.push(emission),
|
| 125 |
147 |
|
}
|
| 126 |
148 |
|
}
|
| 157 |
179 |
|
));
|
| 158 |
180 |
|
}
|
| 159 |
181 |
|
},
|
|
182 |
+ |
"Screen" => match only {
|
|
183 |
+ |
Emission::Screen {
|
|
184 |
+ |
arrangement,
|
|
185 |
+ |
title,
|
|
186 |
+ |
body,
|
|
187 |
+ |
} => {
|
|
188 |
+ |
let title = arg(title)?;
|
|
189 |
+ |
accrete(
|
|
190 |
+ |
body,
|
|
191 |
+ |
Container::Screen,
|
|
192 |
+ |
"e!(::quasi_router::Screen::#arrangement(#title)),
|
|
193 |
+ |
)?
|
|
194 |
+ |
}
|
|
195 |
+ |
other => {
|
|
196 |
+ |
return Err(syn::Error::new(
|
|
197 |
+ |
emission_span(other),
|
|
198 |
+ |
"a `-> Screen` shape is its single `screen` member",
|
|
199 |
+ |
));
|
|
200 |
+ |
}
|
|
201 |
+ |
},
|
|
202 |
+ |
"Row" => match only {
|
|
203 |
+ |
Emission::Row { primary, body } => row(primary, body)?,
|
|
204 |
+ |
other => {
|
|
205 |
+ |
return Err(syn::Error::new(
|
|
206 |
+ |
emission_span(other),
|
|
207 |
+ |
"a `-> Row` shape is its single `row` member",
|
|
208 |
+ |
));
|
|
209 |
+ |
}
|
|
210 |
+ |
},
|
| 160 |
211 |
|
other => {
|
| 161 |
212 |
|
return Err(syn::Error::new(
|
| 162 |
213 |
|
span,
|
| 188 |
239 |
|
|
| 189 |
240 |
|
/// A body that accretes onto something already built.
|
| 190 |
241 |
|
fn accrete(items: &[Item], container: Container, base: &TokenStream) -> Result<TokenStream> {
|
|
242 |
+ |
if items.iter().any(|item| matches!(item, Item::For { .. })) {
|
|
243 |
+ |
return accumulate(items, container, base);
|
|
244 |
+ |
}
|
|
245 |
+ |
|
| 191 |
246 |
|
let mut bindings = Vec::new();
|
| 192 |
247 |
|
let mut steps = Vec::new();
|
| 193 |
248 |
|
for item in items {
|
| 197 |
252 |
|
bindings.push(quote!(let #name = #value;));
|
| 198 |
253 |
|
}
|
| 199 |
254 |
|
Item::Attribute { name, args } => steps.push(attribute(name, args)?),
|
|
255 |
+ |
Item::For { .. } => unreachable!("a loop takes the accumulating form"),
|
| 200 |
256 |
|
Item::Emit(emission) => {
|
| 201 |
257 |
|
// A member is evaluated into its own binding before the
|
| 202 |
258 |
|
// container is built, because a container's own name is often
|
| 213 |
269 |
|
Ok(quote!({ #(#bindings)* #base #(#steps)* }))
|
| 214 |
270 |
|
}
|
| 215 |
271 |
|
|
|
272 |
+ |
/// The same body, for a container a loop adds to.
|
|
273 |
+ |
///
|
|
274 |
+ |
/// A chain cannot hold a loop, so this accumulates instead. The two forms are
|
|
275 |
+ |
/// kept apart rather than merged because the chain is what a container with no
|
|
276 |
+ |
/// loop should read as, and a `let mut` that is never reassigned is a warning
|
|
277 |
+ |
/// in the caller's crate that the caller cannot see the cause of.
|
|
278 |
+ |
fn accumulate(items: &[Item], container: Container, base: &TokenStream) -> Result<TokenStream> {
|
|
279 |
+ |
let built = format_ident!("built", span = Span::call_site());
|
|
280 |
+ |
let statements = statements(items, container, &built)?;
|
|
281 |
+ |
Ok(quote!({
|
|
282 |
+ |
let mut #built = #base;
|
|
283 |
+ |
#(#statements)*
|
|
284 |
+ |
#built
|
|
285 |
+ |
}))
|
|
286 |
+ |
}
|
|
287 |
+ |
|
|
288 |
+ |
/// The statements one body contributes to an accumulating container.
|
|
289 |
+ |
fn statements(
|
|
290 |
+ |
items: &[Item],
|
|
291 |
+ |
container: Container,
|
|
292 |
+ |
built: &proc_macro2::Ident,
|
|
293 |
+ |
) -> Result<Vec<TokenStream>> {
|
|
294 |
+ |
let mut statements = Vec::new();
|
|
295 |
+ |
for (index, item) in items.iter().enumerate() {
|
|
296 |
+ |
statements.push(match item {
|
|
297 |
+ |
Item::Bind { name, source } => {
|
|
298 |
+ |
let value = source_value(source)?;
|
|
299 |
+ |
quote!(let #name = #value;)
|
|
300 |
+ |
}
|
|
301 |
+ |
Item::Attribute { name, args } => {
|
|
302 |
+ |
let call = attribute(name, args)?;
|
|
303 |
+ |
quote!(#built = #built #call;)
|
|
304 |
+ |
}
|
|
305 |
+ |
Item::For {
|
|
306 |
+ |
binder,
|
|
307 |
+ |
iterable,
|
|
308 |
+ |
body,
|
|
309 |
+ |
} => {
|
|
310 |
+ |
let iterable = hole(iterable)?;
|
|
311 |
+ |
let inner = self::statements(body, container, built)?;
|
|
312 |
+ |
quote!(for #binder in #iterable { #(#inner)* })
|
|
313 |
+ |
}
|
|
314 |
+ |
Item::Emit(emission) => {
|
|
315 |
+ |
let held = format_ident!("member_{index}", span = Span::call_site());
|
|
316 |
+ |
let (value, call) = step(emission, container, &held)?;
|
|
317 |
+ |
quote!({ let #held = #value; #built = #built #call; })
|
|
318 |
+ |
}
|
|
319 |
+ |
});
|
|
320 |
+ |
}
|
|
321 |
+ |
Ok(statements)
|
|
322 |
+ |
}
|
|
323 |
+ |
|
| 216 |
324 |
|
/// One setting, as the builder call ATTRIBUTE NAMING says it is.
|
| 217 |
|
- |
fn attribute(name: &syn::Ident, args: &[Arg]) -> Result<TokenStream> {
|
|
325 |
+ |
fn attribute(name: &proc_macro2::Ident, args: &[Arg]) -> Result<TokenStream> {
|
| 218 |
326 |
|
let slot = name.to_string();
|
| 219 |
327 |
|
let enumeration = VOCABULARY
|
| 220 |
328 |
|
.iter()
|
| 268 |
376 |
|
Ok((run, quote!(.across(#held))))
|
| 269 |
377 |
|
}
|
| 270 |
378 |
|
(Container::Slot, other) => Ok((node(other)?, quote!(.with(#held)))),
|
|
379 |
+ |
(Container::Screen, Emission::Region { name, kind, body }) => {
|
|
380 |
+ |
Ok((slot(name, kind, body)?, quote!(.with(#held))))
|
|
381 |
+ |
}
|
|
382 |
+ |
(Container::Screen, other) => Err(syn::Error::new(
|
|
383 |
+ |
emission_span(other),
|
|
384 |
+ |
"a document holds regions: write `region <name> as <kind> { .. }`",
|
|
385 |
+ |
)),
|
|
386 |
+ |
(Container::Row, Emission::Act { .. }) => Ok((act(emission)?, quote!(.act(#held)))),
|
|
387 |
+ |
(Container::Row, other) => Err(syn::Error::new(
|
|
388 |
+ |
emission_span(other),
|
|
389 |
+ |
"a row holds controls: write `act <label> to <action>;`",
|
|
390 |
+ |
)),
|
| 271 |
391 |
|
(Container::Run, Emission::Beside { priority, inner }) => Ok((
|
| 272 |
392 |
|
node(inner)?,
|
| 273 |
393 |
|
quote!(.beside(#held, ::quasi_router::layout::Priority::#priority)),
|
| 316 |
436 |
|
/// One emission as a `Node` value.
|
| 317 |
437 |
|
fn node(emission: &Emission) -> Result<TokenStream> {
|
| 318 |
438 |
|
match emission {
|
| 319 |
|
- |
Emission::Text(text) => {
|
| 320 |
|
- |
let text = arg(text)?;
|
| 321 |
|
- |
Ok(quote!(::quasi_router::Node::text(#text)))
|
|
439 |
+ |
Emission::Simple { member, args } => {
|
|
440 |
+ |
let args = args.iter().map(self::arg).collect::<Result<Vec<_>>>()?;
|
|
441 |
+ |
Ok(quote!(::quasi_router::Node::#member(#(#args),*)))
|
| 322 |
442 |
|
}
|
|
443 |
+ |
Emission::List(items) => list(items),
|
|
444 |
+ |
Emission::Row { .. } => Err(syn::Error::new(
|
|
445 |
+ |
emission_span(emission),
|
|
446 |
+ |
"a row is not a node: put it in a `list`",
|
|
447 |
+ |
)),
|
|
448 |
+ |
Emission::Screen { arrangement, .. } => Err(syn::Error::new(
|
|
449 |
+ |
arrangement.span(),
|
|
450 |
+ |
"a document is not a node",
|
|
451 |
+ |
)),
|
| 323 |
452 |
|
Emission::Link { text, action } => {
|
| 324 |
453 |
|
let text = arg(text)?;
|
| 325 |
454 |
|
let action = self::action(action)?;
|
| 339 |
468 |
|
Ok(quote!(::std::convert::Into::into(#supplier)))
|
| 340 |
469 |
|
}
|
| 341 |
470 |
|
Emission::Region { name, kind, body } => {
|
| 342 |
|
- |
let name = arg(name)?;
|
| 343 |
|
- |
let slot = accrete(
|
| 344 |
|
- |
body,
|
| 345 |
|
- |
Container::Slot,
|
| 346 |
|
- |
"e!(::quasi_router::Slot::new(#name, ::quasi_router::RegionKind::#kind)),
|
| 347 |
|
- |
)?;
|
|
471 |
+ |
let slot = slot(name, kind, body)?;
|
| 348 |
472 |
|
Ok(quote!(::quasi_router::Node::Region(#slot)))
|
| 349 |
473 |
|
}
|
| 350 |
474 |
|
Emission::Given {
|
| 386 |
510 |
|
}
|
| 387 |
511 |
|
}
|
| 388 |
512 |
|
|
|
513 |
+ |
/// One region, as the `Slot` it is. A document holds these; a body wraps them
|
|
514 |
+ |
/// in [`Node::Region`].
|
|
515 |
+ |
fn slot(name: &Arg, kind: &syn::Ident, body: &[Item]) -> Result<TokenStream> {
|
|
516 |
+ |
let name = arg(name)?;
|
|
517 |
+ |
accrete(
|
|
518 |
+ |
body,
|
|
519 |
+ |
Container::Slot,
|
|
520 |
+ |
"e!(::quasi_router::Slot::new(#name, ::quasi_router::RegionKind::#kind)),
|
|
521 |
+ |
)
|
|
522 |
+ |
}
|
|
523 |
+ |
|
|
524 |
+ |
/// The rows of a list, built where they are read.
|
|
525 |
+ |
fn list(items: &[Item]) -> Result<TokenStream> {
|
|
526 |
+ |
let rows = format_ident!("rows", span = Span::call_site());
|
|
527 |
+ |
let statements = list_statements(items, &rows)?;
|
|
528 |
+ |
Ok(quote!({
|
|
529 |
+ |
let mut #rows = ::std::vec::Vec::new();
|
|
530 |
+ |
#(#statements)*
|
|
531 |
+ |
::quasi_router::Node::list(#rows)
|
|
532 |
+ |
}))
|
|
533 |
+ |
}
|
|
534 |
+ |
|
|
535 |
+ |
fn list_statements(items: &[Item], rows: &proc_macro2::Ident) -> Result<Vec<TokenStream>> {
|
|
536 |
+ |
items
|
|
537 |
+ |
.iter()
|
|
538 |
+ |
.map(|item| match item {
|
|
539 |
+ |
Item::Bind { name, source } => {
|
|
540 |
+ |
let value = source_value(source)?;
|
|
541 |
+ |
Ok(quote!(let #name = #value;))
|
|
542 |
+ |
}
|
|
543 |
+ |
Item::For {
|
|
544 |
+ |
binder,
|
|
545 |
+ |
iterable,
|
|
546 |
+ |
body,
|
|
547 |
+ |
} => {
|
|
548 |
+ |
let iterable = hole(iterable)?;
|
|
549 |
+ |
let inner = list_statements(body, rows)?;
|
|
550 |
+ |
Ok(quote!(for #binder in #iterable { #(#inner)* }))
|
|
551 |
+ |
}
|
|
552 |
+ |
Item::Emit(Emission::Row { primary, body }) => {
|
|
553 |
+ |
let row = row(primary, body)?;
|
|
554 |
+ |
Ok(quote!(#rows.push(#row);))
|
|
555 |
+ |
}
|
|
556 |
+ |
Item::Emit(other) => Err(syn::Error::new(
|
|
557 |
+ |
emission_span(other),
|
|
558 |
+ |
"a list holds rows: write `row <primary> { .. }`",
|
|
559 |
+ |
)),
|
|
560 |
+ |
Item::Attribute { name, .. } => Err(syn::Error::new(
|
|
561 |
+ |
name.span(),
|
|
562 |
+ |
"a list has nothing to set: say it on the row",
|
|
563 |
+ |
)),
|
|
564 |
+ |
})
|
|
565 |
+ |
.collect()
|
|
566 |
+ |
}
|
|
567 |
+ |
|
|
568 |
+ |
/// One row of a list: what it says, and what it offers.
|
|
569 |
+ |
fn row(primary: &Arg, body: &[Item]) -> Result<TokenStream> {
|
|
570 |
+ |
let primary = arg(primary)?;
|
|
571 |
+ |
accrete(
|
|
572 |
+ |
body,
|
|
573 |
+ |
Container::Row,
|
|
574 |
+ |
"e!(::quasi_router::Row::new(#primary)),
|
|
575 |
+ |
)
|
|
576 |
+ |
}
|
|
577 |
+ |
|
| 389 |
578 |
|
/// One control: what it is called, what it does, and what it is like.
|
| 390 |
579 |
|
fn act(emission: &Emission) -> Result<TokenStream> {
|
| 391 |
580 |
|
let Emission::Act {
|
| 577 |
766 |
|
Ok(quote!(#value))
|
| 578 |
767 |
|
}
|
| 579 |
768 |
|
Arg::Bool(value) => Ok(quote!(#value)),
|
|
769 |
+ |
Arg::List(items) => {
|
|
770 |
+ |
let items = items.iter().map(self::arg).collect::<Result<Vec<_>>>()?;
|
|
771 |
+ |
Ok(quote!([#(#items),*]))
|
|
772 |
+ |
}
|
| 580 |
773 |
|
Arg::Borrow(inner) => {
|
| 581 |
774 |
|
let inner = self::arg(inner)?;
|
| 582 |
775 |
|
Ok(quote!(&#inner))
|
| 586 |
779 |
|
|
| 587 |
780 |
|
fn emission_span(emission: &Emission) -> Span {
|
| 588 |
781 |
|
match emission {
|
| 589 |
|
- |
Emission::Text(_) | Emission::Link { .. } | Emission::Include(_) => Span::call_site(),
|
|
782 |
+ |
Emission::Include(_) | Emission::Link { .. } => Span::call_site(),
|
|
783 |
+ |
Emission::Simple { member, .. } => member.span(),
|
|
784 |
+ |
Emission::Screen { arrangement, .. } => arrangement.span(),
|
|
785 |
+ |
Emission::Row { .. } | Emission::List(_) => Span::call_site(),
|
| 590 |
786 |
|
Emission::Act { action, .. } => action.verb.span(),
|
| 591 |
787 |
|
Emission::Guarded { guard, .. } => guard.span,
|
| 592 |
788 |
|
Emission::Given { .. } => Span::call_site(),
|