activefacts-cql 1.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +11 -0
  3. data/.rspec +1 -0
  4. data/.travis.yml +4 -0
  5. data/Gemfile +10 -0
  6. data/LICENSE.txt +21 -0
  7. data/README.md +19 -0
  8. data/Rakefile +6 -0
  9. data/activefacts-cql.gemspec +29 -0
  10. data/bin/setup +7 -0
  11. data/lib/activefacts/cql.rb +7 -0
  12. data/lib/activefacts/cql/.gitignore +0 -0
  13. data/lib/activefacts/cql/Rakefile +14 -0
  14. data/lib/activefacts/cql/compiler.rb +156 -0
  15. data/lib/activefacts/cql/compiler/clause.rb +1137 -0
  16. data/lib/activefacts/cql/compiler/constraint.rb +581 -0
  17. data/lib/activefacts/cql/compiler/entity_type.rb +457 -0
  18. data/lib/activefacts/cql/compiler/expression.rb +443 -0
  19. data/lib/activefacts/cql/compiler/fact.rb +390 -0
  20. data/lib/activefacts/cql/compiler/fact_type.rb +421 -0
  21. data/lib/activefacts/cql/compiler/query.rb +106 -0
  22. data/lib/activefacts/cql/compiler/shared.rb +161 -0
  23. data/lib/activefacts/cql/compiler/value_type.rb +174 -0
  24. data/lib/activefacts/cql/parser.rb +234 -0
  25. data/lib/activefacts/cql/parser/CQLParser.treetop +167 -0
  26. data/lib/activefacts/cql/parser/Context.treetop +48 -0
  27. data/lib/activefacts/cql/parser/Expressions.treetop +67 -0
  28. data/lib/activefacts/cql/parser/FactTypes.treetop +358 -0
  29. data/lib/activefacts/cql/parser/Language/English.treetop +315 -0
  30. data/lib/activefacts/cql/parser/Language/French.treetop +315 -0
  31. data/lib/activefacts/cql/parser/Language/Mandarin.treetop +304 -0
  32. data/lib/activefacts/cql/parser/LexicalRules.treetop +253 -0
  33. data/lib/activefacts/cql/parser/ObjectTypes.treetop +210 -0
  34. data/lib/activefacts/cql/parser/Terms.treetop +183 -0
  35. data/lib/activefacts/cql/parser/ValueTypes.treetop +202 -0
  36. data/lib/activefacts/cql/parser/nodes.rb +49 -0
  37. data/lib/activefacts/cql/require.rb +36 -0
  38. data/lib/activefacts/cql/verbaliser.rb +804 -0
  39. data/lib/activefacts/cql/version.rb +5 -0
  40. data/lib/activefacts/input/cql.rb +43 -0
  41. data/lib/rubygems_plugin.rb +12 -0
  42. metadata +167 -0
@@ -0,0 +1,421 @@
1
+ module ActiveFacts
2
+ module CQL
3
+ class Compiler < ActiveFacts::CQL::Parser
4
+
5
+ class Query < ObjectType # A fact type which is objectified (whether derived or not) is also an ObjectType
6
+ attr_reader :context # Exposed for testing purposes
7
+ attr_reader :conditions
8
+
9
+ def initialize name, conditions = nil, returning = nil
10
+ super name
11
+ @conditions = conditions
12
+ @returning = returning || []
13
+ end
14
+
15
+ def to_s
16
+ inspect
17
+ end
18
+
19
+ def inspect
20
+ "Query: " +
21
+ if @conditions.empty?
22
+ ''
23
+ else
24
+ 'where ' + @conditions.map{|c| ((j=c.conjunction) ? j+' ' : '') + c.inspect}*' '
25
+ end
26
+ # REVISIT: @returning = returning
27
+ end
28
+
29
+ def prepare_roles clauses = nil
30
+ trace :binding, "preparing roles" do
31
+ @context ||= CompilationContext.new(@vocabulary)
32
+ @context.bind clauses||[], @conditions, @returning
33
+ end
34
+ end
35
+
36
+ def compile
37
+ # Match roles with players, and match clauses with existing fact types
38
+ prepare_roles unless @context
39
+
40
+ @context.left_contraction_allowed = true
41
+ match_condition_fact_types
42
+
43
+ # Build the query:
44
+ unless @conditions.empty? and @returning.empty?
45
+ trace :query, "building query for derived fact type (returning #{@returning}) with #{@conditions.size} conditions: (#{@conditions.map{|c|c.inspect}*', '})" do
46
+ @query = build_variables(@conditions.flatten)
47
+ @roles_by_binding = build_all_steps(@conditions)
48
+ @query.validate
49
+ @query
50
+ end
51
+ end
52
+ @context.left_contraction_allowed = false
53
+ @query
54
+ end
55
+
56
+ def match_condition_fact_types
57
+ @conditions.each do |condition|
58
+ trace :projection, "matching condition fact_type #{condition.inspect}" do
59
+ fact_type = condition.match_existing_fact_type @context
60
+ raise "Unrecognised fact type #{condition.inspect} in #{self.class}" unless fact_type
61
+ end
62
+ end
63
+ end
64
+
65
+ def detect_projection_by_equality condition
66
+ return false unless condition.is_a?(Comparison)
67
+ if is_projected_role(condition.e1)
68
+ condition.project :left
69
+ elsif is_projected_role(condition.e2)
70
+ condition.project :right
71
+ end
72
+ end
73
+
74
+ def is_projected_role(rr)
75
+ false
76
+ end
77
+ end
78
+
79
+ class FactType < ActiveFacts::CQL::Compiler::Query
80
+ attr_reader :fact_type
81
+ attr_reader :clauses
82
+ attr_writer :name
83
+ attr_writer :pragmas
84
+
85
+ def initialize name, clauses, conditions = nil, returning = nil
86
+ super name, conditions, returning
87
+ @clauses = clauses
88
+ if ec = @clauses.detect{|r| r.is_equality_comparison}
89
+ @clauses.delete(ec)
90
+ @conditions.unshift(ec)
91
+ end
92
+ end
93
+
94
+ def compile
95
+ # Process:
96
+ # * Identify all role players (must be done for both clauses and conditions BEFORE matching clauses)
97
+ # * Match up the players in all @clauses
98
+ # - Be aware of multiple roles with the same player, and bind tight/loose using subscripts/role_names/adjectives
99
+ # - Reject the fact type unless all @clauses match
100
+ # * Find any existing fact type that matches any clause, or make a new one
101
+ # * Add each clause that doesn't already exist in the fact type
102
+ # * Create any ring constraint(s)
103
+ # * Create embedded presence constraints
104
+ # * If fact type has no identifier, arrange to create the implicit one (before first use?)
105
+ # * Objectify the fact type if @name
106
+ #
107
+
108
+ # Prepare to objectify the fact type (so readings for implicit fact types can be created)
109
+ if @name
110
+ entity_type = @vocabulary.valid_entity_type_name(@name)
111
+ raise "You can't objectify #{@name}, it already exists" if entity_type
112
+ @entity_type = @constellation.EntityType(@vocabulary, @name, :fact_type => @fact_type, :concept => :new)
113
+ end
114
+
115
+ prepare_roles @clauses
116
+
117
+ # REVISIT: Compiling the conditions here make it impossible to define a self-referential (transitive) query.
118
+ return super if @clauses.empty? # It's a query
119
+
120
+ return true unless @clauses.size > 0 # Nothing interesting was said.
121
+
122
+ if @entity_type
123
+ # Extract readings for implicit fact types
124
+ @implicit_readings, @clauses =
125
+ @clauses.partition do |clause|
126
+ clause.refs.size == 2 and clause.refs.detect{|ref| ref.player == @entity_type}
127
+ end
128
+ end
129
+
130
+ # See if any existing fact type is being invoked (presumably to objectify or extend it)
131
+ @fact_type = check_compatibility_of_matched_clauses
132
+ verify_matching_roles # All clauses of a fact type must have the same roles
133
+
134
+ if !@fact_type
135
+ # Make a new fact type:
136
+ first_clause = @clauses[0]
137
+ @fact_type = first_clause.make_fact_type(@vocabulary)
138
+ first_clause.make_reading(@vocabulary, @fact_type)
139
+ first_clause.make_embedded_constraints vocabulary
140
+ @fact_type.create_implicit_fact_type_for_unary if @fact_type.all_role.size == 1 && !@name
141
+ @existing_clauses = [first_clause]
142
+ elsif (n = @clauses.size - @existing_clauses.size) > 0
143
+ raise "Cannot extend a negated fact type" if @existing_clauses.detect {|clause| clause.certainty == false }
144
+ trace :binding, "Extending existing fact type with #{n} new readings"
145
+ end
146
+
147
+ # Now make any new readings:
148
+ new_clauses = @clauses - @existing_clauses
149
+ new_clauses.each do |clause|
150
+ clause.make_reading(@vocabulary, @fact_type)
151
+ clause.make_embedded_constraints vocabulary
152
+ end
153
+
154
+ # If a clause matched but the match left extra adjectives, we need to make a new RoleSequence for them:
155
+ @existing_clauses.each do |clause|
156
+ clause.adjust_for_match
157
+ # Add any new constraints that we found in the match (presence, ring, etc)
158
+ clause.make_embedded_constraints(vocabulary)
159
+ end
160
+
161
+ if @name
162
+ # Objectify the fact type:
163
+ @entity_type.fact_type = @fact_type
164
+ if @fact_type.entity_type and @name != @fact_type.entity_type.name
165
+ raise "Cannot objectify fact type as #{@name} and as #{@fact_type.entity_type.name}"
166
+ end
167
+ ifts = @entity_type.create_implicit_fact_types
168
+ create_implicit_readings(ifts)
169
+ if @pragmas
170
+ @entity_type.is_independent = true if @pragmas.delete('independent')
171
+ end
172
+ end
173
+ @pragmas.each do |p|
174
+ @constellation.ConceptAnnotation(:concept => (@entity_type||@fact_type).concept, :mapping_annotation => p)
175
+ end if @pragmas
176
+
177
+ @clauses.each do |clause|
178
+ next unless clause.context_note
179
+ clause.context_note.compile(@constellation, @fact_type)
180
+ end
181
+
182
+ # REVISIT: This isn't the thing to do long term; it needs to be added later only if we find no other constraint
183
+ make_default_identifier_for_fact_type if @conditions.empty?
184
+
185
+ # Compile the conditions:
186
+ super
187
+ unless @conditions.empty?
188
+ @clauses.each do |clause|
189
+ project_clause_roles(clause)
190
+ end
191
+ end
192
+
193
+ @fact_type
194
+ end
195
+
196
+ def create_implicit_readings(ifts)
197
+ @implicit_readings.each do |clause|
198
+ #next false unless clause.refs.size == 2 and clause.refs.detect{|r| r.role.object_type == @entity_type }
199
+ other_ref = clause.refs.detect{|ref| ref.player != @entity_type}
200
+ ift = ifts.detect do |ift|
201
+ other_ref.binding.refs.map{|r| r.role}.include?(ift.implying_role)
202
+ end
203
+ next unless ift
204
+ # This clause is a reading for the implicit LinkFactType ift
205
+ i = 0
206
+ reading_text = clause.phrases.map do |phrase|
207
+ next phrase if String === phrase
208
+ "{#{(i += 1)-1}}"
209
+ end*' '
210
+ ir = ActiveFacts::Metamodel::LinkFactType::ImplicitReading
211
+ irrs = ir::ImplicitReadingRoleSequence
212
+ irrf = irrs::ImplicitReadingRoleRef
213
+ reading = ir.new(ift, reading_text)
214
+ ift.add_reading reading
215
+ end
216
+ end
217
+
218
+ def project_clause_roles(clause)
219
+ # Attach the clause's role references to the projected roles of the query
220
+ clause.refs.each_with_index do |ref, i|
221
+ role, play = @roles_by_binding[ref.binding]
222
+ raise "#{ref} must be a role projected from the conditions" unless role
223
+ raise "#{ref} has already-projected play!" if play.role_ref
224
+ ref.role_ref.play = play
225
+ end
226
+ end
227
+
228
+ # A Comparison in the conditions which projects a role is not treated as a comparison, just as projection
229
+ def is_projected_role(rr)
230
+ # rr is a RoleRef on one side of the comparison.
231
+ # If its binding contains a reference from our readings, it's projected.
232
+ rr.binding.refs.detect do |ref|
233
+ @readings.include?(ref.reading)
234
+ end
235
+ end
236
+
237
+ def check_compatibility_of_matched_clauses
238
+ # REVISIT: If we have conditions, we must match all given clauses exactly (no side-effects)
239
+ @existing_clauses = @clauses.
240
+ select{ |clause| clause.match_existing_fact_type @context }.
241
+ # subtyping match is not allowed for fact type extension:
242
+ reject{ |clause| clause.side_effects.role_side_effects.detect{|se| se.common_supertype } }.
243
+ sort_by{ |clause| clause.side_effects.cost }
244
+ fact_types = @existing_clauses.map{ |clause| clause.fact_type }.uniq.compact
245
+
246
+ return nil if fact_types.empty? # There are no matched fact types
247
+
248
+ if @clauses.size == 1 && @existing_clauses[0].side_effects.cost != 0
249
+ trace :matching, "There's only a single clause, but it's not an exact match"
250
+ return nil
251
+ end
252
+
253
+ if (fact_types.size > 1)
254
+ # There must be only one fact type with exact matches:
255
+ if @existing_clauses[0].side_effects.cost != 0 or
256
+ @existing_clauses.detect{|r| r.fact_type != fact_types[0] && r.side_effects.cost == 0 }
257
+ raise "Clauses match different existing fact types '#{fact_types.map{|ft| ft.preferred_reading.expand}*"', '"}'"
258
+ end
259
+ # Try to make false-matched clauses match the chosen one instead
260
+ @existing_clauses.reject!{|r| r.fact_type != fact_types[0] }
261
+ end
262
+ fact_types[0]
263
+ end
264
+
265
+ def make_default_identifier_for_fact_type(prefer = true)
266
+ # Non-objectified unaries don't need a PI:
267
+ return if @fact_type.all_role.size == 1 && !@fact_type.entity_type
268
+
269
+ # It's possible that this fact type is objectified and inherits identification through a supertype.
270
+ return if @fact_type.entity_type and @fact_type.entity_type.all_type_inheritance_as_subtype.detect{|ti| ti.provides_identification}
271
+
272
+ # If it's a non-objectified binary and there's an alethic uniqueness constraint over the fact type already, we're done
273
+ return if !@fact_type.entity_type &&
274
+ @fact_type.all_role.size == 2 &&
275
+ @fact_type.all_role.
276
+ detect do |r|
277
+ r.all_role_ref.detect do |rr|
278
+ rr.role_sequence.all_presence_constraint.detect do |pc|
279
+ pc.max_frequency == 1 && !pc.enforcement
280
+ end
281
+ end
282
+ end
283
+
284
+ # If there's an existing presence constraint that can be converted into a PC, do that:
285
+ @clauses.each do |clause|
286
+ ref = clause.refs[-1] or next
287
+ epc = ref.embedded_presence_constraint or next
288
+ epc.max_frequency == 1 or next
289
+ next if epc.enforcement
290
+ trace :constraint, "Converting UC into PI for #{@fact_type.entity_type.name}"
291
+ epc.is_preferred_identifier = true
292
+ return
293
+ end
294
+
295
+ # We need to check uniqueness constraints after processing the whole vocabulary
296
+ # raise "Fact type must be named as it has no identifying uniqueness constraint" unless @name || @fact_type.all_role.size == 1
297
+ trace :constraint, "Need to check #{@fact_type.default_reading.inspect} for a uniqueness constraint"
298
+ fact_type.check_and_add_spanning_uniqueness_constraint = proc do
299
+ trace :constraint, "Checking #{@fact_type.default_reading.inspect} for a uniqueness constraint"
300
+ existing_pc = nil
301
+ found = @fact_type.all_role.
302
+ detect do |role|
303
+ role.all_role_ref.detect do |rr|
304
+ # This RoleSequence, to be relevant, must only reference roles of this fact type
305
+ rr.role_sequence.all_role_ref.all? {|rr2| rr2.role.fact_type == @fact_type} and
306
+ # The RoleSequence must have at least one uniqueness constraint
307
+ rr.role_sequence.all_presence_constraint.detect do |pc|
308
+ if pc.max_frequency == 1
309
+ existing_pc = pc
310
+ end
311
+ end
312
+ end
313
+ end
314
+ true # A place for a breakpoint
315
+
316
+ if !found
317
+ # There's no existing uniqueness constraint over the roles of this fact type. Add one
318
+ pc = @constellation.PresenceConstraint(
319
+ :new,
320
+ :vocabulary => @vocabulary,
321
+ :name => @fact_type.entity_type ? @fact_type.entity_type.name+"PK" : '',
322
+ :role_sequence => (rs = @fact_type.preferred_reading.role_sequence),
323
+ :max_frequency => 1,
324
+ :is_preferred_identifier => true # (prefer || !!@fact_type.entity_type)
325
+ )
326
+ pc.concept.topic = @fact_type.concept.topic
327
+ trace :constraint, "Made new fact type implicit PC GUID=#{pc.concept.guid} #{pc.name} min=nil max=1 over #{rs.describe}"
328
+ elsif pc
329
+ trace :constraint, "Will rely on existing UC GUID=#{pc.concept.guid} #{pc.name} to be used as PI over #{rs.describe}"
330
+ end
331
+ end
332
+ end
333
+
334
+ def has_more_adjectives(less, more)
335
+ return false if less.leading_adjective && less.leading_adjective != more.leading_adjective
336
+ return false if less.trailing_adjective && less.trailing_adjective != more.trailing_adjective
337
+ return true
338
+ end
339
+
340
+ def verify_matching_roles
341
+ refs_by_clause_and_key = {}
342
+ clauses_by_refs =
343
+ @clauses.inject({}) do |hash, clause|
344
+ keys = clause.refs.map do |ref|
345
+ key = ref.key.compact
346
+ refs_by_clause_and_key[[clause, key]] = ref
347
+ key
348
+ end.sort_by{|a| a.map{|k|k.to_s}}
349
+ raise "Fact types may not have duplicate roles" if keys.uniq.size < keys.size
350
+ (hash[keys] ||= []) << clause
351
+ hash
352
+ end
353
+
354
+ if clauses_by_refs.size != 1 and @conditions.empty?
355
+ # Attempt loose binding here; it might merge some Compiler::References to share the same Variables
356
+ variants = clauses_by_refs.keys
357
+ (clauses_by_refs.size-1).downto(1) do |m| # Start with the last one
358
+ 0.upto(m-1) do |l| # Try to rebind onto any lower one
359
+ common = variants[m]&variants[l]
360
+ clauses_l = clauses_by_refs[variants[l]]
361
+ clauses_m = clauses_by_refs[variants[m]]
362
+ l_keys = variants[l]-common
363
+ m_keys = variants[m]-common
364
+ trace :binding, "Try to collapse variant #{m} onto #{l}; diffs are #{l_keys.inspect} -> #{m_keys.inspect}"
365
+ rebindings = 0
366
+ l_keys.each_with_index do |l_key, i|
367
+ # Find possible rebinding candidates; there must be exactly one.
368
+ candidates = []
369
+ (0...m_keys.size).each do |j|
370
+ m_key = m_keys[j]
371
+ l_ref = refs_by_clause_and_key[[clauses_l[0], l_key]]
372
+ m_ref = refs_by_clause_and_key[[clauses_m[0], m_key]]
373
+ trace :binding, "Can we match #{l_ref.inspect} (#{i}) with #{m_ref.inspect} (#{j})?"
374
+ next if m_ref.player != l_ref.player
375
+ if has_more_adjectives(m_ref, l_ref)
376
+ trace :binding, "can rebind #{m_ref.inspect} to #{l_ref.inspect}"
377
+ candidates << [m_ref, l_ref]
378
+ elsif has_more_adjectives(l_ref, m_ref)
379
+ trace :binding, "can rebind #{l_ref.inspect} to #{m_ref.inspect}"
380
+ candidates << [l_ref, m_ref]
381
+ end
382
+ end
383
+
384
+ # trace :binding, "found #{candidates.size} rebinding candidates for this role"
385
+ trace :binding, "rebinding is ambiguous so not attempted" if candidates.size > 1
386
+ if (candidates.size == 1)
387
+ candidates[0][0].rebind_to(@context, candidates[0][1])
388
+ rebindings += 1
389
+ end
390
+
391
+ end
392
+ if (rebindings == l_keys.size)
393
+ # Successfully rebound this fact type
394
+ trace :binding, "Successfully rebound clauses #{clauses_l.map{|r|r.inspect}*'; '} on to #{clauses_m.map{|r|r.inspect}*'; '}"
395
+ break
396
+ else
397
+ # No point continuing, we failed on this one.
398
+ raise "All readings in a fact type definition must have matching role players, compare (#{
399
+ clauses_by_refs.keys.map do |keys|
400
+ keys.map{|key| key*'-' }*", "
401
+ end*") with ("
402
+ })"
403
+ end
404
+
405
+ end
406
+ end
407
+ # else all clauses already matched
408
+ end
409
+ end
410
+
411
+ def inspect
412
+ s = super
413
+ "FactType: #{@conditions.size > 0 ? super+' ' : '' }#{@clauses.inspect}" +
414
+ (@pragmas && @pragmas.size > 0 ? ", pragmas [#{@pragmas.flatten.sort*','}]" : '')
415
+
416
+ # REVISIT: @returning = returning
417
+ end
418
+ end
419
+ end
420
+ end
421
+ end
@@ -0,0 +1,106 @@
1
+ module ActiveFacts
2
+ module CQL
3
+ class Compiler < ActiveFacts::CQL::Parser
4
+ class Definition
5
+ # Make a Variable for every binding present in these clauses
6
+ def build_variables(clauses_list)
7
+ trace :query, "Building variables" do
8
+ query = @constellation.Query(:new)
9
+ all_bindings_in_clauses(clauses_list).
10
+ each do |binding|
11
+ trace :query, "Creating variable #{query.all_variable.size} for #{binding.inspect}"
12
+ binding.variable = @constellation.Variable(query, query.all_variable.size, :object_type => binding.player)
13
+ if literal = binding.refs.detect{|r| r.literal}
14
+ if literal.kind_of?(ActiveFacts::CQL::Compiler::Reference)
15
+ # REVISIT: Fix this crappy ad-hoc polymorphism hack
16
+ literal = literal.literal
17
+ end
18
+ unit = @constellation.Unit.detect{|k, v| [v.name, v.plural_name].include? literal.unit} if literal.unit
19
+ binding.variable.value = [literal.literal.to_s, literal.literal.is_a?(String), unit]
20
+ end
21
+ end
22
+ query
23
+ end
24
+ end
25
+
26
+ def build_all_steps(clauses_list)
27
+ roles_by_binding = {}
28
+ trace :query, "Building steps" do
29
+ clauses_list.each do |clause|
30
+ build_step(clause, roles_by_binding)
31
+ end
32
+ end
33
+ roles_by_binding
34
+ end
35
+
36
+ def build_step clause, roles_by_binding = {}, parent_variable = nil
37
+ return unless clause.refs.size > 0 # Empty clause... really?
38
+
39
+ step = @constellation.Step(
40
+ :guid => :new,
41
+ :fact_type => clause.fact_type,
42
+ :alternative_set => nil,
43
+ :is_disallowed => clause.certainty == false,
44
+ :is_optional => clause.certainty == nil
45
+ )
46
+
47
+ trace :query, "Creating Plays for #{clause.inspect} with #{clause.refs.size} refs" do
48
+ is_input = true
49
+ clause.refs.each do |ref|
50
+ # These refs are the Compiler::References, which have associated Metamodel::RoleRefs,
51
+ # but we need to create Plays for those roles.
52
+ # REVISIT: Plays may need to save residual_adjectives
53
+ binding = ref.binding
54
+ role = (ref && ref.role) || (ref.role_ref && ref.role_ref.role)
55
+
56
+ objectification_step = nil
57
+ if ref.nested_clauses
58
+ ref.nested_clauses.each do |nested_clause|
59
+ objectification_step = build_step nested_clause, roles_by_binding
60
+ if ref.binding.player.is_a?(ActiveFacts::Metamodel::EntityType) and
61
+ ref.binding.player.fact_type == nested_clause.fact_type
62
+ objectification_step.objectification_variable = binding.variable
63
+ end
64
+ end
65
+ end
66
+ if clause.is_naked_object_type
67
+ raise "#{self} lacks a proper objectification" if clause.refs[0].nested_clauses and !objectification_step
68
+ return objectification_step
69
+ end
70
+
71
+ if binding.variable.object_type != role.object_type # Type mismatch
72
+ if binding.variable.object_type.common_supertype(role.object_type)
73
+ # REVISIT: there's an implicit subtyping step here, create it; then always raise the error here.
74
+ # I don't want to do this for now because the verbaliser will always verbalise all steps.
75
+ raise "Disallowing implicit subtyping step from #{role.object_type.name} to #{binding.variable.object_type.name} in #{clause.fact_type.default_reading.inspect}"
76
+ end
77
+ raise "A #{role.object_type.name} cannot satisfy #{binding.variable.object_type.name} in #{clause.fact_type.default_reading.inspect}"
78
+ end
79
+
80
+ trace :query, "Creating Play for #{ref.inspect}"
81
+ play = @constellation.Play(:step => step, :role => role, :variable => binding.variable)
82
+ play.is_input = is_input
83
+ is_input = false
84
+
85
+ roles_by_binding[binding] = [role, play]
86
+ end
87
+ end
88
+
89
+ step
90
+ end
91
+
92
+ # Return the unique array of all bindings in these clauses, including in objectification steps
93
+ def all_bindings_in_clauses clauses
94
+ clauses.map do |clause|
95
+ clause.refs.map do |ref|
96
+ raise "Binding reference #{ref.inspect} is not bound to a binding" unless ref.binding
97
+ [ref.binding] + (ref.nested_clauses ? all_bindings_in_clauses(ref.nested_clauses) : [])
98
+ end
99
+ end.
100
+ flatten.
101
+ uniq
102
+ end
103
+ end
104
+ end
105
+ end
106
+ end