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" "#,