activefacts-cql 1.7.1

Sign up to get free protection for your applications and to get access to all the features.
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,443 @@
1
+ module ActiveFacts
2
+ module CQL
3
+ class Compiler
4
+
5
+ # An Operation is a binary or ternary fact type involving an operator,
6
+ # a result, and one or two operands.
7
+ # Viewed as a result, it behaves like a Reference with a nested Clause.
8
+ # Viewed as a fact type, it behaves like a Clause.
9
+ #
10
+ # The only exception here is an equality comparison, where it may
11
+ # turn out that the equality is merely a projection. In this case
12
+ # the Operation is dropped from the clauses and is replaced by the
13
+ # projected operand.
14
+ #
15
+ # Each operand may be a Literal, a Reference, or another Operation,
16
+ # so we need to recurse down the tree to build the query.
17
+ #
18
+ class Operation
19
+ # Reference (in)compatibility:
20
+ [ :term, :leading_adjective, :trailing_adjective, :role_name, :quantifier,
21
+ :value_constraint, :embedded_presence_constraint, :literal
22
+ ].each do |s|
23
+ define_method(s) { raise "Unexpected call to Operation\##{s}" }
24
+ define_method(:"#{s}=") { raise "Unexpected call to Operation\##{s}=" }
25
+ end
26
+ def role_name; nil; end
27
+ def leading_adjective; nil; end
28
+ def trailing_adjective; nil; end
29
+ def value_constraint; nil; end
30
+ def literal; nil; end
31
+ def side_effects; nil; end
32
+ attr_accessor :player # What ObjectType does the Binding denote
33
+ attr_accessor :binding # What Binding for that ObjectType
34
+ attr_accessor :clause # What clause does the result participate in?
35
+ attr_accessor :role # Which Role of this ObjectType
36
+ attr_accessor :role_ref # Which RoleRef to that Role
37
+ attr_accessor :certainty # nil, true, false -> maybe, definitely, not
38
+ def nested_clauses; @nested_clauses ||= [self]; end
39
+ def clause; self; end
40
+ def objectification_of; @fact_type; end
41
+ # Clause (in)compatibility:
42
+ [ :phrases, :qualifiers, :context_note, :reading, :role_sequence, :fact
43
+ ].each do |s|
44
+ define_method(s) { raise "Unexpected call to Operation\##{s}" }
45
+ define_method(:"#{s}=") { raise "Unexpected call to Operation\##{s}=" }
46
+ end
47
+ def conjunction; nil; end
48
+ attr_reader :fact_type
49
+ def objectified_as; self; end # The Reference which objectified this fact type
50
+
51
+ def initialize
52
+ @certainty = true # Assume it's definite
53
+ end
54
+
55
+ def operands context = nil
56
+ raise "REVISIT: Implement operand enumeration in the operator subclass #{self.class.name}"
57
+ end
58
+
59
+ def identify_players_with_role_name context
60
+ # Just recurse, there's no way (yet: REVISIT?) to add a role name to the result of an expression
61
+ refs.each { |o|
62
+ o.identify_players_with_role_name(context)
63
+ }
64
+ # As yet, an operation cannot have a role name:
65
+ # identify_player context if role_name
66
+ end
67
+
68
+ def identify_other_players context
69
+ # Just recurse, there's no way (yet: REVISIT?) to add a role name to the result of an expression
70
+ refs.each { |o|
71
+ o.identify_other_players(context)
72
+ }
73
+ identify_player context
74
+ end
75
+
76
+ def bind context
77
+ refs.each do |o|
78
+ o.bind context
79
+ end
80
+ name = result_type_name(context)
81
+ @player = result_value_type(context, name)
82
+ key = "#{name} #{object_id}" # Every Operation result is a unique Binding
83
+ @binding = (context.bindings[key] ||= Binding.new(@player))
84
+ @binding.refs << self
85
+ @binding
86
+ end
87
+
88
+ def result_type_name(context)
89
+ raise "REVISIT: Implement result_type_name in the #{self.class.name} subclass"
90
+ end
91
+
92
+ def result_value_type(context, name)
93
+ vocabulary = context.vocabulary
94
+ constellation = vocabulary.constellation
95
+ vocabulary.valid_value_type_name(name) ||
96
+ constellation.ValueType(vocabulary, name, :concept => :new)
97
+ end
98
+
99
+ def is_naked_object_type
100
+ false # All Operations are non-naked
101
+ end
102
+
103
+ def match_existing_fact_type context
104
+ opnds = refs
105
+ result_ref = Reference.new(@binding.player.name)
106
+ result_ref.player = @binding.player
107
+ result_ref.binding = @binding
108
+ @binding.refs << result_ref
109
+ clause_ast = Clause.new(
110
+ [result_ref, '='] +
111
+ (opnds.size > 1 ? [opnds[0]] : []) +
112
+ [operator, opnds[-1]]
113
+ )
114
+
115
+ # REVISIT: All operands must be value-types or simply-identified Entity Types.
116
+
117
+ # REVISIT: We should auto-create steps from Entity Types to an identifying ValueType
118
+ # REVISIT: We should traverse up the supertype of ValueTypes to find a DataType
119
+ @fact_type = clause_ast.match_existing_fact_type(context, :exact_type => true)
120
+ if clause.certainty == false
121
+ raise "Negated fact types in expressions are not yet supported: #{clause.inspect}"
122
+ end
123
+ return @fact_type if @fact_type
124
+
125
+ @fact_type = clause_ast.make_fact_type context.vocabulary
126
+ reading = clause_ast.make_reading context.vocabulary, @fact_type
127
+ rrs = reading.role_sequence.all_role_ref_in_order
128
+ opnds[0].role_ref = rrs[0]
129
+ opnds[-1].role_ref = rrs[-1]
130
+ opnds.each do |opnd|
131
+ next unless opnd.is_a?(Operation)
132
+ opnd.match_existing_fact_type context
133
+ if opnd.certainty == false
134
+ raise "Negated fact types in expressions are not yet supported: #{opnd.inspect}"
135
+ end
136
+ end
137
+ @fact_type
138
+ end
139
+
140
+ def is_equality_comparison
141
+ false
142
+ end
143
+
144
+ def operator
145
+ raise "REVISIT: Implement operator access in the operator subclass #{self.class.name}"
146
+ end
147
+
148
+ end
149
+
150
+ class Comparison < Operation
151
+ attr_accessor :operator, :e1, :e2, :qualifiers, :conjunction
152
+
153
+ def initialize operator, e1, e2, certainty = true
154
+ @operator, @e1, @e2, @certainty, @qualifiers = operator, e1, e2, certainty, []
155
+ end
156
+
157
+ def refs
158
+ [@e1, @e2]
159
+ end
160
+
161
+ def bind context
162
+ refs.each do |o|
163
+ o.bind context
164
+ end
165
+
166
+ # REVISIT: Return the projected binding instead:
167
+ return @result = nil if @projection
168
+
169
+ name = 'Boolean'
170
+ @player = result_value_type(context, name)
171
+ key = "#{name} #{object_id}" # Every Comparison result is a unique Binding
172
+ @binding = (context.bindings[key] ||= Binding.new(@player))
173
+ @binding.refs << self
174
+ @binding
175
+ end
176
+
177
+ def result_type_name(context)
178
+ "COMPARE#{operator}<#{[@e1,@e2].map{|e| e.player.name}*' WITH '})>"
179
+ end
180
+
181
+ def is_equality_comparison
182
+ @operator == '='
183
+ end
184
+
185
+ def identify_player context
186
+ @player || begin
187
+ if @projection
188
+ raise "REVISIT: The player is the projected expression"
189
+ end
190
+ v = context.vocabulary
191
+ @boolean ||=
192
+ v.constellation.ValueType[[[v.name], 'Boolean']] ||
193
+ v.constellation.ValueType(v, 'Boolean', :concept => :new)
194
+ @player = @boolean
195
+ end
196
+ end
197
+
198
+ =begin
199
+ def project lr
200
+ @projection = lr
201
+ projected_rr = lr == :left ? @e2 : @e1
202
+ true
203
+ end
204
+ =end
205
+
206
+ def inspect; to_s; end
207
+
208
+ def to_s
209
+ "COMPARE#{
210
+ operator
211
+ }(#{
212
+ case @certainty
213
+ when nil; 'maybe '
214
+ when false; 'negated '
215
+ # else 'definitely '
216
+ end
217
+ }#{
218
+ e1.to_s
219
+ } WITH #{
220
+ e2.to_s
221
+ }#{
222
+ @qualifiers.empty? ? '' : ', ['+@qualifiers*', '+']'
223
+ })"
224
+ end
225
+ end
226
+
227
+ class Sum < Operation
228
+ attr_accessor :terms
229
+ def initialize *terms
230
+ @terms = terms
231
+ end
232
+
233
+ def refs
234
+ @terms
235
+ end
236
+
237
+ def operator
238
+ '+'
239
+ end
240
+
241
+ def identify_player context
242
+ @player || begin
243
+ # The players in the @terms have already been identified
244
+ # REVISIT: Check compliance of all units in @terms, and apply conversions where necessary
245
+ # REVISIT: The type of this result should be derived from type promotion rules. Here, we take the left-most.
246
+ # REVISIT: We should define a subtype of the result type here, and apply the units to it.
247
+ v = context.vocabulary
248
+ @player = @terms[0].player
249
+ @player
250
+ end
251
+ end
252
+
253
+ def result_type_name(context)
254
+ "SUM_OF<#{ @terms.map{|f| f.player.name}*', ' }>"
255
+ end
256
+
257
+ =begin
258
+ def result_value_type(context, name)
259
+ # REVISIT: If there are units involved, check compatibility
260
+ vt = super
261
+ vt
262
+ end
263
+ =end
264
+
265
+ def inspect; to_s; end
266
+
267
+ def to_s
268
+ 'SUM(' + @terms.map{|term| "#{term.to_s}" } * ' PLUS ' + ')'
269
+ end
270
+ end
271
+
272
+ class Product < Operation
273
+ attr_accessor :factors
274
+ def initialize *factors
275
+ @factors = factors
276
+ end
277
+
278
+ def refs
279
+ @factors
280
+ end
281
+
282
+ def operator
283
+ '*'
284
+ end
285
+
286
+ def identify_player context
287
+ @player || begin
288
+ # The players in the @factors have already been identified
289
+ # REVISIT: Calculate the units of the result from the units in @factors
290
+ # REVISIT: The type of this result should be derived from type promotion rules. Here, we take the left-most.
291
+ # REVISIT: We should define a subtype of the result type here, and apply the units to it.
292
+ v = context.vocabulary
293
+ @player = @factors[0].player
294
+ end
295
+ end
296
+
297
+ def result_type_name(context)
298
+ "PRODUCT_OF<#{ @factors.map{|f| f.player.name}*' ' }>"
299
+ end
300
+
301
+ =begin
302
+ def result_value_type(context, name)
303
+ vt = super
304
+ # REVISIT: If there are units involved, create the result units
305
+ vt
306
+ end
307
+ =end
308
+
309
+ def inspect; to_s; end
310
+
311
+ def to_s
312
+ 'PRODUCT(' + @factors.map{|factor| "#{factor.to_s}" } * ' TIMES ' + ')'
313
+ end
314
+ end
315
+
316
+ class Reciprocal < Operation
317
+ attr_accessor :divisor
318
+ def initialize divisor
319
+ @divisor = divisor
320
+ end
321
+
322
+ def operator
323
+ '1/'
324
+ end
325
+
326
+ def refs
327
+ [@divisor]
328
+ end
329
+
330
+ def identify_player context
331
+ @player || begin
332
+ # The player in @divisor has already been identified
333
+ # REVISIT: Calculate the units of the result from the units in @divisor
334
+ # REVISIT: Do we want integer division?
335
+ v = context.vocabulary
336
+ @player = v.constellation.ValueType(v, 'Real', :concept => :new)
337
+ end
338
+ end
339
+
340
+ =begin
341
+ def result_type_name(context)
342
+ raise hell
343
+ end
344
+ =end
345
+
346
+ def inspect; to_s; end
347
+
348
+ def to_s
349
+ "RECIPROCAL(#{factor.to_s})"
350
+ end
351
+ end
352
+
353
+ class Negate
354
+ attr_accessor :term
355
+ def initialize term
356
+ @term = term
357
+ end
358
+
359
+ def operator
360
+ '0-'
361
+ end
362
+
363
+ def identify_player context
364
+ @player || begin
365
+ # The player in @term have already been identified
366
+ v = context.vocabulary
367
+ @player = @term.player
368
+ end
369
+ end
370
+
371
+ =begin
372
+ def result_type_name(context)
373
+ raise hell
374
+ end
375
+ =end
376
+
377
+ def inspect; to_s; end
378
+
379
+ def to_s
380
+ "NEGATIVE(#{term.to_s})"
381
+ end
382
+ end
383
+
384
+ class Literal
385
+ attr_accessor :literal, :unit, :role, :role_ref, :clause
386
+ attr_reader :objectification_of, :leading_adjective, :trailing_adjective, :value_constraint
387
+
388
+ def initialize literal, unit
389
+ @literal, @unit = literal, unit
390
+ end
391
+
392
+ # Stubs:
393
+ def role_name; nil; end
394
+ def nested_clauses; nil; end
395
+
396
+ def inspect; to_s; end
397
+
398
+ def to_s
399
+ unit ? "(#{@literal.to_s} in #{unit.to_s})" : @literal.to_s
400
+ end
401
+
402
+ def player
403
+ @player
404
+ end
405
+
406
+ def identify_players_with_role_name(context)
407
+ # Nothing to do here, move along
408
+ end
409
+
410
+ def identify_other_players(context)
411
+ identify_player context
412
+ end
413
+
414
+ def identify_player context
415
+ @player || begin
416
+ player_name =
417
+ case @literal
418
+ when String; 'String'
419
+ when Float; 'Real'
420
+ when Numeric; 'Integer'
421
+ when TrueClass, FalseClass; 'Boolean'
422
+ end
423
+ v = context.vocabulary
424
+ @player = v.constellation.ValueType(v, player_name)
425
+ end
426
+ end
427
+
428
+ def bind context
429
+ @binding || begin
430
+ key = "#{@player.name} #{@literal}"
431
+ @binding = (context.bindings[key] ||= Binding.new(@player))
432
+ @binding.refs << self
433
+ end
434
+ end
435
+
436
+ def binding
437
+ @binding
438
+ end
439
+ end
440
+
441
+ end
442
+ end
443
+ end
@@ -0,0 +1,390 @@
1
+ module ActiveFacts
2
+ module CQL
3
+ class Compiler < ActiveFacts::CQL::Parser
4
+
5
+ class Fact < Definition
6
+ def initialize clauses, population_name = ''
7
+ @clauses = clauses
8
+ @population_name = population_name
9
+ end
10
+
11
+ def compile
12
+ @population = @constellation.Population[[@vocabulary.identifying_role_values, @population_name]] ||
13
+ @constellation.Population(@vocabulary, @population_name, :concept => :new)
14
+
15
+ @context = CompilationContext.new(@vocabulary)
16
+ @context.bind @clauses
17
+ @context.left_contraction_allowed = true
18
+ @clauses.each do |clause|
19
+ ft = clause.match_existing_fact_type @context
20
+ if clause.certainty == false
21
+ raise "Negated fact #{clause.inspect} is not supported"
22
+ end
23
+ end
24
+
25
+ # Figure out the simple existential facts and find fact types:
26
+ @bound_facts = []
27
+ @unbound_clauses = all_clauses(@clauses).
28
+ map do |clause|
29
+ bind_literal_or_fact_type clause
30
+ end.
31
+ compact
32
+
33
+ # Because the fact types may include forward references, we must
34
+ # process the list repeatedly until we make no further progress.
35
+ @pass = 0 # Repeat until we make no more progress:
36
+ true while bind_more_facts
37
+
38
+ # Any remaining unbound facts are a problem we can bitch about:
39
+ complain_incomplete unless @unbound_clauses.empty?
40
+
41
+ @bound_facts.uniq # N.B. this includes Instance objects (existential facts)
42
+ end
43
+
44
+ def bind_literal_or_fact_type clause
45
+ # Every bound word (term) in the phrases must have a literal
46
+ # OR be bound to an entity type identified by the phrases
47
+
48
+ # Any clause that has one binding and no other word is
49
+ # either a value instance or a simply-identified entity.
50
+ clause.refs.each do |ref|
51
+ next unless l = ref.literal # No literal
52
+ next if ref.binding.instance # Already bound
53
+ player = ref.binding.player
54
+ # raise "A literal may not be an objectification" if ref.role_ref.nested_clauses
55
+ # raise "Not processing facts involving nested clauses yet" if ref.role_ref
56
+ trace :instance_detail, "Making #{player.class.basename} #{player.name} using #{l.inspect}" do
57
+ ref.binding.instance = instance_identified_by_literal(player, l)
58
+ end
59
+ ref
60
+ end
61
+
62
+ if clause.phrases.size == 1 and (ref = clause.phrases[0]).is_a?(Compiler::Reference)
63
+ if ref.nested_clauses
64
+ # Assign the objectified fact type as this clause's fact type?
65
+ clause.fact_type = ref.player.fact_type
66
+ clause
67
+ else
68
+ # This is an existential fact (like "Name 'foo'", or "Company 'Microsoft'")
69
+ nil # Nothing to see here, move along
70
+ end
71
+ else
72
+ raise "Fact Type not found: '#{clause.display}'" unless clause.fact_type
73
+ # This instance will be associated with its binding by our caller
74
+ clause
75
+ end
76
+ end
77
+
78
+ #
79
+ # Try to bind this clause, and return true if it can be completed
80
+ #
81
+ def bind_clause clause
82
+ return true if clause.fact
83
+
84
+ # Find the roles of this clause that do not yet have an instance
85
+ bare_roles = clause.refs.
86
+ select do |ref|
87
+ if !ref.binding.instance and ref.literal
88
+ ref.binding.instance = instance_identified_by_literal(ref.binding.player, ref.literal)
89
+ end
90
+
91
+ next false if ref.binding.instance
92
+ true
93
+ end
94
+
95
+ trace :instance_detail, "Considering '#{clause.display}' with "+
96
+ (bare_roles.empty? ? "no bare roles" : "bare roles: #{bare_roles.map{|ref| ref.player.name}*", "}") do
97
+
98
+ # If all the roles are in place, we can bind the rest of this clause:
99
+ return true if bare_roles.size == 0 && bind_complete_fact(clause)
100
+
101
+ progress = false
102
+ if bare_roles.size == 1 &&
103
+ (binding = bare_roles[0].binding) &&
104
+ (et = binding.player).is_a?(ActiveFacts::Metamodel::EntityType)
105
+ if et.preferred_identifier.role_sequence.all_role_ref.detect{|rr| rr.role.fact_type == clause.fact_type} &&
106
+ bind_entity_if_identifier_ready(clause, et, binding)
107
+ progress = true
108
+ end
109
+ end
110
+
111
+ return true if progress
112
+ trace :instance_detail, "Delaying until all role players are asserted: #{clause.fact_type.default_reading.inspect}"
113
+ nil
114
+ end
115
+ end
116
+
117
+ # Take one pass through the @unbound_clauses, processing (and removing) any that have all pre-requisites
118
+ def bind_more_facts
119
+ return false unless @unbound_clauses.size > 0
120
+ @pass += 1
121
+
122
+ progress = false
123
+ trace :instance_detail, "Pass #{@pass} with #{@unbound_clauses.size} clauses to consider" do
124
+ @unbound_clauses =
125
+ @unbound_clauses.select do |clause|
126
+ action = bind_clause(clause)
127
+ progress = true if action
128
+ !action
129
+ end
130
+ trace :instance_detail, "end of pass, unbound clauses are #{@unbound_clauses.map(&:display)*', '}"
131
+ end # debug
132
+ progress
133
+ end
134
+
135
+ # Occasionally we need to search through all the clauses. This builds a flat list
136
+ def all_clauses clauses
137
+ clauses.map do |clause|
138
+ [clause] + clause.refs.map{|vr| vr.nested_clauses ? all_clauses(vr.nested_clauses) : []}
139
+ end.flatten.compact
140
+ end
141
+
142
+ def bind_complete_fact clause
143
+ return true unless clause.fact_type # An bare objectification
144
+ instances = clause.refs.map{|vr| vr.binding.instance }
145
+ trace :instance_detail, "All role players exist for #{clause.display.inspect} exist: #{instances.map{|i| "#{i.verbalise}"}*", "}"
146
+
147
+ if e = clause.fact_type.entity_type and
148
+ clause.refs[0].binding.instance.object_type == e
149
+ fact = clause.refs[0].binding.instance.fact
150
+ else
151
+ # Check that this fact doesn't already exist
152
+ trace :instance_detail, "Searching for existing fact instance"
153
+
154
+ fact = clause.fact_type.all_fact.detect do |f|
155
+
156
+ # Get the role values of this fact in the order of the clause we just bound
157
+ role_values_in_clause_order = f.all_role_value.sort_by do |rv|
158
+ clause.reading.role_sequence.all_role_ref.detect{|rr| rr.role == rv.role}.ordinal
159
+ end
160
+
161
+ # If all this fact's role values are played by the bound instances, it's the same fact
162
+ !role_values_in_clause_order.zip(instances).detect{|rv, i| rv.instance != i }
163
+ end
164
+ end
165
+ if fact
166
+ clause.fact = fact
167
+ trace :instance, "Already known: #{fact.verbalise.inspect}"
168
+ else
169
+ trace :instance_detail, "Asserting fact of type #{clause.fact_type.default_reading.inspect}"
170
+ fact =
171
+ clause.fact =
172
+ @constellation.Fact(:new, :fact_type => clause.fact_type, :population => @population)
173
+ @bound_facts << fact
174
+ clause.reading.role_sequence.all_role_ref_in_order.zip(instances).each do |rr, instance|
175
+ trace :instance_detail, "Assigning fact role #{instance.object_type.name} to #{instance.value ? instance.value.inspect : instance.verbalise}"
176
+ # REVISIT: Any residual adjectives after the fact type matching are lost here.
177
+ @constellation.RoleValue(:fact => fact, :instance => instance, :role => rr.role, :population => @population)
178
+ end
179
+ trace :instance, "Assert #{fact.verbalise.inspect} #{@population.name.size>0 ? " in "+@population.name.inspect : ''}" unless clause.fact_type.entity_type
180
+ end
181
+
182
+ if !fact.instance && clause.fact_type.entity_type
183
+ # Objectified fact type; create the instance
184
+ # Create the instance that objectifies this fact. We don't have the binding to assign it to though; that'll happen in our caller
185
+ instance =
186
+ @constellation.Instance(:new, :object_type => clause.fact_type.entity_type, :fact => fact, :population => @population)
187
+ trace :instance, "Assert #{instance.verbalise.inspect}"
188
+ @bound_facts << instance
189
+ end
190
+
191
+ if clause.fact and
192
+ clause.objectified_as and
193
+ instance = clause.fact.instance and
194
+ instance.object_type == clause.objectified_as.binding.player
195
+ clause.objectified_as.binding.instance = instance
196
+ end
197
+
198
+ true
199
+ end
200
+
201
+ # If we have one bare role (no literal or instance) played by an entity type,
202
+ # and the bound fact type participates in the identifier, we might now be able
203
+ # to create the entity instance.
204
+ def bind_entity_if_identifier_ready clause, entity_type, binding
205
+ # Check this instance doesn't already exist already:
206
+ identifying_binding = (clause.refs.map{|vr| vr.binding}-[binding])[0]
207
+ return false unless identifying_binding # This happens when we have a bare objectification
208
+ identifying_instance = identifying_binding.instance
209
+ preferred_identifier = entity_type.preferred_identifier
210
+ role_count = preferred_identifier.role_sequence.all_role_ref.size
211
+
212
+ trace :instance, "A #{binding.player.name} is #{role_count > 1 ? 'partly ':''}identified in #{clause.inspect}"
213
+
214
+ identifying_role_ref = preferred_identifier.role_sequence.all_role_ref.detect { |rr|
215
+ rr.role.fact_type == clause.fact_type && rr.role.object_type == identifying_binding.player
216
+ }
217
+ unless identifying_role_ref
218
+ # This should never happen; we already bound all refs
219
+ trace :instance, "Failed to find a #{identifying_instance.object_type.name}"
220
+ return false # We can't do this yet
221
+ end
222
+ role_value = identifying_instance.all_role_value.detect do |rv|
223
+ rv.fact.fact_type == identifying_role_ref.role.fact_type
224
+ end
225
+ if role_value && role_count == 1
226
+ instance = (role_value.fact.all_role_value.to_a-[role_value])[0].instance
227
+ trace :instance, "Existential fact already known: #{instance.verbalise.inspect}"
228
+ binding.instance = instance
229
+ return true # Done with this clause
230
+ end
231
+
232
+ pi_role_refs = preferred_identifier.role_sequence.all_role_ref
233
+ # For each pi role, we have to find the fact clause, which contains the binding we need.
234
+ # Then we have to create an instance of each fact
235
+ identifiers =
236
+ pi_role_refs.map do |rr|
237
+ # Find a clause that provides the identifying_ref for this player:
238
+ identifying_clause = all_clauses(@clauses).detect do |clause|
239
+ rr.role.fact_type == clause.fact_type &&
240
+ clause.refs.detect{|vr| vr.binding == binding}
241
+ end
242
+ return false unless identifying_clause
243
+ identifying_ref = identifying_clause.refs.select{|ref| ref.binding != binding}[0]
244
+ identifying_binding = identifying_ref ? identifying_ref.binding : nil
245
+ identifying_instance = identifying_binding.instance
246
+
247
+ [rr, identifying_clause, identifying_binding, identifying_instance]
248
+ end
249
+ if identifiers.detect{ |i| !i[3] } # Not all required facts are bound yet
250
+ trace :instance, "Can't go through with creating #{binding.player.name}; not all the identifying facts are in"
251
+ return false
252
+ end
253
+
254
+ instance = @constellation.Instance(:new, :object_type => entity_type, :population => @population)
255
+ binding.instance = instance
256
+ @bound_facts << instance
257
+ identifiers.each do |rr, identifying_clause, identifying_binding, identifying_instance|
258
+ # This clause provides the identifying literal for the entity_type
259
+ id_fact =
260
+ identifying_clause.fact =
261
+ @constellation.Fact(:new, :fact_type => rr.role.fact_type, :population => @population)
262
+ @bound_facts << id_fact
263
+ role = (rr.role.fact_type.all_role.to_a-[rr.role])[0]
264
+ @constellation.RoleValue(:instance => instance, :fact => id_fact, :population => @population, :role => role)
265
+ @constellation.RoleValue(:instance => identifying_instance, :fact => id_fact, :role => rr.role, :population => @population)
266
+ trace :instance, "Assert #{id_fact.verbalise.inspect} (existential) #{@population.name.size>0 ? " in "+@population.name.inspect : ''}"
267
+ end
268
+ trace :instance, "Assert #{instance.verbalise.inspect} #{@population.name.size>0 ? " in "+@population.name.inspect : ''}"
269
+
270
+ true # Done with this clause
271
+ end
272
+
273
+ def instance_identified_by_literal object_type, literal
274
+ if object_type.is_a?(ActiveFacts::Metamodel::EntityType)
275
+ entity_identified_by_literal object_type, literal
276
+ else
277
+ trace :instance_detail, "Assert Value #{object_type.name} #{literal.inspect}" do
278
+ is_literal_string = literal.literal.is_a?(String)
279
+ # REVISIT: Check for subtypes and supertypes also, and promote type if necessary
280
+ instance = object_type.all_instance.detect do |i|
281
+ #instance = @constellation.Instance.detect do |key, i|
282
+ # REVISIT: And same unit
283
+ trace :instance_detail2, "Comparing #{i.value.literal.inspect} to #{literal.literal.to_s.inspect}"
284
+ i.population == @population &&
285
+ i.value &&
286
+ i.value.literal.inspect == literal.literal.to_s.inspect &&
287
+ i.value.is_literal_string == is_literal_string
288
+ end
289
+ #instance = object_type.all_instance.detect { |instance|
290
+ # instance.population == @population && instance.value == literal
291
+ #}
292
+ if instance
293
+ trace :instance, "Instance already known: #{instance.verbalise.inspect}"
294
+ else
295
+ instance = @constellation.Instance(:new)
296
+ instance.object_type = object_type
297
+ instance.population = @population
298
+ instance.value = [literal.to_s, is_literal_string, nil]
299
+ trace :instance, "Assert #{instance.verbalise.inspect} #{@population.name.size>0 ? " in "+@population.name.inspect : ''}"
300
+ @bound_facts << instance
301
+ end
302
+ instance
303
+ end
304
+ end
305
+ end
306
+
307
+ def entity_identified_by_literal object_type, literal
308
+ # A literal that identifies an entity type means the entity type has only one identifying role
309
+ # That role is played either by a value type, or by another similarly single-identified entity type
310
+ trace :instance_detail, "Assert Entity #{object_type.name} identified by '#{literal}'" do
311
+ identifying_role_refs = object_type.preferred_identifier.role_sequence.all_role_ref
312
+ raise "Single literal cannot satisfy multiple identifying roles for #{object_type.name}" if identifying_role_refs.size > 1
313
+ role = identifying_role_refs.single.role
314
+ # This instance has no binding; the binding is of the entity type not the identifying value type
315
+ identifying_instance = instance_identified_by_literal role.object_type, literal
316
+ existing_instance = nil
317
+ instance_rv = identifying_instance.all_role_value.detect { |rv|
318
+ next false unless rv.population == @population # Not this population
319
+ next false unless rv.fact.fact_type == role.fact_type # Not this fact type
320
+ other_role_value = (rv.fact.all_role_value-[rv])[0]
321
+ existing_instance = other_role_value.instance
322
+ other_role_value.instance.object_type == object_type # Is it this object_type?
323
+ }
324
+ if instance_rv
325
+ instance = existing_instance
326
+ trace :instance, "Already known: #{instance.verbalise.inspect}"
327
+ else
328
+ # This fact has no clause.
329
+ trace :instance_detail, "Creating implicit existential fact #{role.fact_type.default_reading}"
330
+ fact = @constellation.Fact(:new, :fact_type => role.fact_type, :population => @population)
331
+ @bound_facts << fact
332
+ # This instance will be associated with its binding by our caller
333
+ instance = @constellation.Instance(:new, :object_type => object_type, :population => @population)
334
+ trace :instance_detail, "Creating Entity #{object_type.name} identified by '#{literal}' #{@population.name.size>0 ? " in "+@population.name.inspect : ''}"
335
+ @bound_facts << instance
336
+ # The identifying fact type has two roles; create both role instances:
337
+ @constellation.RoleValue(:instance => identifying_instance, :fact => fact, :population => @population, :role => role)
338
+ @constellation.RoleValue(:instance => instance, :fact => fact, :population => @population, :role => (role.fact_type.all_role-[role])[0])
339
+ trace :instance, "Assert #{instance.verbalise.inspect}"
340
+ end
341
+ instance
342
+ end
343
+ end
344
+
345
+ def complain_incomplete
346
+ if @unbound_clauses.size > 0
347
+ # Provide a readable description of the problem here, by showing each binding with no instance
348
+ missing_bindings = @unbound_clauses.
349
+ map do |clause|
350
+ clause.refs.
351
+ select do |refs|
352
+ !refs.binding.instance
353
+ end.
354
+ map do |ref|
355
+ ref.binding
356
+ end
357
+ end.
358
+ flatten.
359
+ uniq
360
+
361
+ raise "Not enough facts are given to identify #{
362
+ missing_bindings.
363
+ sort_by{|b| b.key}.
364
+ map do |b|
365
+ player_identifier =
366
+ if b.player.is_a?(ActiveFacts::Metamodel::EntityType)
367
+ "lacking " +
368
+ b.player.preferred_identifier.role_sequence.all_role_ref.map do |rr|
369
+ [ rr.leading_adjective, rr.role.role_name || rr.role.object_type.name, rr.trailing_adjective ].compact*" "
370
+ end*", "
371
+ else
372
+ "needs a value"
373
+ end
374
+ [
375
+ b.refs[0].leading_adjective, b.player.name, b.refs[0].trailing_adjective
376
+ ].compact*" " +
377
+ " (#{player_identifier})"
378
+ end*" or "
379
+ }"
380
+ end
381
+ end
382
+
383
+ def to_s
384
+ super+@clauses.map(&:to_s)*', '
385
+ end
386
+
387
+ end
388
+ end
389
+ end
390
+ end