<?xml version="1.0" encoding="UTF-8"?>
<commit>
  <added type="array"/>
  <modified type="array">
    <modified>
      <diff>@@ -512,8 +512,8 @@ module ActiveFacts
       end
 
       # Arrange the clauses of one or more fact types into groups having the same role players
-      def clauses_by_terms(clauses)
-        cbt = {}
+      def clauses_by_players(clauses)
+        cbp = {}
         clauses.each do |clause|
           kind, qualifiers, phrases, context = *clause
           players = []
@@ -521,187 +521,199 @@ module ActiveFacts
             next unless phrase.is_a?(Hash)
             players &lt;&lt; phrase[:player]
           end
-          (cbt[players.map{|p| p.name}.sort] ||= []) &lt;&lt; clause
+          (cbp[players.map{|p| p.name}.sort] ||= []) &lt;&lt; clause
         end
-        cbt
+        cbp
       end
 
-      # Twisty curves. This is an awkward bit of code which isn't quite complete.
-      #
-      # Decide if any existing fact type matches.
-      # The clause must have been resolved already (see resolve_players above)
-      #
-      # Find each existing fact type that has the same players
-      #   For each such fact type, select each reading having the players in the same order
-      #     For each such reading, match each element where element means
-      #       a player (perhaps with adjectives)
-      #         Our clause must either
-      #           be a player with, for each adjective on the reading
-      #             the same adjective embedded, or
-      #           be a word that introduces a matching player with the correct adjectives
-      #       a word that matches the clause's
+      # Decide if any existing fact type matches this clause.
+      # The roles must have been resolved already (see resolve_players above)
+      # Check each existing fact type that has the same players, and check
+      # each reading having them in the same order.
       def find_existing_fact_type(clause)
         kind, qualifiers, phrases, context = *clause
-        players = phrases.select{|phrase| phrase.is_a?(Hash)}.map{|phrase| phrase[:player]}
+        player_phrases = phrases.select{|phrase| phrase.is_a?(Hash)}
+        players = player_phrases.map{|phrase| phrase[:player]}
         players_sorted_by_name = players.sort_by{|p| p.name}
         player_having_fewest_roles = players.sort_by{|p| p.all_role.size}[0]
         # REVISIT: Note: we will need to handle implicit subtyping joins here.
-        player_having_fewest_roles.all_role.each do |role|
-          next unless role.fact_type.all_role.size == players.size
-          next unless role.fact_type.all_role.map{|r| r.concept}.sort_by{|p| p.name} == players_sorted_by_name
-          # role.fact_type has the same players. See if there's a matching reading
-          role.fact_type.all_reading.each do |reading|
-            return role.fact_type if reading_matches_phrases(reading, phrases)
+        debug :matching, &quot;Looking for existing fact type to match '#{show_phrases(phrases)}'&quot; do
+          player_having_fewest_roles.all_role.each do |role|
+            next unless role.fact_type.all_role.size == players.size
+            next unless role.fact_type.all_role.map{|r| r.concept}.sort_by{|p| p.name} == players_sorted_by_name
+            # role.fact_type has the same players. See if there's a matching reading
+            role.fact_type.all_reading.each do |reading|
+              return role.fact_type if reading_matches_phrases(reading, phrases)
+            end
           end
         end
         nil
       end
 
+      # Twisty curves. This is a complex bit of code!
+      # Find whether the phrases of this clause match the fact type reading,
+      # which may require absorbing unmarked adjectives.
+      #
+      # If it does match, make the required changes and set [:role_ref] to the matching role.
+      # Adjectives that were used to match are removed (and leaving any additional adjectives intact).
+      #
+      # Approach:
+      #   Match each element where element means:
+      #     a role player phrase (perhaps with adjectives)
+      #       Our phrase must either be
+      #         a player that contains the same adjectives as in the reading.
+      #         a word (unmarked leading adjective) that introduces a sequence
+      #           of adjectives leading up to a matching player
+      #       trailing adjectives, both marked and unmarked, are absorbed too.
+      #     a word that matches the clause's
+      #
       def reading_matches_phrases(reading, phrases)
         phrase_num = 0
         player_details = []    # An array of items for each role, describing any side-effects of the match.
-        reading.text.split(/\s+/).each do |element|
-          if element !~ /\{(\d+)\}/
-            # Just a word; it must match
-            return nil unless phrases[phrase_num] == element
-            phrase_num += 1
-          else
-            role_ref = reading.role_sequence.all_role_ref.sort_by{|rr| rr.ordinal}[$1.to_i]
-
-            # Figure out what's next in this phrase (the next player and the words leading up to it)
-            next_player_phrase = nil
-            intervening_words = []
-            while (phrase = phrases[phrase_num])
+        debug :matching, &quot;Does '#{show_phrases(phrases)}' match '#{reading.expand}'&quot; do
+          reading.text.split(/\s+/).each do |element|
+            if element !~ /\{(\d+)\}/
+              # Just a word; it must match
+              unless phrases[phrase_num] == element
+                debug :matching, &quot;Mismatched ordinary word #{element} (wanted #{element})&quot;
+                return nil
+              end
               phrase_num += 1
-              if phrase.is_a?(Hash)
-                next_player_phrase = phrase
-                next_player_phrase_num = phrase_num-1
-                break
-              else
-                intervening_words &lt;&lt; phrase
+            else
+              role_ref = reading.role_sequence.all_role_ref.sort_by{|rr| rr.ordinal}[$1.to_i]
+
+              # Figure out what's next in this phrase (the next player and the words leading up to it)
+              next_player_phrase = nil
+              intervening_words = []
+              while (phrase = phrases[phrase_num])
+                phrase_num += 1
+                if phrase.is_a?(Hash)
+                  next_player_phrase = phrase
+                  next_player_phrase_num = phrase_num-1
+                  break
+                else
+                  intervening_words &lt;&lt; phrase
+                end
               end
-            end
 
-            # The next player must match:
-            # REVISIT: Note: we will need to handle implicit subtyping joins here.
-            player = role_ref.role.concept
-            return nil unless next_player_phrase and next_player_phrase[:player] == player
-
-            # It's the right player. Do the adjectives match?
-
-            absorbed_precursors = 0
-            if la = role_ref.leading_adjective and !la.empty?
-              # The leading adjectives must match, one way or another
-              la = la.split(/\s+/)
-              return nil unless la[0,intervening_words.size] == intervening_words
-              # Any intervening_words matched, see what remains
-              la.slice!(0, intervening_words.size)
-
-              # If there were intervening_words, the remaining reading adjectives must match the phrase's leading_adjective exactly.
-              phrase_la = (next_player_phrase[:leading_adjective]||'').split(/\s+/)
-              return nil if !intervening_words.empty? &amp;&amp; la != phrase_la
-              # If not, the phrase's leading_adjectives must *end* with the reading's
-              return nil if phrase_la[-la.size..-1] != la
-              # The leading adjectives and the player matched! Check the trailing adjectives.
-              absorbed_precursors = intervening_words.size
-            end
+              # The next player must match:
+              # REVISIT: Note: we will need to handle implicit subtyping joins here.
+              player = role_ref.role.concept
+              return nil unless next_player_phrase and next_player_phrase[:player] == player
+
+              # It's the right player. Do the adjectives match?
+
+              absorbed_precursors = 0
+              if la = role_ref.leading_adjective and !la.empty?
+                # The leading adjectives must match, one way or another
+                la = la.split(/\s+/)
+                return nil unless la[0,intervening_words.size] == intervening_words
+                # Any intervening_words matched, see what remains
+                la.slice!(0, intervening_words.size)
+
+                # If there were intervening_words, the remaining reading adjectives must match the phrase's leading_adjective exactly.
+                phrase_la = (next_player_phrase[:leading_adjective]||'').split(/\s+/)
+                return nil if !intervening_words.empty? &amp;&amp; la != phrase_la
+                # If not, the phrase's leading_adjectives must *end* with the reading's
+                return nil if phrase_la[-la.size..-1] != la
+                # The leading adjectives and the player matched! Check the trailing adjectives.
+                absorbed_precursors = intervening_words.size
+              end
 
-            absorbed_followers = 0
-            if ta = role_ref.trailing_adjective and !ta.empty?
-              ta = ta.split(/\s+/)  # These are the trailing adjectives to match
+              absorbed_followers = 0
+              if ta = role_ref.trailing_adjective and !ta.empty?
+                ta = ta.split(/\s+/)  # These are the trailing adjectives to match
 
-              phrase_ta = (next_player_phrase[:trailing_adjective]||'').split(/\s+/)
-              i = 0   # Pad the phrases up to the size of the trailing_adjectives
-              while phrase_ta.size &lt; ta.size
-                break unless (word = phrases[phrase_num+i]).is_a?(String)
-                phrase_ta &lt;&lt; word
-                i += 1
+                phrase_ta = (next_player_phrase[:trailing_adjective]||'').split(/\s+/)
+                i = 0   # Pad the phrases up to the size of the trailing_adjectives
+                while phrase_ta.size &lt; ta.size
+                  break unless (word = phrases[phrase_num+i]).is_a?(String)
+                  phrase_ta &lt;&lt; word
+                  i += 1
+                end
+                return nil if ta != phrase_ta[0,ta.size]
+                absorbed_followers = i
+                phrase_num += i # Skip following words that were consumed as trailing adjectives
               end
-              return nil if ta != phrase_ta[0,ta.size]
-              absorbed_followers = i
-              phrase_num += i # Skip following words that were consumed as trailing adjectives
-            end
 
-            # The phrases matched this reading's next role_ref, save data to apply the side-effects:
-            # puts &quot;Saving player #{next_player_phrase[:term]} with #{role_ref ? &quot;a&quot; : &quot;no&quot; } role_ref&quot;
-            player_details &lt;&lt; [next_player_phrase, role_ref, next_player_phrase_num, absorbed_precursors, absorbed_followers]
+              # The phrases matched this reading's next role_ref, save data to apply the side-effects:
+              debug :matching, &quot;Saving matched player #{next_player_phrase[:term]} with #{role_ref ? &quot;a&quot; : &quot;no&quot; } role_ref&quot;
+              player_details &lt;&lt; [next_player_phrase, role_ref, next_player_phrase_num, absorbed_precursors, absorbed_followers]
+            end
           end
-        end
 
-        # Enact the side-effects of this match (delete the consumed adjectives):
-        player_details.reverse.each do |phrase, role_ref, num, precursors, followers|
-          phrase[:role_ref] = role_ref    # Used if no extra adjectives were used
+          # Enact the side-effects of this match (delete the consumed adjectives):
+          debug :matching, &quot;It does match, apply side-effects&quot; do
+            player_details.reverse.each do |phrase, role_ref, num, precursors, followers|
+              phrase[:role_ref] = role_ref    # Used if no extra adjectives were used
 
-          # Where this phrase has leading or trailing adjectives that are in excess of those of
-          # the role_ref, those must be local, and we'll need to extract them.
+              # Where this phrase has leading or trailing adjectives that are in excess of those of
+              # the role_ref, those must be local, and we'll need to extract them.
 
-          if rra = role_ref.trailing_adjective
-            #p role_ref.trailing_adjective
+              if rra = role_ref.trailing_adjective
+                debug :matching, &quot;Deleting matched trailing adjective '#{rra}'#{followers&gt;0 ? &quot;in #{followers} followers&quot; : &quot;&quot;}&quot;
 
-            # These adjective(s) matched either an adjective here, or a follower word, or both.
-            if a = phrase[:trailing_adjective]
-              if a.size &gt;= rra.size
-                a.slice!(0, rra.size+1) # Remove the matched adjectives and the space (if any)
-                phrase.delete(:trailing_adjective) if a.empty?
+                # These adjective(s) matched either an adjective here, or a follower word, or both.
+                if a = phrase[:trailing_adjective]
+                  if a.size &gt;= rra.size
+                    a.slice!(0, rra.size+1) # Remove the matched adjectives and the space (if any)
+                    phrase.delete(:trailing_adjective) if a.empty?
+                  end
+                elsif followers &gt; 0
+                  phrase.delete(:trailing_adjective)
+                  phrases.slice!(num+1, followers)
+                end
               end
-            elsif followers &gt; 0
-              phrase.delete(:trailing_adjective)
-              phrases.slice!(num+1, followers)
-            end
-          end
 
-          if rra = role_ref.leading_adjective
-            #p role_ref.leading_adjective
+              if rra = role_ref.leading_adjective
+                debug :matching, &quot;Deleting matched leading adjective '#{rra}'#{precursors&gt;0 ? &quot;in #{precursors} precursors&quot; : &quot;&quot;}}&quot;
 
-            # These adjective(s) matched either an adjective here, or a precursor word, or both.
-            if a = phrase[:leading_adjective]
-              if a.size &gt;= rra.size
-                a.slice!(-rra.size, 1000) # Remove the matched adjectives and the space
-                a.slice!(0,1) if a[0,1] == ' '
-                phrase.delete(:leading_adjective) if a.empty?
+                # These adjective(s) matched either an adjective here, or a precursor word, or both.
+                if a = phrase[:leading_adjective]
+                  if a.size &gt;= rra.size
+                    a.slice!(-rra.size, 1000) # Remove the matched adjectives and the space
+                    a.slice!(0,1) if a[0,1] == ' '
+                    phrase.delete(:leading_adjective) if a.empty?
+                  end
+                elsif precursors &gt; 0
+                  phrase.delete(:leading_adjective)
+                  phrases.slice!(num-precursors, precursors)
+                end
               end
-            elsif precursors &gt; 0
-              phrase.delete(:leading_adjective)
-              phrases.slice!(num-precursors, precursors)
             end
           end
-
-          # If we have remaining adjectives, the role_ref cannot be used
-          # we need a new RoleSequence and RoleRefs for the underlying roles.
-          # if (phrase[:leading_adjective] or phrase[:trailing_adjective])
         end
+        debug :matching, &quot;Matched reading '#{reading.expand}'&quot;
 
         true
       end
-      #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
       def make_reading_for_fact_type(fact_type, clause)
         role_sequence = @constellation.RoleSequence(:new)
         reading_words = []
         kind, qualifiers, phrases, context = *clause
-        #puts &quot;Making new fact reading for #{phrases.inspect}&quot;
-        phrases.each do |phrase|
-          if phrase.is_a?(Hash)
-            index = role_sequence.all_role_ref.size
-            roles = fact_type.all_role.select{|r| r.concept == phrase[:player]}
-            raise &quot;REVISIT: This doesn't work if a concept plays more than one role&quot; if roles.size &gt; 1
-            # Can annotate the phrase during binding perhaps:
-            role = phrase[:role] || roles[0]
-            raise &quot;Role player #{phrase[:player].name} not found for reading&quot; unless role
-            rr = @constellation.RoleRef(role_sequence, index, :role =&gt; role)
-            if la = phrase[:leading_adjective]
-              # If we have used one or more adjective to match an existing reading, that has already been removed.
-              rr.leading_adjective = la
-            end
-            if ta = phrase[:trailing_adjective]
-              rr.trailing_adjective = ta
+        debug :matching, &quot;Making new reading for #{show_phrases(phrases)}&quot; do
+          phrases.each do |phrase|
+            if phrase.is_a?(Hash)
+              index = role_sequence.all_role_ref.size
+              role = phrase[:role]
+              raise &quot;Role player #{phrase[:player].name} not found for reading: REVISIT Phrase is #{phrase.inspect}&quot; unless role
+              rr = @constellation.RoleRef(role_sequence, index, :role =&gt; role)
+              phrase[:role_ref] = rr
+              if la = phrase[:leading_adjective]
+                # If we have used one or more adjective to match an existing reading, that has already been removed.
+                rr.leading_adjective = la
+              end
+              if ta = phrase[:trailing_adjective]
+                rr.trailing_adjective = ta
+              end
+              reading_words &lt;&lt; &quot;{#{index}}&quot;
+            else
+              reading_words &lt;&lt; phrase
             end
-            reading_words &lt;&lt; &quot;{#{index}}&quot;
-          else
-            reading_words &lt;&lt; phrase
           end
+          @constellation.Reading(fact_type, fact_type.all_reading.size, :role_sequence =&gt; role_sequence, :text =&gt; reading_words*&quot; &quot;)
         end
-        @constellation.Reading(fact_type, fact_type.all_reading.size, :role_sequence =&gt; role_sequence, :text =&gt; reading_words*&quot; &quot;)
       end
 
       def role_sequence_for_matched_reading(fact_type, clause)
@@ -717,9 +729,12 @@ module ActiveFacts
           if phrase.is_a?(Hash)
             role_phrases &lt;&lt; phrase
             reading_words &lt;&lt; &quot;{#{phrase[:role_ref].ordinal}}&quot;
-            new_role_sequence_needed = true if phrase[:leading_adjective] ||
-                phrase[:trailing_adjective] ||
-                phrase[:role_name]
+            if phrase[:leading_adjective] ||
+              phrase[:trailing_adjective] ||
+              phrase[:role_name]
+              debug :matching, &quot;phrase in matched reading has residual adjectives or role name, so needs a new role_sequence&quot; if fact_type.all_reading.size &gt; 0
+              new_role_sequence_needed = true
+            end
           else
             reading_words &lt;&lt; phrase
             false
@@ -729,30 +744,30 @@ module ActiveFacts
         reading_text = reading_words*&quot; &quot;
         if new_role_sequence_needed
           role_sequence = @constellation.RoleSequence(:new)
-          #extra_adjectives = []
+          extra_adjectives = []
           role_phrases.each_with_index do |rp, i|
             role_ref = @constellation.RoleRef(role_sequence, i, :role =&gt; rp[:role_ref].role)
             if a = rp[:leading_adjective]
               role_ref.leading_adjective = a
-              #extra_adjectives &lt;&lt; a+&quot;-&quot;
+              extra_adjectives &lt;&lt; a+&quot;-&quot;
             end
             if a = rp[:trailing_adjective]
               role_ref.trailing_adjective = a
-              #extra_adjectives &lt;&lt; &quot;-&quot;+a
+              extra_adjectives &lt;&lt; &quot;-&quot;+a
             end
             if a = rp[:role_name]
-              #extra_adjectives &lt;&lt; &quot;(as #{a})&quot;
+              extra_adjectives &lt;&lt; &quot;(as #{a})&quot;
             end
           end
-          #puts &quot;Making new role sequence for #{reading_words*&quot; &quot;} due to #{extra_adjectives.inspect}&quot;
+          debug :matching, &quot;Making new role sequence for new reading #{reading_words*&quot; &quot;} due to #{extra_adjectives.inspect}&quot;
         else
           # Use existing RoleSequence
           role_sequence = role_phrases[0][:role_ref].role_sequence
           if role_sequence.all_reading.detect{|r| r.text == reading_text }
-            #puts &quot;No need to re-create identical reading for #{reading_words*&quot; &quot;}&quot;
+            debug :matching, &quot;No need to re-create identical reading for #{reading_words*&quot; &quot;}&quot;
             return role_sequence
           else
-            #puts &quot;Using existing role sequence for #{reading_words*&quot; &quot;}&quot;
+            debug :matching, &quot;Using existing role sequence for new reading '#{reading_words*&quot; &quot;}'&quot;
           end
         end
         @constellation.Reading(fact_type, fact_type.all_reading.size, :role_sequence =&gt; role_sequence, :text =&gt; reading_words*&quot; &quot;)
@@ -770,20 +785,104 @@ module ActiveFacts
           )
       end
 
+      def show_phrases(phrases)
+        phrases.map do |phrase|
+          if phrase.is_a?(Hash)
+            ((l = phrase[:leading_adjective]) ? l+&quot;- &quot; : &quot;&quot;) +
+              phrase[:term] +
+              ((t = phrase[:trailing_adjective]) ? &quot; -&quot;+t : &quot;&quot;) +
+              ((r = phrase[:role_name]) ? (r.is_a?(Integer) ? &quot; (#{r})&quot; : &quot; (as #{r})&quot;) : &quot;&quot;)
+          else
+            phrase
+          end
+        end*&quot; &quot;
+      end
+
+      # We have a clause that doesn't match any existing fact reading, and one or more
+      # clauses that match the fact it's a reading for.
+      # Try to match the roles against those of any matched clause.
+      # The role matching must be exact for all (or all but one) role of each player.
+      # An exact role match occurs where a subscript matches (must be the same player!)
+      # If the role has no subscript but does have a role name, the role name matches.
+      # If the role has neither, the adjectives must match.
+      # Finally, a role is an inexact match if the player matches and no other role of this player is inexact.
+      def match_clause_against_clauses(clause, matched_clauses)
+        kind, qualifiers, phrases, context = *clause
+        role_phrases = phrases.select{|p|p.is_a?(Hash)}
+        debug :matching, &quot;Looking for match for roles of '#{show_phrases(phrases)}'&quot; do
+          matched_clauses.detect do |matched_clause|
+            m_kind, m_qualifiers, m_phrases, m_context = *matched_clause
+            mr_phrases = m_phrases.select{|p|p.is_a?(Hash)}
+            inexact_phrases = []
+            debug :matching, &quot;Looking in roles of '#{show_phrases(m_phrases)}'&quot; do
+              matched = nil
+              role_phrases.each do |phrase|
+                debug :matching, &quot;Looking for match for #{phrase[:player].name}&quot; do
+                  kind = nil
+                  if (role_name = phrase[:role_name]).is_a?(Integer) and
+                    matched = mr_phrases.detect {|mrp| mrp[:role_name] == role_name }
+                    kind = &quot;subscript&quot; # Matched on same subscript
+                  elsif role_name and (matched = mr_phrases.detect {|mrp| mrp[:term] == role_name })
+                    kind = &quot;role definition&quot;  # Matched on a role name that the unmatchd clause defined
+                  elsif (matched = mr_phrases.detect {|mrp| mrp[:role_name] == phrase[:term] })
+                    kind = &quot;role reference&quot; # Matched on a role name that the matchd clause defined
+                  elsif matched = mr_phrases.detect do |mrp|
+                        m_role_ref = mrp[:role_ref]
+                        #t = phrase[:trailing_adjective]
+                        #l = phrase[:leading_adjective]
+                        #w = &quot;#{l ? l+'- ' : ''}#{phrase[:term]}#{t ? ' -'+t : ''}&quot;
+                        #debug :matching, &quot;Trying adjective of '#{w}' against '#{m_role_ref.leading_adjective}- #{m_role_ref.role.concept.name} -#{m_role_ref.trailing_adjective}'&quot;
+                        m_role_ref.leading_adjective == phrase[:leading_adjective] and
+                        m_role_ref.trailing_adjective == phrase[:trailing_adjective] and
+                        m_role_ref.role.concept.name == phrase[:term]
+                      end
+                    kind = &quot;adjectives&quot; # Matched on all adjectives
+                  else
+                    inexact_phrases &lt;&lt; phrase
+                    next    # We have to leave this until all exact matches are consumed
+                  end
+                  debug :matching, &quot;Matched role #{phrase[:player].name} using #{kind} against #{matched.inspect}&quot;
+                  mr_phrases.delete(matched)  # We can't use this phrase for another match
+          # REVISIT: we shouldn't do this until we know the whole thing matches; and then we should remove the adjectives so we re-use the same reading
+                  phrase[:role] = matched[:role] || matched[:role_ref].role
+                end
+              end
+            end
+
+            debug :matching, &quot;Need to try inexact match for #{inexact_phrases.inspect}&quot; if inexact_phrases.size &gt; 0
+            inexact_players = inexact_phrases.map{|p| p[:player]}
+            if (iep = inexact_players.uniq).size &lt; inexact_players.size
+              raise &quot;Ambiguous role match for #{iep.map{|p| p.name}*', '}&quot;
+            end
+            inexact_phrases.each do |phrase|
+              matched = mr_phrases.detect {|mrp| mrp[:player] == phrase[:player] }
+              raise &quot;Role for #{phrase[:player].name} does not match&quot; unless matched
+              mr_phrases.delete(matched)  # We can't use this phrase for another match
+              phrase[:role] = matched[:role] || matched[:role_ref].role
+            end
+            raise &quot;Not enough roles to match, only #{role_phrases.map{|p| p[:player].name}*', '}&quot; if mr_phrases.size &gt; 0
+          end
+        end
+      end
+
       def fact_type(name, clauses, conditions) 
-        debug &quot;Processing clauses for fact type&quot; do
+        debug :matching, &quot;Processing clauses for fact type&quot; do
           fact_type = nil
 
           # REVISIT: Any role names defined in the conditions aren't handled here, only those in the clauses
 
+          # Find and set [:player] to the concept (object type) that plays each role
           resolve_players(clauses)
 
-          cbt = clauses_by_terms(clauses)
-          terms = cbt.keys[0]     # This is the sorted array of player's names
-          raise &quot;Subsequent fact type clauses must involve the same players as the first (#{terms*', '})&quot; unless cbt.size == 1
+          # Arrange the clauses according to the players (the hash key is the sorted array of player's names)
+          cbp = clauses_by_players(clauses)
+          terms = cbp.keys[0]
+
+          # For a fact type, all clauses must have the same players:
+          raise &quot;Subsequent fact type clauses must involve the same players as the first (#{terms*', '})&quot; unless cbp.size == 1
 
           # Find whether any clause matches an existing fact type.
-          # Ensure that for any such clauses all match the same fact type.
+          # Ensure that any matched clauses all match the same fact type.
           # Massage any unmarked adjectives into the role phrase hash if needed.
           matched_clauses, unmatched_clauses =
             *clauses.partition do |clause|
@@ -793,30 +892,43 @@ module ActiveFacts
               fact_type = ft
             end
 
-          # We know the role players are the same in all clauses, but we haven't matched them up.
-          # If any player is duplicated and isn't used with consistent adjectives, we must use
-          # loose adjective binding or require subscripts.
-          if terms.uniq.size &lt; terms.size
-            raise &quot;REVISIT: disambiguate duplicate roles of (#{terms*', '})&quot;
-          end
-
           # Make a new fact type if we didn't match any reading
           if !fact_type
             fact_type = @constellation.FactType(:new)
             kind, qualifiers, phrases, context = *unmatched_clauses[0]
-            # puts &quot;Making new fact type for #{phrases.inspect}&quot;
-            phrases.each do |phrase|
-              next unless phrase.is_a?(Hash)
-              @constellation.Role(fact_type, fact_type.all_role.size, :concept =&gt; phrase[:player])
+            debug :matching, &quot;Making new fact type for #{show_phrases(phrases)}&quot; do
+              phrases.each do |phrase|
+                next unless phrase.is_a?(Hash)
+                role = @constellation.Role(fact_type, fact_type.all_role.size, :concept =&gt; phrase[:player])
+                phrase[:role] = role
+              end
             end
           end
 
+          # We know the role players are the same in all clauses, but we haven't matched them up.
+          # If any player is duplicated and isn't used with consistent adjectives, we must use
+          # loose adjective binding or require subscripts.
+
+          # If we have no matched clause, make a fact type and reading for the first clause.
+          # Treat this new reading as a matched clause.
+          first_clause = nil
+          if matched_clauses.size == 0
+            matched_clauses &lt;&lt; (first_clause = unmatched_clauses.shift)
+            make_reading_for_fact_type(fact_type, first_clause)
+          end
+
+          # Then, for each remaining unmatched clause, try to match the roles against those of any matched clause.
+          unmatched_clauses.each do |clause|
+            match_clause_against_clauses(clause, matched_clauses)
+          end
+
           # Make new readings for all unmatched clauses
           unmatched_clauses.each do |clause|
             make_reading_for_fact_type(fact_type, clause)
           end
 
-          matched_clauses.each do |clause|
+          (matched_clauses-[first_clause]).each do |clause|
+            # This might create a new reading and a new role sequence if needed, or use the matched one
             role_sequence_for_matched_reading(fact_type, clause)
           end
 </diff>
      <filename>lib/activefacts/cql/compiler.rb</filename>
    </modified>
    <modified>
      <diff>@@ -15,17 +15,29 @@ describe &quot;Fact Type Matching&quot; do
     Girl is written as String;
   }
 
-  OneFactTwoReadings = 
+  def self.OneFactNReadings n
     lambda {|c|
       c.FactType.size.should == 1
-      c.FactType.values[0].all_reading.size.should == 2
+      unless c.FactType.values[0].all_reading.size == n
+        puts &quot;SPEC FAILED, wrong number of readings (should be #{n}):\n\t#{
+          c.FactType.values[0].all_reading.map{ |r| r.expand}*&quot;\n\t&quot;
+        }&quot;
+      end
+      c.FactType.values[0].all_reading.size.should == n
     }
+  end
 
-  OneFactThreeReadings = 
-    lambda {|c|
-      c.FactType.size.should == 1
-      c.FactType.values[0].all_reading.size.should == 3
+  def self.FactTypeHasNPresenceConstraints(n)
+    lambda{ |c|
+      # pending
+      fact_type = c.FactType.values[0]
+      fact_type.all_role.map{|r|
+        r.all_role_ref.map{|rr|
+          rr.role_sequence.all_presence_constraint.to_a
+        }
+      }.flatten.uniq.size.should == n
     }
+  end
 
   ReadingContainsHyphenatedWord =
     lambda {|c|
@@ -37,13 +49,21 @@ describe &quot;Fact Type Matching&quot; do
     }
 
   Tests = [
+    [ # Simple create
+      %q{Girl is going out with at most one Boy; },
+      OneFactNReadings(1),
+    ],
+    [ # Create with explicit adjective
+      %q{Girl is going out with at most one ugly-Boy;},
+      OneFactNReadings(1),
+    ],
     [ # Simple match
       %q{Girl is going out with at most one Boy; },
       %q{
         Girl is going out with Boy,
           Boy is going out with Girl;
       },
-      OneFactTwoReadings
+      OneFactNReadings(2)
     ],
     [ # Simple match with a new presence Constraint
       %q{Girl is going out with at most one Boy; },
@@ -51,18 +71,8 @@ describe &quot;Fact Type Matching&quot; do
         Girl is going out with Boy,
           Boy is going out with at most one Girl;
       },
-      OneFactTwoReadings,
-=begin
-      lambda{ |c|
-        # pending
-        fact_type = c.FactType.values[0]
-        fact_type.all_role.map{|r|
-          r.all_role_ref.map{|rr|
-            rr.role_sequence.all_presence_constraint.to_a
-          }
-        }.flatten.uniq.size.should == 2
-      }
-=end
+      OneFactNReadings(2),
+      # FactTypeHasNPresenceConstraints(2)
     ],
     [ # RoleName matching
       %q{Girl is going out with at most one Boy;},
@@ -70,63 +80,63 @@ describe &quot;Fact Type Matching&quot; do
         Boy is going out with Girlfriend,
           Girl (as Girlfriend) is going out with at most one Boy;
       },
-      OneFactThreeReadings
+      OneFactNReadings(3)
     ],
     [ # Match with explicit adjective
       %q{Girl is going out with at most one ugly-Boy;},
       %q{Girl is going out with ugly-Boy,
-        ugly-Boy is going out with Girl;
+        ugly-Boy is best friend of Girl;
       },
-      OneFactTwoReadings,
+      OneFactNReadings(2),
     ],
     [ # Match with implicit adjective
       %q{Girl is going out with at most one ugly-Boy;},
       %q{Girl is going out with ugly Boy,
         Boy is going out with Girl;
       },
-      OneFactTwoReadings,
+      OneFactNReadings(2),
     ],
     [ # Match with explicit trailing adjective
       %q{Girl is going out with at most one Boy-monster;},
       %q{Girl is going out with Boy-monster,
         Boy is going out with Girl;
       },
-      OneFactTwoReadings,
+      OneFactNReadings(2),
     ],
     [ # Match with implicit trailing adjective
       %q{Girl is going out with at most one Boy-monster;},
       %q{Girl is going out with Boy monster,
         Boy is going out with Girl;
       },
-      OneFactTwoReadings,
+      OneFactNReadings(2),
     ],
     [ # Match with two explicit adjectives
       %q{Girl is going out with at most one ugly- bad Boy;},
       %q{Girl is going out with ugly- bad Boy,
         ugly- bad Boy is going out with Girl;
       },
-      OneFactTwoReadings,
+      OneFactNReadings(2),
     ],
     [ # Match with two implicit adjective
       %q{Girl is going out with at most one ugly- bad Boy;},
       %q{Girl is going out with ugly bad Boy,
         Boy is going out with Girl;
       },
-      OneFactTwoReadings,
+      OneFactNReadings(2),
     ],
     [ # Match with two explicit trailing adjective
       %q{Girl is going out with at most one Boy real -monster;},
       %q{Girl is going out with Boy real -monster,
         Boy is going out with Girl;
       },
-      OneFactTwoReadings,
+      OneFactNReadings(2),
     ],
     [ # Match with two implicit trailing adjectives
       %q{Girl is going out with at most one Boy real -monster;},
       %q{Girl is going out with Boy real monster,
         Boy is going out with Girl;
       },
-      OneFactTwoReadings,
+      OneFactNReadings(2),
     ],
     [ # Match with hyphenated word
       %q{Girl is going out with at most one Boy; },
@@ -134,7 +144,7 @@ describe &quot;Fact Type Matching&quot; do
         Girl is going out with Boy,
           Boy is out driving a semi-trailer with Girl;
       },
-      OneFactTwoReadings,
+      OneFactNReadings(2),
       ReadingContainsHyphenatedWord
     ],
     [ # Match with implicit leading ignoring explicit trailing adjective
@@ -142,28 +152,28 @@ describe &quot;Fact Type Matching&quot; do
       %q{Girl is going out with ugly Boy-monster,
         Boy is going out with Girl;
       },
-      OneFactThreeReadings,
+      OneFactNReadings(3),
     ],
     [ # Match with implicit leading ignoring implicit trailing adjective
       %q{Girl is going out with at most one ugly-Boy;},
       %q{Girl is going out with ugly Boy monster,
         Boy-monster is going out with Girl;
       },
-      OneFactThreeReadings,
+      OneFactNReadings(3),
     ],
     [ # Match with implicit trailing ignoring explicit leading adjective
       %q{Girl is going out with at most one Boy-monster;},
       %q{Girl is going out with ugly-Boy monster,
         Boy is going out with Girl;
       },
-      OneFactThreeReadings,
+      OneFactNReadings(3),
     ],
     [ # Match with implicit trailing ignoring implicit leading adjective
       %q{Girl is going out with at most one Boy-monster;},
       %q{Girl is going out with ugly Boy monster,
         ugly-Boy is going out with Girl;
       },
-      OneFactThreeReadings,
+      OneFactNReadings(3),
     ],
   ]
 </diff>
      <filename>spec/cql/matching_spec.rb</filename>
    </modified>
  </modified>
  <removed type="array"/>
  <parents type="array">
    <parent>
      <id>8c3a376351d978b3a50d5b5e77cc01872acf4d3b</id>
    </parent>
  </parents>
  <author>
    <name>Clifford Heath</name>
    <email>clifford.heath@gmail.com</email>
  </author>
  <url>http://github.com/cjheath/activefacts/commit/132d983d46f5c34b5bebfdea1ef65f466d865d7c</url>
  <id>132d983d46f5c34b5bebfdea1ef65f466d865d7c</id>
  <committed-date>2009-11-01T17:58:22-08:00</committed-date>
  <authored-date>2009-11-01T17:58:22-08:00</authored-date>
  <message>Create readings where necessary for clauses matched using extra adjectives</message>
  <tree>4c65459602fe6ce6ec0606ee4c440d18a05926d5</tree>
  <committer>
    <name>Clifford Heath</name>
    <email>clifford.heath@gmail.com</email>
  </committer>
</commit>
