| 322 |
322 |
|
}
|
| 323 |
323 |
|
Item::Attribute {
|
| 324 |
324 |
|
name: name.clone(),
|
| 325 |
|
- |
args: self::args(args, counters)?,
|
|
325 |
+ |
args: if structured(name) {
|
|
326 |
+ |
through(args, counters)?
|
|
327 |
+ |
} else {
|
|
328 |
+ |
self::args(args, counters)?
|
|
329 |
+ |
},
|
| 326 |
330 |
|
guard: None,
|
| 327 |
331 |
|
}
|
| 328 |
332 |
|
}
|
| 455 |
459 |
|
}
|
| 456 |
460 |
|
}
|
| 457 |
461 |
|
|
|
462 |
+ |
/// The slots whose value is a structure the description builds, not a string a
|
|
463 |
+ |
/// request brings.
|
|
464 |
+ |
///
|
|
465 |
+ |
/// A hole in one of these is a constructor call: `token Tag::badge(kind)`,
|
|
466 |
+ |
/// `option Choice::new(id, name)`, `stats [Figure::new(count, "Items")]`. What
|
|
467 |
+ |
/// the slot takes is a `Tag`, a `Choice`, a `Figure` -- none of which has a
|
|
468 |
+ |
/// sentinel, so the call cannot become one, and staging it as a value fails
|
|
469 |
+ |
/// with `the trait bound Tag: Fill is not satisfied` against generated code.
|
|
470 |
+ |
///
|
|
471 |
+ |
/// The arguments are a different matter. Each of those is an ordinary value a
|
|
472 |
+ |
/// request brings, and each does have a sentinel. So the call is kept and
|
|
473 |
+ |
/// staged **through**: the derivation builds the tag with a sentinel in it, the
|
|
474 |
+ |
/// renderer draws the tag's markup around the sentinel, and the residual holds
|
|
475 |
+ |
/// the markup as a literal with the word as a hole. That is the same trade
|
|
476 |
+ |
/// [`value`] makes for a `const` path, one level down.
|
|
477 |
+ |
///
|
|
478 |
+ |
/// Slot-directed rather than a rule about calls, and that is not a shortcut.
|
|
479 |
+ |
/// `text tier_line(profile.priced, prices)` is a supplier answering a string,
|
|
480 |
+ |
/// where the whole call is the value and staging through it would hand
|
|
481 |
+ |
/// `tier_line` a sentinel where it wants a `&TierPrices`. Only the slot knows
|
|
482 |
+ |
/// which of the two it is looking at.
|
|
483 |
+ |
const STRUCTURED: &[&str] = &["token", "meter", "stats", "option"];
|
|
484 |
+ |
|
|
485 |
+ |
/// Whether this slot takes a structure rather than a value.
|
|
486 |
+ |
fn structured(name: &Ident) -> bool {
|
|
487 |
+ |
STRUCTURED.contains(&name.to_string().as_str())
|
|
488 |
+ |
}
|
|
489 |
+ |
|
|
490 |
+ |
/// Stage a structured slot's arguments, keeping the calls that build them.
|
|
491 |
+ |
///
|
|
492 |
+ |
/// See [`STRUCTURED`]. A call is kept and its own arguments staged; anything
|
|
493 |
+ |
/// else is an ordinary value and goes through [`arg`].
|
|
494 |
+ |
fn through(args: &[Arg], counters: &mut Counters) -> Result<Vec<Arg>> {
|
|
495 |
+ |
args.iter().map(|one| one_through(one, counters)).collect()
|
|
496 |
+ |
}
|
|
497 |
+ |
|
|
498 |
+ |
fn one_through(arg: &Arg, counters: &mut Counters) -> Result<Arg> {
|
|
499 |
+ |
Ok(match arg {
|
|
500 |
+ |
Arg::List(items) => Arg::List(through(items, counters)?),
|
|
501 |
+ |
Arg::Borrow(inner) => Arg::Borrow(Box::new(one_through(inner, counters)?)),
|
|
502 |
+ |
Arg::Hole(hole) => match &hole.root {
|
|
503 |
+ |
HoleRoot::Call { path, args } => Arg::Hole(Hole {
|
|
504 |
+ |
root: HoleRoot::Call {
|
|
505 |
+ |
path: path.clone(),
|
|
506 |
+ |
args: self::args(args, counters)?,
|
|
507 |
+ |
},
|
|
508 |
+ |
// A builder chain on the constructor -- `Tag::badge(kind).tone(..)`
|
|
509 |
+ |
// -- carries values of its own, and they are staged the same way.
|
|
510 |
+ |
steps: hole
|
|
511 |
+ |
.steps
|
|
512 |
+ |
.iter()
|
|
513 |
+ |
.map(|step| {
|
|
514 |
+ |
Ok(match step {
|
|
515 |
+ |
crate::ast::Step::Field(name) => crate::ast::Step::Field(name.clone()),
|
|
516 |
+ |
crate::ast::Step::Method { name, args } => crate::ast::Step::Method {
|
|
517 |
+ |
name: name.clone(),
|
|
518 |
+ |
args: self::args(args, counters)?,
|
|
519 |
+ |
},
|
|
520 |
+ |
})
|
|
521 |
+ |
})
|
|
522 |
+ |
.collect::<Result<Vec<_>>>()?,
|
|
523 |
+ |
}),
|
|
524 |
+ |
// Not a constructor, so there is nothing to stage through and the
|
|
525 |
+ |
// slot is being handed a value from somewhere else. Left as
|
|
526 |
+ |
// written: a path is a const the renderer bakes in, and a binding
|
|
527 |
+ |
// or a method chain is refused by rustc against the slot's own
|
|
528 |
+ |
// type, which names the site.
|
|
529 |
+ |
_ => Arg::Hole(hole.clone()),
|
|
530 |
+ |
},
|
|
531 |
+ |
other => self::arg(other, counters)?,
|
|
532 |
+ |
})
|
|
533 |
+ |
}
|
|
534 |
+ |
|
| 458 |
535 |
|
fn args(args: &[Arg], counters: &mut Counters) -> Result<Vec<Arg>> {
|
| 459 |
536 |
|
args.iter().map(|arg| self::arg(arg, counters)).collect()
|
| 460 |
537 |
|
}
|
| 499 |
576 |
|
Ok(match emission {
|
| 500 |
577 |
|
Emission::Simple { member, args, body } => Emission::Simple {
|
| 501 |
578 |
|
member: member.clone(),
|
| 502 |
|
- |
args: self::args(args, counters)?,
|
|
579 |
+ |
args: if structured(member) {
|
|
580 |
+ |
through(args, counters)?
|
|
581 |
+ |
} else {
|
|
582 |
+ |
self::args(args, counters)?
|
|
583 |
+ |
},
|
| 503 |
584 |
|
body: items(body, counters)?,
|
| 504 |
585 |
|
},
|
| 505 |
586 |
|
Emission::Chip {
|