activefacts 0.8.16 → 0.8.18

Sign up to get free protection for your applications and to get access to all the features.
Files changed (100) hide show
  1. checksums.yaml +15 -0
  2. data/Manifest.txt +10 -4
  3. data/bin/afgen +26 -20
  4. data/bin/cql +1 -1
  5. data/css/orm2.css +89 -9
  6. data/examples/CQL/CompanyDirectorEmployee.cql +4 -4
  7. data/examples/CQL/Genealogy.cql +5 -5
  8. data/examples/CQL/Metamodel.cql +121 -91
  9. data/examples/CQL/MonthInSeason.cql +2 -6
  10. data/examples/CQL/SeparateSubtype.cql +11 -9
  11. data/examples/CQL/ServiceDirector.cql +21 -33
  12. data/examples/CQL/Supervision.cql +0 -3
  13. data/examples/CQL/WindowInRoomInBldg.cql +10 -4
  14. data/examples/CQL/unit.cql +1 -1
  15. data/lib/activefacts.rb +1 -0
  16. data/lib/activefacts/cql/CQLParser.treetop +5 -1
  17. data/lib/activefacts/cql/Context.treetop +2 -7
  18. data/lib/activefacts/cql/Expressions.treetop +2 -2
  19. data/lib/activefacts/cql/FactTypes.treetop +37 -31
  20. data/lib/activefacts/cql/Language/English.treetop +21 -4
  21. data/lib/activefacts/cql/LexicalRules.treetop +59 -1
  22. data/lib/activefacts/cql/ObjectTypes.treetop +22 -12
  23. data/lib/activefacts/cql/Terms.treetop +13 -9
  24. data/lib/activefacts/cql/ValueTypes.treetop +30 -11
  25. data/lib/activefacts/cql/compiler.rb +34 -5
  26. data/lib/activefacts/cql/compiler/clause.rb +207 -116
  27. data/lib/activefacts/cql/compiler/constraint.rb +129 -105
  28. data/lib/activefacts/cql/compiler/entity_type.rb +49 -27
  29. data/lib/activefacts/cql/compiler/expression.rb +71 -42
  30. data/lib/activefacts/cql/compiler/fact.rb +70 -64
  31. data/lib/activefacts/cql/compiler/fact_type.rb +108 -57
  32. data/lib/activefacts/cql/compiler/query.rb +178 -0
  33. data/lib/activefacts/cql/compiler/shared.rb +13 -12
  34. data/lib/activefacts/cql/compiler/value_type.rb +10 -4
  35. data/lib/activefacts/cql/nodes.rb +1 -1
  36. data/lib/activefacts/cql/parser.rb +6 -2
  37. data/lib/activefacts/generate/absorption.rb +6 -3
  38. data/lib/activefacts/generate/cql.rb +140 -84
  39. data/lib/activefacts/generate/dm.rb +12 -6
  40. data/lib/activefacts/generate/help.rb +25 -6
  41. data/lib/activefacts/generate/helpers/oo.rb +195 -0
  42. data/lib/activefacts/generate/helpers/ordered.rb +589 -0
  43. data/lib/activefacts/generate/helpers/rails.rb +57 -0
  44. data/lib/activefacts/generate/html/glossary.rb +274 -54
  45. data/lib/activefacts/generate/json.rb +25 -22
  46. data/lib/activefacts/generate/null.rb +1 -0
  47. data/lib/activefacts/generate/rails/models.rb +244 -0
  48. data/lib/activefacts/generate/rails/schema.rb +185 -0
  49. data/lib/activefacts/generate/records.rb +1 -0
  50. data/lib/activefacts/generate/ruby.rb +51 -30
  51. data/lib/activefacts/generate/sql/mysql.rb +5 -3
  52. data/lib/activefacts/generate/sql/server.rb +8 -4
  53. data/lib/activefacts/generate/text.rb +1 -0
  54. data/lib/activefacts/generate/transform/surrogate.rb +209 -0
  55. data/lib/activefacts/generate/version.rb +1 -0
  56. data/lib/activefacts/input/orm.rb +234 -181
  57. data/lib/activefacts/mapping/rails.rb +122 -0
  58. data/lib/activefacts/persistence/columns.rb +34 -18
  59. data/lib/activefacts/persistence/foreignkey.rb +129 -71
  60. data/lib/activefacts/persistence/index.rb +42 -12
  61. data/lib/activefacts/persistence/reference.rb +37 -23
  62. data/lib/activefacts/persistence/tables.rb +53 -19
  63. data/lib/activefacts/registry.rb +11 -0
  64. data/lib/activefacts/support.rb +28 -10
  65. data/lib/activefacts/version.rb +1 -1
  66. data/lib/activefacts/vocabulary/extensions.rb +246 -117
  67. data/lib/activefacts/vocabulary/metamodel.rb +105 -65
  68. data/lib/activefacts/vocabulary/verbaliser.rb +226 -194
  69. data/spec/absorption_spec.rb +1 -0
  70. data/spec/cql/comparison_spec.rb +8 -8
  71. data/spec/cql/contractions_spec.rb +16 -43
  72. data/spec/cql/entity_type_spec.rb +2 -1
  73. data/spec/cql/expressions_spec.rb +2 -2
  74. data/spec/cql/fact_type_matching_spec.rb +4 -1
  75. data/spec/cql/parser/bad_literals_spec.rb +30 -30
  76. data/spec/cql/parser/entity_types_spec.rb +6 -6
  77. data/spec/cql/parser/expressions_spec.rb +25 -19
  78. data/spec/cql/samples_spec.rb +5 -4
  79. data/spec/cql_cql_spec.rb +2 -1
  80. data/spec/cql_dm_spec.rb +4 -0
  81. data/spec/cql_mysql_spec.rb +4 -0
  82. data/spec/cql_parse_spec.rb +2 -0
  83. data/spec/cql_ruby_spec.rb +4 -0
  84. data/spec/cql_sql_spec.rb +4 -0
  85. data/spec/cqldump_spec.rb +7 -4
  86. data/spec/helpers/parse_to_ast_matcher.rb +7 -3
  87. data/spec/helpers/test_parser.rb +2 -0
  88. data/spec/norma_cql_spec.rb +5 -2
  89. data/spec/norma_ruby_spec.rb +4 -1
  90. data/spec/norma_ruby_sql_spec.rb +4 -1
  91. data/spec/norma_sql_spec.rb +4 -1
  92. data/spec/norma_tables_spec.rb +2 -2
  93. data/spec/ruby_api_spec.rb +1 -1
  94. data/spec/spec_helper.rb +2 -0
  95. data/spec/transform_surrogate_spec.rb +59 -0
  96. metadata +70 -60
  97. data/TODO +0 -308
  98. data/lib/activefacts/cql/compiler/join.rb +0 -162
  99. data/lib/activefacts/generate/oo.rb +0 -176
  100. data/lib/activefacts/generate/ordered.rb +0 -602
@@ -26,23 +26,23 @@ module ActiveFacts
26
26
  @context.left_contraction_allowed = true
27
27
  match_condition_fact_types
28
28
 
29
- # Build the join:
29
+ # Build the query:
30
30
  unless @conditions.empty? and !@returning
31
- debug :join, "building fact_type join" do
32
- @join = build_join_nodes(@conditions.flatten)
33
- @roles_by_variable = build_all_join_steps(@conditions)
34
- @join.validate
35
- @join
31
+ debug :query, "building query for derived fact type" do
32
+ @query = build_variables(@conditions.flatten)
33
+ @roles_by_binding = build_all_steps(@conditions)
34
+ @query.validate
35
+ @query
36
36
  end
37
37
  end
38
38
  @context.left_contraction_allowed = false
39
- @join
39
+ @query
40
40
  end
41
41
 
42
42
  def match_condition_fact_types
43
43
  @conditions.each do |condition|
44
44
  next if condition.is_naked_object_type
45
- # REVISIT: Many conditions will imply a number of different join steps, which need to be handled (similar to nested_clauses).
45
+ # REVISIT: Many conditions will imply a number of different steps, which need to be handled (similar to nested_clauses).
46
46
  debug :projection, "matching condition fact_type #{condition.inspect}" do
47
47
  fact_type = condition.match_existing_fact_type @context
48
48
  raise "Unrecognised fact type #{condition.inspect} in #{self.class}" unless fact_type
@@ -93,6 +93,13 @@ module ActiveFacts
93
93
  # * Objectify the fact type if @name
94
94
  #
95
95
 
96
+ # Prepare to objectify the fact type (so readings for implicit fact types can be created)
97
+ if @name
98
+ entity_type = @vocabulary.valid_entity_type_name(@name)
99
+ raise "You can't objectify #{@name}, it already exists" if entity_type
100
+ @entity_type = @constellation.EntityType(@vocabulary, @name, :fact_type => @fact_type, :guid => :new)
101
+ end
102
+
96
103
  prepare_roles @clauses
97
104
 
98
105
  # REVISIT: Compiling the conditions here make it impossible to define a self-referential (transitive) query.
@@ -102,9 +109,16 @@ module ActiveFacts
102
109
  @clauses.reject!{|clause| clause.is_existential_type }
103
110
  return true unless @clauses.size > 0 # Nothing interesting was said.
104
111
 
112
+ if @entity_type
113
+ # Extract readings for implicit fact types
114
+ @implicit_readings, @clauses =
115
+ @clauses.partition do |clause|
116
+ clause.refs.size == 2 and clause.refs.detect{|ref| ref.player == @entity_type}
117
+ end
118
+ end
119
+
105
120
  # See if any existing fact type is being invoked (presumably to objectify or extend it)
106
121
  @fact_type = check_compatibility_of_matched_clauses
107
-
108
122
  verify_matching_roles # All clauses of a fact type must have the same roles
109
123
 
110
124
  if !@fact_type
@@ -116,6 +130,7 @@ module ActiveFacts
116
130
  @fact_type.create_implicit_fact_type_for_unary if @fact_type.all_role.size == 1 && !@name
117
131
  @existing_clauses = [first_clause]
118
132
  elsif (n = @clauses.size - @existing_clauses.size) > 0
133
+ raise "Cannot extend a negated fact type" if @existing_clauses.detect {|clause| clause.certainty == false }
119
134
  debug :binding, "Extending existing fact type with #{n} new readings"
120
135
  end
121
136
 
@@ -133,17 +148,16 @@ module ActiveFacts
133
148
  clause.make_embedded_constraints(vocabulary)
134
149
  end
135
150
 
136
- # Objectify the fact type if necessary:
137
151
  if @name
152
+ # Objectify the fact type:
153
+ @entity_type.fact_type = @fact_type
138
154
  if @fact_type.entity_type and @name != @fact_type.entity_type.name
139
155
  raise "Cannot objectify fact type as #{@name} and as #{@fact_type.entity_type.name}"
140
156
  end
141
- e = @constellation.EntityType[[@vocabulary, @name]]
142
- raise "You can't objectify #{@name}, it already exists" if e
143
- e = @constellation.EntityType(@vocabulary, @name, :fact_type => @fact_type, :guid => :new)
144
- e.create_implicit_fact_types
157
+ ifts = @entity_type.create_implicit_fact_types
158
+ create_implicit_readings(ifts)
145
159
  if @pragmas
146
- e.is_independent = true if @pragmas.delete('independent')
160
+ @entity_type.is_independent = true if @pragmas.delete('independent')
147
161
  end
148
162
  if @pragmas && @pragmas.size > 0
149
163
  $stderr.puts "Mapping pragmas #{@pragmas.inspect} are ignored for objectified fact type #{@name}"
@@ -169,13 +183,35 @@ module ActiveFacts
169
183
  @fact_type
170
184
  end
171
185
 
186
+ def create_implicit_readings(ifts)
187
+ @implicit_readings.each do |clause|
188
+ #next false unless clause.refs.size == 2 and clause.refs.detect{|r| r.role.object_type == @entity_type }
189
+ other_ref = clause.refs.detect{|ref| ref.player != @entity_type}
190
+ ift = ifts.detect do |ift|
191
+ other_ref.binding.refs.map{|r| r.role}.include?(ift.implying_role)
192
+ end
193
+ next unless ift
194
+ # This clause is a reading for the implicit LinkFactType ift
195
+ i = 0
196
+ reading_text = clause.phrases.map do |phrase|
197
+ next phrase if String === phrase
198
+ "{#{(i += 1)-1}}"
199
+ end*' '
200
+ ir = ActiveFacts::Metamodel::LinkFactType::ImplicitReading
201
+ irrs = ir::ImplicitReadingRoleSequence
202
+ irrf = irrs::ImplicitReadingRoleRef
203
+ reading = ir.new(ift, reading_text)
204
+ ift.add_reading reading
205
+ end
206
+ end
207
+
172
208
  def project_clause_roles(clause)
173
- # Attach the clause's role references to the projected roles of the join
174
- clause.var_refs.each_with_index do |var_ref, i|
175
- role, join_role = @roles_by_variable[var_ref.variable]
176
- raise "#{var_ref} must be a role projected from the conditions" unless role
177
- raise "#{var_ref} has already-projected join role!" if join_role.role_ref
178
- var_ref.role_ref.join_role = join_role
209
+ # Attach the clause's role references to the projected roles of the query
210
+ clause.refs.each_with_index do |ref, i|
211
+ role, play = @roles_by_binding[ref.binding]
212
+ raise "#{ref} must be a role projected from the conditions" unless role
213
+ raise "#{ref} has already-projected play!" if play.role_ref
214
+ ref.role_ref.play = play
179
215
  end
180
216
  end
181
217
 
@@ -183,7 +219,7 @@ module ActiveFacts
183
219
  def is_projected_role(rr)
184
220
  # rr is a RoleRef on one side of the comparison.
185
221
  # If its binding contains a reference from our readings, it's projected.
186
- rr.variable.refs.detect do |ref|
222
+ rr.binding.refs.detect do |ref|
187
223
  @readings.include?(ref.reading)
188
224
  end
189
225
  end
@@ -237,25 +273,40 @@ module ActiveFacts
237
273
 
238
274
  # If there's an existing presence constraint that can be converted into a PC, do that:
239
275
  @clauses.each do |clause|
240
- var_ref = clause.var_refs[-1] or next
241
- epc = var_ref.embedded_presence_constraint or next
276
+ ref = clause.refs[-1] or next
277
+ epc = ref.embedded_presence_constraint or next
242
278
  epc.max_frequency == 1 or next
243
279
  next if epc.enforcement
244
280
  epc.is_preferred_identifier = true
245
281
  return
246
282
  end
247
283
 
248
- # REVISIT: We need to check uniqueness constraints after processing the whole vocabulary
284
+ # We need to check uniqueness constraints after processing the whole vocabulary
249
285
  # raise "Fact type must be named as it has no identifying uniqueness constraint" unless @name || @fact_type.all_role.size == 1
250
-
251
- @constellation.PresenceConstraint(
252
- :new,
253
- :vocabulary => @vocabulary,
254
- :name => @fact_type.entity_type ? @fact_type.entity_type.name+"PK" : '',
255
- :role_sequence => @fact_type.preferred_reading.role_sequence,
256
- :max_frequency => 1,
257
- :is_preferred_identifier => prefer
258
- )
286
+ debug :constraint, "Need to check #{@fact_type.default_reading.inspect} for a uniqueness constraint"
287
+ fact_type.check_and_add_spanning_uniqueness_constraint = proc do
288
+ debug :constraint, "Checking #{@fact_type.default_reading.inspect} for a uniqueness constraint"
289
+ if !@fact_type.all_role.
290
+ detect do |role|
291
+ role.all_role_ref.detect do |rr|
292
+ # This RoleSequence, to be relevant, must only reference roles of this fact type
293
+ rr.role_sequence.all_role_ref.all? {|rr2| rr2.role.fact_type == @fact_type} and
294
+ # The RoleSequence must have at least one uniqueness constraint
295
+ rr.role_sequence.all_presence_constraint.detect{|pc| pc.max_frequency == 1}
296
+ end
297
+ end
298
+ # There's no existing uniqueness constraint over the roles of this fact type. Add one
299
+ pc = @constellation.PresenceConstraint(
300
+ :new,
301
+ :vocabulary => @vocabulary,
302
+ :name => @fact_type.entity_type ? @fact_type.entity_type.name+"PK" : '',
303
+ :role_sequence => (rs = @fact_type.preferred_reading.role_sequence),
304
+ :max_frequency => 1,
305
+ :is_preferred_identifier => true # (prefer || !!@fact_type.entity_type)
306
+ )
307
+ debug :constraint, "Made new fact type implicit PC GUID=#{pc.guid} #{pc.name} min=nil max=1 over #{rs.describe}"
308
+ end
309
+ end
259
310
  end
260
311
 
261
312
  def has_more_adjectives(less, more)
@@ -265,12 +316,12 @@ module ActiveFacts
265
316
  end
266
317
 
267
318
  def verify_matching_roles
268
- var_refs_by_clause_and_key = {}
269
- clauses_by_var_refs =
319
+ refs_by_clause_and_key = {}
320
+ clauses_by_refs =
270
321
  @clauses.inject({}) do |hash, clause|
271
- keys = clause.var_refs.map do |var_ref|
272
- key = var_ref.key.compact
273
- var_refs_by_clause_and_key[[clause, key]] = var_ref
322
+ keys = clause.refs.map do |ref|
323
+ key = ref.key.compact
324
+ refs_by_clause_and_key[[clause, key]] = ref
274
325
  key
275
326
  end.sort_by{|a| a.map{|k|k.to_s}}
276
327
  raise "Fact types may not have duplicate roles" if keys.uniq.size < keys.size
@@ -278,14 +329,14 @@ module ActiveFacts
278
329
  hash
279
330
  end
280
331
 
281
- if clauses_by_var_refs.size != 1 and @conditions.empty?
282
- # Attempt loose binding here; it might merge some Compiler::VarRefs to share the same Variables
283
- variants = clauses_by_var_refs.keys
284
- (clauses_by_var_refs.size-1).downto(1) do |m| # Start with the last one
332
+ if clauses_by_refs.size != 1 and @conditions.empty?
333
+ # Attempt loose binding here; it might merge some Compiler::References to share the same Variables
334
+ variants = clauses_by_refs.keys
335
+ (clauses_by_refs.size-1).downto(1) do |m| # Start with the last one
285
336
  0.upto(m-1) do |l| # Try to rebind onto any lower one
286
337
  common = variants[m]&variants[l]
287
- clauses_l = clauses_by_var_refs[variants[l]]
288
- clauses_m = clauses_by_var_refs[variants[m]]
338
+ clauses_l = clauses_by_refs[variants[l]]
339
+ clauses_m = clauses_by_refs[variants[m]]
289
340
  l_keys = variants[l]-common
290
341
  m_keys = variants[m]-common
291
342
  debug :binding, "Try to collapse variant #{m} onto #{l}; diffs are #{l_keys.inspect} -> #{m_keys.inspect}"
@@ -295,16 +346,16 @@ module ActiveFacts
295
346
  candidates = []
296
347
  (0...m_keys.size).each do |j|
297
348
  m_key = m_keys[j]
298
- l_var_ref = var_refs_by_clause_and_key[[clauses_l[0], l_key]]
299
- m_var_ref = var_refs_by_clause_and_key[[clauses_m[0], m_key]]
300
- debug :binding, "Can we match #{l_var_ref.inspect} (#{i}) with #{m_var_ref.inspect} (#{j})?"
301
- next if m_var_ref.player != l_var_ref.player
302
- if has_more_adjectives(m_var_ref, l_var_ref)
303
- debug :binding, "can rebind #{m_var_ref.inspect} to #{l_var_ref.inspect}"
304
- candidates << [m_var_ref, l_var_ref]
305
- elsif has_more_adjectives(l_var_ref, m_var_ref)
306
- debug :binding, "can rebind #{l_var_ref.inspect} to #{m_var_ref.inspect}"
307
- candidates << [l_var_ref, m_var_ref]
349
+ l_ref = refs_by_clause_and_key[[clauses_l[0], l_key]]
350
+ m_ref = refs_by_clause_and_key[[clauses_m[0], m_key]]
351
+ debug :binding, "Can we match #{l_ref.inspect} (#{i}) with #{m_ref.inspect} (#{j})?"
352
+ next if m_ref.player != l_ref.player
353
+ if has_more_adjectives(m_ref, l_ref)
354
+ debug :binding, "can rebind #{m_ref.inspect} to #{l_ref.inspect}"
355
+ candidates << [m_ref, l_ref]
356
+ elsif has_more_adjectives(l_ref, m_ref)
357
+ debug :binding, "can rebind #{l_ref.inspect} to #{m_ref.inspect}"
358
+ candidates << [l_ref, m_ref]
308
359
  end
309
360
  end
310
361
 
@@ -323,7 +374,7 @@ module ActiveFacts
323
374
  else
324
375
  # No point continuing, we failed on this one.
325
376
  raise "All readings in a fact type definition must have matching role players, compare (#{
326
- clauses_by_var_refs.keys.map do |keys|
377
+ clauses_by_refs.keys.map do |keys|
327
378
  keys.map{|key| key*'-' }*", "
328
379
  end*") with ("
329
380
  })"
@@ -345,7 +396,7 @@ module ActiveFacts
345
396
  else
346
397
  ''
347
398
  end +
348
- (@pragmas && @pragmas.size > 0 ? ", pragmas [#{@pragmas.sort*','}]" : '')
399
+ (@pragmas && @pragmas.size > 0 ? ", pragmas [#{@pragmas.flatten.sort*','}]" : '')
349
400
 
350
401
  # REVISIT: @returning = returning
351
402
  end
@@ -0,0 +1,178 @@
1
+ module ActiveFacts
2
+ module CQL
3
+ class Compiler < ActiveFacts::CQL::Parser
4
+ class Definition
5
+ # Make a Variable for every binding present in these clauses
6
+ def build_variables(clauses_list)
7
+ debug :query, "Building variables" do
8
+ query = @constellation.Query(:new)
9
+ all_bindings_in_clauses(clauses_list).
10
+ each do |binding|
11
+ debug :query, "Creating variable #{query.all_variable.size} for #{binding.inspect}"
12
+ binding.variable = @constellation.Variable(query, query.all_variable.size, :object_type => binding.player)
13
+ if literal = binding.refs.detect{|r| r.literal}
14
+ if literal.kind_of?(ActiveFacts::CQL::Compiler::Reference)
15
+ # REVISIT: Fix this crappy ad-hoc polymorphism hack
16
+ literal = literal.literal
17
+ end
18
+ unit = @constellation.Unit.detect{|k, v| [v.name, v.plural_name].include? literal.unit} if literal.unit
19
+ binding.variable.value = [literal.literal.to_s, literal.literal.is_a?(String), unit]
20
+ end
21
+ end
22
+ query
23
+ end
24
+ end
25
+
26
+ def build_all_steps(clauses_list)
27
+ roles_by_binding = {}
28
+ debug :query, "Building steps" do
29
+ clauses_list.each do |clause|
30
+ next if clause.is_naked_object_type
31
+ build_steps(clause, roles_by_binding)
32
+ end
33
+ end
34
+ roles_by_binding
35
+ end
36
+
37
+ def build_steps clause, roles_by_binding = {}, objectification_node = nil
38
+ plays = []
39
+ incidental_roles = []
40
+ debug :query, "Creating Role Sequence for #{clause.inspect} with #{clause.refs.size} role refs" do
41
+ objectification_step = nil
42
+ clause.refs.each do |ref|
43
+ # These refs are the Compiler::References, which have associated Metamodel::RoleRefs,
44
+ # but we need to create Plays for those roles.
45
+ # REVISIT: Plays may need to save residual_adjectives
46
+ binding = ref.binding
47
+ role = (ref && ref.role) || (ref.role_ref && ref.role_ref.role)
48
+ play = nil
49
+
50
+ debugger unless clause.fact_type
51
+ if (clause.fact_type.entity_type)
52
+ # This clause is of an objectified fact type.
53
+ # We need a step from this role to the phantom role, but not
54
+ # for a role that has only one ref (this one) in their binding.
55
+ # Create the Variable and Play in any case though.
56
+ refs_count = binding.refs.size
57
+ objectification_ref_count = 0
58
+ if ref.nested_clauses
59
+ ref.nested_clauses.each do |ojc|
60
+ objectification_ref_count += ojc.refs.select{|ref| ref.binding.refs.size > 1}.size
61
+ end
62
+ end
63
+ refs_count += objectification_ref_count
64
+
65
+ debug :query, "Creating Variable #{ref.inspect} (counts #{refs_count}/#{objectification_ref_count}) and objectification Step for #{ref.inspect}" do
66
+
67
+ raise "Internal error: Trying to add role of #{role.object_type.name} to variable for #{binding.variable.object_type.name}" unless binding.variable.object_type == role.object_type
68
+ play = @constellation.Play(binding.variable, role)
69
+
70
+ if (refs_count <= 1) # Our work here is done if there are no other refs
71
+ if objectification_step
72
+ play.step = objectification_step
73
+ else
74
+ incidental_roles << play
75
+ end
76
+ next
77
+ end
78
+
79
+ plays << play
80
+ unless objectification_node
81
+ # This is an implicit objectification, just the FT clause, not ET(where ...clause...)
82
+ # We need to create a Variable for this object, even though it has no References
83
+ query = binding.variable.query
84
+ debug :query, "Creating JN#{query.all_variable.size} for #{clause.fact_type.entity_type.name} in objectification"
85
+ objectification_node = @constellation.Variable(query, query.all_variable.size, :object_type => clause.fact_type.entity_type)
86
+ end
87
+ raise "Internal error: Trying to add role of #{role.link_fact_type.all_role.single.object_type.name} to variable for #{objectification_node.object_type.name}" unless objectification_node.object_type == role.link_fact_type.all_role.single.object_type
88
+
89
+ irole = role.link_fact_type.all_role.single
90
+ raise "Internal error: Trying to add role of #{irole.object_type.name} to variable for #{objectification_node.object_type.name}" unless objectification_node.object_type == irole.object_type
91
+ objectification_role = @constellation.Play(objectification_node, role.link_fact_type.all_role.single)
92
+ objectification_step = @constellation.Step(
93
+ objectification_role,
94
+ play,
95
+ :fact_type => role.link_fact_type,
96
+ :is_optional => false,
97
+ :is_disallowed => clause.certainty == false
98
+ )
99
+ if clause.certainty == nil
100
+ objectification_step.is_optional = true
101
+ end
102
+ debug :query, "New #{objectification_step.describe}"
103
+ debug :query, "Associating #{incidental_roles.map(&:describe)*', '} incidental roles with #{objectification_step.describe}" if incidental_roles.size > 0
104
+ incidental_roles.each { |jr| jr.step = objectification_step }
105
+ incidental_roles = []
106
+ plays = []
107
+ end
108
+ else
109
+ debug :query, "Creating Reference for #{ref.inspect}" do
110
+ # REVISIT: If there's an implicit subtyping step here, create it; then always raise the error here.
111
+ # I don't want to do this for now because the verbaliser will always verbalise all steps.
112
+ if binding.variable.object_type != role.object_type and
113
+ 0 == (binding.variable.object_type.supertypes_transitive & role.object_type.supertypes_transitive).size
114
+ raise "Internal error: Trying to add role of #{role.object_type.name} to variable #{binding.variable.ordinal} for #{binding.variable.object_type.name} in '#{clause.fact_type.default_reading}'"
115
+ end
116
+ raise "Internal error: Trying to add role of #{role.object_type.name} to variable #{binding.variable.ordinal} for #{binding.variable.object_type.name}" unless binding.variable.object_type == role.object_type
117
+ begin
118
+ play = @constellation.Play(binding.variable, role)
119
+ rescue ArgumentError => e
120
+ play = @constellation.Play(binding.variable, role)
121
+ end
122
+ plays << play
123
+ end
124
+ end
125
+
126
+ if ref.nested_clauses
127
+ # We are looking at a role whose player is an objectification of a fact type,
128
+ # which will have ImplicitFactTypes for each role.
129
+ # Each of these ImplicitFactTypes has a single phantom role played by the objectifying entity type
130
+ # One of these phantom roles is likely to be the subject of an objectification step.
131
+ ref.nested_clauses.each do |r|
132
+ debug :query, "Building objectification step for #{ref.nested_clauses.inspect}" do
133
+ build_steps r, roles_by_binding, binding.variable
134
+ end
135
+ end
136
+ end
137
+ roles_by_binding[binding] = [role, play]
138
+ end
139
+ end
140
+
141
+ if plays.size > 0
142
+ end_node = plays[-1].variable
143
+ if !clause.fact_type.entity_type and role = clause.fact_type.all_role.single
144
+ # Don't give the ImplicitBoolean a variable. We can live without one, for now.
145
+ # The Step will have a duplicate node, and the fact type will tell us what's happening
146
+ plays << plays[0]
147
+ end
148
+ # We aren't talking about objectification here, so there must be exactly two roles.
149
+ raise "REVISIT: Internal error constructing step for #{clause.inspect}" if plays.size != 2
150
+ js = @constellation.Step(
151
+ plays[0],
152
+ plays[1],
153
+ :fact_type => clause.fact_type,
154
+ :is_disallowed => clause.certainty == false,
155
+ :is_optional => clause.certainty == nil
156
+ )
157
+ debug :query, "New #{js.describe}"
158
+ debug :query, "Associating #{incidental_roles.map(&:describe)*', '} incidental roles with #{js.describe}" if incidental_roles.size > 0
159
+ incidental_roles.each { |jr| jr.step = js }
160
+ end
161
+ roles_by_binding
162
+ end
163
+
164
+ # Return the unique array of all bindings in these clauses, including in objectification steps
165
+ def all_bindings_in_clauses clauses
166
+ clauses.map do |clause|
167
+ clause.refs.map do |ref|
168
+ raise "Binding reference #{ref.inspect} is not bound to a binding" unless ref.binding
169
+ [ref.binding] + (ref.nested_clauses ? all_bindings_in_clauses(ref.nested_clauses) : [])
170
+ end
171
+ end.
172
+ flatten.
173
+ uniq
174
+ end
175
+ end
176
+ end
177
+ end
178
+ end