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.
- checksums.yaml +7 -0
- data/.gitignore +11 -0
- data/.rspec +1 -0
- data/.travis.yml +4 -0
- data/Gemfile +10 -0
- data/LICENSE.txt +21 -0
- data/README.md +19 -0
- data/Rakefile +6 -0
- data/activefacts-cql.gemspec +29 -0
- data/bin/setup +7 -0
- data/lib/activefacts/cql.rb +7 -0
- data/lib/activefacts/cql/.gitignore +0 -0
- data/lib/activefacts/cql/Rakefile +14 -0
- data/lib/activefacts/cql/compiler.rb +156 -0
- data/lib/activefacts/cql/compiler/clause.rb +1137 -0
- data/lib/activefacts/cql/compiler/constraint.rb +581 -0
- data/lib/activefacts/cql/compiler/entity_type.rb +457 -0
- data/lib/activefacts/cql/compiler/expression.rb +443 -0
- data/lib/activefacts/cql/compiler/fact.rb +390 -0
- data/lib/activefacts/cql/compiler/fact_type.rb +421 -0
- data/lib/activefacts/cql/compiler/query.rb +106 -0
- data/lib/activefacts/cql/compiler/shared.rb +161 -0
- data/lib/activefacts/cql/compiler/value_type.rb +174 -0
- data/lib/activefacts/cql/parser.rb +234 -0
- data/lib/activefacts/cql/parser/CQLParser.treetop +167 -0
- data/lib/activefacts/cql/parser/Context.treetop +48 -0
- data/lib/activefacts/cql/parser/Expressions.treetop +67 -0
- data/lib/activefacts/cql/parser/FactTypes.treetop +358 -0
- data/lib/activefacts/cql/parser/Language/English.treetop +315 -0
- data/lib/activefacts/cql/parser/Language/French.treetop +315 -0
- data/lib/activefacts/cql/parser/Language/Mandarin.treetop +304 -0
- data/lib/activefacts/cql/parser/LexicalRules.treetop +253 -0
- data/lib/activefacts/cql/parser/ObjectTypes.treetop +210 -0
- data/lib/activefacts/cql/parser/Terms.treetop +183 -0
- data/lib/activefacts/cql/parser/ValueTypes.treetop +202 -0
- data/lib/activefacts/cql/parser/nodes.rb +49 -0
- data/lib/activefacts/cql/require.rb +36 -0
- data/lib/activefacts/cql/verbaliser.rb +804 -0
- data/lib/activefacts/cql/version.rb +5 -0
- data/lib/activefacts/input/cql.rb +43 -0
- data/lib/rubygems_plugin.rb +12 -0
- 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
|