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,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