Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 10 additions & 3 deletions shared/yeast-macros/src/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,15 @@ pub(crate) enum Template {
Node(Node),
/// An embedded Rust block such as `{args}`.
Splice(syn::Block),
/// Ordered alternatives such as `[(identifier #{name}) (missing)]`.
Choice(Vec<Alternative>),
}

pub(crate) struct Alternative {
/// Template to construct when the guard and dependencies succeed.
pub(crate) template: Template,
/// Optional postfix guard such as `if { name.is_some() }`.
pub(crate) guard: Option<syn::Block>,
}

pub(crate) struct Node {
Expand All @@ -121,15 +130,13 @@ pub(crate) enum Content {

pub(crate) enum Field {
/// A named field such as `argument: {args}` or
/// `label: (identifier #{name})?`.
/// `label: (identifier #{name})`.
Named {
/// The field name before `:`, such as `argument`.
name: Ident,
/// The node or Rust splice after `:`, such as `(identifier "x")` or
/// `{args}`.
value: Template,
/// Whether the field value has a trailing `?`.
optional: bool,
},
/// Source-location metadata such as `location: {source}`.
Location {
Expand Down
41 changes: 25 additions & 16 deletions shared/yeast-macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ pub fn query(input: TokenStream) -> TokenStream {

/// Build a single AST node from a template, returning its `OutputId`.
///
/// A top-level choice returns `Option<OutputId>`.
///
/// # Template syntax
///
/// ```text
Expand All @@ -63,36 +65,43 @@ pub fn query(input: TokenStream) -> TokenStream {
/// `Option<OutputId>`, iterator chains) splice
/// their elements
/// field: {expr} - extend a named field with `{expr}`'s ids
/// field: (kind ...)? - set the field only if every `#{expr}`
/// beneath it has a value (see below)
/// location: {expr} - override this node's source range with the
/// span of one or more nodes; may be repeated
/// field: [(kind ...)] - set the field only if the alternative can
/// be constructed (see below)
/// field: [(a) (b)] - use the first constructible alternative
/// field: [(a) if {guard} (b)] - skip an alternative when its guard is false
/// ```
///
/// # Optional fields
/// # Template choices
///
/// A `?` on a field's value makes that field fallible: if a `#{expr}`
/// anywhere beneath it interpolates an absent value — an `Option` that is
/// `None` — the subtree is abandoned and the field is left unset. This
/// replaces the surrounding `Option::map` that would otherwise be needed:
/// Brackets contain one or more ordered alternatives. For each alternative,
/// its optional `if {guard}` is evaluated first, followed by every `#{expr}`
/// dependency beneath it. An absent dependency — an `Option` that is `None` —
/// skips the alternative without constructing any of its template nodes. The
/// first alternative whose guard and dependencies succeed is constructed:
///
/// ```text
/// (break_expr label: {lbl.map(|l| tree!((identifier #{l})))}) // before
/// (break_expr label: (identifier #{lbl})?) // after
/// (break_expr label: [(identifier #{lbl})]) // after
/// ```
///
/// The value may be nested arbitrarily deeply, and a nested `?` catches
/// first, so an inner absent value need not discard the outer node:
/// Dependencies may be nested arbitrarily deeply. A nested choice handles its
/// own dependencies, so an inner absent value need not discard an outer node:
///
/// ```text
/// (parameter pattern: (name_pattern identifier: (identifier #{name}))? type: {ty})
/// (parameter pattern: [
/// (name_pattern identifier: [(identifier #{name})])
/// ] type: {ty})
/// ```
///
/// Only `#{expr}` propagates absence. A `{expr}` splice is unaffected, since
/// yielding no ids already leaves a field unset, and `?` is rejected on one.
/// Outside a `?`, interpolating an `Option` with `#{expr}` remains a compile
/// error, so the choice between "unset the field" and "unwrap it" stays
/// explicit.
/// Only `#{expr}` is treated as a dependency. A `{expr}` splice is unaffected,
/// since yielding no ids already leaves its field unset. Outside a choice,
/// interpolating an `Option` with `#{expr}` remains a compile error.
///
/// A choice used as a field or list item contributes zero or one node. A
/// top-level choice passed to `tree!` returns `Option<OutputId>`; ordinary
/// templates continue to return `OutputId`.
///
/// Uses the `BuildCtx` binding named `ctx` from the surrounding scope:
///
Expand Down
Loading
Loading