@@ -877,18 +877,13 @@ export class LangiumGrammarValidator {
877877 checkOperatorMultiplicitiesForMultiAssignments ( rule : ast . ParserRule , accept : ValidationAcceptor ) : void {
878878 // for usual parser rules AND for fragments, but not for data type rules!
879879 if ( ! rule . dataType ) {
880- this . checkOperatorMultiplicitiesForMultiAssignmentsLogic ( [ rule . definition ] , accept ) ;
880+ this . checkOperatorMultiplicitiesForMultiAssignmentsIndependent ( [ rule . definition ] , accept ) ;
881881 }
882882 }
883883
884- private checkOperatorMultiplicitiesForMultiAssignmentsLogic ( startNodes : AstNode [ ] , accept : ValidationAcceptor ) : void {
885- // new map to store usage information of the assignments
886- const map : Map < string , AssignmentUse > = new Map ( ) ;
887-
888- // top-down traversal for all starting nodes
889- for ( const node of startNodes ) {
890- this . checkAssignmentNumbersForNode ( node , 1 , map , accept ) ;
891- }
884+ private checkOperatorMultiplicitiesForMultiAssignmentsIndependent ( startNodes : AstNode [ ] , accept : ValidationAcceptor , map : Map < string , AssignmentUse > = new Map ( ) ) : void {
885+ // check all starting nodes
886+ this . checkOperatorMultiplicitiesForMultiAssignmentsNested ( startNodes , 1 , map , accept ) ;
892887
893888 // create the warnings
894889 for ( const entry of map . values ( ) ) {
@@ -906,72 +901,79 @@ export class LangiumGrammarValidator {
906901 }
907902 }
908903
909- private checkAssignmentNumbersForNode ( currentNode : AstNode , parentMultiplicity : number , map : Map < string , AssignmentUse > , accept : ValidationAcceptor ) {
910- // the current element can occur multiple times => its assignments can occur multiple times as well
911- let currentMultiplicity = parentMultiplicity ;
912- if ( ast . isAbstractElement ( currentNode ) && isArrayCardinality ( currentNode . cardinality ) ) {
913- currentMultiplicity *= 2 ; // note, that the result is not exact (but it is sufficient for the current case)!
914- }
904+ private checkOperatorMultiplicitiesForMultiAssignmentsNested ( nodes : AstNode [ ] , parentMultiplicity : number , map : Map < string , AssignmentUse > , accept : ValidationAcceptor ) : boolean {
905+ let resultCreatedNewObject = false ;
906+ // check all given elements
907+ for ( let i = 0 ; i < nodes . length ; i ++ ) {
908+ const currentNode = nodes [ i ] ;
909+
910+ // Tree-Rewrite-Actions are a special case: a new object is created => following assignments are put into the new object
911+ if ( ast . isAction ( currentNode ) && currentNode . feature ) {
912+ // (This does NOT count for unassigned actions, i.e. actions without feature name, since they change only the type of the current object.)
913+ const mapForNewObject = new Map ( ) ;
914+ storeAssignmentUse ( mapForNewObject , currentNode . feature , 1 , currentNode ) ; // remember the special rewriting feature
915+ // all following nodes are put into the new object => check their assignments independently
916+ this . checkOperatorMultiplicitiesForMultiAssignmentsIndependent ( nodes . slice ( i + 1 ) , accept , mapForNewObject ) ;
917+ resultCreatedNewObject = true ;
918+ break ; // breaks the current loop
919+ }
915920
916- // assignment
917- if ( ast . isAssignment ( currentNode ) ) {
918- storeAssignmentUse ( map , currentNode . feature , 1 * currentMultiplicity , currentNode ) ;
919- }
921+ // all other elements don't create new objects themselves:
920922
921- // Search for assignments in used fragments as well, since their property values are stored in the current object.
922- // But do not search in calls of regular parser rules, since parser rules create new objects.
923- if ( ast . isRuleCall ( currentNode ) && ast . isParserRule ( currentNode . rule . ref ) && currentNode . rule . ref . fragment ) {
924- this . checkAssignmentNumbersForNode ( currentNode . rule . ref . definition , currentMultiplicity , map , accept ) ;
925- }
923+ // the current element can occur multiple times => its assignments can occur multiple times as well
924+ let currentMultiplicity = parentMultiplicity ;
925+ if ( ast . isAbstractElement ( currentNode ) && isArrayCardinality ( currentNode . cardinality ) ) {
926+ currentMultiplicity *= 2 ; // note that the result is not exact (but it is sufficient for the current use case)!
927+ }
926928
927- // rewriting actions are a special case for assignments
928- if ( ast . isAction ( currentNode ) && currentNode . feature ) {
929- storeAssignmentUse ( map , currentNode . feature , 1 * currentMultiplicity , currentNode ) ;
930- }
929+ // assignment
930+ if ( ast . isAssignment ( currentNode ) ) {
931+ storeAssignmentUse ( map , currentNode . feature , currentMultiplicity , currentNode ) ;
932+ }
931933
932- // look for assignments to the same feature nested within groups
933- if ( ast . isGroup ( currentNode ) || ast . isUnorderedGroup ( currentNode ) || ast . isAlternatives ( currentNode ) ) {
934- const mapAllAlternatives : Map < string , AssignmentUse > = new Map ( ) ; // store assignments for Alternatives separately
935- let nodesForNewObject : AstNode [ ] = [ ] ;
936- // check all elements inside the current group
937- for ( const child of currentNode . elements ) {
938- if ( ast . isAction ( child ) ) {
939- // Actions are a special case: a new object is created => following assignments are put into the new object
940- // (This counts for rewriting actions as well as for unassigned actions, i.e. actions without feature name)
941- if ( nodesForNewObject . length > 0 ) {
942- // all collected nodes are put into the new object => check their assignments independently
943- this . checkOperatorMultiplicitiesForMultiAssignmentsLogic ( nodesForNewObject , accept ) ;
944- // is it possible to have two or more Actions within the same parser rule? the grammar allows that ...
945- nodesForNewObject = [ ] ;
946- }
947- // push the current node into a new object
948- nodesForNewObject . push ( child ) ;
949- } else {
950- // for non-Actions
951- if ( nodesForNewObject . length > 0 ) {
952- // nodes go into a new object
953- nodesForNewObject . push ( child ) ;
954- } else {
955- // count the relevant child assignments
956- if ( ast . isAlternatives ( currentNode ) ) {
957- // for alternatives, only a single alternative is used => assume the worst case and take the maximum number of assignments
958- const mapCurrentAlternative : Map < string , AssignmentUse > = new Map ( ) ;
959- this . checkAssignmentNumbersForNode ( child , currentMultiplicity , mapCurrentAlternative , accept ) ;
960- mergeAssignmentUse ( mapCurrentAlternative , mapAllAlternatives , ( s , t ) => Math . max ( s , t ) ) ;
961- } else {
962- // all members of the group are relavant => collect them all
963- this . checkAssignmentNumbersForNode ( child , currentMultiplicity , map , accept ) ;
964- }
934+ // Search for assignments in used fragments as well, since their property values are stored in the current object.
935+ // But do not search in calls of regular parser rules, since parser rules create new objects.
936+ if ( ast . isRuleCall ( currentNode ) && ast . isParserRule ( currentNode . rule . ref ) && currentNode . rule . ref . fragment ) {
937+ const createdNewObject = this . checkOperatorMultiplicitiesForMultiAssignmentsNested ( [ currentNode . rule . ref . definition ] , currentMultiplicity , map , accept ) ;
938+ resultCreatedNewObject = createdNewObject || resultCreatedNewObject ;
939+ }
940+
941+ // look for assignments to the same feature nested within groups
942+ if ( ast . isGroup ( currentNode ) || ast . isUnorderedGroup ( currentNode ) ) {
943+ // all members of the group are relavant => collect them all
944+ const mapGroup : Map < string , AssignmentUse > = new Map ( ) ; // store assignments for Alternatives separately
945+ const createdNewObject = this . checkOperatorMultiplicitiesForMultiAssignmentsNested ( currentNode . elements , 1 , mapGroup , accept ) ;
946+ mergeAssignmentUse ( mapGroup , map , createdNewObject
947+ ? ( s , t ) => ( s + t ) // if a new object is created in the group: ignore the current multiplicity, since a new object is created for each loop cycle!
948+ : ( s , t ) => ( s * currentMultiplicity + t ) // otherwise as usual: take the current multiplicity into account
949+ ) ;
950+ resultCreatedNewObject = createdNewObject || resultCreatedNewObject ;
951+ }
952+
953+ // for alternatives, only a single alternative is used => assume the worst case and take the maximum number of assignments
954+ if ( ast . isAlternatives ( currentNode ) ) {
955+ const mapAllAlternatives : Map < string , AssignmentUse > = new Map ( ) ; // store assignments for Alternatives separately
956+ let countCreatedObjects = 0 ;
957+ for ( const child of currentNode . elements ) {
958+ const mapCurrentAlternative : Map < string , AssignmentUse > = new Map ( ) ;
959+ const createdNewObject = this . checkOperatorMultiplicitiesForMultiAssignmentsNested ( [ child ] , 1 , mapCurrentAlternative , accept ) ;
960+ mergeAssignmentUse ( mapCurrentAlternative , mapAllAlternatives , createdNewObject
961+ ? ( s , t ) => Math . max ( s , t ) // if a new object is created in an alternative: ignore the current multiplicity, since a new object is created for each loop cycle!
962+ : ( s , t ) => Math . max ( s * currentMultiplicity , t ) // otherwise as usual: take the current multiplicity into account
963+ ) ;
964+ if ( createdNewObject ) {
965+ countCreatedObjects ++ ;
965966 }
966967 }
967- }
968- // merge alternatives
969- mergeAssignmentUse ( mapAllAlternatives , map ) ;
970- if ( nodesForNewObject . length >= 1 ) {
971- // these nodes are put into a new object => check their assignments independently
972- this . checkOperatorMultiplicitiesForMultiAssignmentsLogic ( nodesForNewObject , accept ) ;
968+ // merge alternatives
969+ mergeAssignmentUse ( mapAllAlternatives , map ) ;
970+ // since we assume the worst case, we define, that the entire Alternatives node created a new object, if ALL its alternatives created a new object
971+ if ( countCreatedObjects === currentNode . elements . length ) {
972+ resultCreatedNewObject = true ;
973+ }
973974 }
974975 }
976+ return resultCreatedNewObject ; // indicates, whether a new object was created
975977 }
976978
977979 checkInterfacePropertyTypes ( interfaceDecl : ast . Interface , accept : ValidationAcceptor ) : void {
@@ -1243,6 +1245,7 @@ function mergeAssignmentUse(mapSoure: Map<string, AssignmentUse>, mapTarget: Map
12431245 target . counter = counterOperation ( source . counter , target . counter ) ;
12441246 } else {
12451247 mapTarget . set ( key , source ) ;
1248+ source . counter = counterOperation ( source . counter , 0 ) ;
12461249 }
12471250 }
12481251 mapSoure . clear ( ) ;
0 commit comments