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,25 @@ 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.
36+ /// Optional source range explicitly inherited by every synthetic node built
37+ /// through this context.
3738 pub source_range : Option < Range > ,
39+ /// Source range of the node matched by the current rule.
40+ ///
41+ /// The `rule!` macro applies this range to locally-created result roots
42+ /// after the transform completes. Nested synthetic nodes derive their
43+ /// ranges from their children unless `source_range` is explicitly set.
44+ pub matched_source_range : Option < Range > ,
3845 /// User-supplied context, accessible directly via `ctx.field` (via Deref).
3946 pub user_ctx : & ' a mut C ,
4047 /// Optional translator handle, populated when the context is built by
4148 /// the framework's rule driver. None when the context is built by hand.
4249 pub ( crate ) translator : Option < TranslatorHandle < ' a , C > > ,
50+ /// Nodes built directly through this context without an explicit source
51+ /// range in either their content or constructor argument. Recursive
52+ /// translations use their own context and therefore do not contribute to
53+ /// this list.
54+ created_nodes : BTreeSet < Id > ,
4355}
4456
4557impl < ' a , C > BuildCtx < ' a , C > {
@@ -54,8 +66,10 @@ impl<'a, C> BuildCtx<'a, C> {
5466 captures,
5567 fresh,
5668 source_range : None ,
69+ matched_source_range : None ,
5770 user_ctx,
5871 translator : None ,
72+ created_nodes : BTreeSet :: new ( ) ,
5973 }
6074 }
6175
@@ -71,8 +85,10 @@ impl<'a, C> BuildCtx<'a, C> {
7185 captures,
7286 fresh,
7387 source_range,
88+ matched_source_range : None ,
7489 user_ctx,
7590 translator : None ,
91+ created_nodes : BTreeSet :: new ( ) ,
7692 }
7793 }
7894
@@ -90,10 +106,79 @@ impl<'a, C> BuildCtx<'a, C> {
90106 ast,
91107 captures,
92108 fresh,
93- source_range,
109+ source_range : None ,
110+ matched_source_range : source_range,
94111 user_ctx,
95112 translator : Some ( translator) ,
113+ created_nodes : BTreeSet :: new ( ) ,
114+ }
115+ }
116+
117+ /// Create a node and record it as constructed by this rule invocation.
118+ pub fn create_node_with_range (
119+ & mut self ,
120+ kind : KindId ,
121+ content : NodeContent ,
122+ fields : BTreeMap < FieldId , Vec < Id > > ,
123+ is_named : bool ,
124+ source_range : Option < Range > ,
125+ ) -> Id {
126+ let has_explicit_source_range =
127+ source_range. is_some ( ) || matches ! ( & content, NodeContent :: Range ( _) ) ;
128+ let id = self
129+ . ast
130+ . create_node_with_range ( kind, content, fields, is_named, source_range) ;
131+ if !has_explicit_source_range {
132+ self . created_nodes . insert ( id) ;
133+ }
134+ id
135+ }
136+
137+ /// Create a node using this context's explicit default source range.
138+ pub fn create_node (
139+ & mut self ,
140+ kind : KindId ,
141+ content : NodeContent ,
142+ fields : BTreeMap < FieldId , Vec < Id > > ,
143+ is_named : bool ,
144+ ) -> Id {
145+ self . create_node_with_range ( kind, content, fields, is_named, self . source_range )
146+ }
147+
148+ /// Create a named token and record it as constructed by this rule invocation.
149+ pub fn create_named_token_with_range (
150+ & mut self ,
151+ kind : & ' static str ,
152+ content : String ,
153+ source_range : Option < Range > ,
154+ ) -> Id {
155+ let has_explicit_source_range = source_range. is_some ( ) ;
156+ let id = self
157+ . ast
158+ . create_named_token_with_range ( kind, content, source_range) ;
159+ if !has_explicit_source_range {
160+ self . created_nodes . insert ( id) ;
161+ }
162+ id
163+ }
164+
165+ /// Create a named token using this context's explicit default source range.
166+ pub fn create_named_token ( & mut self , kind : & ' static str , content : String ) -> Id {
167+ self . create_named_token_with_range ( kind, content, self . source_range )
168+ }
169+
170+ /// Finish the current rule invocation by applying the matched source range
171+ /// to locally-created result roots.
172+ #[ doc( hidden) ]
173+ pub fn finish_rule ( self , results : Vec < Id > ) -> Vec < Id > {
174+ if let Some ( source_range) = self . matched_source_range {
175+ for & id in & results {
176+ if self . created_nodes . contains ( & id) {
177+ self . ast . extend_source_range ( id, source_range) ;
178+ }
179+ }
96180 }
181+ results
97182 }
98183
99184 /// Look up a capture variable, returning its node Id.
@@ -133,30 +218,29 @@ impl<'a, C> BuildCtx<'a, C> {
133218 . unwrap_or_else ( || panic ! ( "build: field '{name}' not found" ) ) ;
134219 field_map. entry ( field_id) . or_default ( ) . extend ( ids) ;
135220 }
136- self . ast . create_node_with_range (
221+ self . create_node (
137222 kind_id,
138223 NodeContent :: DynamicString ( String :: new ( ) ) ,
139224 field_map,
140225 true ,
141- self . source_range ,
142226 )
143227 }
144228
145229 /// Create a leaf node with a fixed string content.
146230 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 )
231+ self . create_named_token ( kind, value. to_string ( ) )
149232 }
150233
151234 /// 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.
235+ /// If `source_range` is `None`, falls back to this context's explicit
236+ /// default range.
153237 pub fn literal_with_source_range (
154238 & mut self ,
155239 kind : & ' static str ,
156240 value : & str ,
157241 source_range : Option < Range > ,
158242 ) -> Id {
159- self . ast . create_named_token_with_range (
243+ self . create_named_token_with_range (
160244 kind,
161245 value. to_string ( ) ,
162246 source_range. or ( self . source_range ) ,
@@ -166,8 +250,7 @@ impl<'a, C> BuildCtx<'a, C> {
166250 /// Create a leaf node with an auto-generated unique name.
167251 pub fn fresh ( & mut self , kind : & ' static str , name : & str ) -> Id {
168252 let generated = self . fresh . resolve ( name) ;
169- self . ast
170- . create_named_token_with_range ( kind, generated, self . source_range )
253+ self . create_named_token ( kind, generated)
171254 }
172255}
173256
@@ -203,8 +286,9 @@ impl<C: Clone> BuildCtx<'_, C> {
203286
204287 /// Run `f` with a temporary child [`BuildCtx`] whose `user_ctx` is
205288 /// 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`
289+ /// (`ast`, `captures`, `fresh`, source ranges, `translator`) by re-borrow.
290+ /// Nodes constructed through the child remain part of the current rule
291+ /// invocation. Any mutations `f` makes to the child's `user_ctx`
208292 /// are discarded when it returns — no restore needed, because the
209293 /// mutations only ever happened on a local clone.
210294 ///
@@ -236,10 +320,16 @@ impl<C: Clone> BuildCtx<'_, C> {
236320 captures : self . captures ,
237321 fresh : self . fresh ,
238322 source_range : self . source_range ,
323+ matched_source_range : self . matched_source_range ,
239324 user_ctx : & mut child_user_ctx,
240325 translator : self . translator ,
326+ created_nodes : BTreeSet :: new ( ) ,
241327 } ;
242- f ( & mut child)
328+ let result = f ( & mut child) ;
329+ let created_nodes = std:: mem:: take ( & mut child. created_nodes ) ;
330+ drop ( child) ;
331+ self . created_nodes . extend ( created_nodes) ;
332+ result
243333 // child_user_ctx dropped; the outer `self` is unaffected.
244334 }
245335}
0 commit comments