The Runes
A reference for the parsed shapes the decipher walk hands a rune. Each
type below is exactly what your inscribe_* method receives — read this
chapter when writing the body of an inscribe handler and you’re not sure
what fields are available.
The shape of DsTree
Everything the parser builds is one type:
pub struct DsTree {
parent: Option<DsTreeRef>,
node: DsNode,
children: Vec<DsTreeRef>,
}
parent— set by the parser as it links the tree; backends rarely read it directly. The push/pop idiom in Binding the Rune threads parent identity through the rune, not through this field.node— what kind of casting node this is, seeDsNodebelow.children— sub-trees. Empty for leaf forms; non-empty for widget bodies, control bodies, niche bodies. Match arm children live on the arm, not here.
DsTreeRef is what DsRef mints around DsTree: an
Rc<RefCell<DsTree>> newtype. Borrow it with .borrow() /
.borrow_mut() like any RefCell. The reference-counted shape is what
lets the parser link parents and children without lifetimes; it’s not
something the inscribe path generally needs to clone or mutate.
Read access on a borrowed DsTree:
| Method | Returns | Use |
|---|---|---|
get_node() | &DsNode | Pattern-match to figure out which kind of node you have. |
get_children() | &[DsTreeRef] | Iterate when you recurse via decipher(child, self). |
set_parent(parent) | () | Parser-only. Backends don’t call this. |
DsNode — six variants
pub enum DsNode {
Root(syn::Expr),
Widget(DsWidget),
If(DsIf),
Iter(DsIter),
Niche(DsNiche),
Match(DsMatch),
}
You almost never match on DsNode directly — decipher already
dispatches each variant to the right inscribe_* method. The variant
names map one-to-one to the trait:
DsNode variant | Dispatched to | Reaches the rune as |
|---|---|---|
Root(expr) | inscribe_root | parent_expr: &syn::Expr |
Widget(w) | inscribe_widget | full widget unpacked into 5 args |
If(node) | inscribe_if | condition: &syn::Expr + children |
Iter(node) | inscribe_iter | iterable + variable + children |
Niche(node) | inscribe_niche | name: &syn::Ident + children |
Match(node) | inscribe_match | scrutinee: &syn::Expr + arms: &[DsMatchArm] |
There is no On variant — on EventKind { … } clauses fold into
the widget they attach to. They reach the rune as the
on_handlers: &[DsOn] slice on inscribe_widget, never as a top-level
node.
The peek-side enum DsNodeType is a parser-only thing
(Widget / If / Iter / Niche / Match) — same names minus
Root. Backends never see it.
DsRoot — the cast envelope
pub struct DsRoot { /* private */ }
impl DsRoot {
pub fn get_parent(&self) -> syn::Expr;
pub fn get_content(&self) -> DsTreeRef;
pub fn get_context_attrs(&self) -> &[DsAttr];
}
You hit DsRoot exactly once per cast — at the host’s macro entry,
right before calling inscribe_root and decipher:
let root: xrune::ds_node::DsRoot = syn::parse2(tokens)?;
rune.inscribe_root(&root.get_parent());
decipher(&root.get_content(), &mut rune);
get_parent()returns a clone of theparent:value from the:( … :)header, suitable for splicing into emitted code.get_content()returns the body of the cast — the actualDsTreeRefdecipherwalks.get_context_attrs()returns every attr in the header, includingparentitself. Use it when your rune defines extra context keys (world,theme, …) and wants to read them before the walk.
DsRoot also implements Deref<Target = DsTreeRef>, but that’s a
parser-side convenience; backends use the explicit getters.
Per-node types
DsWidget
pub struct DsWidget { /* private */ }
impl DsWidget {
pub fn get_name(&self) -> &syn::Ident;
pub fn get_attrs(&self) -> &DsAttrs;
pub fn get_enchants(&self) -> &[syn::Expr];
pub fn get_on_handlers(&self) -> &[DsOn];
}
inscribe_widget already unpacks all four fields plus the children for
you. The DsWidget value itself is what the parser holds; you only
reach for it when walking DsNode manually (e.g. in an offline tool
like xrune-fmt).
DsAttr and DsAttrs
pub struct DsAttr {
pub name: Option<syn::Ident>,
pub value: syn::Expr,
}
impl DsAttr {
pub fn name_str(&self) -> Option<String>;
}
pub struct DsAttrs {
pub attrs: Vec<DsAttr>,
}
name: Option<syn::Ident>isSomeforname: valueform,Nonefor positional attrs.name_str()is the matching-friendly version.value: syn::Expris whatever the user wrote — match on it as you would anysyn::Expr, or splice it viaquote!into emitted code.
DsOn (event handlers)
pub struct DsOn { /* private */ }
impl DsOn {
pub fn get_qualifier(&self) -> Option<&syn::Ident>;
pub fn get_name(&self) -> &syn::Ident;
pub fn get_args(&self) -> &[syn::Expr];
pub fn get_body(&self) -> Option<&syn::Block>;
}
get_qualifier()isSome(Slider)foron Slider::ValueChanged,Nonefor bareon Tap.get_name()is always present —Tap,ValueChanged, …get_args()is the comma-separated expr list inside(…).get_body()isSomefor the{ … }body form,Nonefor the callback form (on Tap(cb)); in the callback case the rune typically reads the last element ofget_args()as the callable.
DsIf
impl DsIf {
pub fn get_condition(&self) -> &syn::Expr;
}
Children come from the surrounding DsTree’s get_children().
DsIter (walk … with …)
impl DsIter {
pub fn get_iterable(&self) -> &syn::Expr;
pub fn get_variable(&self) -> &syn::Ident;
}
iterable is what comes after walk; variable is the binding after
with. The body is again on the surrounding DsTree.
DsNiche (@name { … })
impl DsNiche {
pub fn get_name(&self) -> &syn::Ident;
}
Single-segment ident only — @foo::bar is a parse error.
DsMatch and DsMatchArm
impl DsMatch {
pub fn get_scrutinee(&self) -> &syn::Expr;
pub fn get_arms(&self) -> &[DsMatchArm];
}
impl DsMatchArm {
pub fn get_pat(&self) -> &syn::Pat;
pub fn get_children(&self) -> &[DsTreeRef];
}
DsMatch is the only node where children don’t sit on the
surrounding DsTree. They’re partitioned across arms — each arm
carries its own get_children(). That’s why inscribe_match takes
arms: &[DsMatchArm] and the rune writes a two-level loop. See the
example in Binding the Rune § Hosting xrune.
Custom keywords
walk, with, and on are registered as syn::custom_keyword! —
they cannot be used as widget names, attr names, or any other
identifier. The parser dispatches them before the widget peek, so
on Foo is not a widget called on.
What you don’t need to know
A few items are public but only matter to the parser or to xrune itself:
DsContext/DsContextRef—#[allow(dead_code)], an auxiliary structure not on the inscribe path.DsNodeIsMe— peek protocol each node parser implements; onlyDsNode::what_type()calls into it.DsTreeRef’s innerRc<RefCell<DsTree>>shape — fordecipherto share children across borrows. You won’t normally clone or manipulate theRcdirectly from a rune.
Source-of-truth
Everything above is in crates/xrune_nexus/src/ds_node/. One file per
type: ds_root.rs, ds_widget.rs, ds_attr.rs, ds_on.rs,
ds_if.rs, ds_iter.rs, ds_niche.rs, ds_match.rs,
node_enum.rs. The DsRune trait that consumes them is in
crates/xrune_nexus/src/ds_rune/mod.rs; the decipher walker beside
it.