1- use std:: collections:: BTreeMap ;
1+ use std:: collections:: { BTreeMap , BTreeSet } ;
22
33use crate :: captures:: Captures ;
44use crate :: tree_builder:: FreshScope ;
5- use crate :: { Ast , FieldId , Id , NodeContent , Range , TranslatorHandle } ;
5+ use crate :: { Ast , FieldId , Id , KindId , NodeContent , Range , TranslatorHandle } ;
66
77/// Context for building new AST nodes during a transformation.
88///
@@ -33,13 +33,22 @@ pub struct BuildCtx<'a, C: 'a = ()> {
3333 pub ast : & ' a mut Ast ,
3434 pub captures : & ' a Captures ,
3535 pub fresh : & ' a FreshScope ,
36- /// Source range of the matched node, inherited by synthetic nodes.
37- pub source_range : Option < Range > ,
36+ /// Source range of the node matched by the current rule.
37+ ///
38+ /// The `rule!` macro applies this range to locally-created result roots
39+ /// after the transform completes. Nested synthetic nodes derive their
40+ /// ranges from their children.
41+ pub matched_source_range : Option < Range > ,
3842 /// User-supplied context, accessible directly via `ctx.field` (via Deref).
3943 pub user_ctx : & ' a mut C ,
4044 /// Optional translator handle, populated when the context is built by
4145 /// the framework's rule driver. None when the context is built by hand.
4246 pub ( crate ) translator : Option < TranslatorHandle < ' a , C > > ,
47+ /// Nodes built directly through this context without an explicit source
48+ /// range in either their content or constructor argument. Recursive
49+ /// translations use their own context and therefore do not contribute to
50+ /// this list.
51+ created_nodes : BTreeSet < Id > ,
4352}
4453
4554impl < ' a , C > BuildCtx < ' a , C > {
@@ -53,26 +62,10 @@ impl<'a, C> BuildCtx<'a, C> {
5362 ast,
5463 captures,
5564 fresh,
56- source_range : None ,
57- user_ctx,
58- translator : None ,
59- }
60- }
61-
62- pub fn with_source_range (
63- ast : & ' a mut Ast ,
64- captures : & ' a Captures ,
65- fresh : & ' a FreshScope ,
66- source_range : Option < Range > ,
67- user_ctx : & ' a mut C ,
68- ) -> Self {
69- Self {
70- ast,
71- captures,
72- fresh,
73- source_range,
65+ matched_source_range : None ,
7466 user_ctx,
7567 translator : None ,
68+ created_nodes : BTreeSet :: new ( ) ,
7669 }
7770 }
7871
@@ -90,12 +83,80 @@ impl<'a, C> BuildCtx<'a, C> {
9083 ast,
9184 captures,
9285 fresh,
93- source_range,
86+ matched_source_range : source_range,
9487 user_ctx,
9588 translator : Some ( translator) ,
89+ created_nodes : BTreeSet :: new ( ) ,
9690 }
9791 }
9892
93+ /// Create a node and record it as constructed by this rule invocation.
94+ pub fn create_node_with_range (
95+ & mut self ,
96+ kind : KindId ,
97+ content : NodeContent ,
98+ fields : BTreeMap < FieldId , Vec < Id > > ,
99+ is_named : bool ,
100+ source_range : Option < Range > ,
101+ ) -> Id {
102+ let has_explicit_source_range =
103+ source_range. is_some ( ) || matches ! ( & content, NodeContent :: Range ( _) ) ;
104+ let id = self
105+ . ast
106+ . create_node_with_range ( kind, content, fields, is_named, source_range) ;
107+ if !has_explicit_source_range {
108+ self . created_nodes . insert ( id) ;
109+ }
110+ id
111+ }
112+
113+ /// Create a node using this context's explicit default source range.
114+ pub fn create_node (
115+ & mut self ,
116+ kind : KindId ,
117+ content : NodeContent ,
118+ fields : BTreeMap < FieldId , Vec < Id > > ,
119+ is_named : bool ,
120+ ) -> Id {
121+ self . create_node_with_range ( kind, content, fields, is_named, None )
122+ }
123+
124+ /// Create a named token and record it as constructed by this rule invocation.
125+ pub fn create_named_token_with_range (
126+ & mut self ,
127+ kind : & ' static str ,
128+ content : String ,
129+ source_range : Option < Range > ,
130+ ) -> Id {
131+ let has_explicit_source_range = source_range. is_some ( ) ;
132+ let id = self
133+ . ast
134+ . create_named_token_with_range ( kind, content, source_range) ;
135+ if !has_explicit_source_range {
136+ self . created_nodes . insert ( id) ;
137+ }
138+ id
139+ }
140+
141+ /// Create a named token using this context's explicit default source range.
142+ pub fn create_named_token ( & mut self , kind : & ' static str , content : String ) -> Id {
143+ self . create_named_token_with_range ( kind, content, None )
144+ }
145+
146+ /// Finish the current rule invocation by applying the matched source range
147+ /// to locally-created result roots.
148+ #[ doc( hidden) ]
149+ pub fn finish_rule ( self , results : Vec < Id > ) -> Vec < Id > {
150+ if let Some ( source_range) = self . matched_source_range {
151+ for & id in & results {
152+ if self . created_nodes . contains ( & id) {
153+ self . ast . extend_source_range ( id, source_range) ;
154+ }
155+ }
156+ }
157+ results
158+ }
159+
99160 /// Look up a capture variable, returning its node Id.
100161 pub fn capture ( & self , name : & str ) -> Id {
101162 self . captures
@@ -133,41 +194,33 @@ impl<'a, C> BuildCtx<'a, C> {
133194 . unwrap_or_else ( || panic ! ( "build: field '{name}' not found" ) ) ;
134195 field_map. entry ( field_id) . or_default ( ) . extend ( ids) ;
135196 }
136- self . ast . create_node_with_range (
197+ self . create_node (
137198 kind_id,
138199 NodeContent :: DynamicString ( String :: new ( ) ) ,
139200 field_map,
140201 true ,
141- self . source_range ,
142202 )
143203 }
144204
145205 /// Create a leaf node with a fixed string content.
146206 pub fn literal ( & mut self , kind : & ' static str , value : & str ) -> Id {
147- self . ast
148- . create_named_token_with_range ( kind, value. to_string ( ) , self . source_range )
207+ self . create_named_token ( kind, value. to_string ( ) )
149208 }
150209
151- /// Create a leaf node with fixed content and an optional preferred source range.
152- /// If `source_range` is `None`, falls back to this context's inherited range.
210+ /// Create a leaf node with fixed content and an optional source range.
153211 pub fn literal_with_source_range (
154212 & mut self ,
155213 kind : & ' static str ,
156214 value : & str ,
157215 source_range : Option < Range > ,
158216 ) -> Id {
159- self . ast . create_named_token_with_range (
160- kind,
161- value. to_string ( ) ,
162- source_range. or ( self . source_range ) ,
163- )
217+ self . create_named_token_with_range ( kind, value. to_string ( ) , source_range)
164218 }
165219
166220 /// Create a leaf node with an auto-generated unique name.
167221 pub fn fresh ( & mut self , kind : & ' static str , name : & str ) -> Id {
168222 let generated = self . fresh . resolve ( name) ;
169- self . ast
170- . create_named_token_with_range ( kind, generated, self . source_range )
223+ self . create_named_token ( kind, generated)
171224 }
172225}
173226
@@ -203,8 +256,9 @@ impl<C: Clone> BuildCtx<'_, C> {
203256
204257 /// Run `f` with a temporary child [`BuildCtx`] whose `user_ctx` is
205258 /// a fresh clone of the current one, sharing everything else
206- /// (`ast`, `captures`, `fresh`, `source_range`, `translator`) by
207- /// re-borrow. Any mutations `f` makes to the child's `user_ctx`
259+ /// (`ast`, `captures`, `fresh`, source ranges, `translator`) by re-borrow.
260+ /// Nodes constructed through the child remain part of the current rule
261+ /// invocation. Any mutations `f` makes to the child's `user_ctx`
208262 /// are discarded when it returns — no restore needed, because the
209263 /// mutations only ever happened on a local clone.
210264 ///
@@ -235,11 +289,16 @@ impl<C: Clone> BuildCtx<'_, C> {
235289 ast : & mut * self . ast ,
236290 captures : self . captures ,
237291 fresh : self . fresh ,
238- source_range : self . source_range ,
292+ matched_source_range : self . matched_source_range ,
239293 user_ctx : & mut child_user_ctx,
240294 translator : self . translator ,
295+ created_nodes : BTreeSet :: new ( ) ,
241296 } ;
242- f ( & mut child)
297+ let result = f ( & mut child) ;
298+ let created_nodes = std:: mem:: take ( & mut child. created_nodes ) ;
299+ drop ( child) ;
300+ self . created_nodes . extend ( created_nodes) ;
301+ result
243302 // child_user_ctx dropped; the outer `self` is unaffected.
244303 }
245304}
0 commit comments