From e05355a1d8293efe5943e220484eba6e792eef48 Mon Sep 17 00:00:00 2001 From: Taus Date: Wed, 23 Sep 2026 14:35:21 +0000 Subject: [PATCH] yeast: Remove support for fresh literals We don't use these, and have no immediate plans to do so either, so in the interest of cleaning things up, I'm getting rid of this now. We can always add them back later if necessary. --- shared/yeast-macros/src/lib.rs | 1 - shared/yeast-macros/src/parse.rs | 18 ++-------- shared/yeast/doc/yeast.md | 60 ++------------------------------ shared/yeast/src/build.rs | 28 +++------------ shared/yeast/src/lib.rs | 59 +++++++++---------------------- shared/yeast/src/tree_builder.rs | 43 ----------------------- shared/yeast/tests/test.rs | 40 ++++++++++----------- 7 files changed, 43 insertions(+), 206 deletions(-) delete mode 100644 shared/yeast/src/tree_builder.rs diff --git a/shared/yeast-macros/src/lib.rs b/shared/yeast-macros/src/lib.rs index 6793c48857c3..568273f9b411 100644 --- a/shared/yeast-macros/src/lib.rs +++ b/shared/yeast-macros/src/lib.rs @@ -40,7 +40,6 @@ pub fn query(input: TokenStream) -> TokenStream { /// ```text /// (kind "literal") - leaf with static content /// (kind #{expr}) - leaf with computed content (expr.to_string()) -/// (kind $fresh) - leaf with auto-generated unique name /// {expr} - embed a Rust expression, dispatched via /// the `IntoFieldIds` trait: `Id` pushes a /// single id; iterables (`Vec`, diff --git a/shared/yeast-macros/src/parse.rs b/shared/yeast-macros/src/parse.rs index 5ab3f80fa162..e0fd63fc4d68 100644 --- a/shared/yeast-macros/src/parse.rs +++ b/shared/yeast-macros/src/parse.rs @@ -422,7 +422,7 @@ fn parse_direct_node( } /// Parse the inside of a parenthesized node: `kind fields... children...` -/// or `kind "literal"` or `kind $fresh`. +/// or `kind "literal"`. fn parse_direct_node_inner( tokens: &mut Tokens, ctx: &Ident, @@ -474,14 +474,6 @@ fn parse_direct_node_inner( }); } - // Check for (kind $fresh) - if peek_is_dollar(tokens) { - tokens.next(); - let name = expect_ident(tokens, "expected fresh variable name after $")?; - let name_str = name.to_string(); - return Ok(quote! { #ctx.fresh(#kind_str, #name_str) }); - } - // Parse named fields let mut stmts = Vec::new(); let mut field_args = Vec::new(); @@ -973,7 +965,7 @@ pub fn parse_rule_top(input: TokenStream) -> Result { let #ctx_ident = __user_ctx; Ok(#guard) }), - Box::new(|__ast: &mut yeast::Ast, mut __captures: yeast::captures::Captures, __fresh: &yeast::tree_builder::FreshScope, __source_range: Option, __user_ctx: &mut _, __translator: yeast::TranslatorHandle<'_, _>| { + Box::new(|__ast: &mut yeast::Ast, mut __captures: yeast::captures::Captures, __source_range: Option, __user_ctx: &mut _, __translator: yeast::TranslatorHandle<'_, _>| { // Auto-translation prefix: recursively translate every // captured node before invoking the user's transform body, // except for `@@name` captures listed in `__skip` which the @@ -985,7 +977,7 @@ pub fn parse_rule_top(input: TokenStream) -> Result { __translator.auto_translate_captures(&mut __captures, __ast, __user_ctx, __skip)?; #(#raw_bindings)* #(#translated_bindings)* - let mut #ctx_ident = yeast::build::BuildCtx::with_translator(__ast, &__captures, __fresh, __source_range, __user_ctx, __translator); + let mut #ctx_ident = yeast::build::BuildCtx::with_translator(__ast, &__captures, __source_range, __user_ctx, __translator); let __result: Vec = { #transform_body }; let __result = #ctx_ident.finish_rule(__result); Ok(__result) @@ -1048,10 +1040,6 @@ fn peek_is_literal(tokens: &mut Tokens) -> bool { matches!(tokens.peek(), Some(TokenTree::Literal(_))) } -fn peek_is_dollar(tokens: &mut Tokens) -> bool { - matches!(tokens.peek(), Some(TokenTree::Punct(p)) if p.as_char() == '$') -} - fn peek_is_hash(tokens: &mut Tokens) -> bool { matches!(tokens.peek(), Some(TokenTree::Punct(p)) if p.as_char() == '#') } diff --git a/shared/yeast/doc/yeast.md b/shared/yeast/doc/yeast.md index 0ab98a50ca31..aabc206ba131 100644 --- a/shared/yeast/doc/yeast.md +++ b/shared/yeast/doc/yeast.md @@ -184,8 +184,8 @@ yeast::rule!( ); // Standalone — explicit context -let fresh = yeast::tree_builder::FreshScope::new(); -let mut ctx = BuildCtx::new(ast, &captures, &fresh); +let mut user_ctx = (); +let mut ctx = BuildCtx::new(ast, &captures, &mut user_ctx); let id = yeast::tree!(ctx, (assignment left: {ctx.capture("lhs")} @@ -374,25 +374,6 @@ Outside a `?`, interpolating an `Option` with `#{expr}` remains a compile error. That is deliberate: it keeps the choice between "leave the field unset" and "unwrap it" explicit at every interpolation. -### Fresh identifiers - -`(kind $name)` creates a leaf node with an auto-generated unique name. All -occurrences of the same `$name` within one `BuildCtx` share the same value: - -```rust -(block - parameters: (block_parameters - (identifier $tmp) // generates e.g. "$tmp-0" - ) - body: (block_body - (assignment - left: {pat} - right: (identifier $tmp) // same "$tmp-0" value - ) - ) -) -``` - ### Embedded Rust expressions `{expr}` embeds a Rust expression whose value is appended to the @@ -479,43 +460,6 @@ Mix `@` and `@@` freely in the same rule. In a Repeating phase both markers are equivalent (auto-translation is a no-op for repeating rules). -## Complete example: for-loop desugaring - -This rule rewrites Ruby's `for pat in val do body end` into -`val.each { |tmp| pat = tmp; body }`: - -```rust -let for_rule = yeast::rule!( - (for - pattern: (_) @pat - value: (in (_) @val) - body: (do (_)* @body) - ) - => - (call - receiver: {val} - method: (identifier "each") - block: (block - parameters: (block_parameters - (identifier $tmp) - ) - body: (block_body - (assignment - left: {pat} - right: (identifier $tmp) - ) - {..body} - ) - ) - ) -); -``` - -Captures from the query (`@pat`, `@val`, `@body`) become Rust variables -automatically: single captures bind as `Id`, repeated captures (after -`*` or `+`) as `Vec`, and optional captures (after `?`) as -`Option`. - ## The `rule!` macro `rule!` combines a query and a transform into a single declaration. diff --git a/shared/yeast/src/build.rs b/shared/yeast/src/build.rs index ee8d0803a814..9b895eb837e9 100644 --- a/shared/yeast/src/build.rs +++ b/shared/yeast/src/build.rs @@ -1,15 +1,13 @@ use std::collections::{BTreeMap, BTreeSet}; use crate::captures::Captures; -use crate::tree_builder::FreshScope; use crate::{Ast, FieldId, Id, KindId, NodeContent, Range, TranslatorHandle}; /// Context for building new AST nodes during a transformation. /// /// Used by the `tree!` and `trees!` macros. Holds a mutable reference to the -/// AST, a reference to the captures from a query match, a `FreshScope` for -/// generating unique identifiers, and a mutable reference to a user-defined -/// context of type `C`. +/// AST, a reference to the captures from a query match, and a mutable reference +/// to a user-defined context of type `C`. /// /// The user context `C` is shared across rules via the framework's driver: /// outer rules can write to it before recursive translation, and inner rules @@ -32,7 +30,6 @@ use crate::{Ast, FieldId, Id, KindId, NodeContent, Range, TranslatorHandle}; pub struct BuildCtx<'a, C: 'a = ()> { pub ast: &'a mut Ast, pub captures: &'a Captures, - pub fresh: &'a FreshScope, /// Source range of the node matched by the current rule. /// /// The `rule!` macro applies this range to locally-created result roots @@ -54,16 +51,10 @@ pub struct BuildCtx<'a, C: 'a = ()> { } impl<'a, C> BuildCtx<'a, C> { - pub fn new( - ast: &'a mut Ast, - captures: &'a Captures, - fresh: &'a FreshScope, - user_ctx: &'a mut C, - ) -> Self { + pub fn new(ast: &'a mut Ast, captures: &'a Captures, user_ctx: &'a mut C) -> Self { Self { ast, captures, - fresh, source_range: None, user_ctx, translator: None, @@ -75,14 +66,12 @@ impl<'a, C> BuildCtx<'a, C> { pub fn with_source_range( ast: &'a mut Ast, captures: &'a Captures, - fresh: &'a FreshScope, source_range: Option, user_ctx: &'a mut C, ) -> Self { Self { ast, captures, - fresh, source_range, user_ctx, translator: None, @@ -95,7 +84,6 @@ impl<'a, C> BuildCtx<'a, C> { pub fn with_translator( ast: &'a mut Ast, captures: &'a Captures, - fresh: &'a FreshScope, source_range: Option, user_ctx: &'a mut C, translator: TranslatorHandle<'a, C>, @@ -103,7 +91,6 @@ impl<'a, C> BuildCtx<'a, C> { Self { ast, captures, - fresh, source_range, user_ctx, translator: Some(translator), @@ -262,12 +249,6 @@ impl<'a, C> BuildCtx<'a, C> { let source_range = self.source_range_of(source).map(Range::empty_at_start); self.literal_with_source_range(kind, value, source_range) } - - /// Create a leaf node with an auto-generated unique name. - pub fn fresh(&mut self, kind: &'static str, name: &str) -> Id { - let generated = self.fresh.resolve(name); - self.create_named_token_with_range(kind, generated, None) - } } impl BuildCtx<'_, C> { @@ -302,7 +283,7 @@ impl BuildCtx<'_, C> { /// Run `f` with a temporary child [`BuildCtx`] whose `user_ctx` is /// a fresh clone of the current one, sharing everything else - /// (`ast`, `captures`, `fresh`, source ranges, `translator`) by re-borrow. + /// (`ast`, `captures`, source ranges, `translator`) by re-borrow. /// Nodes constructed through the child remain part of the current rule /// invocation. Any mutations `f` makes to the child's `user_ctx` /// are discarded when it returns — no restore needed, because the @@ -334,7 +315,6 @@ impl BuildCtx<'_, C> { let mut child = BuildCtx { ast: &mut *self.ast, captures: self.captures, - fresh: self.fresh, source_range: self.source_range, user_ctx: &mut child_user_ctx, translator: self.translator, diff --git a/shared/yeast/src/lib.rs b/shared/yeast/src/lib.rs index 45f57fb70c94..fd7cbccef831 100644 --- a/shared/yeast/src/lib.rs +++ b/shared/yeast/src/lib.rs @@ -12,7 +12,6 @@ pub mod node_types_yaml; pub mod query; mod range; pub mod schema; -pub mod tree_builder; mod visitor; pub use range::{Point, Range}; @@ -1006,7 +1005,6 @@ enum TranslatorImpl<'a, C> { /// OneShot phase translator: recursively applies OneShot rules. OneShot { index: &'a RuleIndex<'a, C>, - fresh: &'a tree_builder::FreshScope, rewrite_depth: usize, /// The id of the node the current rule is matching. Used by /// [`auto_translate_captures`] to avoid infinite recursion when a @@ -1038,10 +1036,9 @@ impl<'a, C: Clone> TranslatorHandle<'a, C> { match &self.inner { TranslatorImpl::OneShot { index, - fresh, rewrite_depth, .. - } => apply_one_shot_rules_inner(index, ast, user_ctx, id, fresh, rewrite_depth + 1), + } => apply_one_shot_rules_inner(index, ast, user_ctx, id, rewrite_depth + 1), TranslatorImpl::Repeating => { Err("translate() is not available in a Repeating phase".into()) } @@ -1085,11 +1082,11 @@ impl<'a, C: Clone> TranslatorHandle<'a, C> { /// The transform function for a rule. /// -/// Takes the AST, the (raw, untranslated) captured variables, a fresh-name -/// scope, the source range of the matched node, a mutable reference to the -/// user context of type `C`, and a [`TranslatorHandle`] for recursively -/// translating nodes. Returns the IDs of the replacement nodes, or an -/// error message if the transform could not be completed. +/// Takes the AST, the (raw, untranslated) captured variables, the source range +/// of the matched node, a mutable reference to the user context of type `C`, +/// and a [`TranslatorHandle`] for recursively translating nodes. Returns the +/// IDs of the replacement nodes, or an error message if the transform could +/// not be completed. /// /// Transforms produced by [`Rule::new`] receive **raw** captures and must /// translate them themselves (via the handle). Transforms produced by the @@ -1099,7 +1096,6 @@ pub type Transform = Box< dyn Fn( &mut Ast, Captures, - &tree_builder::FreshScope, Option, &mut C, TranslatorHandle<'_, C>, @@ -1197,14 +1193,11 @@ impl Rule { ast: &mut Ast, captures: Captures, node: Id, - fresh: &tree_builder::FreshScope, user_ctx: &mut C, translator: TranslatorHandle<'_, C>, ) -> Result, String> { - fresh.next_scope(); - let source_range = - ast.source_range_ignoring_fields(node, &self.ignored_location_fields); - (self.transform)(ast, captures, fresh, source_range, user_ctx, translator) + let source_range = ast.source_range_ignoring_fields(node, &self.ignored_location_fields); + (self.transform)(ast, captures, source_range, user_ctx, translator) } } @@ -1245,10 +1238,9 @@ fn apply_repeating_rules( ast: &mut Ast, user_ctx: &mut C, id: Id, - fresh: &tree_builder::FreshScope, ) -> Result, String> { let index = RuleIndex::new(rules); - apply_repeating_rules_inner(&index, ast, user_ctx, id, fresh, 0, None) + apply_repeating_rules_inner(&index, ast, user_ctx, id, 0, None) } fn apply_repeating_rules_inner( @@ -1256,7 +1248,6 @@ fn apply_repeating_rules_inner( ast: &mut Ast, user_ctx: &mut C, id: Id, - fresh: &tree_builder::FreshScope, rewrite_depth: usize, skip_rule: Option<*const Rule>, ) -> Result, String> { @@ -1293,7 +1284,7 @@ fn apply_repeating_rules_inner( let translator = TranslatorHandle { inner: TranslatorImpl::Repeating, }; - let result_nodes = rule.run_transform(ast, captures, id, fresh, &mut local, translator)?; + let result_nodes = rule.run_transform(ast, captures, id, &mut local, translator)?; // For non-repeated rules, suppress further application of *this* // rule on the result root, so a rule whose output matches its own @@ -1306,7 +1297,6 @@ fn apply_repeating_rules_inner( ast, &mut local, node, - fresh, rewrite_depth + 1, next_skip, )?); @@ -1326,15 +1316,8 @@ fn apply_repeating_rules_inner( for children in fields.values_mut() { let mut new_children: Option> = None; for (i, &child_id) in children.iter().enumerate() { - let result = apply_repeating_rules_inner( - index, - ast, - user_ctx, - child_id, - fresh, - rewrite_depth, - None, - )?; + let result = + apply_repeating_rules_inner(index, ast, user_ctx, child_id, rewrite_depth, None)?; let unchanged = result.len() == 1 && result[0] == child_id; match (&mut new_children, unchanged) { (None, true) => {} // unchanged so far, no allocation needed @@ -1368,10 +1351,9 @@ fn apply_one_shot_rules( ast: &mut Ast, user_ctx: &mut C, id: Id, - fresh: &tree_builder::FreshScope, ) -> Result, String> { let index = RuleIndex::new(rules); - apply_one_shot_rules_inner(&index, ast, user_ctx, id, fresh, 0) + apply_one_shot_rules_inner(&index, ast, user_ctx, id, 0) } fn apply_one_shot_rules_inner( @@ -1379,7 +1361,6 @@ fn apply_one_shot_rules_inner( ast: &mut Ast, user_ctx: &mut C, id: Id, - fresh: &tree_builder::FreshScope, rewrite_depth: usize, ) -> Result, String> { if rewrite_depth > MAX_REWRITE_DEPTH { @@ -1412,12 +1393,11 @@ fn apply_one_shot_rules_inner( let translator = TranslatorHandle { inner: TranslatorImpl::OneShot { index, - fresh, rewrite_depth, matched_root: id, }, }; - let result = rule.run_transform(ast, captures, id, fresh, &mut local, translator)?; + let result = rule.run_transform(ast, captures, id, &mut local, translator)?; return Ok(result); } @@ -1677,19 +1657,12 @@ impl<'a, C: Clone> Runner<'a, C> { } /// Apply each phase in turn to the AST, threading the root through. - /// A single `FreshScope` is shared across phases so that fresh - /// identifiers generated in different phases don't collide. fn run_phases(&self, ast: &mut Ast, user_ctx: &mut C) -> Result<(), String> { - let fresh = tree_builder::FreshScope::new(); let mut root = ast.get_root(); for phase in self.phases { let res = match phase.kind { - PhaseKind::Repeating => { - apply_repeating_rules(&phase.rules, ast, user_ctx, root, &fresh) - } - PhaseKind::OneShot => { - apply_one_shot_rules(&phase.rules, ast, user_ctx, root, &fresh) - } + PhaseKind::Repeating => apply_repeating_rules(&phase.rules, ast, user_ctx, root), + PhaseKind::OneShot => apply_one_shot_rules(&phase.rules, ast, user_ctx, root), } .map_err(|e| format!("Phase `{}`: {e}", phase.name))?; if res.len() != 1 { diff --git a/shared/yeast/src/tree_builder.rs b/shared/yeast/src/tree_builder.rs deleted file mode 100644 index c735c272d283..000000000000 --- a/shared/yeast/src/tree_builder.rs +++ /dev/null @@ -1,43 +0,0 @@ -use std::cell::Cell; -use std::collections::BTreeMap; - -/// Tracks fresh identifier generation during a single tree-building operation. -/// All occurrences of the same `$name` within one build share the same generated value. -pub struct FreshScope { - counter: Cell, - resolved: std::cell::RefCell>, -} - -impl Default for FreshScope { - fn default() -> Self { - Self::new() - } -} - -impl FreshScope { - pub fn new() -> Self { - Self { - counter: Cell::new(0), - resolved: std::cell::RefCell::new(BTreeMap::new()), - } - } - - pub fn resolve(&self, name: &str) -> String { - self.resolved - .borrow_mut() - .entry(name.to_string()) - .or_insert_with(|| { - let id = self.counter.get(); - self.counter.set(id + 1); - format!("${name}-{id}") - }) - .clone() - } - - /// Clear resolved names but keep the counter. Called between rule - /// applications so that `$tmp` in different rules gets different values - /// while the counter increases monotonically. - pub fn next_scope(&self) { - self.resolved.borrow_mut().clear(); - } -} diff --git a/shared/yeast/tests/test.rs b/shared/yeast/tests/test.rs index b0c707ea8b26..f17e1c11f9be 100644 --- a/shared/yeast/tests/test.rs +++ b/shared/yeast/tests/test.rs @@ -650,9 +650,8 @@ fn test_tree_builder() { query.do_match(&ast, ast.get_root(), &mut captures).unwrap(); // Swap left and right - let fresh = yeast::tree_builder::FreshScope::new(); let mut user_ctx = (); - let mut ctx = yeast::build::BuildCtx::new(&mut ast, &captures, &fresh, &mut user_ctx); + let mut ctx = yeast::build::BuildCtx::new(&mut ast, &captures, &mut user_ctx); let new_id = yeast::tree!(ctx, (program child: (assignment @@ -679,9 +678,8 @@ fn test_tree_builder() { /// content. fn build_optional_right(ast: &mut Ast, value: Option) -> (yeast::Id, yeast::Id) { let captures = yeast::captures::Captures::new(); - let fresh = yeast::tree_builder::FreshScope::new(); let mut user_ctx = (); - let mut ctx = yeast::build::BuildCtx::new(ast, &captures, &fresh, &mut user_ctx); + let mut ctx = yeast::build::BuildCtx::new(ast, &captures, &mut user_ctx); let left = yeast::tree!(ctx, (identifier "x")); let root = yeast::tree!(ctx, (assignment @@ -734,9 +732,8 @@ fn test_optional_field_propagates_through_nested_nodes() { let mut ast = runner.run("x = 1").unwrap(); let captures = yeast::captures::Captures::new(); - let fresh = yeast::tree_builder::FreshScope::new(); let mut user_ctx = (); - let mut ctx = yeast::build::BuildCtx::new(&mut ast, &captures, &fresh, &mut user_ctx); + let mut ctx = yeast::build::BuildCtx::new(&mut ast, &captures, &mut user_ctx); // The absent value sits two levels below the `?`, so the whole // `left_assignment_list` subtree is abandoned along with it. @@ -764,9 +761,8 @@ fn test_innermost_optional_field_catches_first() { let mut ast = runner.run("x = 1").unwrap(); let captures = yeast::captures::Captures::new(); - let fresh = yeast::tree_builder::FreshScope::new(); let mut user_ctx = (); - let mut ctx = yeast::build::BuildCtx::new(&mut ast, &captures, &fresh, &mut user_ctx); + let mut ctx = yeast::build::BuildCtx::new(&mut ast, &captures, &mut user_ctx); // The inner `?` catches, so only `child` is dropped; `left` survives. let absent: Option = None; @@ -800,7 +796,7 @@ fn ruby_rules() -> Vec { ) => (assignment - left: (identifier $tmp) + left: (identifier "assignment_tmp") right: {right} ) {left.iter().enumerate().map(|(i, &lhs)| @@ -808,7 +804,7 @@ fn ruby_rules() -> Vec { (assignment left: {lhs} right: (element_reference - object: (identifier $tmp) + object: (identifier "assignment_tmp") index: (integer #{i}) ) ) @@ -828,12 +824,12 @@ fn ruby_rules() -> Vec { method: (identifier "each") block: (block parameters: (block_parameters - parameter: (identifier $tmp) + parameter: (identifier "loop_tmp") ) body: (block_body stmt: (assignment left: {pat} - right: (identifier $tmp) + right: (identifier "loop_tmp") ) stmt: {body} ) @@ -852,19 +848,19 @@ fn test_desugar_multiple_assignment() { r#" program assignment - left: identifier "$tmp-0" + left: identifier "assignment_tmp" right: identifier "e" assignment left: identifier "x" right: element_reference - object: identifier "$tmp-0" + object: identifier "assignment_tmp" index: integer "0" assignment left: identifier "y" right: element_reference - object: identifier "$tmp-0" + object: identifier "assignment_tmp" index: integer "1" "#, ); @@ -885,11 +881,11 @@ fn test_desugar_for_loop() { stmt: assignment left: identifier "x" - right: identifier "$tmp-0" + right: identifier "loop_tmp" identifier "y" parameters: block_parameters - parameter: identifier "$tmp-0" + parameter: identifier "loop_tmp" method: identifier "each" receiver: identifier "list" "#, @@ -1532,24 +1528,24 @@ fn test_desugar_for_with_multiple_assignment() { block_body stmt: assignment - left: identifier "$tmp-1" - right: identifier "$tmp-0" + left: identifier "assignment_tmp" + right: identifier "loop_tmp" assignment left: identifier "a" right: element_reference - object: identifier "$tmp-1" + object: identifier "assignment_tmp" index: integer "0" assignment left: identifier "b" right: element_reference - object: identifier "$tmp-1" + object: identifier "assignment_tmp" index: integer "1" identifier "x" parameters: block_parameters - parameter: identifier "$tmp-0" + parameter: identifier "loop_tmp" method: identifier "each" receiver: identifier "list" "#,