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
@@ -28,7 +28,7 @@ module ActiveFacts
28
28
  :context_note_kind => @context_kind,
29
29
  :discussion => @discussion
30
30
  )
31
- context_note.concept = target
31
+ context_note.relevant_concept = target
32
32
  if @agreed_date || @agreed_agents
33
33
  agreement = constellation.Agreement(context_note)
34
34
  agreement.date = @agreed_date if @agreed_date
@@ -74,6 +74,7 @@ module ActiveFacts
74
74
  clauses_list.each do |clause|
75
75
  fact_type = clause.match_existing_fact_type @context
76
76
  raise "Unrecognised fact type #{clause.inspect} in #{self.class}" unless fact_type
77
+ raise "Negated fact type #{clause.inspect} in #{self.class} is not yet supported" if clause.certainty == false
77
78
  end
78
79
  end
79
80
 
@@ -82,22 +83,22 @@ module ActiveFacts
82
83
  loose_binding
83
84
 
84
85
  # Ok, we have bound all players by subscript/role_name, by adjectives, and by loose binding,
85
- # and matched all the fact types that matter. Now assemble a join (with all join steps) for
86
- # each join list, and build an array of the variables that are involved in the join steps.
87
- @variables_by_list =
86
+ # and matched all the fact types that matter. Now assemble a query (with all steps) for
87
+ # each query list, and build an array of the bindings that are involved in the steps.
88
+ @bindings_by_list =
88
89
  @clauses_lists.map do |clauses_list|
89
- all_variables_in_clauses(clauses_list)
90
+ all_bindings_in_clauses(clauses_list)
90
91
  end
91
92
 
92
- warn_ignored_joins
93
+ warn_ignored_queries
93
94
  end
94
95
 
95
- def warn_ignored_joins
96
- # Warn about ignored joins
96
+ def warn_ignored_queries
97
+ # Warn about ignored queries
97
98
  @clauses_lists.each do |clauses_list|
98
- fact_types = clauses_list.map{|join| join.var_refs[0].role_ref.role.fact_type}.uniq
99
+ fact_types = clauses_list.map{|clauses| (rr = clauses.refs[0].role_ref) && rr.role.fact_type}.compact.uniq
99
100
  if fact_types.size > 1
100
- puts "------->>>> Join ignored in #{self.class}: #{fact_types.map{|ft| ft.preferred_reading.expand}*' and '}"
101
+ raise "------->>>> join ignored in #{self.class}: #{fact_types.map{|ft| ft.preferred_reading.expand}*' and '}"
101
102
  end
102
103
  end
103
104
  end
@@ -107,28 +108,28 @@ module ActiveFacts
107
108
  debug :binding, "Loose binding on #{self.class.name}" do
108
109
  @clauses_lists.each do |clauses_list|
109
110
  clauses_list.each do |clause|
110
- clause.var_refs.each_with_index do |var_ref, i|
111
- next if var_ref.variable.refs.size > 1
111
+ clause.refs.each_with_index do |ref, i|
112
+ next if ref.binding.refs.size > 1
112
113
  # if clause.side_effects && !clause.side_effects.role_side_effects[i].residual_adjectives
113
- # debug :binding, "Discounting #{var_ref.inspect} as needing loose binding because it has no residual_adjectives"
114
+ # debug :binding, "Discounting #{ref.inspect} as needing loose binding because it has no residual_adjectives"
114
115
  # next
115
116
  # end
116
- # This var_ref didn't match any other var_ref. Have a scout around for a suitable partner
117
- candidates = @context.variables.
118
- select do |key, variable|
119
- variable.player == var_ref.variable.player and
120
- variable != var_ref.variable and
121
- variable.role_name == var_ref.variable.role_name and # Both will be nil if they match
122
- # REVISIT: Don't bind to a variable with a role occurrence in the same clause
123
- !variable.refs.detect{|vr|
117
+ # This ref didn't match any other ref. Have a scout around for a suitable partner
118
+ candidates = @context.bindings.
119
+ select do |key, binding|
120
+ binding.player == ref.binding.player and
121
+ binding != ref.binding and
122
+ binding.role_name == ref.binding.role_name and # Both will be nil if they match
123
+ # REVISIT: Don't bind to a binding with a role occurrence in the same clause
124
+ !binding.refs.detect{|vr|
124
125
  x = vr.clause == clause
125
- # 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
126
+ # puts "Discounting binding #{binding.inspect} as a match for #{ref.inspect} because it's already bound to a player in #{ref.clause.inspect}" if x
126
127
  x
127
128
  }
128
129
  end.map{|k,b| b}
129
130
  next if candidates.size != 1 # Fail
130
- debug :binding, "Loose binding #{var_ref.inspect} to #{candidates[0].inspect}"
131
- var_ref.rebind_to(@context, candidates[0].refs[0])
131
+ debug :binding, "Loose binding #{ref.inspect} to #{candidates[0].inspect}"
132
+ ref.rebind_to(@context, candidates[0].refs[0])
132
133
  end
133
134
  end
134
135
  end
@@ -138,28 +139,28 @@ module ActiveFacts
138
139
  def loose_bind
139
140
  # Apply loose binding over applicable @roles:
140
141
  debug :binding, "Check for loose bindings on #{@roles.size} roles in #{self.class.name}" do
141
- @roles.each do |var_ref|
142
- if var_ref.variable.refs.size < @clauses_lists.size+1
143
- debug :binding, "Insufficient bindings for #{var_ref.inspect} (#{var_ref.variable.refs.size}, expected #{@clauses_lists.size+1}), attempting loose binding" do
142
+ @roles.each do |ref|
143
+ if ref.binding.refs.size < @clauses_lists.size+1
144
+ debug :binding, "Insufficient bindings for #{ref.inspect} (#{ref.binding.refs.size}, expected #{@clauses_lists.size+1}), attempting loose binding" do
144
145
  @clauses_lists.each do |clauses_list|
145
146
  candidates = []
146
147
  next if clauses_list.
147
148
  detect do |clause|
148
149
  debug :binding, "Checking #{clause.inspect}"
149
- clause.var_refs.
150
+ clause.refs.
150
151
  detect do |vr|
151
- already_bound = vr.variable == var_ref.variable
152
- if !already_bound && vr.player == var_ref.player
152
+ already_bound = vr.binding == ref.binding
153
+ if !already_bound && vr.player == ref.player
153
154
  candidates << vr
154
155
  end
155
156
  already_bound
156
157
  end
157
158
  end
158
- debug :binding, "Attempting loose binding for #{var_ref.inspect} in #{clauses_list.inspect}, from the following candidates: #{candidates.inspect}"
159
+ debug :binding, "Attempting loose binding for #{ref.inspect} in #{clauses_list.inspect}, from the following candidates: #{candidates.inspect}"
159
160
 
160
161
  if candidates.size == 1
161
- debug :binding, "Rebinding #{candidates[0].inspect} to #{var_ref.inspect}"
162
- candidates[0].rebind_to(@context, var_ref)
162
+ debug :binding, "Rebinding #{candidates[0].inspect} to #{ref.inspect}"
163
+ candidates[0].rebind_to(@context, ref)
163
164
  end
164
165
  end
165
166
  end
@@ -168,10 +169,10 @@ module ActiveFacts
168
169
  end
169
170
  end
170
171
 
171
- def common_variables
172
- @common_variables ||= @variables_by_list[1..-1].inject(@variables_by_list[0]) { |r, b| r & b }
173
- raise "#{self.class} must cover some of the same roles, see #{@variables_by_list.inspect}" unless @common_variables.size > 0
174
- @common_variables
172
+ def common_bindings
173
+ @common_bindings ||= @bindings_by_list[1..-1].inject(@bindings_by_list[0]) { |r, b| r & b }
174
+ raise "#{self.class} must cover some of the same roles, see #{@bindings_by_list.inspect}" unless @common_bindings.size > 0
175
+ @common_bindings
175
176
  end
176
177
 
177
178
  def to_s
@@ -180,33 +181,33 @@ module ActiveFacts
180
181
  end
181
182
 
182
183
  class PresenceConstraint < Constraint
183
- def initialize context_note, enforcement, clauses_lists, var_refs, quantifier
184
+ def initialize context_note, enforcement, clauses_lists, refs, quantifier
184
185
  super context_note, enforcement, clauses_lists
185
- @var_refs = var_refs || []
186
+ @refs = refs || []
186
187
  @quantifier = quantifier
187
188
  end
188
189
 
189
190
  def compile
190
191
  @clauses = @clauses_lists.map do |clauses_list|
191
- raise "REVISIT: Join presence constraints not supported yet" if clauses_list.size > 1 or
192
- clauses_list.detect{|clause| clause.var_refs.detect{|vr| vr.nested_clauses } }
192
+ raise "REVISIT: join presence constraints not supported yet" if clauses_list.size > 1 or
193
+ clauses_list.detect{|clause| clause.refs.detect{|vr| vr.nested_clauses } }
193
194
  clauses_list[0]
194
195
  end
195
196
 
196
- bind_clauses @var_refs
197
+ bind_clauses @refs
197
198
 
198
- if @var_refs.size > 0
199
+ if @refs.size > 0
199
200
  bind_constrained_roles
200
201
  else
201
- cb = common_variables
202
+ cb = common_bindings
202
203
  raise "Either/or must have only one duplicated role, not #{cb.inspect}" unless cb.size == 1
203
- @var_refs = cb[0].refs.reverse # REVISIT: Should have order these by clause, not like this
204
+ @refs = cb[0].refs.reverse # REVISIT: Should have order these by clause, not like this
204
205
  end
205
206
 
206
207
  role_sequence = @constellation.RoleSequence(:new)
207
- @var_refs.each do |var_ref|
208
- raise "The constrained role #{var_ref.inspect} was not found in the invoked fact types" if var_ref.variable.refs.size == 1
209
- (var_ref.variable.refs-[var_ref]).each do |ref|
208
+ @refs.each do |ref|
209
+ raise "The constrained role #{ref.inspect} was not found in the invoked fact types" if ref.binding.refs.size == 1
210
+ (ref.binding.refs-[ref]).each do |ref|
210
211
  role = (ref.role_ref && ref.role_ref.role) || ref.role
211
212
  raise "FactType role not found for #{ref.inspect}" unless role
212
213
  @constellation.RoleRef(role_sequence, role_sequence.all_role_ref.size, :role => role)
@@ -225,6 +226,7 @@ module ActiveFacts
225
226
  :is_mandatory => @quantifier.min && @quantifier.min > 0
226
227
  )
227
228
  @enforcement.compile(@constellation, @constraint) if @enforcement
229
+ debug :constraint, "Made new PC GUID=#{@constraint.guid} min=#{@quantifier.min.inspect} max=#{@quantifier.max.inspect} over #{role_sequence.describe}"
228
230
  super
229
231
  end
230
232
 
@@ -234,23 +236,23 @@ module ActiveFacts
234
236
  end
235
237
 
236
238
  def bind_constrained_roles
237
- @var_refs.each do |var_ref|
238
- if var_ref.variable.refs.size == 1
239
+ @refs.each do |ref|
240
+ if ref.binding.refs.size == 1
239
241
  # Apply loose binding over the constrained roles
240
242
  candidates =
241
243
  @clauses.map do |clause|
242
- clause.var_refs.select{ |vr| vr.player == var_ref.player }
244
+ clause.refs.select{ |vr| vr.player == ref.player }
243
245
  end.flatten
244
246
  if candidates.size == 1
245
- debug :binding, "Rebinding #{var_ref.inspect} to #{candidates[0].inspect} in presence constraint"
246
- var_ref.rebind_to(@context, candidates[0])
247
+ debug :binding, "Rebinding #{ref.inspect} to #{candidates[0].inspect} in presence constraint"
248
+ ref.rebind_to(@context, candidates[0])
247
249
  end
248
250
  end
249
251
  end
250
252
  end
251
253
 
252
254
  def to_s
253
- "#{super} #{@quantifier.min}-#{@quantifier.max} over (#{@var_refs.map{|vr| vr.inspect}*', '})"
255
+ "#{super} #{@quantifier.min}-#{@quantifier.max} over (#{@refs.map{|vr| vr.inspect}*', '})"
254
256
  end
255
257
  end
256
258
 
@@ -259,53 +261,56 @@ module ActiveFacts
259
261
  super context_note, enforcement, clauses_lists
260
262
  end
261
263
 
262
- def warn_ignored_joins
264
+ def warn_ignored_queries
263
265
  # No warnings needed here any more
264
266
  end
265
267
 
266
- def role_sequences_for_common_variables ignore_trailing_joins = false
268
+ def role_sequences_for_common_bindings ignore_trailing_steps = false
267
269
  @clauses_lists.
268
- zip(@variables_by_list).
269
- map do |clauses_list, variables|
270
- # Does this clauses_list involve a join?
270
+ zip(@bindings_by_list).
271
+ map do |clauses_list, bindings|
272
+ # Does this clauses_list involve a query?
271
273
  if clauses_list.size > 1 or
272
- clauses_list.detect{|clause| clause.var_refs.detect{|var_ref| var_ref.nested_clauses } }
273
-
274
- debug :join, "Building join for #{clauses_list.inspect}" do
275
- debug :join, "Constrained variables are #{@common_variables.inspect}"
276
- # Every Variable in these clauses becomes a Join Node,
277
- # and every clause becomes a JoinStep (and a RoleSequence).
278
- # The returned RoleSequences contains the RoleRefs for the common_variables.
279
-
280
- # Create a join with a join node for every variable and all join steps:
281
- join = build_join_nodes(clauses_list)
282
- roles_by_variable = build_all_join_steps(clauses_list)
283
- join.validate
274
+ clauses_list.detect do |clause|
275
+ clause.refs.detect{|ref| ref.nested_clauses } or
276
+ clause.includes_literals
277
+ end
278
+
279
+ debug :query, "Building query for #{clauses_list.inspect}" do
280
+ debug :query, "Constrained bindings are #{@common_bindings.inspect}"
281
+ # Every Binding in these clauses becomes a Variable,
282
+ # and every clause becomes a Step (and a RoleSequence).
283
+ # The returned RoleSequences contains the RoleRefs for the common_bindings.
284
+
285
+ # Create a query with a variable for every binding and all steps:
286
+ query = build_variables(clauses_list)
287
+ roles_by_binding = build_all_steps(clauses_list)
288
+ query.validate
284
289
 
285
290
  # Create the projected RoleSequence for the constraint:
286
291
  role_sequence = @constellation.RoleSequence(:new)
287
- @common_variables.each do |variable|
288
- role, join_role = *roles_by_variable[variable]
289
- @constellation.RoleRef(role_sequence, role_sequence.all_role_ref.size, :role => role, :join_role => join_role)
292
+ @common_bindings.each do |binding|
293
+ role, play = *roles_by_binding[binding]
294
+ @constellation.RoleRef(role_sequence, role_sequence.all_role_ref.size, :role => role, :play => play)
290
295
  end
291
296
 
292
297
  role_sequence
293
298
  end
294
299
  else
295
- # There's no join in this clauses_list, just create a role_sequence
300
+ # There's no query in this clauses_list, just create a role_sequence
296
301
  role_sequence = @constellation.RoleSequence(:new)
297
- join_variables = variables-@common_variables
298
- unless join_variables.empty? or ignore_trailing_joins && join_variables.size <= 1
299
- debug :constraint, "REVISIT: #{self.class}: Ignoring join from #{@common_variables.inspect} to #{join_variables.inspect} in #{clauses_list.inspect}"
302
+ query_bindings = bindings-@common_bindings
303
+ unless query_bindings.empty? or ignore_trailing_steps && query_bindings.size <= 1
304
+ debug :constraint, "REVISIT: #{self.class}: Ignoring query from #{@common_bindings.inspect} to #{query_bindings.inspect} in #{clauses_list.inspect}"
300
305
  end
301
- @common_variables.each do |variable|
306
+ @common_bindings.each do |binding|
302
307
  roles = clauses_list.
303
308
  map do |clause|
304
- clause.var_refs.detect{|vr| vr.variable == variable }
309
+ clause.refs.detect{|vr| vr.binding == binding }
305
310
  end.
306
- compact. # A join clause will probably not have the common variable
307
- map do |var_ref|
308
- var_ref.role_ref && var_ref.role_ref.role or var_ref.role
311
+ compact. # A query clause will probably not have the common binding
312
+ map do |ref|
313
+ ref.role_ref && ref.role_ref.role or ref.role
309
314
  end.
310
315
  compact
311
316
  # REVISIT: Should use clause side effects to preserve residual adjectives here.
@@ -326,10 +331,10 @@ module ActiveFacts
326
331
 
327
332
  def compile
328
333
  bind_clauses
329
- common_variables
334
+ common_bindings
330
335
 
331
336
  role_sequences =
332
- role_sequences_for_common_variables
337
+ role_sequences_for_common_bindings
333
338
 
334
339
  @constraint =
335
340
  @constellation.SubsetConstraint(
@@ -362,10 +367,10 @@ module ActiveFacts
362
367
 
363
368
  def compile
364
369
  bind_clauses @roles
365
- common_variables
370
+ common_bindings
366
371
 
367
372
  role_sequences =
368
- role_sequences_for_common_variables
373
+ role_sequences_for_common_bindings
369
374
 
370
375
  @constraint = @constellation.SetExclusionConstraint(
371
376
  :new,
@@ -397,10 +402,10 @@ module ActiveFacts
397
402
 
398
403
  def compile
399
404
  bind_clauses
400
- common_variables
405
+ common_bindings
401
406
 
402
407
  role_sequences =
403
- role_sequences_for_common_variables
408
+ role_sequences_for_common_bindings
404
409
 
405
410
  @constraint = @constellation.SetEqualityConstraint(
406
411
  :new,
@@ -419,8 +424,9 @@ module ActiveFacts
419
424
  end
420
425
 
421
426
  class RingConstraint < Constraint
422
- Types = %w{acyclic intransitive symmetric asymmetric transitive antisymmetric irreflexive reflexive}
427
+ Types = %w{acyclic intransitive stronglyintransitive symmetric asymmetric transitive antisymmetric irreflexive reflexive}
423
428
  Pairs = {
429
+ :stronglyintransitive => [:acyclic, :asymmetric, :symmetric],
424
430
  :intransitive => [:acyclic, :asymmetric, :symmetric],
425
431
  :transitive => [:acyclic],
426
432
  :acyclic => [:transitive],
@@ -487,28 +493,42 @@ module ActiveFacts
487
493
  end
488
494
 
489
495
  class ValueConstraint < Constraint
490
- def initialize value_ranges, units, enforcement
496
+ def initialize ast, enforcement
491
497
  super nil, enforcement
492
- @value_ranges = value_ranges
493
- @units = units
498
+ @value_ranges = ast[:ranges]
499
+ @units = ast[:units]
500
+ @regular_expression = ast[:regular_expression]
494
501
  end
495
502
 
503
+ def assert_value(val)
504
+ if val.is_a?(String)
505
+ @constellation.Value(eval(val), true, nil)
506
+ elsif val
507
+ @constellation.Value(val.to_s, false , nil)
508
+ else
509
+ nil
510
+ end
511
+ end
512
+
496
513
  def compile
497
514
  @constraint = @constellation.ValueConstraint(:new)
498
515
  raise "Units on value constraints are not yet processed (at line #{'REVISIT'})" if @units
499
516
  # @string.line_of(node.interval.first)
500
517
 
501
- @value_ranges.each do |range|
502
- min, max = Array === range ? range : [range, range]
503
- v_range = @constellation.ValueRange(
504
- min ? [[String === min ? eval(min) : min.to_s, String === min, nil], true] : nil,
505
- max ? [[String === max ? eval(max) : max.to_s, String === max, nil], true] : nil
506
- )
507
- ar = @constellation.AllowedRange(@constraint, v_range)
508
- end
509
- @enforcement.compile(@constellation, @constraint) if @enforcement
510
- super
511
- end
518
+ if @value_ranges
519
+ @value_ranges.each do |range|
520
+ min, max = Array === range ? range : [range, range]
521
+ v_range = @constellation.ValueRange(
522
+ min && @constellation.Bound(:value => assert_value(min), :is_inclusive => true),
523
+ max && @constellation.Bound(:value => assert_value(max), :is_inclusive => true))
524
+ ar = @constellation.AllowedRange(@constraint, v_range)
525
+ end
526
+ else
527
+ @constraint.regular_expression = @regular_expression
528
+ end
529
+ @enforcement.compile(@constellation, @constraint) if @enforcement
530
+ super
531
+ end
512
532
 
513
533
  def vrto_s vr
514
534
  if Array === vr
@@ -529,7 +549,11 @@ module ActiveFacts
529
549
  end
530
550
 
531
551
  def to_s
532
- "#{super} to (#{@value_ranges.map{|vr| vrto_s(vr) }.inspect })#{ @units ? " in #{@units.inspect}" : ''}"
552
+ "#{super} to " +
553
+ (@value_ranges ?
554
+ "(#{@value_ranges.map{|vr| vrto_s(vr) }.inspect })#{ @units ? " in #{@units.inspect}" : ''}" :
555
+ @regular_expression
556
+ )
533
557
  end
534
558
  end
535
559
 
@@ -19,16 +19,18 @@ module ActiveFacts
19
19
  end
20
20
 
21
21
  class EntityType < ObjectType
22
- def initialize name, supertypes, identification, pragmas, clauses
22
+ def initialize name, supertypes, identification, pragmas, clauses, context_note
23
23
  super name
24
24
  @supertypes = supertypes
25
25
  @identification = identification
26
26
  @pragmas = pragmas
27
27
  @clauses = clauses || []
28
+ @context_note = context_note
28
29
  end
29
30
 
30
31
  def compile
31
- @entity_type = @constellation.EntityType(@vocabulary, @name, :guid => :new)
32
+ @entity_type = @vocabulary.valid_entity_type_name(@name) ||
33
+ @constellation.EntityType(@vocabulary, @name, :guid => :new)
32
34
  @entity_type.is_independent = true if (@pragmas.include? 'independent')
33
35
 
34
36
  # REVISIT: CQL needs a way to indicate whether subtype migration can occur.
@@ -47,7 +49,7 @@ module ActiveFacts
47
49
  # Create the fact types that define the identifying roles:
48
50
  fact_types = create_identifying_fact_types context
49
51
 
50
- # At this point, @identification is an array of VarRefs and/or Clauses (for unary fact types)
52
+ # At this point, @identification is an array of References and/or Clauses (for unary fact types)
51
53
  # Have to do this after creating the necessary fact types
52
54
  complete_reference_mode_fact_type fact_types
53
55
 
@@ -56,6 +58,10 @@ module ActiveFacts
56
58
 
57
59
  make_preferred_identifier_over_roles identifying_roles
58
60
 
61
+ if @context_note
62
+ @context_note.compile(@constellation, @entity_type)
63
+ end
64
+
59
65
  @clauses.each do |clause|
60
66
  next unless clause.context_note
61
67
  clause.context_note.compile(@constellation, @entity_type)
@@ -70,7 +76,7 @@ module ActiveFacts
70
76
  if @identification.is_a? ReferenceMode
71
77
  make_entity_type_refmode_valuetypes(name, @identification.name, @identification.parameters)
72
78
  vt_name = @reference_mode_value_type.name
73
- @identification = [Compiler::VarRef.new(vt_name, nil, nil, nil, nil, nil, @identification.value_constraint, nil)]
79
+ @identification = [Compiler::Reference.new(vt_name, nil, nil, nil, nil, nil, @identification.value_constraint, nil)]
74
80
  else
75
81
  context.allowed_forward_terms = legal_forward_references(@identification)
76
82
  end
@@ -80,16 +86,16 @@ module ActiveFacts
80
86
  # Names used in the identifying roles list may be forward referenced:
81
87
  def legal_forward_references(identification_phrases)
82
88
  identification_phrases.map do |phrase|
83
- phrase.is_a?(VarRef) ? phrase.term : nil
89
+ phrase.is_a?(Reference) ? phrase.term : nil
84
90
  end.compact.uniq
85
91
  end
86
92
 
87
93
  def bind_identifying_roles context
88
94
  return unless @identification
89
95
  @identification.map do |id|
90
- if id.is_a?(VarRef)
91
- variable = id.variable
92
- roles = variable.refs.map{|r| r.role}.compact.uniq
96
+ if id.is_a?(Reference)
97
+ binding = id.binding
98
+ roles = binding.refs.map{|r|r.role || (rr=r.role_ref and rr.role)}.compact.uniq
93
99
  raise "Looking for an occurrence of identifying role #{id.inspect}, but found #{roles.size == 0 ? "none" : roles.size}" if roles.size != 1
94
100
  roles[0]
95
101
  else
@@ -129,6 +135,7 @@ module ActiveFacts
129
135
  #:is_mandatory => true,
130
136
  #:min_frequency => 1,
131
137
  )
138
+ debug :constraint, "Made new preferred PC GUID=#{pc.guid} min=nil max=1 over #{role_sequence.describe}"
132
139
  end
133
140
  end
134
141
 
@@ -147,7 +154,7 @@ module ActiveFacts
147
154
  fact_types = []
148
155
  # Categorise the clauses into fact types according to the roles they play.
149
156
  @clauses.inject({}) do |hash, clause|
150
- players_key = clause.var_refs.map{|vr| vr.key.compact}.sort
157
+ players_key = clause.refs.map{|vr| vr.key.compact}.sort
151
158
  (hash[players_key] ||= []) << clause
152
159
  hash
153
160
  end.each do |players_key, clauses|
@@ -155,7 +162,9 @@ module ActiveFacts
155
162
 
156
163
  fact_type = create_identifying_fact_type(context, clauses)
157
164
  fact_types << fact_type if fact_type
158
- objectify_existing_fact_type(fact_type) unless fact_type.all_role.detect{|r| r.object_type == @entity_type}
165
+ unless fact_type.all_role.detect{|r| r.object_type == @entity_type}
166
+ objectify_existing_fact_type(fact_type)
167
+ end
159
168
  end
160
169
  fact_types
161
170
  end
@@ -167,10 +176,13 @@ module ActiveFacts
167
176
 
168
177
  # See if any fact type already exists (this ET cannot be a player, but might objectify it)
169
178
  existing_clauses = clauses.select{ |clause| clause.match_existing_fact_type context }
179
+ if negation = existing_clauses.detect{|c| c.certainty == false }
180
+ raise "#{@name} cannot be identified by negated fact type #{negation.inspect}"
181
+ end
170
182
  any_matched = existing_clauses.size > 0
171
183
 
172
184
  operation = any_matched ? 'Objectifying' : 'Creating'
173
- player_names = clauses[0].var_refs.map{|vr| vr.key.compact*'-'}
185
+ player_names = clauses[0].refs.map{|vr| vr.key.compact*'-'}
174
186
  debug :matching, "#{operation} fact type for #{clauses.size} clauses over (#{player_names*', '})" do
175
187
  if any_matched # There's an existing fact type we must be objectifying
176
188
  fact_type = objectify_existing_fact_type(existing_clauses[0].fact_type)
@@ -194,10 +206,13 @@ module ActiveFacts
194
206
 
195
207
  def objectify_existing_fact_type fact_type
196
208
  raise "#{@name} cannot objectify fact type '#{fact_type.entity_type.name}' that's already objectified" if fact_type.entity_type
197
- raise "#{@name} must only objectify one fact type" if @fact_type
209
+ if @fact_type
210
+ raise "#{@name} cannot objectify '#{fact_type.default_reading}', it already objectifies '#{@fact_type.default_reading}'"
211
+ end
212
+
198
213
  if fact_type.internal_presence_constraints.select{|pc| pc.max_frequency == 1}.size == 0
199
214
  # If there's no existing uniqueness constraint over this fact type, make a spanning one.
200
- @constellation.PresenceConstraint(
215
+ pc = @constellation.PresenceConstraint(
201
216
  :new,
202
217
  :vocabulary => @vocabulary,
203
218
  :name => @entity_type.name+"UQ",
@@ -205,18 +220,20 @@ module ActiveFacts
205
220
  :is_preferred_identifier => false, # We only get here when there is a reference mode on the entity type
206
221
  :max_frequency => 1
207
222
  )
223
+ debug :constraint, "Made new objectification PC GUID=#{pc.guid} min=nil max=1 over #{fact_type.preferred_reading.role_sequence.describe}"
208
224
  end
209
225
 
210
226
  @fact_type = @entity_type.fact_type = fact_type
211
- @entity_type.create_implicit_fact_types
227
+ @entity_type.create_implicit_fact_types # REVISIT: Could there be readings for the implicit fact types here?
212
228
  @fact_type
213
229
  end
214
230
 
215
231
  def add_supertype(supertype_name, not_identifying)
216
- debug :supertype, "Adding supertype #{supertype_name}" do
217
- supertype = @constellation.EntityType(@vocabulary, supertype_name, :guid => :new)
232
+ debug :supertype, "Adding #{not_identifying ? '' : 'identifying '}supertype #{supertype_name} to #{@entity_type.name}" do
233
+ supertype = @vocabulary.valid_entity_type_name(supertype_name) ||
234
+ @constellation.EntityType(@vocabulary, supertype_name, :guid => :new) # Should always already exist
218
235
 
219
- # Did we already know about this supertype?
236
+ # Did we already know about this supertyping?
220
237
  return if @entity_type.all_type_inheritance_as_subtype.detect{|ti| ti.supertype == supertype}
221
238
 
222
239
  # By default, the first supertype identifies this entity type
@@ -235,14 +252,14 @@ module ActiveFacts
235
252
  rs = @constellation.RoleSequence(:new)
236
253
  @constellation.RoleRef(rs, 0, :role => sub_role)
237
254
  @constellation.RoleRef(rs, 1, :role => super_role)
238
- @constellation.Reading(inheritance_fact, 0, :role_sequence => rs, :text => "{0} is a kind of {1}")
255
+ @constellation.Reading(inheritance_fact, 0, :role_sequence => rs, :text => "{0} is a kind of {1}", :is_negative => false)
239
256
 
240
257
  rs2 = @constellation.RoleSequence(:new)
241
258
  @constellation.RoleRef(rs2, 0, :role => super_role)
242
259
  @constellation.RoleRef(rs2, 1, :role => sub_role)
243
260
  # Decide in which order to include is a/is an. Provide both, but in order.
244
261
  n = 'aeiouh'.include?(sub_role.object_type.name.downcase[0]) ? 'n' : ''
245
- @constellation.Reading(inheritance_fact, 2, :role_sequence => rs2, :text => "{0} is a#{n} {1}")
262
+ @constellation.Reading(inheritance_fact, 2, :role_sequence => rs2, :text => "{0} is a#{n} {1}", :is_negative => false)
246
263
 
247
264
  if is_identifying_supertype
248
265
  inheritance_fact.provides_identification = true
@@ -258,6 +275,7 @@ module ActiveFacts
258
275
  pc1.min_frequency = 1
259
276
  pc1.max_frequency = 1
260
277
  pc1.is_preferred_identifier = false
278
+ debug :constraint, "Made new subtype PC GUID=#{pc1.guid} min=1 max=1 over #{p1rs.describe}"
261
279
 
262
280
  p2rs = @constellation.RoleSequence(:new)
263
281
  constellation.RoleRef(p2rs, 0).role = super_role
@@ -269,6 +287,8 @@ module ActiveFacts
269
287
  pc2.max_frequency = 1
270
288
  # The supertype role often identifies the subtype:
271
289
  pc2.is_preferred_identifier = inheritance_fact.provides_identification
290
+ debug :supertype, "identification of #{@entity_type.name} via supertype #{supertype.name} was #{inheritance_fact.provides_identification ? '' : 'not '}added"
291
+ debug :constraint, "Made new supertype PC GUID=#{pc2.guid} min=1 max=1 over #{p2rs.describe}"
272
292
  end
273
293
  end
274
294
 
@@ -276,10 +296,12 @@ module ActiveFacts
276
296
  vt_name = "#{name}#{mode}"
277
297
  vt = nil
278
298
  debug :entity, "Preparing value type #{vt_name} for reference mode" do
279
- # Find or Create an appropriate ValueType called '#{vt_name}', of the supertype '#{mode}'
280
- unless vt = @constellation.ObjectType[[@vocabulary.identifying_role_values, vt_name]] or
281
- vt = @constellation.ObjectType[[@vocabulary.identifying_role_values, vt_name = "#{name} #{mode}"]]
282
- base_vt = @constellation.ValueType(@vocabulary, mode, :guid => :new)
299
+ # Find an existing ValueType called 'vt_name' or 'name vtname'
300
+ # or find/create the supertype '#{mode}' and the subtype
301
+ unless vt = @vocabulary.valid_object_type_name(vt_name) or
302
+ vt = @vocabulary.valid_object_type_name(vt_name = "#{name} #{mode}")
303
+ base_vt = @vocabulary.valid_value_type_name(mode) ||
304
+ @constellation.ValueType(@vocabulary, mode, :guid => :new)
283
305
  vt = @constellation.ValueType(@vocabulary, vt_name, :supertype => base_vt, :guid => :new)
284
306
  if parameters
285
307
  length, scale = *parameters
@@ -340,7 +362,7 @@ module ActiveFacts
340
362
  @constellation.RoleRef(rs01, 1, :role => identifying_role)
341
363
  end
342
364
  if rs01.all_reading.empty?
343
- @constellation.Reading(fact_type, fact_type.all_reading.size, :role_sequence => rs01, :text => "{0} has {1}")
365
+ @constellation.Reading(fact_type, fact_type.all_reading.size, :role_sequence => rs01, :text => "{0} has {1}", :is_negative => false)
344
366
  debug :mode, "Creating new forward reading '#{entity_role.object_type.name} has #{identifying_type.name}'"
345
367
  else
346
368
  debug :mode, "Using existing forward reading"
@@ -354,7 +376,7 @@ module ActiveFacts
354
376
  @constellation.RoleRef(rs10, 1, :role => entity_role)
355
377
  end
356
378
  if rs10.all_reading.empty?
357
- @constellation.Reading(fact_type, fact_type.all_reading.size, :role_sequence => rs10, :text => "{0} is of {1}")
379
+ @constellation.Reading(fact_type, fact_type.all_reading.size, :role_sequence => rs10, :text => "{0} is of {1}", :is_negative => false)
358
380
  debug :mode, "Creating new reverse reading '#{identifying_type.name} is of #{entity_role.object_type.name}'"
359
381
  else
360
382
  debug :mode, "Using existing reverse reading"
@@ -381,7 +403,7 @@ module ActiveFacts
381
403
  :is_preferred_identifier => false,
382
404
  :is_mandatory => true
383
405
  )
384
- debug :mode, "Creating new EntityType PresenceConstraint"
406
+ debug :constraint, "Made new refmode PC GUID=#{constraint.guid} min=1 max=1 over #{rs0.describe}"
385
407
  else
386
408
  debug :mode, "Using existing EntityType PresenceConstraint"
387
409
  end
@@ -409,7 +431,7 @@ module ActiveFacts
409
431
  :is_preferred_identifier => true,
410
432
  :is_mandatory => false
411
433
  )
412
- debug :mode, "Creating new ValueType PresenceConstraint"
434
+ debug :constraint, "Made new refmode ValueType PC GUID=#{constraint.guid} min=0 max=1 over #{rs1.describe}"
413
435
  else
414
436
  debug :mode, "Marking existing ValueType PresenceConstraint as preferred"
415
437
  rs1.all_presence_constraint.single.is_preferred_identifier = true