Skip to content

Commit e9de618

Browse files
committed
yeast: Remove support for repeating phases
These were never used in practice (only in tests), and we don't foresee an immediate use for them. If necessary, we can always add them back later.
1 parent f69f365 commit e9de618

7 files changed

Lines changed: 191 additions & 495 deletions

File tree

‎shared/yeast-macros/src/lib.rs‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -164,8 +164,8 @@ pub fn rule(input: TokenStream) -> TokenStream {
164164
///
165165
/// 1. A **bare rule body** `(query) => (template)` — the `rule!(...)`
166166
/// wrapper is implicit.
167-
/// 2. An explicit `rule!(...)` invocation, possibly chained as
168-
/// `rule!(...).repeated()` or path-prefixed as `yeast::rule!(...)`.
167+
/// 2. An explicit `rule!(...)` invocation, possibly path-prefixed as
168+
/// `yeast::rule!(...)`.
169169
/// 3. Any other expression returning a `Rule` (helper-function calls,
170170
/// conditionals).
171171
///
@@ -176,7 +176,7 @@ pub fn rule(input: TokenStream) -> TokenStream {
176176
/// [
177177
/// (source_file (_)* @cs) => (top_level body: {..cs}),
178178
/// (simple_identifier) @id => (name_expr identifier: (identifier #{id})),
179-
/// rule!((integer_literal) @lit => (int_literal #{lit})).repeated(),
179+
/// rule!((integer_literal) @lit => (int_literal #{lit})),
180180
/// helper_fn(),
181181
/// ]
182182
/// };

‎shared/yeast-macros/src/parse.rs‎

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -970,9 +970,6 @@ pub fn parse_rule_top(input: TokenStream) -> Result<TokenStream> {
970970
// captured node before invoking the user's transform body,
971971
// except for `@@name` captures listed in `__skip` which the
972972
// body consumes raw.
973-
// For OneShot rules this preserves the legacy behaviour
974-
// (input-schema captures translated to output-schema
975-
// nodes); for Repeating rules it is a no-op.
976973
let __skip: &[&str] = &[#(#raw_capture_names),*];
977974
__translator.auto_translate_captures(&mut __captures, __ast, __user_ctx, __skip)?;
978975
#(#raw_bindings)*
@@ -1141,8 +1138,8 @@ fn expect_repetition(tokens: &mut Tokens) -> Result<TokenStream> {
11411138
/// Each item in the bracketed list can be:
11421139
/// * a **bare rule body** `(query) => (template)` — wrapped implicitly
11431140
/// in `yeast::rule! { ... }` for codegen;
1144-
/// * an explicit `rule!(...)` (or `rule!(...).repeated()`,
1145-
/// `yeast::rule!(...)`, etc.) — passed through verbatim;
1141+
/// * an explicit `rule!(...)` (including `yeast::rule!(...)`) — passed
1142+
/// through verbatim;
11461143
/// * any other expression returning a `Rule` (helper-function calls,
11471144
/// conditionals) — passed through verbatim.
11481145
///

‎shared/yeast/doc/yeast.md‎

Lines changed: 25 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -53,29 +53,18 @@ A YEAST `Rule` has two parts:
5353
pattern language.
5454
2. A **transform** that produces replacement nodes from the match captures.
5555

56-
The `Runner` applies rules by walking the tree top-down. At each node, it
57-
tries each rule in order. If a rule's query matches, the node is replaced by
58-
the transform's output, and the rules are re-applied to the result. If no
59-
rule matches, the node is kept and its children are processed recursively.
56+
The `Runner` translates the root with the first matching rule. Before the
57+
rule's transform runs, captured input nodes are recursively translated with
58+
the same rules. Every visited input node must match a rule; a missing match is
59+
an error.
6060

6161
A rule can replace one node with zero nodes (deletion), one node (rewriting),
6262
or multiple nodes (expansion).
6363

64-
By default a rule fires **at most once on a given node**: after firing, the
65-
engine will not re-try that same rule on the result root. Other rules may
66-
still fire on the result, and the rule may still fire on different nodes
67-
(including the result's children). To opt into iterative behaviour — when a
68-
rule's output is intentionally re-matched by the same rule — call
69-
`.repeated()` on the constructed `Rule`:
70-
71-
```rust
72-
let r = yeast::rule!((foo ...) => (foo ...)).repeated();
73-
```
74-
75-
Without `.repeated()`, a rule whose output happens to match its own query
76-
simply fires once and stops. With `.repeated()`, the rule is allowed to
77-
re-match indefinitely; the runner still enforces a global rewrite-depth
78-
limit (currently 100) as a safety net against accidental cycles.
64+
A rule's output is final for the current phase: it is not matched or traversed
65+
again. This allows rule queries to describe the input schema while transforms
66+
build nodes from a different output schema. Use another named phase when the
67+
output of one exhaustive translation must become the input to another.
7968

8069
## Query language
8170

@@ -429,10 +418,9 @@ rule!(
429418

430419
### Raw captures (`@@name`)
431420

432-
The default `@name` capture marker is *auto-translated*: in OneShot
433-
phases the macro recursively translates the captured node before
434-
binding it, so `{name}` in the output template splices a node that
435-
already conforms to the output schema.
421+
The default `@name` capture marker is *auto-translated*: the macro recursively
422+
translates the captured node before binding it, so `{name}` in the output
423+
template splices a node that already conforms to the output schema.
436424

437425
For rules that need the raw (input-schema) capture — typically to read
438426
its source text or to translate it explicitly with mutable context
@@ -456,9 +444,7 @@ yeast::rule!(
456444
);
457445
```
458446

459-
Mix `@` and `@@` freely in the same rule. In a Repeating phase both
460-
markers are equivalent (auto-translation is a no-op for repeating
461-
rules).
447+
Mix `@` and `@@` freely in the same rule.
462448

463449
## The `rule!` macro
464450

@@ -574,18 +560,17 @@ Prefer the simplest form that fits:
574560

575561
## Integration with the extractor
576562

577-
A YEAST desugaring pass is configured with a [`DesugaringConfig`], which
578-
carries one or more named [`Phase`]s of rules and an optional output
579-
node-types schema (in YAML format). Each phase is a complete traversal
580-
that runs to completion before the next phase starts; only the current
581-
phase's rules are considered during that traversal. Attach the config to
582-
a language spec
583-
to enable rewriting:
563+
A YEAST translation pass is configured with a [`DesugaringConfig`], which
564+
carries one or more named [`Phase`]s of exhaustive rules and an optional
565+
output node-types schema (in YAML format). Each phase translates the previous
566+
phase's root before the next phase starts; only the current phase's rules are
567+
considered during that translation. Attach the config to a language spec to
568+
enable rewriting:
584569

585570
```rust
586571
let desugar = yeast::DesugaringConfig::new()
587-
.add_phase("cleanup", yeast::PhaseKind::Repeating, cleanup_rules())
588-
.add_phase("translate", yeast::PhaseKind::OneShot, translate_rules())
572+
.add_phase("normalize", normalization_rules())
573+
.add_phase("translate", translation_rules())
589574
.with_output_node_types_yaml(include_str!("output-node-types.yml"));
590575

591576
let lang = simple::LanguageSpec {
@@ -600,14 +585,10 @@ let lang = simple::LanguageSpec {
600585
A single-phase config is just `.add_phase(...)` called once. Phase names
601586
appear in error messages so you can tell which phase failed.
602587

603-
There are two kinds of phases:
604-
- **Repeating**:
605-
Each node is re-processed until none of the rules in the phase matches.
606-
When a node no longer matches any rules, its children are recursively processed. In practice this is used to desugar or simplify an AST, while staying mostly within the same schema.
607-
- **One-shot**:
608-
Each node is processed by the first matching rule, and the engine panics if no rule matches.
609-
Rules are then recursively applied to every captured node.
610-
In practice this is used when translating from one AST schema to another, where an exhaustive match is required.
588+
Every phase uses one-shot translation: each visited input node is processed by
589+
the first matching rule, captured nodes are recursively translated, and the
590+
phase errors if no rule matches. Output nodes are not reprocessed in the same
591+
phase.
611592

612593
The same YAML node-types is used for both the runtime yeast `Schema` (so
613594
rules can refer to output-only kinds and fields) and TRAP validation (it
@@ -645,8 +626,7 @@ let translation_rules: Vec<yeast::Rule> = yeast::rules! {
645626
Each comma-separated item in the bracketed list may be:
646627

647628
- A **bare rule body** `(query) => (template)` — no `rule!(...)` wrapper.
648-
- An explicit `rule!(...)` invocation, with optional postfix calls such
649-
as `rule!(...).repeated()`.
629+
- An explicit `rule!(...)` invocation.
650630
- Any other expression returning a `Rule` (helper functions, etc.).
651631

652632
Schema paths are resolved relative to the consuming crate's

‎shared/yeast/src/build.rs‎

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -253,10 +253,8 @@ impl<'a, C> BuildCtx<'a, C> {
253253

254254
impl<C: Clone> BuildCtx<'_, C> {
255255
/// Recursively translate every id in the given iterable via the
256-
/// framework's rule machinery. In a OneShot phase, applies OneShot
257-
/// rules to each id and returns the accumulated resulting node ids
258-
/// in order. In a Repeating phase, errors (translation is not
259-
/// meaningful when input and output share a schema).
256+
/// framework's rule machinery and return the accumulated resulting node
257+
/// ids in order.
260258
///
261259
/// The single-`Id` case works too, because `Id: IntoIterator<Item
262260
/// = Id>` is a singleton iterator — so `ctx.translate(some_id)?`

0 commit comments

Comments
 (0)