@@ -526,18 +526,39 @@ impl Rule {
526526 node : Id ,
527527 fresh : & tree_builder:: FreshScope ,
528528 ) -> Result < Option < Vec < Id > > , String > {
529+ match self . try_match ( ast, node) ? {
530+ Some ( captures) => Ok ( Some ( self . run_transform ( ast, captures, node, fresh) ) ) ,
531+ None => Ok ( None ) ,
532+ }
533+ }
534+
535+ /// Attempt to match this rule's query against `node`, returning the
536+ /// resulting captures on success. Does not invoke the transform.
537+ fn try_match ( & self , ast : & Ast , node : Id ) -> Result < Option < Captures > , String > {
529538 let mut captures = Captures :: new ( ) ;
530539 if self . query . do_match ( ast, node, & mut captures) ? {
531- fresh. next_scope ( ) ;
532- let source_range = ast. get_node ( node) . and_then ( |n| match n. content {
533- NodeContent :: Range ( r) => Some ( r) ,
534- _ => n. source_range ,
535- } ) ;
536- Ok ( Some ( ( self . transform ) ( ast, captures, fresh, source_range) ) )
540+ Ok ( Some ( captures) )
537541 } else {
538542 Ok ( None )
539543 }
540544 }
545+
546+ /// Run this rule's transform with the given captures, using `node`'s
547+ /// source range as the source range of the produced nodes.
548+ fn run_transform (
549+ & self ,
550+ ast : & mut Ast ,
551+ captures : Captures ,
552+ node : Id ,
553+ fresh : & tree_builder:: FreshScope ,
554+ ) -> Vec < Id > {
555+ fresh. next_scope ( ) ;
556+ let source_range = ast. get_node ( node) . and_then ( |n| match n. content {
557+ NodeContent :: Range ( r) => Some ( r) ,
558+ _ => n. source_range ,
559+ } ) ;
560+ ( self . transform ) ( ast, captures, fresh, source_range)
561+ }
541562}
542563
543564const MAX_REWRITE_DEPTH : usize = 100 ;
@@ -572,17 +593,17 @@ impl<'a> RuleIndex<'a> {
572593 }
573594}
574595
575- fn apply_rules (
596+ fn apply_repeating_rules (
576597 rules : & [ Rule ] ,
577598 ast : & mut Ast ,
578599 id : Id ,
579600 fresh : & tree_builder:: FreshScope ,
580601) -> Result < Vec < Id > , String > {
581602 let index = RuleIndex :: new ( rules) ;
582- apply_rules_inner ( & index, ast, id, fresh, 0 , None )
603+ apply_repeating_rules_inner ( & index, ast, id, fresh, 0 , None )
583604}
584605
585- fn apply_rules_inner (
606+ fn apply_repeating_rules_inner (
586607 index : & RuleIndex ,
587608 ast : & mut Ast ,
588609 id : Id ,
@@ -611,7 +632,7 @@ fn apply_rules_inner(
611632 let next_skip = if rule. repeated { None } else { Some ( rule_ptr) } ;
612633 let mut results = Vec :: new ( ) ;
613634 for node in result_node {
614- results. extend ( apply_rules_inner (
635+ results. extend ( apply_repeating_rules_inner (
615636 index,
616637 ast,
617638 node,
@@ -636,7 +657,7 @@ fn apply_rules_inner(
636657 for children in fields. values_mut ( ) {
637658 let mut new_children: Option < Vec < Id > > = None ;
638659 for ( i, & child_id) in children. iter ( ) . enumerate ( ) {
639- let result = apply_rules_inner ( index, ast, child_id, fresh, rewrite_depth, None ) ?;
660+ let result = apply_repeating_rules_inner ( index, ast, child_id, fresh, rewrite_depth, None ) ?;
640661 let unchanged = result. len ( ) == 1 && result[ 0 ] == child_id;
641662 match ( & mut new_children, unchanged) {
642663 ( None , true ) => { } // unchanged so far, no allocation needed
@@ -661,6 +682,75 @@ fn apply_rules_inner(
661682 Ok ( vec ! [ id] )
662683}
663684
685+ /// Apply rules using `OneShot` semantics: the first matching rule fires on
686+ /// each visited node, recursion proceeds only through captured nodes (not
687+ /// through the input node's children directly), and an error is returned if
688+ /// no rule matches a visited node.
689+ fn apply_one_shot_rules (
690+ rules : & [ Rule ] ,
691+ ast : & mut Ast ,
692+ id : Id ,
693+ fresh : & tree_builder:: FreshScope ,
694+ ) -> Result < Vec < Id > , String > {
695+ let index = RuleIndex :: new ( rules) ;
696+ apply_one_shot_rules_inner ( & index, ast, id, fresh, 0 )
697+ }
698+
699+ fn apply_one_shot_rules_inner (
700+ index : & RuleIndex ,
701+ ast : & mut Ast ,
702+ id : Id ,
703+ fresh : & tree_builder:: FreshScope ,
704+ rewrite_depth : usize ,
705+ ) -> Result < Vec < Id > , String > {
706+ if rewrite_depth > MAX_REWRITE_DEPTH {
707+ return Err ( format ! (
708+ "Desugaring exceeded maximum rewrite depth ({MAX_REWRITE_DEPTH}). \
709+ This likely indicates a non-terminating rule cycle."
710+ ) ) ;
711+ }
712+
713+ let node_kind = ast. get_node ( id) . map ( |n| n. kind ( ) ) . unwrap_or ( "" ) ;
714+ for rule in index. rules_for_kind ( node_kind) {
715+ if let Some ( mut captures) = rule. try_match ( ast, id) ? {
716+ // Recursively translate every captured node before invoking the
717+ // transform. The transform's output uses output-schema kinds, so
718+ // we must translate captured input-schema nodes to their
719+ // output-schema equivalents first.
720+ captures. try_map_all_captures ( |captured_id| {
721+ let result =
722+ apply_one_shot_rules_inner ( index, ast, captured_id, fresh, rewrite_depth + 1 ) ?;
723+ if result. len ( ) != 1 {
724+ return Err ( format ! (
725+ "OneShot: recursion on captured node produced {} results, expected exactly 1" ,
726+ result. len( )
727+ ) ) ;
728+ }
729+ Ok ( result[ 0 ] )
730+ } ) ?;
731+ return Ok ( rule. run_transform ( ast, captures, id, fresh) ) ;
732+ }
733+ }
734+
735+ Err ( format ! (
736+ "OneShot: no rule matched node of kind '{node_kind}'"
737+ ) )
738+ }
739+
740+ #[ derive( Clone , Copy , Debug , PartialEq , Eq ) ]
741+ pub enum PhaseKind {
742+ /// A node is re-processed until none of the rules in the phase matches,
743+ /// albeit a single rule cannot be applied twice in a row unless that rule is also marked as repeating.
744+ /// When a node no longer matches any rules, its children are recursively processed (top down).
745+ Repeating ,
746+
747+ /// A node is processed by the first matching rule, and the engine panics if no rule matches.
748+ /// Rules are then recursively applied to every captured node.
749+ /// In practice this is used when translating from one AST schema to another, where every node must be rewritten,
750+ /// and it would be a type error to match the rule patterns (based on the input schema) against the output nodes (which conform to the output schema).
751+ OneShot ,
752+ }
753+
664754/// One phase of a desugaring pass: a named bundle of rules that runs to
665755/// completion (a full traversal applying its rules) before the next phase
666756/// starts. Rules within a phase compete for matches as usual; rules in
@@ -670,13 +760,15 @@ pub struct Phase {
670760 /// Name used in error messages.
671761 pub name : String ,
672762 pub rules : Vec < Rule > ,
763+ pub kind : PhaseKind ,
673764}
674765
675766impl Phase {
676- pub fn new ( name : impl Into < String > , rules : Vec < Rule > ) -> Self {
767+ pub fn new ( name : impl Into < String > , kind : PhaseKind , rules : Vec < Rule > ) -> Self {
677768 Self {
678769 name : name. into ( ) ,
679770 rules,
771+ kind,
680772 }
681773 }
682774}
@@ -694,8 +786,8 @@ impl Phase {
694786///
695787/// ```ignore
696788/// let config = yeast::DesugaringConfig::new()
697- /// .add_phase("cleanup", cleanup_rules)
698- /// .add_phase("desugar", desugar_rules)
789+ /// .add_phase("cleanup", PhaseKind::Repeating, cleanup_rules)
790+ /// .add_phase("desugar", PhaseKind::Repeating, desugar_rules)
699791/// .with_output_node_types_yaml(yaml);
700792/// ```
701793#[ derive( Default ) ]
@@ -715,9 +807,14 @@ impl DesugaringConfig {
715807 Self :: default ( )
716808 }
717809
718- /// Append a new phase with the given name and rules.
719- pub fn add_phase ( mut self , name : impl Into < String > , rules : Vec < Rule > ) -> Self {
720- self . phases . push ( Phase :: new ( name, rules) ) ;
810+ /// Append a new phase with the given name, kind, and rules.
811+ pub fn add_phase (
812+ mut self ,
813+ name : impl Into < String > ,
814+ kind : PhaseKind ,
815+ rules : Vec < Rule > ,
816+ ) -> Self {
817+ self . phases . push ( Phase :: new ( name, kind, rules) ) ;
721818 self
722819 }
723820
@@ -806,8 +903,11 @@ impl<'a> Runner<'a> {
806903 let fresh = tree_builder:: FreshScope :: new ( ) ;
807904 let mut root = ast. get_root ( ) ;
808905 for phase in self . phases {
809- let res = apply_rules ( & phase. rules , ast, root, & fresh)
810- . map_err ( |e| format ! ( "Phase `{}`: {e}" , phase. name) ) ?;
906+ let res = match phase. kind {
907+ PhaseKind :: Repeating => apply_repeating_rules ( & phase. rules , ast, root, & fresh) ,
908+ PhaseKind :: OneShot => apply_one_shot_rules ( & phase. rules , ast, root, & fresh) ,
909+ }
910+ . map_err ( |e| format ! ( "Phase `{}`: {e}" , phase. name) ) ?;
811911 if res. len ( ) != 1 {
812912 return Err ( format ! (
813913 "Phase `{}`: expected exactly one result node, got {}" ,
0 commit comments