activefacts 0.8.9 → 0.8.10

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 (120) hide show
  1. data/.gemtest +0 -0
  2. data/Manifest.txt +28 -33
  3. data/Rakefile +11 -12
  4. data/bin/cql +90 -46
  5. data/examples/CQL/Blog.cql +2 -1
  6. data/examples/CQL/CompanyDirectorEmployee.cql +2 -2
  7. data/examples/CQL/Death.cql +1 -1
  8. data/examples/CQL/Diplomacy.cql +9 -9
  9. data/examples/CQL/Genealogy.cql +3 -2
  10. data/examples/CQL/Insurance.cql +10 -7
  11. data/examples/CQL/JoinEquality.cql +2 -2
  12. data/examples/CQL/Marriage.cql +1 -1
  13. data/examples/CQL/Metamodel.cql +73 -53
  14. data/examples/CQL/MetamodelNext.cql +89 -67
  15. data/examples/CQL/OneToOnes.cql +2 -2
  16. data/examples/CQL/ServiceDirector.cql +10 -5
  17. data/examples/CQL/Supervision.cql +3 -3
  18. data/examples/CQL/Tests.Test5.Load.cql +1 -1
  19. data/examples/CQL/Warehousing.cql +4 -2
  20. data/lib/activefacts/cql/CQLParser.treetop +26 -60
  21. data/lib/activefacts/cql/Context.treetop +12 -2
  22. data/lib/activefacts/cql/Expressions.treetop +14 -30
  23. data/lib/activefacts/cql/FactTypes.treetop +165 -110
  24. data/lib/activefacts/cql/Language/English.treetop +167 -54
  25. data/lib/activefacts/cql/LexicalRules.treetop +16 -2
  26. data/lib/activefacts/cql/{Concepts.treetop → ObjectTypes.treetop} +36 -37
  27. data/lib/activefacts/cql/Terms.treetop +57 -27
  28. data/lib/activefacts/cql/ValueTypes.treetop +39 -13
  29. data/lib/activefacts/cql/compiler.rb +5 -3
  30. data/lib/activefacts/cql/compiler/{reading.rb → clause.rb} +407 -285
  31. data/lib/activefacts/cql/compiler/constraint.rb +178 -275
  32. data/lib/activefacts/cql/compiler/entity_type.rb +73 -64
  33. data/lib/activefacts/cql/compiler/expression.rb +418 -0
  34. data/lib/activefacts/cql/compiler/fact.rb +146 -145
  35. data/lib/activefacts/cql/compiler/fact_type.rb +197 -80
  36. data/lib/activefacts/cql/compiler/join.rb +159 -0
  37. data/lib/activefacts/cql/compiler/shared.rb +51 -23
  38. data/lib/activefacts/cql/compiler/value_type.rb +56 -2
  39. data/lib/activefacts/cql/parser.rb +15 -4
  40. data/lib/activefacts/generate/absorption.rb +7 -7
  41. data/lib/activefacts/generate/cql.rb +100 -37
  42. data/lib/activefacts/generate/oo.rb +28 -51
  43. data/lib/activefacts/generate/ordered.rb +60 -36
  44. data/lib/activefacts/generate/ruby.rb +6 -6
  45. data/lib/activefacts/generate/sql/server.rb +4 -4
  46. data/lib/activefacts/input/orm.rb +71 -53
  47. data/lib/activefacts/persistence.rb +1 -1
  48. data/lib/activefacts/persistence/columns.rb +27 -23
  49. data/lib/activefacts/persistence/foreignkey.rb +6 -6
  50. data/lib/activefacts/persistence/index.rb +17 -17
  51. data/lib/activefacts/persistence/{concept.rb → object_type.rb} +9 -9
  52. data/lib/activefacts/persistence/reference.rb +61 -36
  53. data/lib/activefacts/persistence/tables.rb +61 -59
  54. data/lib/activefacts/support.rb +54 -29
  55. data/lib/activefacts/version.rb +1 -1
  56. data/lib/activefacts/vocabulary/extensions.rb +99 -54
  57. data/lib/activefacts/vocabulary/metamodel.rb +43 -37
  58. data/lib/activefacts/vocabulary/verbaliser.rb +134 -109
  59. data/spec/absorption_spec.rb +8 -8
  60. data/spec/cql/comparison_spec.rb +91 -0
  61. data/spec/cql/contractions_spec.rb +251 -0
  62. data/spec/cql/entity_type_spec.rb +319 -0
  63. data/spec/cql/expressions_spec.rb +63 -0
  64. data/spec/cql/fact_type_matching_spec.rb +283 -0
  65. data/spec/cql/french_spec.rb +21 -0
  66. data/spec/cql/parser/bad_literals_spec.rb +86 -0
  67. data/spec/cql/parser/constraints_spec.rb +19 -0
  68. data/spec/cql/parser/entity_types_spec.rb +106 -0
  69. data/spec/cql/parser/expressions_spec.rb +179 -0
  70. data/spec/cql/parser/fact_types_spec.rb +41 -0
  71. data/spec/cql/parser/literals_spec.rb +312 -0
  72. data/spec/cql/parser/pragmas_spec.rb +89 -0
  73. data/spec/cql/parser/value_types_spec.rb +42 -0
  74. data/spec/cql/role_matching_spec.rb +147 -0
  75. data/spec/cql/samples_spec.rb +9 -9
  76. data/spec/cql_cql_spec.rb +1 -1
  77. data/spec/cql_dm_spec.rb +116 -0
  78. data/spec/cql_mysql_spec.rb +1 -1
  79. data/spec/cql_ruby_spec.rb +1 -1
  80. data/spec/cql_sql_spec.rb +3 -3
  81. data/spec/cql_symbol_tables_spec.rb +30 -30
  82. data/spec/cqldump_spec.rb +4 -4
  83. data/spec/helpers/array_matcher.rb +32 -27
  84. data/spec/helpers/diff_matcher.rb +6 -26
  85. data/spec/helpers/file_matcher.rb +41 -32
  86. data/spec/helpers/parse_to_ast_matcher.rb +76 -0
  87. data/spec/helpers/string_matcher.rb +32 -31
  88. data/spec/norma_cql_spec.rb +1 -1
  89. data/spec/norma_ruby_spec.rb +1 -1
  90. data/spec/norma_ruby_sql_spec.rb +1 -1
  91. data/spec/norma_sql_spec.rb +3 -1
  92. data/spec/norma_tables_spec.rb +1 -1
  93. data/spec/ruby_api_spec.rb +23 -0
  94. data/spec/spec_helper.rb +5 -4
  95. metadata +66 -66
  96. data/examples/CQL/OrienteeringER.cql +0 -58
  97. data/lib/activefacts/api.rb +0 -44
  98. data/lib/activefacts/api/concept.rb +0 -410
  99. data/lib/activefacts/api/constellation.rb +0 -128
  100. data/lib/activefacts/api/entity.rb +0 -256
  101. data/lib/activefacts/api/instance.rb +0 -60
  102. data/lib/activefacts/api/instance_index.rb +0 -80
  103. data/lib/activefacts/api/numeric.rb +0 -167
  104. data/lib/activefacts/api/role.rb +0 -80
  105. data/lib/activefacts/api/role_proxy.rb +0 -70
  106. data/lib/activefacts/api/role_values.rb +0 -117
  107. data/lib/activefacts/api/standard_types.rb +0 -87
  108. data/lib/activefacts/api/support.rb +0 -65
  109. data/lib/activefacts/api/value.rb +0 -135
  110. data/lib/activefacts/api/vocabulary.rb +0 -82
  111. data/spec/api/autocounter.rb +0 -82
  112. data/spec/api/constellation.rb +0 -130
  113. data/spec/api/entity_type.rb +0 -103
  114. data/spec/api/instance.rb +0 -461
  115. data/spec/api/roles.rb +0 -124
  116. data/spec/api/value_type.rb +0 -112
  117. data/spec/api_spec.rb +0 -13
  118. data/spec/cql/matching_spec.rb +0 -517
  119. data/spec/cql/unit_spec.rb +0 -394
  120. data/spec/spec.opts +0 -1
@@ -29,8 +29,8 @@ module ActiveFacts
29
29
  :discussion => @discussion
30
30
  )
31
31
  case target
32
- when ActiveFacts::Metamodel::Concept
33
- context_note.concept = target
32
+ when ActiveFacts::Metamodel::ObjectType
33
+ context_note.object_type = target
34
34
  when ActiveFacts::Metamodel::Constraint
35
35
  context_note.constraint = target
36
36
  when ActiveFacts::Metamodel::FactType
@@ -52,10 +52,13 @@ module ActiveFacts
52
52
  end
53
53
 
54
54
  class Constraint < Definition
55
- def initialize context_note, enforcement, readings_lists = []
55
+ def initialize context_note, enforcement, clauses_lists = []
56
+ if context_note.is_a?(Treetop::Runtime::SyntaxNode)
57
+ context_note = context_note.empty? ? nil : context_note.ast
58
+ end
56
59
  @context_note = context_note
57
60
  @enforcement = enforcement
58
- @readings_lists = readings_lists
61
+ @clauses_lists = clauses_lists
59
62
  end
60
63
 
61
64
  def compile
@@ -67,38 +70,29 @@ module ActiveFacts
67
70
  # Override for constraint types that need loose binding (same role player matching with different adjectives)
68
71
  end
69
72
 
70
- # Return the unique array of all bindings in these readings, including in objectification joins
71
- def all_bindings_in_readings readings
72
- readings.map do |reading|
73
- reading.role_refs.map do |rr|
74
- [rr.binding] + (rr.objectification_join ? all_bindings_in_readings(rr.objectification_join) : [])
75
- end
76
- end.
77
- flatten.
78
- uniq
79
- end
80
-
81
- def bind_readings
73
+ def bind_clauses extra = []
82
74
  @context = CompilationContext.new(@vocabulary)
83
-
84
- @readings_lists.map do |readings_list|
85
- readings_list.each{ |reading| reading.identify_players_with_role_name(@context) }
86
- readings_list.each{ |reading| reading.identify_other_players(@context) }
87
- readings_list.each{ |reading| reading.bind_roles @context } # Create the Compiler::Bindings
88
- readings_list.each do |reading|
89
- fact_type = reading.match_existing_fact_type @context
90
- raise "Unrecognised fact type #{reading.inspect} in #{self.class}" unless fact_type
75
+ @context.left_contraction_allowed = true
76
+
77
+ @context.bind @clauses_lists, extra
78
+ @clauses_lists.map do |clauses_list|
79
+ @context.left_contractable_clause = nil # Don't contract outside this set of clauses
80
+ clauses_list.each do |clause|
81
+ fact_type = clause.match_existing_fact_type @context
82
+ raise "Unrecognised fact type #{clause.inspect} in #{self.class}" unless fact_type
91
83
  end
92
84
  end
93
85
 
86
+ # Any constrained roles will be first identified here.
87
+ # This means that they can't introduce role names.
94
88
  loose_binding
95
89
 
96
90
  # Ok, we have bound all players by subscript/role_name, by adjectives, and by loose binding,
97
91
  # and matched all the fact types that matter. Now assemble a join (with all join steps) for
98
- # each join list, and build an array of the bindings that are involved in the join steps.
99
- @bindings_by_list =
100
- @readings_lists.map do |readings_list|
101
- all_bindings_in_readings(readings_list)
92
+ # each join list, and build an array of the variables that are involved in the join steps.
93
+ @variables_by_list =
94
+ @clauses_lists.map do |clauses_list|
95
+ all_variables_in_clauses(clauses_list)
102
96
  end
103
97
 
104
98
  warn_ignored_joins
@@ -106,8 +100,8 @@ module ActiveFacts
106
100
 
107
101
  def warn_ignored_joins
108
102
  # Warn about ignored joins
109
- @readings_lists.each do |readings_list|
110
- fact_types = readings_list.map{|join| join.role_refs[0].role_ref.role.fact_type}.uniq
103
+ @clauses_lists.each do |clauses_list|
104
+ fact_types = clauses_list.map{|join| join.var_refs[0].role_ref.role.fact_type}.uniq
111
105
  if fact_types.size > 1
112
106
  puts "------->>>> Join ignored in #{self.class}: #{fact_types.map{|ft| ft.preferred_reading.expand}*' and '}"
113
107
  end
@@ -117,63 +111,61 @@ module ActiveFacts
117
111
  def loose_bind_wherever_possible
118
112
  # Apply loose binding over applicable roles:
119
113
  debug :binding, "Loose binding on #{self.class.name}" do
120
- @readings_lists.each do |readings_list|
121
- readings_list.each do |reading|
122
- reading.role_refs.each_with_index do |role_ref, i|
123
- next if role_ref.binding.refs.size > 1
124
- # if reading.side_effects && !reading.side_effects.role_side_effects[i].residual_adjectives
125
- # debug :binding, "Discounting #{role_ref.inspect} as needing loose binding because it has no residual_adjectives"
114
+ @clauses_lists.each do |clauses_list|
115
+ clauses_list.each do |clause|
116
+ clause.var_refs.each_with_index do |var_ref, i|
117
+ next if var_ref.variable.refs.size > 1
118
+ # if clause.side_effects && !clause.side_effects.role_side_effects[i].residual_adjectives
119
+ # debug :binding, "Discounting #{var_ref.inspect} as needing loose binding because it has no residual_adjectives"
126
120
  # next
127
121
  # end
128
- # This role_ref didn't match any other role_ref. Have a scout around for a suitable partner
129
- candidates = @context.bindings.
130
- select do |key, binding|
131
- binding.player == role_ref.binding.player and
132
- binding != role_ref.binding and
133
- binding.role_name == role_ref.binding.role_name and # Both will be nil if they match
134
- # REVISIT: Don't bind to a binding with a role occurrence in the same reading
135
- !binding.refs.detect{|rr|
136
- x = rr.reading == reading
137
- # puts "Discounting binding #{binding.inspect} as a match for #{role_ref.inspect} because it's already bound to a player in #{role_ref.reading.inspect}" if x
122
+ # This var_ref didn't match any other var_ref. Have a scout around for a suitable partner
123
+ candidates = @context.variables.
124
+ select do |key, variable|
125
+ variable.player == var_ref.variable.player and
126
+ variable != var_ref.variable and
127
+ variable.role_name == var_ref.variable.role_name and # Both will be nil if they match
128
+ # REVISIT: Don't bind to a variable with a role occurrence in the same clause
129
+ !variable.refs.detect{|vr|
130
+ x = vr.clause == clause
131
+ # puts "Discounting variable #{variable.inspect} as a match for #{var_ref.inspect} because it's already bound to a player in #{var_ref.clause.inspect}" if x
138
132
  x
139
133
  }
140
134
  end.map{|k,b| b}
141
135
  next if candidates.size != 1 # Fail
142
- debug :binding, "Loose binding #{role_ref.inspect} to #{candidates[0].inspect}"
143
- role_ref.rebind_to(@context, candidates[0].refs[0])
136
+ debug :binding, "Loose binding #{var_ref.inspect} to #{candidates[0].inspect}"
137
+ var_ref.rebind_to(@context, candidates[0].refs[0])
144
138
  end
145
139
  end
146
140
  end
147
141
  end
148
142
  end
149
143
 
150
- def loose_bind_roles
144
+ def loose_bind
151
145
  # Apply loose binding over applicable @roles:
152
146
  debug :binding, "Check for loose bindings on #{@roles.size} roles in #{self.class.name}" do
153
- @roles.each do |role_ref|
154
- role_ref.identify_player @context
155
- role_ref.bind @context
156
- if role_ref.binding.refs.size < @readings_lists.size+1
157
- debug :binding, "Insufficient bindings for #{role_ref.inspect} (#{role_ref.binding.refs.size}, expected #{@readings_lists.size+1}), attempting loose binding" do
158
- @readings_lists.each do |readings_list|
147
+ @roles.each do |var_ref|
148
+ if var_ref.variable.refs.size < @clauses_lists.size+1
149
+ debug :binding, "Insufficient bindings for #{var_ref.inspect} (#{var_ref.variable.refs.size}, expected #{@clauses_lists.size+1}), attempting loose binding" do
150
+ @clauses_lists.each do |clauses_list|
159
151
  candidates = []
160
- next if readings_list.
161
- detect do |reading|
162
- debug :binding, "Checking #{reading.inspect}"
163
- reading.role_refs.
164
- detect do |rr|
165
- already_bound = rr.binding == role_ref.binding
166
- if !already_bound && rr.player == role_ref.player
167
- candidates << rr
152
+ next if clauses_list.
153
+ detect do |clause|
154
+ debug :binding, "Checking #{clause.inspect}"
155
+ clause.var_refs.
156
+ detect do |vr|
157
+ already_bound = vr.variable == var_ref.variable
158
+ if !already_bound && vr.player == var_ref.player
159
+ candidates << vr
168
160
  end
169
161
  already_bound
170
162
  end
171
163
  end
172
- debug :binding, "Attempting loose binding for #{role_ref.inspect} in #{readings_list.inspect}, from the following candidates: #{candidates.inspect}"
164
+ debug :binding, "Attempting loose binding for #{var_ref.inspect} in #{clauses_list.inspect}, from the following candidates: #{candidates.inspect}"
173
165
 
174
166
  if candidates.size == 1
175
- debug :binding, "Rebinding #{candidates[0].inspect} to #{role_ref.inspect}"
176
- candidates[0].rebind_to(@context, role_ref)
167
+ debug :binding, "Rebinding #{candidates[0].inspect} to #{var_ref.inspect}"
168
+ candidates[0].rebind_to(@context, var_ref)
177
169
  end
178
170
  end
179
171
  end
@@ -182,41 +174,45 @@ module ActiveFacts
182
174
  end
183
175
  end
184
176
 
185
- def common_bindings
186
- @common_bindings ||= @bindings_by_list[1..-1].inject(@bindings_by_list[0]) { |r, b| r & b }
187
- raise "#{self.class} must cover some of the same roles, see #{@bindings_by_list.inspect}" unless @common_bindings.size > 0
188
- @common_bindings
177
+ def common_variables
178
+ @common_variables ||= @variables_by_list[1..-1].inject(@variables_by_list[0]) { |r, b| r & b }
179
+ raise "#{self.class} must cover some of the same roles, see #{@variables_by_list.inspect}" unless @common_variables.size > 0
180
+ @common_variables
181
+ end
182
+
183
+ def to_s
184
+ "#{self.class.name.sub(/.*::/,'')}" + (@clauses_lists.size > 0 ? " over #{@clauses_lists.inspect}" : '')
189
185
  end
190
186
  end
191
187
 
192
188
  class PresenceConstraint < Constraint
193
- def initialize context_note, enforcement, readings_lists, role_refs, quantifier
194
- super context_note, enforcement, readings_lists
195
- @role_refs = role_refs || []
189
+ def initialize context_note, enforcement, clauses_lists, var_refs, quantifier
190
+ super context_note, enforcement, clauses_lists
191
+ @var_refs = var_refs || []
196
192
  @quantifier = quantifier
197
193
  end
198
194
 
199
195
  def compile
200
- @readings = @readings_lists.map do |readings_list|
201
- raise "REVISIT: Join presence constraints not supported yet" if readings_list.size > 1 or
202
- readings_list.detect{|reading| reading.role_refs.detect{|rr| rr.objectification_join } }
203
- readings_list[0]
196
+ @clauses = @clauses_lists.map do |clauses_list|
197
+ raise "REVISIT: Join presence constraints not supported yet" if clauses_list.size > 1 or
198
+ clauses_list.detect{|clause| clause.var_refs.detect{|vr| vr.nested_clauses } }
199
+ clauses_list[0]
204
200
  end
205
201
 
206
- bind_readings
202
+ bind_clauses @var_refs
207
203
 
208
- if @role_refs.size > 0
209
- bind_roles
204
+ if @var_refs.size > 0
205
+ bind_constrained_roles
210
206
  else
211
- cb = common_bindings
207
+ cb = common_variables
212
208
  raise "Either/or must have only one duplicated role, not #{cb.inspect}" unless cb.size == 1
213
- @role_refs = cb[0].refs.reverse # REVISIT: Should have order these by reading, not like this
209
+ @var_refs = cb[0].refs.reverse # REVISIT: Should have order these by clause, not like this
214
210
  end
215
211
 
216
212
  role_sequence = @constellation.RoleSequence(:new)
217
- @role_refs.each do |role_ref|
218
- raise "The constrained role #{role_ref.inspect} was not found in the invoked fact types" if role_ref.binding.refs.size == 1
219
- (role_ref.binding.refs-[role_ref]).each do |ref|
213
+ @var_refs.each do |var_ref|
214
+ raise "The constrained role #{var_ref.inspect} was not found in the invoked fact types" if var_ref.variable.refs.size == 1
215
+ (var_ref.variable.refs-[var_ref]).each do |ref|
220
216
  role = (ref.role_ref && ref.role_ref.role) || ref.role
221
217
  raise "FactType role not found for #{ref.inspect}" unless role
222
218
  @constellation.RoleRef(role_sequence, role_sequence.all_role_ref.size, :role => role)
@@ -238,207 +234,87 @@ module ActiveFacts
238
234
  super
239
235
  end
240
236
 
241
- # In a PresenceConstraint, each role in "each XYZ" must occur in exactly one readings_list
237
+ # In a PresenceConstraint, each role in "each XYZ" must occur in exactly one clauses_list
242
238
  def loose_binding
243
239
  # loose_bind_wherever_possible
244
240
  end
245
241
 
246
- def bind_roles
247
- @role_refs.each do |role_ref|
248
- role_ref.identify_player @context
249
- role_ref.bind @context
250
- if role_ref.binding.refs.size == 1
242
+ def bind_constrained_roles
243
+ @var_refs.each do |var_ref|
244
+ if var_ref.variable.refs.size == 1
251
245
  # Apply loose binding over the constrained roles
252
246
  candidates =
253
- @readings.map do |reading|
254
- reading.role_refs.select{ |rr| rr.player == role_ref.player }
247
+ @clauses.map do |clause|
248
+ clause.var_refs.select{ |vr| vr.player == var_ref.player }
255
249
  end.flatten
256
250
  if candidates.size == 1
257
- debug :binding, "Rebinding #{role_ref.inspect} to #{candidates[0].inspect} in presence constraint"
258
- role_ref.rebind_to(@context, candidates[0])
251
+ debug :binding, "Rebinding #{var_ref.inspect} to #{candidates[0].inspect} in presence constraint"
252
+ var_ref.rebind_to(@context, candidates[0])
259
253
  end
260
254
  end
261
255
  end
262
256
  end
263
257
 
258
+ def to_s
259
+ "#{super} #{@quantifier.min}-#{@quantifier.max} over (#{@var_refs.map{|vr| vr.inspect}*', '})"
260
+ end
264
261
  end
265
262
 
266
263
  class SetConstraint < Constraint
267
- def initialize context_note, enforcement, readings_lists
268
- super context_note, enforcement, readings_lists
264
+ def initialize context_note, enforcement, clauses_lists
265
+ super context_note, enforcement, clauses_lists
269
266
  end
270
267
 
271
268
  def warn_ignored_joins
272
269
  # No warnings needed here any more
273
270
  end
274
271
 
275
- # Make a JoinNode for every binding present in these readings
276
- def build_join_nodes(readings_list)
277
- debug :join, "Building join nodes" do
278
- join = @constellation.Join(:new)
279
- all_bindings_in_readings(readings_list).
280
- each do |binding|
281
- debug :join, "Creating join node #{join.all_join_node.size} for #{binding.inspect}"
282
- binding.join_node = @constellation.JoinNode(join, join.all_join_node.size, :concept => binding.player)
283
- end
284
- join
285
- end
286
- end
287
-
288
- def build_join_steps reading, constrained_rs, objectification_node = nil
289
- join_roles = []
290
- incidental_roles = []
291
- debug :join, "Creating join Role Sequence for #{reading.inspect} with #{reading.role_refs.size} role refs" do
292
- objectification_step = nil
293
- reading.role_refs.each do |role_ref|
294
- # These role_refs are the Compiler::RoleRefs. These have associated Metamodel::RoleRefs,
295
- # but we need to create JoinRoles for those roles. # REVISIT: JoinRoles may need to save residual_adjectives
296
- binding = role_ref.binding
297
- role = role_ref.role || role_ref.role_ref.role
298
- join_role = nil
299
-
300
- if (reading.fact_type.entity_type)
301
- # This reading is of an objectified fact type.
302
- # We need a join step from this role to the phantom role, but not
303
- # for a role that has only one role_ref (this one) in their binding.
304
- # Create the JoinNode and JoinRole in any case though.
305
- refs_count = binding.refs.size
306
- objectification_ref_count = 0
307
- if role_ref.objectification_join
308
- role_ref.objectification_join.each do |r|
309
- objectification_ref_count += r.role_refs.select{|rr| rr.binding.refs.size > 1}.size
310
- end
311
- end
312
- refs_count += objectification_ref_count
313
-
314
- debug :join, "Creating Join Node #{role_ref.inspect} (counts #{refs_count}/#{objectification_ref_count}) and objectification Join Step for #{role_ref.inspect}" do
272
+ def role_sequences_for_common_variables ignore_trailing_joins = false
273
+ @clauses_lists.
274
+ zip(@variables_by_list).
275
+ map do |clauses_list, variables|
276
+ # Does this clauses_list involve a join?
277
+ if clauses_list.size > 1 or
278
+ clauses_list.detect{|clause| clause.var_refs.detect{|var_ref| var_ref.nested_clauses } }
315
279
 
316
- raise "Internal error: Trying to add role of #{role.concept.name} to join node for #{binding.join_node.concept.name}" unless binding.join_node.concept == role.concept
317
- join_role = @constellation.JoinRole(binding.join_node, role)
280
+ debug :join, "Building join for #{clauses_list.inspect}" do
281
+ debug :join, "Constrained variables are #{@common_variables.inspect}"
282
+ # Every Variable in these clauses becomes a Join Node,
283
+ # and every clause becomes a JoinStep (and a RoleSequence).
284
+ # The returned RoleSequences contains the RoleRefs for the common_variables.
318
285
 
319
- if (refs_count <= 1) # Our work here is done if there are no other refs
320
- if objectification_step
321
- join_role.join_step = objectification_step
322
- else
323
- incidental_roles << join_role
324
- end
325
- next
326
- end
327
-
328
- join_roles << join_role
329
- unless objectification_node
330
- # This is an implicit objectification, just the FT reading, not ET(where ...reading...)
331
- # We need to create a JoinNode for this object, even though it has no RoleRefs
332
- join = binding.join_node.join
333
- debug :join, "Creating JN#{join.all_join_node.size} for #{reading.fact_type.entity_type.name} in objectification"
334
- objectification_node = @constellation.JoinNode(join, join.all_join_node.size, :concept => reading.fact_type.entity_type)
335
- end
336
- raise "Internal error: Trying to add role of #{role.implicit_fact_type.all_role.single.concept.name} to join node for #{objectification_node.concept.name}" unless objectification_node.concept == role.implicit_fact_type.all_role.single.concept
337
-
338
- irole = role.implicit_fact_type.all_role.single
339
- raise "Internal error: Trying to add role of #{irole.concept.name} to join node for #{objectification_node.concept.name}" unless objectification_node.concept == irole.concept
340
- objectification_role = @constellation.JoinRole(objectification_node, role.implicit_fact_type.all_role.single)
341
- objectification_step = @constellation.JoinStep(objectification_role, join_role, :fact_type => role.implicit_fact_type)
342
- debug :join, "New #{objectification_step.describe}"
343
- debug :join, "Associating #{incidental_roles.map(&:describe)*', '} incidental roles with #{objectification_step.describe}" if incidental_roles.size > 0
344
- incidental_roles.each { |jr| jr.join_step = objectification_step }
345
- incidental_roles = []
346
- join_roles = []
347
- end
348
- else
349
- debug :join, "Creating Role Ref for #{role_ref.inspect}" do
350
- # REVISIT: If there's an implicit subtyping join here, create it; then always raise the error here.
351
- # I don't want to do this for now because the verbaliser will always verbalise all join steps.
352
- if binding.join_node.concept != role.concept and
353
- 0 == (binding.join_node.concept.supertypes_transitive & role.concept.supertypes_transitive).size
354
- raise "Internal error: Trying to add role of #{role.concept.name} to join node for #{binding.join_node.concept.name} in '#{reading.fact_type.default_reading}'"
355
- end
356
- raise "Internal error: Trying to add role of #{role.concept.name} to join node for #{binding.join_node.concept.name}" unless binding.join_node.concept == role.concept
357
- join_role = @constellation.JoinRole(binding.join_node, role)
358
- join_roles << join_role
359
- end
360
- end
361
-
362
- if role_ref.objectification_join
363
- # We are looking at a role whose player is an objectification of a fact type,
364
- # which will have ImplicitFactTypes for each role.
365
- # Each of these ImplicitFactTypes has a single phantom role played by the objectifying entity type
366
- # One of these phantom roles is likely to be the subject of an objectification join step.
367
- role_ref.objectification_join.each do |r|
368
- debug :join, "Building objectification join for #{role_ref.objectification_join.inspect}" do
369
- build_join_steps r, constrained_rs, binding.join_node
370
- end
371
- end
372
- end
373
- if (@common_bindings.include?(binding))
374
- debug :join, "#{binding.inspect} is a constrained binding, add the Role Ref for #{role.concept.name}"
375
- raise "Internal error: Trying to add role of #{role.concept.name} to join node for #{binding.join_node.concept.name}" unless binding.join_node.concept == role.concept
376
- @constellation.RoleRef(constrained_rs, constrained_rs.all_role_ref.size, :role => role, :join_role => join_role)
377
- end
378
- end
379
- end
380
-
381
- if join_roles.size > 0
382
- end_node = join_roles[-1].join_node
383
- if !reading.fact_type.entity_type and role = reading.fact_type.all_role.single
384
- # Don't give the ImplicitBoolean a join_node. We can live without one, for now.
385
- # The Join Step will have a duplicate node, and the fact type will tell us what's happening
386
- join_roles << join_roles[0]
387
- end
388
- # We aren't talking about objectification here, so there must be exactly two roles.
389
- raise "REVISIT: Internal error constructing join for #{reading.inspect}" if join_roles.size != 2
390
- js = @constellation.JoinStep(join_roles[0], join_roles[1], :fact_type => reading.fact_type)
391
- debug :join, "New Join Step #{js.describe}"
392
- debug :join, "Associating #{incidental_roles.map(&:describe)*', '} incidental roles with #{js.describe}" if incidental_roles.size > 0
393
- incidental_roles.each { |jr| jr.join_step = js }
394
- end
395
- end
396
-
397
- def role_sequences_for_common_bindings ignore_trailing_joins = false
398
- @readings_lists.
399
- zip(@bindings_by_list).
400
- map do |readings_list, bindings|
401
- # Does this readings_list involve a join?
402
- if readings_list.size > 1 or
403
- readings_list.detect{|reading| reading.role_refs.detect{|role_ref| role_ref.objectification_join } }
404
-
405
- debug :join, "Building join for #{readings_list.inspect}" do
406
- debug :join, "Constrained bindings are #{@common_bindings.inspect}"
407
- # Every Binding in these readings becomes a Join Node,
408
- # and every reading becomes a JoinStep (and a RoleSequence).
409
- # The returned RoleSequences contains the RoleRefs for the common_bindings.
410
-
411
- # Create a join with a join node for every binding:
412
- join = build_join_nodes(readings_list)
286
+ # Create a join with a join node for every variable and all join steps:
287
+ join = build_join_nodes(clauses_list)
288
+ roles_by_variable = build_all_join_steps(clauses_list)
289
+ join.validate
413
290
 
291
+ # Create the projected RoleSequence for the constraint:
414
292
  role_sequence = @constellation.RoleSequence(:new)
415
- debug :join, "Building join steps" do
416
- readings_list.each do |reading|
417
- build_join_steps(reading, role_sequence)
418
- end
293
+ @common_variables.each do |variable|
294
+ role, join_role = *roles_by_variable[variable]
295
+ @constellation.RoleRef(role_sequence, role_sequence.all_role_ref.size, :role => role, :join_role => join_role)
419
296
  end
420
- join.validate
421
297
 
422
298
  role_sequence
423
299
  end
424
300
  else
425
- # There's no join in this readings_list, just create a role_sequence
301
+ # There's no join in this clauses_list, just create a role_sequence
426
302
  role_sequence = @constellation.RoleSequence(:new)
427
- join_bindings = bindings-@common_bindings
428
- unless join_bindings.empty? or ignore_trailing_joins && join_bindings.size <= 1
429
- debug :constraint, "REVISIT: #{self.class}: Ignoring join from #{@common_bindings.inspect} to #{join_bindings.inspect} in #{readings_list.inspect}"
303
+ join_variables = variables-@common_variables
304
+ unless join_variables.empty? or ignore_trailing_joins && join_variables.size <= 1
305
+ debug :constraint, "REVISIT: #{self.class}: Ignoring join from #{@common_variables.inspect} to #{join_variables.inspect} in #{clauses_list.inspect}"
430
306
  end
431
- @common_bindings.each do |binding|
432
- roles = readings_list.
433
- map do |reading|
434
- reading.role_refs.detect{|rr| rr.binding == binding }
307
+ @common_variables.each do |variable|
308
+ roles = clauses_list.
309
+ map do |clause|
310
+ clause.var_refs.detect{|vr| vr.variable == variable }
435
311
  end.
436
- compact. # A join reading will probably not have the common binding
437
- map do |role_ref|
438
- role_ref.role_ref && role_ref.role_ref.role or role_ref.role
312
+ compact. # A join clause will probably not have the common variable
313
+ map do |var_ref|
314
+ var_ref.role_ref && var_ref.role_ref.role or var_ref.role
439
315
  end.
440
316
  compact
441
- # REVISIT: Should use reading side effects to preserve residual adjectives here.
317
+ # REVISIT: Should use clause side effects to preserve residual adjectives here.
442
318
  @constellation.RoleRef(role_sequence, role_sequence.all_role_ref.size, :role => roles[0])
443
319
  end
444
320
  role_sequence
@@ -448,18 +324,18 @@ module ActiveFacts
448
324
  end
449
325
 
450
326
  class SubsetConstraint < SetConstraint
451
- def initialize context_note, enforcement, readings_lists
452
- super context_note, enforcement, readings_lists
453
- @subset_readings = @readings_lists[0]
454
- @superset_readings = @readings_lists[1]
327
+ def initialize context_note, enforcement, clauses_lists
328
+ super context_note, enforcement, clauses_lists
329
+ @subset_clauses = @clauses_lists[0]
330
+ @superset_clauses = @clauses_lists[1]
455
331
  end
456
332
 
457
333
  def compile
458
- bind_readings
459
- common_bindings
334
+ bind_clauses
335
+ common_variables
460
336
 
461
337
  role_sequences =
462
- role_sequences_for_common_bindings
338
+ role_sequences_for_common_variables
463
339
 
464
340
  @constraint =
465
341
  @constellation.SubsetConstraint(
@@ -478,24 +354,24 @@ module ActiveFacts
478
354
  end
479
355
 
480
356
  class SetComparisonConstraint < SetConstraint
481
- def initialize context_note, enforcement, readings_lists
482
- super context_note, enforcement, readings_lists
357
+ def initialize context_note, enforcement, clauses_lists
358
+ super context_note, enforcement, clauses_lists
483
359
  end
484
360
  end
485
361
 
486
362
  class SetExclusionConstraint < SetComparisonConstraint
487
- def initialize context_note, enforcement, readings_lists, roles, quantifier
488
- super context_note, enforcement, readings_lists
363
+ def initialize context_note, enforcement, clauses_lists, roles, quantifier
364
+ super context_note, enforcement, clauses_lists
489
365
  @roles = roles || []
490
366
  @quantifier = quantifier
491
367
  end
492
368
 
493
369
  def compile
494
- bind_readings
495
- common_bindings
370
+ bind_clauses @roles
371
+ common_variables
496
372
 
497
373
  role_sequences =
498
- role_sequences_for_common_bindings
374
+ role_sequences_for_common_variables
499
375
 
500
376
  @constraint = @constellation.SetExclusionConstraint(
501
377
  :new,
@@ -509,28 +385,28 @@ module ActiveFacts
509
385
  super
510
386
  end
511
387
 
512
- # In a SetExclusionConstraint, each role in "for each XYZ" must occur in each readings_list
388
+ # In a SetExclusionConstraint, each role in "for each XYZ" must occur in each clauses_list
513
389
  def loose_binding
514
390
  if @roles.size == 0
515
391
  loose_bind_wherever_possible
516
392
  else
517
- loose_bind_roles
393
+ loose_bind
518
394
  end
519
395
  end
520
396
 
521
397
  end
522
398
 
523
399
  class SetEqualityConstraint < SetComparisonConstraint
524
- def initialize context_note, enforcement, readings_lists
525
- super context_note, enforcement, readings_lists
400
+ def initialize context_note, enforcement, clauses_lists
401
+ super context_note, enforcement, clauses_lists
526
402
  end
527
403
 
528
404
  def compile
529
- bind_readings
530
- common_bindings
405
+ bind_clauses
406
+ common_variables
531
407
 
532
408
  role_sequences =
533
- role_sequences_for_common_bindings
409
+ role_sequences_for_common_variables
534
410
 
535
411
  @constraint = @constellation.SetEqualityConstraint(
536
412
  :new,
@@ -546,7 +422,6 @@ module ActiveFacts
546
422
  def loose_binding
547
423
  loose_bind_wherever_possible
548
424
  end
549
-
550
425
  end
551
426
 
552
427
  class RingConstraint < Constraint
@@ -564,10 +439,10 @@ module ActiveFacts
564
439
  # Process the ring constraints:
565
440
  return if @rings.empty?
566
441
 
567
- role_refs = @role_sequence.all_role_ref.to_a
442
+ role_refs = @role_sequence.all_role_ref_in_order.to_a
568
443
  supertypes_by_position = role_refs.
569
444
  map do |role_ref|
570
- role_ref.role.concept.supertypes_transitive
445
+ role_ref.role.object_type.supertypes_transitive
571
446
  end
572
447
  role_pairs = []
573
448
  supertypes_by_position.each_with_index do |sts, i|
@@ -606,16 +481,22 @@ module ActiveFacts
606
481
  debug :constraint, "Added #{@constraint.verbalise} #{@constraint.class.roles.keys.map{|k|"#{k} => "+@constraint.send(k).verbalise}*", "}"
607
482
  super
608
483
  end
484
+
485
+ def to_s
486
+ "#{super} #{@rings*','} over #{@clauses_lists.inspect}"
487
+ end
609
488
  end
610
489
 
611
490
  class ValueConstraint < Constraint
612
- def initialize value_ranges, enforcement
491
+ def initialize value_ranges, units, enforcement
613
492
  super nil, enforcement
614
493
  @value_ranges = value_ranges
494
+ @units = units
615
495
  end
616
496
 
617
497
  def compile
618
498
  @constraint = @constellation.ValueConstraint(:new)
499
+ $stderr.puts "REVISIT: units on value constraints are not yet processed" if @units
619
500
  @value_ranges.each do |range|
620
501
  min, max = Array === range ? range : [range, range]
621
502
  v_range = @constellation.ValueRange(
@@ -627,6 +508,28 @@ module ActiveFacts
627
508
  @enforcement.compile(@constellation, @constraint) if @enforcement
628
509
  super
629
510
  end
511
+
512
+ def vrto_s vr
513
+ if Array === vr
514
+ min = vr[0]
515
+ max = vr[1]
516
+ if Numeric === min or Numeric === max
517
+ infinite = 1.0/0
518
+ min ||= -infinite
519
+ max ||= infinite
520
+ else
521
+ min ||= 'MIN'
522
+ max ||= 'MAX'
523
+ end
524
+ Range.new(min, max)
525
+ else
526
+ vr
527
+ end
528
+ end
529
+
530
+ def to_s
531
+ "#{super} to (#{@value_ranges.map{|vr| vrto_s(vr) }.inspect })#{ @units ? " in #{@units.inspect}" : ''}"
532
+ end
630
533
  end
631
534
 
632
535
  end