Class: ActiveFacts::CQL::Compiler::Reading
- Inherits:
-
Object
- Object
- ActiveFacts::CQL::Compiler::Reading
- Defined in:
- lib/activefacts/cql/compiler/reading.rb
Instance Attribute Summary collapse
-
#context_note ⇒ Object
Returns the value of attribute context_note.
-
#fact ⇒ Object
When binding fact instances the fact goes here.
-
#fact_type ⇒ Object
These are the Metamodel objects.
-
#objectified_as ⇒ Object
The Reading::RoleRef which objectified this fact type.
-
#phrases ⇒ Object
readonly
Returns the value of attribute phrases.
-
#qualifiers ⇒ Object
Returns the value of attribute qualifiers.
-
#reading ⇒ Object
readonly
These are the Metamodel objects.
-
#role_sequence ⇒ Object
readonly
These are the Metamodel objects.
-
#side_effects ⇒ Object
readonly
Returns the value of attribute side_effects.
Instance Method Summary collapse
-
#adjust_for_match ⇒ Object
When we match an existing reading, we might have matched using additional adjectives.
- #apply_side_effects(context, side_effects) ⇒ Object
- #bind_roles(context) ⇒ Object
- #display ⇒ Object
- #identify_other_players(context) ⇒ Object
- #identify_players_with_role_name(context) ⇒ Object
- #includes_literals ⇒ Object
-
#initialize(role_refs_and_words, qualifiers = [], context_note = nil) ⇒ Reading
constructor
A new instance of Reading.
- #inspect ⇒ Object
-
#is_existential_type ⇒ Object
A reading that contains only the name of a Concept and no literal or reading text refers only to the existence of that Concept (as opposed to an instance of the concept).
- #make_embedded_constraints(vocabulary) ⇒ Object
-
#make_fact_type(vocabulary) ⇒ Object
Make a new fact type with roles for this reading.
- #make_reading(vocabulary, fact_type) ⇒ Object
-
#match_existing_fact_type(context) ⇒ Object
This method chooses the existing fact type which matches most closely.
- #phrases_match(phrases) ⇒ Object
-
#reading_matches(fact_type, reading) ⇒ Object
The ActiveFacts::Metamodel::Reading passed has the same players as this Compiler::Reading.
- #role_refs ⇒ Object
Constructor Details
#initialize(role_refs_and_words, qualifiers = [], context_note = nil) ⇒ Reading
Returns a new instance of Reading.
14 15 16 17 18 19 |
# File 'lib/activefacts/cql/compiler/reading.rb', line 14 def initialize role_refs_and_words, qualifiers = [], context_note = nil @phrases = role_refs_and_words role_refs.each { |role_ref| role_ref.reading = self } @qualifiers = qualifiers @context_note = context_note end |
Instance Attribute Details
#context_note ⇒ Object
Returns the value of attribute context_note.
7 8 9 |
# File 'lib/activefacts/cql/compiler/reading.rb', line 7 def context_note @context_note end |
#fact ⇒ Object
When binding fact instances the fact goes here
11 12 13 |
# File 'lib/activefacts/cql/compiler/reading.rb', line 11 def fact @fact end |
#fact_type ⇒ Object
These are the Metamodel objects
8 9 10 |
# File 'lib/activefacts/cql/compiler/reading.rb', line 8 def fact_type @fact_type end |
#objectified_as ⇒ Object
The Reading::RoleRef which objectified this fact type
12 13 14 |
# File 'lib/activefacts/cql/compiler/reading.rb', line 12 def objectified_as @objectified_as end |
#phrases ⇒ Object (readonly)
Returns the value of attribute phrases.
6 7 8 |
# File 'lib/activefacts/cql/compiler/reading.rb', line 6 def phrases @phrases end |
#qualifiers ⇒ Object
Returns the value of attribute qualifiers.
7 8 9 |
# File 'lib/activefacts/cql/compiler/reading.rb', line 7 def qualifiers @qualifiers end |
#reading ⇒ Object (readonly)
These are the Metamodel objects
8 9 10 |
# File 'lib/activefacts/cql/compiler/reading.rb', line 8 def reading @reading end |
#role_sequence ⇒ Object (readonly)
These are the Metamodel objects
8 9 10 |
# File 'lib/activefacts/cql/compiler/reading.rb', line 8 def role_sequence @role_sequence end |
#side_effects ⇒ Object (readonly)
Returns the value of attribute side_effects.
9 10 11 |
# File 'lib/activefacts/cql/compiler/reading.rb', line 9 def side_effects @side_effects end |
Instance Method Details
#adjust_for_match ⇒ Object
When we match an existing reading, we might have matched using additional adjectives. These adjectives have been removed from the phrases. If there are any remaining adjectives, we need to make a new RoleSequence, otherwise we can use the existing one.
515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 |
# File 'lib/activefacts/cql/compiler/reading.rb', line 515 def adjust_for_match return unless @applied_side_effects new_role_sequence_needed = @applied_side_effects.residual_adjectives role_phrases = [] reading_words = [] new_role_sequence_needed = false @phrases.each do |phrase| if phrase.is_a?(RoleRef) role_phrases << phrase reading_words << "{#{phrase.role_ref.ordinal}}" if phrase.role_name # || # phrase.leading_adjective || # phrase.trailing_adjective debug :matching, "phrase in matched reading has residual adjectives or role name, so needs a new role_sequence" if @fact_type.all_reading.size > 0 new_role_sequence_needed = true end else reading_words << phrase false end end debug :matching, "Reading '#{reading_words*' '}' #{new_role_sequence_needed ? 'requires' : 'does not require'} a new Role Sequence" constellation = @fact_type.constellation reading_text = reading_words*" " if new_role_sequence_needed @role_sequence = constellation.RoleSequence(:new) extra_adjectives = [] role_phrases.each_with_index do |rp, i| role_ref = constellation.RoleRef(@role_sequence, i, :role => rp.role_ref.role) if a = rp.leading_adjective role_ref.leading_adjective = a extra_adjectives << a+"-" end if a = rp.trailing_adjective role_ref.trailing_adjective = a extra_adjectives << "-"+a end if (a = rp.role_name) && (e = rp.role_ref.role.role_name) && a != e raise "Can't create new reading '#{reading_text}' for '#{reading.}' with alternate role name #{a}" extra_adjectives << "(as #{a})" end end debug :matching, "Making new role sequence for new reading #{reading_text} due to #{extra_adjectives.inspect}" else # Use existing RoleSequence @role_sequence = role_phrases[0].role_ref.role_sequence if @role_sequence.all_reading.detect{|r| r.text == reading_text } debug :matching, "No need to re-create identical reading for #{reading_text}" return @role_sequence else debug :matching, "Using existing role sequence for new reading '#{reading_text}'" end end if @fact_type.all_reading.detect{|r| r.text == reading_text} raise "Reading '#{@reading.}' already exists, so why are we creating a duplicate (with #{extra_adjectives.inspect})?" end constellation.Reading(@fact_type, @fact_type.all_reading.size, :role_sequence => @role_sequence, :text => reading_text) @role_sequence end |
#apply_side_effects(context, side_effects) ⇒ Object
388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 |
# File 'lib/activefacts/cql/compiler/reading.rb', line 388 def apply_side_effects(context, side_effects) @applied_side_effects = side_effects # Enact the side-effects of this match (delete the consumed adjectives): # Since this deletes words from the phrases, we do it in reverse order. debug :matching, "Apply side-effects" do side_effects.apply_all do |se| # We re-use the role_ref if possible (no extra adjectives were used, no rolename or join, etc). debug :matching, "side-effect means binding #{se.phrase.inspect} matches role ref #{se.role_ref.role.concept.name}" se.phrase.role_ref = se.role_ref changed = false # 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 = se.role_ref.trailing_adjective debug :matching, "Deleting matched trailing adjective '#{rra}'#{se.absorbed_followers>0 ? " in #{se.absorbed_followers} followers" : ""}" # These adjective(s) matched either an adjective here, or a follower word, or both. if a = se.phrase.trailing_adjective if a.size >= rra.size a.slice!(0, rra.size+1) # Remove the matched adjectives and the space (if any) se.phrase.wipe_trailing_adjective if a.empty? changed = true end elsif se.absorbed_followers > 0 se.phrase.wipe_trailing_adjective # This phrase is absorbing non-hyphenated adjective(s), which changes its binding se.phrase.trailing_adjective = @phrases.slice!(se.num+1, se.absorbed_followers)*' ' se.phrase.rebind context changed = true end end if rra = se.role_ref.leading_adjective debug :matching, "Deleting matched leading adjective '#{rra}'#{se.absorbed_precursors>0 ? " in #{se.absorbed_precursors} precursors" : ""}}" # These adjective(s) matched either an adjective here, or a precursor word, or both. if a = se.phrase.leading_adjective if a.size >= rra.size a.slice!(-rra.size, 1000) # Remove the matched adjectives and the space a.chop! se.phrase.wipe_leading_adjective if a.empty? changed = true end elsif se.absorbed_precursors > 0 se.phrase.wipe_leading_adjective # This phrase is absorbing non-hyphenated adjective(s), which changes its binding se.phrase.leading_adjective = @phrases.slice!(se.num-se.absorbed_precursors, se.absorbed_precursors)*' ' se.phrase.rebind context changed = true end end end end end |
#bind_roles(context) ⇒ Object
67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 |
# File 'lib/activefacts/cql/compiler/reading.rb', line 67 def bind_roles context role_names = role_refs.map{ |role_ref| role_ref.role_name }.compact # Check uniqueness of role names and subscripts within this reading: role_names.each do |rn| next if role_names.select{|rn2| rn2 == rn}.size == 1 raise "Duplicate role #{rn.is_a?(Integer) ? "subscript" : "name"} '#{rn}' in reading" end role_refs.each do |role_ref| role_ref.bind context # Include players in an objectification join, if any role_ref.objectification_join.each{|reading| reading.bind_roles(context)} if role_ref.objectification_join end end |
#display ⇒ Object
33 34 35 36 37 38 39 40 41 |
# File 'lib/activefacts/cql/compiler/reading.rb', line 33 def display "#{ @qualifiers && @qualifiers.size > 0 ? @qualifiers.inspect+' ' : nil }#{ @phrases.map{|p| p.to_s }*" " }#{ @context_note && ' '+@context_note.inspect }" end |
#identify_other_players(context) ⇒ Object
55 56 57 58 59 60 61 |
# File 'lib/activefacts/cql/compiler/reading.rb', line 55 def identify_other_players context role_refs.each do |role_ref| role_ref.identify_player(context) unless role_ref.player # Include players in an objectification join, if any role_ref.objectification_join.each{|reading| reading.identify_other_players(context)} if role_ref.objectification_join end end |
#identify_players_with_role_name(context) ⇒ Object
47 48 49 50 51 52 53 |
# File 'lib/activefacts/cql/compiler/reading.rb', line 47 def identify_players_with_role_name context role_refs.each do |role_ref| role_ref.identify_player(context) if role_ref.role_name # Include players in an objectification join, if any role_ref.objectification_join.each{|reading| reading.identify_players_with_role_name(context)} if role_ref.objectification_join end end |
#includes_literals ⇒ Object
63 64 65 |
# File 'lib/activefacts/cql/compiler/reading.rb', line 63 def includes_literals role_refs.detect{|role_ref| role_ref.literal || (ja = role_ref.objectification_join and ja.detect{|jr| jr.includes_literals })} end |
#inspect ⇒ Object
43 44 45 |
# File 'lib/activefacts/cql/compiler/reading.rb', line 43 def inspect "#{@qualifiers && @qualifiers.size > 0 ? @qualifiers.inspect+' ' : nil}#{@phrases.map{|p|p.inspect}*" "}#{@context_note && ' '+@context_note.inspect}" end |
#is_existential_type ⇒ Object
A reading that contains only the name of a Concept and no literal or reading text refers only to the existence of that Concept (as opposed to an instance of the concept).
27 28 29 30 31 |
# File 'lib/activefacts/cql/compiler/reading.rb', line 27 def is_existential_type @phrases.size == 1 and @phrases[0].is_a?(RoleRef) and !@phrases[0].literal end |
#make_embedded_constraints(vocabulary) ⇒ Object
578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 |
# File 'lib/activefacts/cql/compiler/reading.rb', line 578 def vocabulary role_refs.each do |role_ref| next unless role_ref.quantifier # puts "Quantifier #{role_ref.inspect} not implemented as a presence constraint" role_ref. vocabulary end if @qualifiers && @qualifiers.size > 0 rc = RingConstraint.new(@role_sequence, @qualifiers) rc.vocabulary = vocabulary rc.constellation = vocabulary.constellation rc.compile # REVISIT: Check maybe and other qualifiers: debug :constraint, "Need to make constraints for #{@qualifiers*', '}" if @qualifiers.size > 0 end end |
#make_fact_type(vocabulary) ⇒ Object
Make a new fact type with roles for this reading. Don’t assign @fact_type; that will happen when the reading is added
449 450 451 452 453 454 455 456 457 458 459 |
# File 'lib/activefacts/cql/compiler/reading.rb', line 449 def make_fact_type vocabulary fact_type = vocabulary.constellation.FactType(:new) debug :matching, "Making new fact type for #{@phrases.inspect}" do @phrases.each do |phrase| next unless phrase.is_a?(RoleRef) phrase.role = vocabulary.constellation.Role(fact_type, fact_type.all_role.size, :concept => phrase.player) phrase.role.role_name = phrase.role_name if phrase.role_name && phrase.role_name.is_a?(String) end end fact_type end |
#make_reading(vocabulary, fact_type) ⇒ Object
461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 |
# File 'lib/activefacts/cql/compiler/reading.rb', line 461 def make_reading vocabulary, fact_type @fact_type = fact_type constellation = vocabulary.constellation @role_sequence = constellation.RoleSequence(:new) reading_words = @phrases.clone index = 0 debug :matching, "Making new reading for #{@phrases.inspect}" do reading_words.map! do |phrase| if phrase.is_a?(RoleRef) # phrase.role will be set if this reading was used to make_fact_type. # Otherwise we have to find the existing role via the Binding. This is pretty ugly. unless phrase.role # Find another binding for this phrase which already has a role_ref to the same fact type: ref = phrase.binding.refs.detect{|ref| ref.role_ref && ref.role_ref.role.fact_type == fact_type} role_ref = ref.role_ref phrase.role = role_ref.role end rr = constellation.RoleRef(@role_sequence, index, :role => phrase.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 if phrase.value_constraint raise "The role #{rr.inspect} already has a value constraint" if rr.role.role_value_constraint phrase.value_constraint.constellation = fact_type.constellation rr.role.role_value_constraint = phrase.value_constraint.compile end index += 1 "{#{index-1}}" else phrase end end if existing = @fact_type.all_reading.detect{|r| r.text == reading_words*' ' and r.role_sequence.all_role_ref_in_order.map{|rr| rr.role.concept} == role_sequence.all_role_ref_in_order.map{|rr| rr.role.concept} } raise "Reading '#{existing.}' already exists, so why are we creating a duplicate?" end constellation.Reading(@fact_type, @fact_type.all_reading.size, :role_sequence => @role_sequence, :text => reading_words*" ") end end |
#match_existing_fact_type(context) ⇒ Object
This method chooses the existing fact type which matches most closely. It returns nil if there is none, or a ReadingMatchSideEffects object if matched.
As this match may not necessarily be used (depending on the side effects), no change is made to this Reading object - those will be done later.
100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 |
# File 'lib/activefacts/cql/compiler/reading.rb', line 100 def match_existing_fact_type context raise "Internal error, reading already matched, should not match again" if @fact_type rrs = role_refs players = rrs.map{|rr| rr.player} raise "Must identify players before matching fact types" if players.include? nil raise "A fact type must involve at least one object type, but there are none in '#{inspect}'" if players.size == 0 player_names = players.map{|p| p.name} debug :matching, "Looking for existing #{players.size}-ary fact types matching '#{inspect}'" do debug :matching, "Players are '#{player_names.inspect}'" # Match existing fact types in objectification joins first: rrs.each do |role_ref| next unless joins = role_ref.objectification_join and !joins.empty? role_ref.objectification_join.each do |oj| ft = oj.match_existing_fact_type(context) raise "Unrecognised fact type #{oj.display}" unless ft if (ft && ft.entity_type == role_ref.player) role_ref.objectification_of = ft oj.objectified_as = role_ref end end raise "#{role_ref.inspect} contains objectification joins that do not objectify it" unless role_ref.objectification_of end # For each role player, find the compatible types (the set of all subtypes and supertypes). # For a player that's an objectification, we don't allow implicit supertype joins = rrs.map do |role_ref| player = role_ref.player ((role_ref.objectification_of ? [] : player.supertypes_transitive) + player.subtypes_transitive).uniq end debug :matching, "Players must match '#{.map{|pa| pa.map{|p|p.name}}.inspect}'" # The candidate fact types have the right number of role players of related types. # If any role is played by a supertype or subtype of the required type, there's an implicit subtyping join candidate_fact_types = [0].map do || .all_role.select do |role| all_roles = role.fact_type.all_role next if all_roles.size != players.size # Wrong number of players next if role.fact_type.is_a?(ActiveFacts::Metamodel::ImplicitFactType) all_players = all_roles.map{|r| r.concept} # All the players of this candidate fact type next if [1..-1]. # We know the first player is compatible, check the rest detect do |player_types| # Make sure that there remains a compatible player # player_types is an array of the types compatible with the Nth player compatible_player = nil all_players.each_with_index do |p, i| if player_types.include?(p) compatible_player = p all_players.delete_at(i) break end end !compatible_player end true end. map{ |role| role.fact_type} end.flatten.uniq # If there is more than one possible exact match (same adjectives) with different subyping, the implicit join is ambiguous and is not allowed debug :matching, "Looking amongst #{candidate_fact_types.size} existing fact types for one matching '#{inspect}'" do matches = {} candidate_fact_types.map do |fact_type| fact_type.all_reading.map do |reading| next unless side_effects = reading_matches(fact_type, reading) matches[reading] = side_effects if side_effects end end # REVISIT: Side effects that leave extra adjectives should only be allowed if the # same extra adjectives exist in some other reading in the same declaration. # The extra adjectives are then necessary to associate the two role players # when consumed adjectives were required to bind to the underlying fact types. # This requires the final decision on fact type matching to be postponed until # the whole declaration has been processed and the extra adjectives can be matched. best_matches = matches.keys.sort_by{|match| # Between equivalents, prefer the one without a join on the first role (m = matches[match]).cost*2 + ((!(e = m.role_side_effects[0]) || e.cost) == 0 ? 0 : 1) } debug :matching_fails, "Found #{matches.size} valid matches#{matches.size > 0 ? ', best is '+best_matches[0]. : ''}" if matches.size > 1 first = matches[best_matches[0]] cost = first.cost equal_best = matches.select{|k,m| m.cost == cost} if equal_best.size > 1 and equal_best.detect{|k,m| !m.fact_type.is_a?(Metamodel::TypeInheritance)} # Complain if there's more than one equivalent cost match (unless all are TypeInheritance): raise "#{@phrases.inspect} could match any of the following:\n\t"+ best_matches.map { |reading| reading. + " with " + matches[reading].describe } * "\n\t" end end if matches.size >= 1 @reading = best_matches[0] @side_effects = matches[@reading] @fact_type = @side_effects.fact_type debug :matching, "Matched '#{@fact_type.default_reading}'" apply_side_effects(context, @side_effects) return @fact_type end end debug :matching, "No fact type matched, candidates were '#{candidate_fact_types.map{|ft| ft.default_reading}*"', '"}'" end @fact_type = nil end |
#phrases_match(phrases) ⇒ Object
83 84 85 86 87 88 89 90 91 92 93 |
# File 'lib/activefacts/cql/compiler/reading.rb', line 83 def phrases_match(phrases) @phrases.zip(phrases).each do |mine, theirs| return false if mine.is_a?(RoleRef) != theirs.is_a?(RoleRef) if mine.is_a?(RoleRef) return false unless mine.key == theirs.key else return false unless mine == theirs end end true end |
#reading_matches(fact_type, reading) ⇒ Object
The ActiveFacts::Metamodel::Reading passed has the same players as this Compiler::Reading. Does it match? Twisty curves. This is a complex bit of code! Find whether the phrases of this reading 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 reading's
236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 |
# File 'lib/activefacts/cql/compiler/reading.rb', line 236 def reading_matches(fact_type, reading) side_effects = [] # An array of items for each role, describing any side-effects of the match. intervening_words = nil residual_adjectives = false debug :matching_fails, "Does '#{@phrases.inspect}' match '#{reading.}'" do phrase_num = 0 reading_parts = reading.text.split(/\s+/) # Check that the number of roles matches (skipped, the caller should have done it): # return nil unless reading_parts.select{|p| p =~ /\{(\d+)\}/}.size == role_refs.size reading_parts.each do |element| if element !~ /\{(\d+)\}/ # Just a word; it must match unless @phrases[phrase_num] == element debug :matching_fails, "Mismatched ordinary word #{@phrases[phrase_num].inspect} (wanted #{element})" return nil end phrase_num += 1 next else role_ref = reading.role_sequence.all_role_ref.sort_by{|rr| rr.ordinal}[$1.to_i] end # 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?(RoleRef) next_player_phrase = phrase next_player_phrase_num = phrase_num-1 break else intervening_words << phrase end end player = role_ref.role.concept return nil unless next_player_phrase # reading has more players than we do. # The next player must match: common_supertype = nil if next_player_phrase.player != player # This relies on the supertypes being in breadth-first order: common_supertype = (next_player_phrase.player.supertypes_transitive & player.supertypes_transitive)[0] if !common_supertype debug :matching_fails, "Reading discounted because next player #{player.name} doesn't match #{next_player_phrase.player.name}" return nil end debug :matching_fails, "Subtype join is required between #{player.name} and #{next_player_phrase.player.name} via common supertype #{common_supertype.name}" end # It's the right player. Do the adjectives match? This must include the intervening_words, if any. role_has_residual_adjectives = false 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? && 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 role_has_residual_adjectives = true if phrase_la.size > la.size # The leading adjectives and the player matched! Check the trailing adjectives. absorbed_precursors = intervening_words.size intervening_words = [] elsif intervening_words.size > 0 || next_player_phrase.leading_adjective role_has_residual_adjectives = true end 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 < ta.size break unless (word = @phrases[phrase_num+i]).is_a?(String) phrase_ta << word i += 1 end return nil if ta != phrase_ta[0,ta.size] role_has_residual_adjectives = true if phrase_ta.size > ta.size || i < ta.size absorbed_followers = i phrase_num += i # Skip following words that were consumed as trailing adjectives elsif next_player_phrase.trailing_adjective role_has_residual_adjectives = true end # REVISIT: I'm not even sure I should be caring about role names here. # Role names are on roles, and are only useful within the fact type definition. # At some point, we need to worry about role names on readings within fact type derivations, # which means they'll move to the Role Ref class; but even then they only match within the # definition that creates that Role Ref. =begin if a = (!phrase.role_name.is_a?(Integer) && phrase.role_name) and e = role_ref.role.role_name and a != e debug :matching, "Role names #{e.inspect} for #{player.name} and #{a.inspect} for #{next_player_phrase.player.name} don't match" return nil end =end residual_adjectives ||= role_has_residual_adjectives if residual_adjectives && next_player_phrase.binding.refs.size == 1 debug :matching_fails, "Residual adjectives have no other purpose, so this match fails" return nil end # The phrases matched this reading's next role_ref, save data to apply the side-effects: debug :matching_fails, "Saving side effects for #{next_player_phrase.term}, absorbs #{absorbed_precursors}/#{absorbed_followers}#{common_supertype ? ', join over supertype '+ common_supertype.name : ''}" if absorbed_precursors+absorbed_followers+(common_supertype ? 1 : 0) > 0 side_effects << ReadingMatchSideEffect.new(next_player_phrase, role_ref, next_player_phrase_num, absorbed_precursors, absorbed_followers, common_supertype, role_has_residual_adjectives) end if phrase_num != @phrases.size || !intervening_words.empty? debug :matching_fails, "Extra words #{(intervening_words + @phrases[phrase_num..-1]).inspect}" return nil end if fact_type.is_a?(Metamodel::TypeInheritance) # There may be only one subtyping join on a TypeInheritance fact type. ti_joins = side_effects.select{|se| se.common_supertype} if ti_joins.size > 1 # Not allowed debug :matching_fails, "Can't have more than one subtyping join on a TypeInheritance fact type" return nil end if ti = ti_joins[0] # The Type Inheritance join must continue in the same direction as this reading. allowed = fact_type.supertype == ti.role_ref.role.concept ? fact_type.subtype.supertypes_transitive : fact_type.supertype.subtypes_transitive if !allowed.include?(ti.common_supertype) debug :matching_fails, "Implicit subtyping join extends in the wrong direction" return nil end end end debug :matching, "Matched reading '#{reading.}' with #{side_effects.map{|se| se.absorbed_precursors+se.absorbed_followers + (se.common_supertype ? 1 : 0) }.inspect} side effects#{residual_adjectives ? ' and residual adjectives' : ''}" end # There will be one side_effects for each role player ReadingMatchSideEffects.new(fact_type, self, residual_adjectives, side_effects) end |
#role_refs ⇒ Object
21 22 23 |
# File 'lib/activefacts/cql/compiler/reading.rb', line 21 def role_refs @phrases.select{|r| r.is_a?(RoleRef)} end |