@@ -866,28 +866,17 @@ export class Compiler {
866866 const { rules} = this ;
867867 const patternsByRule = new Map ( ) ;
868868
869- const visit = irExp => {
870- switch ( irExp . type ) {
871- case 'Alt' :
872- return ir . alt ( irExp . children . map ( e => visit ( e ) ) ) ;
873- case 'Any' :
874- case 'End' :
875- case 'LiftedTerminal' :
876- return irExp ;
877- case 'Apply' : {
878- const { ruleName, children} = irExp ;
869+ const specialize = exp =>
870+ ir . rewrite ( exp , {
871+ Apply : app => {
872+ const { ruleName, children} = app ;
879873 // Inline these. TODO: Handle this elsewhere.
880874 if ( [ 'caseInsensitive' , 'liquidRawTagImpl' , 'liquidTagRule' ] . includes ( ruleName ) ) {
881875 const ruleInfo = getNotNull ( rules , ruleName ) ;
882- return visit ( ir . substituteParams ( ruleInfo . body , children ) ) ;
876+ return specialize ( ir . substituteParams ( ruleInfo . body , children ) ) ;
883877 }
884878
885- const specializedName = ir . specializedName ( irExp ) ;
886- if ( specializedName !== ruleName ) {
887- // Record this pattern.
888- const rulePatterns = setdefault ( patternsByRule , ruleName , ( ) => new Map ( ) ) ;
889- rulePatterns . set ( specializedName , children ) ;
890- }
879+ const specializedName = ir . specializedName ( app ) ;
891880 this . _ensureRuleId ( specializedName ) ;
892881
893882 // If not yet seen, recursively visit the body of the specialized
@@ -898,49 +887,51 @@ export class Compiler {
898887
899888 // Visit the body with the parameter substituted, to ensure we
900889 // discover all possible applications that can occur at runtime.
901- let body = visit ( ir . substituteParams ( ruleInfo . body , children ) ) ;
902-
903- if ( children . length !== 0 ) {
904- // The specialized rule just applies the generalized rule.
905- // Note that we *don't* visit this application, and it won't be
906- // assigned a rule id yet!
907- body = ir . apply ( irExp . ruleName ) ;
890+ let body = specialize ( ir . substituteParams ( ruleInfo . body , children ) ) ;
891+
892+ // If there are any args, replace the body with an application of
893+ // the generalized rule.
894+ if ( children . length > 0 ) {
895+ // This is the first time we've seen this pattern; record it.
896+ const rulePatterns = setdefault ( patternsByRule , ruleName , ( ) => new Map ( ) ) ;
897+ rulePatterns . set ( specializedName , children ) ;
898+
899+ // Note that we deliberately *don't* visit this application yet,
900+ // so it won't be assigned a rule ID.
901+ const caseIdx = rulePatterns . size - 1 ;
902+ body = ir . applyGeneralized ( ruleName , caseIdx ) ;
908903 }
909904 newRules . set ( specializedName , { ...ruleInfo , body, formals : [ ] } ) ;
910905 }
911906 // Replace with an application of the specialized rule.
912907 return ir . apply ( specializedName ) ;
913- }
914- case 'Lex' :
915- case 'Lookahead' :
916- case 'Not' :
917- case 'Opt' :
918- case 'Plus' :
919- case 'Star' :
920- return { type : irExp . type , child : visit ( irExp . child ) } ;
921- case 'Seq' :
922- return { type : irExp . type , children : irExp . children . map ( visit ) } ;
923- case 'Param' :
924- case 'Range' :
925- case 'Terminal' :
926- case 'UnicodeChar' :
927- return irExp ; // Leaf nodes can be shared.
928- default :
929- throw new Error ( `not handled: ${ irExp . type } ` ) ;
930- }
931- } ;
932- visit ( ir . apply ( this . grammar . defaultStartRule ) ) ;
908+ } ,
909+ } ) ;
910+ specialize ( ir . apply ( this . grammar . defaultStartRule ) ) ;
933911 this . rules = newRules ;
934912
913+ const insertDispatches = ( exp , patterns ) =>
914+ ir . rewrite ( exp , {
915+ Apply : app => {
916+ if ( app . children . length === 0 ) return app ;
917+ return { type : 'Dispatch' , child : app , patterns} ;
918+ } ,
919+ Param : p => {
920+ return { type : 'Dispatch' , child : p , patterns} ;
921+ } ,
922+ } ) ;
923+
935924 // Save the observed patterns of the parameterized rules.
936925 // All non-parameterized & specialized rules have been discovered and
937926 // assigned IDs; any rule IDs assigned here won't be memoized.
938927 for ( const [ name , patterns ] of patternsByRule . entries ( ) ) {
939928 this . _ensureRuleId ( name , { notMemoized : true } ) ;
940929 const ruleInfo = getNotNull ( rules , name ) ;
930+ const patternsArr = [ ...patterns . values ( ) ] ;
941931 newRules . set ( name , {
942932 ...ruleInfo ,
943- patterns : [ ...patterns . values ( ) ] ,
933+ body : insertDispatches ( ruleInfo . body , patternsArr ) ,
934+ patterns : patternsArr ,
944935 } ) ;
945936 }
946937 }
@@ -1101,7 +1092,7 @@ export class Compiler {
11011092 // rule; they take an i32 `caseIdx` argument that selects the behaviour.
11021093 // Then, for any Param -- or Apply that involves a Param -- we dynamically
11031094 // dispatch to the correct specialized version of the rule.
1104- emitDispatch ( exp , patterns ) {
1095+ emitDispatch ( { child : exp , patterns} ) {
11051096 const { asm} = this ;
11061097
11071098 const cases = patterns . map ( ( actuals , i ) => ( ) => {
@@ -1144,8 +1135,8 @@ export class Compiler {
11441135 this . emitApply ( exp ) ;
11451136 return ;
11461137 }
1147- if ( this . _currRuleInfo . patterns && [ 'Apply' , 'Param' ] . includes ( exp . type ) ) {
1148- this . emitDispatch ( exp , this . _currRuleInfo . patterns ) ;
1138+ if ( exp . type === 'ApplyGeneralized' ) {
1139+ this . emitApplyGeneralized ( exp ) ;
11491140 return ;
11501141 }
11511142
@@ -1161,6 +1152,7 @@ export class Compiler {
11611152 switch ( exp . type ) {
11621153 case 'Alt' : this . emitAlt ( exp ) ; break ;
11631154 case 'Any' : this . emitAny ( ) ; break ;
1155+ case 'Dispatch' : this . emitDispatch ( exp ) ; break ;
11641156 case 'End' : this . emitEnd ( ) ; break ;
11651157 case 'LiftedTerminal' : this . emitApplyTerm ( exp ) ; break ;
11661158 case 'Lookahead' : this . emitLookahead ( exp , true ) ; break ;
@@ -1214,27 +1206,16 @@ export class Compiler {
12141206 // Need to know which case we're applying!
12151207 emitApplyGeneralized ( exp ) {
12161208 const { asm} = this ;
1217- const { patterns} = getNotNull ( this . rules , exp . ruleName ) ;
1218- // TODO: Should we cache these? We'll reconstruct them many times for the same set of patterns.
1219- const keys = patterns . map ( actuals => ir . specializedName ( ir . apply ( exp . ruleName , actuals ) ) ) ;
1220- const caseIdx = keys . indexOf ( this . _currRuleName ) ;
1221- assert ( caseIdx >= 0 ) ;
12221209 asm . i32Const ( this . ruleId ( exp . ruleName ) ) ;
1223- asm . i32Const ( caseIdx ) ;
1210+ asm . i32Const ( exp . caseIdx ) ;
12241211 asm . callPrebuiltFunc ( 'evalApplyGeneralized' ) ;
12251212 asm . localSet ( 'ret' ) ;
12261213 }
12271214
12281215 emitApply ( exp ) {
1229- const { asm} = this ;
1230-
1231- // Are we applying a generalized rule from a specialized one?
1232- if ( getNotNull ( this . rules , exp . ruleName ) . formals . length > 0 ) {
1233- this . emitApplyGeneralized ( exp ) ;
1234- return ;
1235- }
12361216 assert ( exp . children . length === 0 ) ;
12371217
1218+ const { asm} = this ;
12381219 asm . i32Const ( this . ruleId ( exp . ruleName ) ) ;
12391220
12401221 // TODO: Handle this at grammar parse time, not here.
0 commit comments