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
@@ -5,21 +5,23 @@ module ActiveFacts
5
5
  class Clause
6
6
  attr_reader :phrases
7
7
  attr_accessor :qualifiers, :context_note
8
+ attr_accessor :certainty # nil, true, false -> maybe, definitely, not
8
9
  attr_accessor :conjunction # one of {nil, 'and', ',', 'or', 'where'}
9
10
  attr_accessor :fact_type
10
11
  attr_reader :reading, :role_sequence # These are the Metamodel objects
11
12
  attr_reader :side_effects # How to adjust the phrases if this fact_type match is accepted
12
13
  attr_accessor :fact # When binding fact instances the fact goes here
13
- attr_accessor :objectified_as # The VarRef which objectified this fact type
14
+ attr_accessor :objectified_as # The Reference which objectified this fact type
14
15
 
15
16
  def initialize phrases, qualifiers = [], context_note = nil
16
17
  @phrases = phrases
17
- var_refs.each { |var_ref| var_ref.clause = self }
18
+ refs.each { |ref| ref.clause = self }
19
+ @certainty = true
18
20
  @qualifiers = qualifiers
19
21
  @context_note = context_note
20
22
  end
21
23
 
22
- def var_refs
24
+ def refs
23
25
  @phrases.select{|r| r.respond_to?(:player)}
24
26
  end
25
27
 
@@ -27,7 +29,7 @@ module ActiveFacts
27
29
  # refers only to the existence of that ObjectType (as opposed to an instance of the object_type).
28
30
  def is_existential_type
29
31
  @phrases.size == 1 and
30
- @phrases[0].is_a?(VarRef) and
32
+ @phrases[0].is_a?(Reference) and
31
33
  !@phrases[0].literal
32
34
  end
33
35
 
@@ -35,22 +37,29 @@ module ActiveFacts
35
37
  to_s
36
38
  end
37
39
 
38
- def inspect
39
- to_s
40
+ def inspect phrases = nil
41
+ to_s(phrases||@phrases)
40
42
  end
41
43
 
42
- def to_s
44
+ def to_s phrases = nil
45
+ phrases ||= @phrases
43
46
  "#{
44
47
  @qualifiers && @qualifiers.size > 0 ? @qualifiers.sort.inspect+' ' : nil
48
+ }#{
49
+ case @certainty
50
+ when nil; 'maybe '
51
+ when false; 'negated '
52
+ # else 'definitely '
53
+ end
45
54
  }#{
46
55
  quotes = false
47
- @phrases.inject(""){|s, p|
56
+ phrases.inject(""){|s, p|
48
57
  if String === p
49
58
  s + (quotes ? '' : (quotes = true; '"')) + p.to_s + ' '
50
59
  # REVISIT: Add something here when I re-add functions
51
60
  # elsif FunctionCallChain === p
52
61
  # s[0..-2] + (quotes ? (quotes = false; '" ') : '') + p.to_s
53
- else # if VarRef === p
62
+ else # if Reference === p
54
63
  s[0..-2] + (quotes ? (quotes = false; '" ') : '') + p.to_s +
55
64
  ((oj = p.nested_clauses) ? ' ('+ oj.map{|c| ((j=c.conjunction) ? j+' ' : '') + c.to_s}*' ' + ')' : '') +
56
65
  ' '
@@ -64,21 +73,21 @@ module ActiveFacts
64
73
  end
65
74
 
66
75
  def identify_players_with_role_name context
67
- var_refs.each do |var_ref|
68
- var_ref.identify_players_with_role_name(context)
76
+ refs.each do |ref|
77
+ ref.identify_players_with_role_name(context)
69
78
  end
70
79
  end
71
80
 
72
81
  def identify_other_players context
73
- var_refs.each do |var_ref|
74
- var_ref.identify_other_players(context)
82
+ refs.each do |ref|
83
+ ref.identify_other_players(context)
75
84
  # Include players in nested clauses, if any
76
- var_ref.nested_clauses.each{|clause| clause.identify_other_players(context)} if var_ref.nested_clauses
85
+ ref.nested_clauses.each{|clause| clause.identify_other_players(context)} if ref.nested_clauses
77
86
  end
78
87
  end
79
88
 
80
89
  def includes_literals
81
- var_refs.detect{|var_ref| var_ref.literal || (ja = var_ref.nested_clauses and ja.detect{|jr| jr.includes_literals })}
90
+ refs.detect{|ref| ref.literal || (ja = ref.nested_clauses and ja.detect{|jr| jr.includes_literals })}
82
91
  end
83
92
 
84
93
  def is_equality_comparison
@@ -86,7 +95,7 @@ module ActiveFacts
86
95
  end
87
96
 
88
97
  def bind context
89
- role_names = var_refs.map{ |var_ref| var_ref.role_name }.compact
98
+ role_names = refs.map{ |ref| ref.role_name }.compact
90
99
 
91
100
  # Check uniqueness of role names and subscripts within this clause:
92
101
  role_names.each do |rn|
@@ -94,15 +103,15 @@ module ActiveFacts
94
103
  raise "Duplicate role #{rn.is_a?(Integer) ? "subscript" : "name"} '#{rn}' in clause"
95
104
  end
96
105
 
97
- var_refs.each do |var_ref|
98
- var_ref.bind context
106
+ refs.each do |ref|
107
+ ref.bind context
99
108
  end
100
109
  end
101
110
 
102
111
  def phrases_match(phrases)
103
112
  @phrases.zip(phrases).each do |mine, theirs|
104
- return false if mine.is_a?(VarRef) != theirs.is_a?(VarRef)
105
- if mine.is_a?(VarRef)
113
+ return false if mine.is_a?(Reference) != theirs.is_a?(Reference)
114
+ if mine.is_a?(Reference)
106
115
  return false unless mine.key == theirs.key
107
116
  else
108
117
  return false unless mine == theirs
@@ -118,8 +127,9 @@ module ActiveFacts
118
127
  # no change is made to this Clause object - those will be done later.
119
128
  #
120
129
  def match_existing_fact_type context, options = {}
130
+ raise "Cannot match a clause that contains no object types" if refs.size == 0
121
131
  raise "Internal error, clause already matched, should not match again" if @fact_type
122
- # If we fail to match, try to a left contraction (or save this for a subsequent left contraction):
132
+ # If we fail to match, try a left contraction (or save this for a subsequent left contraction):
123
133
  left_contract_this_onto = context.left_contractable_clause
124
134
  new_conjunction = (conjunction == nil || conjunction == ',')
125
135
  changed_conjunction = (lcc = context.left_contraction_conjunction) && lcc != conjunction
@@ -131,55 +141,100 @@ module ActiveFacts
131
141
  end
132
142
  context.left_contraction_conjunction = new_conjunction ? nil : @conjunction
133
143
 
134
- contracted_role = nil
144
+ phrases = @phrases
145
+ vrs = []+refs
146
+
147
+ # A left contraction is where the first player in the previous clause continues as first player of this clause
148
+ contracted_left = false
149
+ can_contract_right = false
150
+ left_insertion = nil
151
+ right_insertion = nil
152
+ supposed_roles = [] # Arrange to unbind incorrect references supposed due to contraction
153
+ contract_left = proc do
154
+ contracted_from = left_contract_this_onto.refs[0]
155
+ contraction_player = contracted_from.player
156
+ contracted_role = Reference.new(contraction_player.name)
157
+ supposed_roles << contracted_role
158
+ left_insertion = contracted_role.inspect+' '
159
+ contracted_role.player = contracted_from.player
160
+ contracted_role.role_name = contracted_from.role_name
161
+ contracted_role.bind(context)
162
+ vrs.unshift contracted_role
163
+ contracted_left = true
164
+ phrases = [contracted_role]+phrases
165
+ debug :matching, "Failed to match #{inspect}. Trying again using left contraction onto #{contraction_player.name}"
166
+ end
167
+
168
+ contract_right = proc do
169
+ contracted_from = left_contract_this_onto.refs[-1]
170
+ contraction_player = contracted_from.player
171
+ contracted_role = Reference.new(contraction_player.name)
172
+ supposed_roles << contracted_role
173
+ right_insertion = ' '+contracted_role.inspect
174
+ contracted_role.player = contracted_from.player
175
+ contracted_role.role_name = contracted_from.role_name
176
+ contracted_role.bind(context)
177
+ vrs.push contracted_role
178
+ phrases = phrases+[contracted_role]
179
+ debug :matching, "Failed to match #{inspect}. Trying again using right contraction onto #{contraction_player.name}"
180
+ end
135
181
 
136
- vrs = []+var_refs
137
182
  begin
138
183
  players = vrs.map{|vr| vr.player}
184
+
185
+ if players.size == 0
186
+ can_contract_right = left_contract_this_onto.refs.size == 2
187
+ contract_left.call
188
+ redo
189
+ end
190
+
139
191
  raise "Must identify players before matching fact types" if players.include? nil
140
192
  raise "A fact type must involve at least one object type, but there are none in '#{inspect}'" if players.size == 0 && !left_contract_this_onto
141
193
 
142
194
  player_names = players.map{|p| p.name}
143
195
 
144
- debug :matching, "Looking for existing #{players.size}-ary fact types matching '#{contracted_role && contracted_role.inspect+' '}#{inspect}'" do
196
+ debug :matching, "Looking for existing #{players.size}-ary fact types matching '#{inspect(phrases)}'" do
145
197
  debug :matching, "Players are '#{player_names.inspect}'"
146
198
 
147
- # Match existing fact types in nested clauses first (not for contractions):
148
- if !contracted_role
149
- vrs.each do |var_ref|
150
- next if var_ref.is_a?(Operation)
151
- next unless joins = var_ref.nested_clauses and !joins.empty?
152
- var_ref.nested_clauses.each do |oj|
199
+ # Match existing fact types in nested clauses first:
200
+ # (not for contractions) REVISIT: Why not?
201
+ if !contracted_left
202
+ vrs.each do |ref|
203
+ next if ref.is_a?(Operation)
204
+ next unless steps = ref.nested_clauses and !steps.empty?
205
+ ref.nested_clauses.each do |oj|
153
206
  ft = oj.match_existing_fact_type(context)
154
207
  raise "Unrecognised fact type #{oj.display}" unless ft
155
- if (ft && ft.entity_type == var_ref.player)
156
- var_ref.objectification_of = ft
157
- oj.objectified_as = var_ref
208
+ if (ft && ft.entity_type == ref.player)
209
+ ref.objectification_of = ft
210
+ oj.objectified_as = ref
158
211
  end
159
212
  end
160
- raise "#{var_ref.inspect} contains objectification joins that do not objectify it" unless var_ref.objectification_of
213
+ raise "#{ref.inspect} contains objectification steps that do not objectify it" unless ref.objectification_of
161
214
  end
162
215
  end
163
216
 
164
217
  # For each role player, find the compatible types (the set of all subtypes and supertypes).
165
- # For a player that's an objectification, we don't allow implicit supertype joins
218
+ # For a player that's an objectification, we don't allow implicit supertype steps
166
219
  player_related_types =
167
- vrs.zip(players).map do |var_ref, player|
168
- disallow_subtyping = var_ref && var_ref.objectification_of || options[:exact_type]
220
+ vrs.zip(players).map do |ref, player|
221
+ disallow_subtyping = ref && ref.objectification_of || options[:exact_type]
169
222
  ((disallow_subtyping ? [] : player.supertypes_transitive) +
170
223
  player.subtypes_transitive).uniq
171
224
  end
172
225
 
173
226
  debug :matching, "Players must match '#{player_related_types.map{|pa| pa.map{|p|p.name}}.inspect}'"
174
227
 
228
+ start_obj = player_related_types[0] || [left_contract_this_onto.refs[-1].player]
175
229
  # The candidate fact types have the right number of role players of related types.
176
- # If any role is played by a supertype or subtype of the required type, there's an implicit subtyping join
230
+ # If any role is played by a supertype or subtype of the required type, there's an implicit subtyping steps
231
+ # REVISIT: A double contraction results in player_related_types being empty here
177
232
  candidate_fact_types =
178
- player_related_types[0].map do |related_type|
233
+ start_obj.map do |related_type|
179
234
  related_type.all_role.select do |role|
180
235
  all_roles = role.fact_type.all_role
181
236
  next if all_roles.size != players.size # Wrong number of players
182
- next if role.fact_type.is_a?(ActiveFacts::Metamodel::ImplicitFactType)
237
+ next if role.fact_type.is_a?(ActiveFacts::Metamodel::LinkFactType)
183
238
 
184
239
  all_players = all_roles.map{|r| r.object_type} # All the players of this candidate fact type
185
240
 
@@ -202,13 +257,13 @@ module ActiveFacts
202
257
  map{ |role| role.fact_type}
203
258
  end.flatten.uniq
204
259
 
205
- # If there is more than one possible exact match (same adjectives) with different subyping, the implicit join is ambiguous and is not allowed
260
+ # If there is more than one possible exact match (same adjectives) with different subyping, the implicit query is ambiguous and is not allowed
206
261
 
207
- debug :matching, "Looking amongst #{candidate_fact_types.size} existing fact types for one matching '#{contracted_role && contracted_role.inspect+' '}#{inspect}'" do
262
+ debug :matching, "Looking amongst #{candidate_fact_types.size} existing fact types for one matching #{left_insertion}'#{inspect}'#{right_insertion}" do
208
263
  matches = {}
209
264
  candidate_fact_types.map do |fact_type|
210
265
  fact_type.all_reading.map do |reading|
211
- next unless side_effects = clause_matches(fact_type, reading, contracted_role)
266
+ next unless side_effects = clause_matches(fact_type, reading, phrases)
212
267
  matches[reading] = side_effects if side_effects
213
268
  end
214
269
  end
@@ -221,7 +276,7 @@ module ActiveFacts
221
276
  # the whole declaration has been processed and the extra adjectives can be matched.
222
277
 
223
278
  best_matches = matches.keys.sort_by{|match|
224
- # Between equivalents, prefer the one without a join on the first role
279
+ # Between equivalents, prefer the one without steps on the first role
225
280
  (m = matches[match]).cost*2 + ((!(e = m.role_side_effects[0]) || e.cost) == 0 ? 0 : 1)
226
281
  }
227
282
  debug :matching_fails, "Found #{matches.size} valid matches#{matches.size > 0 ? ', best is '+best_matches[0].expand : ''}"
@@ -243,7 +298,7 @@ module ActiveFacts
243
298
  @side_effects = matches[@reading]
244
299
  @fact_type = @side_effects.fact_type
245
300
  debug :matching, "Matched '#{@fact_type.default_reading}'"
246
- @phrases.unshift(contracted_role) if contracted_role
301
+ @phrases = phrases
247
302
  apply_side_effects(context, @side_effects)
248
303
  return @fact_type
249
304
  end
@@ -251,21 +306,19 @@ module ActiveFacts
251
306
  end
252
307
  debug :matching, "No fact type matched, candidates were '#{candidate_fact_types.map{|ft| ft.default_reading}*"', '"}'"
253
308
  end
254
- if left_contract_this_onto && !contracted_role
255
- contracted_from = left_contract_this_onto.var_refs[0]
256
- contraction_player = contracted_from.player
257
- contracted_role = VarRef.new(contraction_player.name)
258
- contracted_role.player = contracted_from.player
259
- contracted_role.role_name = contracted_from.role_name
260
- contracted_role.bind(context)
261
- vrs.unshift contracted_role
262
-
263
- debug :matching, "Failed to match #{inspect}. Trying again using left contraction onto #{contraction_player.name}"
264
- redo
309
+ if left_contract_this_onto
310
+ if !contracted_left
311
+ contract_left.call
312
+ redo
313
+ elsif can_contract_right
314
+ contract_right.call
315
+ can_contract_right = false
316
+ redo
317
+ end
265
318
  end
266
319
  end until true # Once through, unless we hit a redo
267
- if contracted_role
268
- contracted_role.unbind context
320
+ supposed_roles.each do |role|
321
+ role.unbind context
269
322
  end
270
323
  @fact_type = nil
271
324
  end
@@ -275,7 +328,7 @@ module ActiveFacts
275
328
  # Find whether the phrases of this clause match the fact type reading,
276
329
  # which may require absorbing unmarked adjectives.
277
330
  #
278
- # If it does match, make the required changes and set @var_ref to the matching role ref.
331
+ # If it does match, make the required changes and set @ref to the matching role ref.
279
332
  # Adjectives that were used to match are removed (and leaving any additional adjectives intact).
280
333
  #
281
334
  # Approach:
@@ -288,18 +341,33 @@ module ActiveFacts
288
341
  # trailing adjectives, both marked and unmarked, are absorbed too.
289
342
  # a word that matches the reading's
290
343
  #
291
- def clause_matches(fact_type, reading, contracted_role = nil)
344
+ def clause_matches(fact_type, reading, phrases = @phrases)
345
+ implicitly_negated = false
292
346
  side_effects = [] # An array of items for each role, describing any side-effects of the match.
293
347
  intervening_words = nil
294
348
  residual_adjectives = false
295
- phrases = [contracted_role].compact+@phrases
349
+
350
+ # The following form of negation is, e.g., where "Person was invited to no Party",
351
+ # as opposed to where "Person was not invited to that Party". Quite different meaning,
352
+ # because a free Party variable is required, but the join step is still disallowed.
353
+ # REVISIT: I'll create the free variable when I implement some/that binding
354
+ # REVISIT: the verbaliser will need to know about a negated step to a free variable
355
+ implicitly_negated = true if refs.detect{|ref| q = ref.quantifier and q.is_zero }
356
+
296
357
  debug :matching_fails, "Does '#{phrases.inspect}' match '#{reading.expand}'" do
297
358
  phrase_num = 0
298
359
  reading_parts = reading.text.split(/\s+/)
299
360
  reading_parts.each do |element|
361
+ phrase = phrases[phrase_num]
362
+ if phrase == 'not'
363
+ raise "Stop playing games with your double negatives: #{phrases.inspect}" if implicitly_negated
364
+ debug :matching, "Negation detected"
365
+ implicitly_negated = true
366
+ phrase = phrases[phrase_num += 1]
367
+ end
300
368
  if element !~ /\{(\d+)\}/
301
369
  # Just a word; it must match
302
- unless phrases[phrase_num] == element
370
+ unless phrase == element
303
371
  debug :matching_fails, "Mismatched ordinary word #{phrases[phrase_num].inspect} (wanted #{element})"
304
372
  return nil
305
373
  end
@@ -337,7 +405,7 @@ module ActiveFacts
337
405
  return nil
338
406
  end
339
407
 
340
- debug :matching_fails, "Subtype join is required between #{player.name} and #{next_player_phrase.player.name} via common supertype #{common_supertype.name}"
408
+ debug :matching_fails, "Subtype step is required between #{player.name} and #{next_player_phrase.player.name} via common supertype #{common_supertype.name}"
341
409
  else
342
410
  if !next_player_phrase
343
411
  next # Contraction succeeded so far
@@ -406,7 +474,7 @@ module ActiveFacts
406
474
  =end
407
475
 
408
476
  residual_adjectives ||= role_has_residual_adjectives
409
- if residual_adjectives && next_player_phrase.variable.refs.size == 1
477
+ if residual_adjectives && next_player_phrase.binding.refs.size == 1
410
478
  # This makes matching order-dependent, because there may be no "other purpose"
411
479
  # until another reading has been matched and the roles rebound.
412
480
  debug :matching_fails, "Residual adjectives have no other purpose, so this match fails"
@@ -423,20 +491,20 @@ module ActiveFacts
423
491
  end
424
492
 
425
493
  if fact_type.is_a?(Metamodel::TypeInheritance)
426
- # There may be only one subtyping join on a TypeInheritance fact type.
427
- ti_joins = side_effects.select{|side_effect| side_effect.common_supertype}
428
- if ti_joins.size > 1 # Not allowed
429
- debug :matching_fails, "Can't have more than one subtyping join on a TypeInheritance fact type"
494
+ # There may be only one subtyping step on a TypeInheritance fact type.
495
+ ti_steps = side_effects.select{|side_effect| side_effect.common_supertype}
496
+ if ti_steps.size > 1 # Not allowed
497
+ debug :matching_fails, "Can't have more than one subtyping step on a TypeInheritance fact type"
430
498
  return nil
431
499
  end
432
500
 
433
- if ti = ti_joins[0]
434
- # The Type Inheritance join must continue in the same direction as this reading.
501
+ if ti = ti_steps[0]
502
+ # The Type Inheritance step must continue in the same direction as this reading.
435
503
  allowed = fact_type.supertype == ti.role_ref.role.object_type ?
436
504
  fact_type.subtype.supertypes_transitive :
437
505
  fact_type.supertype.subtypes_transitive
438
506
  if !allowed.include?(ti.common_supertype)
439
- debug :matching_fails, "Implicit subtyping join extends in the wrong direction"
507
+ debug :matching_fails, "Implicit subtyping step extends in the wrong direction"
440
508
  return nil
441
509
  end
442
510
  end
@@ -449,7 +517,9 @@ module ActiveFacts
449
517
  } side effects)#{residual_adjectives ? ' and residual adjectives' : ''}"
450
518
  end
451
519
  # There will be one side_effects for each role player
452
- ClauseMatchSideEffects.new(fact_type, self, residual_adjectives, side_effects)
520
+ @certainty = !@certainty if implicitly_negated
521
+ @certainty = !@certainty if reading.is_negative
522
+ ClauseMatchSideEffects.new(fact_type, self, residual_adjectives, side_effects, implicitly_negated)
453
523
  end
454
524
 
455
525
  def apply_side_effects(context, side_effects)
@@ -460,8 +530,8 @@ module ActiveFacts
460
530
  side_effects.apply_all do |side_effect|
461
531
  phrase = side_effect.phrase
462
532
 
463
- # We re-use the role_ref if possible (no extra adjectives were used, no rolename or join, etc).
464
- debug :matching, "side-effect means variable #{phrase.inspect} matches role ref #{side_effect.role_ref.role.object_type.name}"
533
+ # We re-use the role_ref if possible (no extra adjectives were used, no rolename or step, etc).
534
+ debug :matching, "side-effect means binding #{phrase.inspect} matches role ref #{side_effect.role_ref.role.object_type.name}"
465
535
  phrase.role_ref = side_effect.role_ref
466
536
 
467
537
  changed = false
@@ -482,7 +552,7 @@ module ActiveFacts
482
552
  end
483
553
  elsif side_effect.absorbed_followers > 0
484
554
  # The following statement is incorrect. The absorbed adjective is what caused the match.
485
- # This phrase is absorbing non-hyphenated adjective(s), which changes its variable
555
+ # This phrase is absorbing non-hyphenated adjective(s), which changes its binding
486
556
  # phrase.trailing_adjective =
487
557
  @phrases.slice!(side_effect.num+1, side_effect.absorbed_followers)*' '
488
558
  changed = true
@@ -502,7 +572,7 @@ module ActiveFacts
502
572
  end
503
573
  elsif side_effect.absorbed_precursors > 0
504
574
  # The following statement is incorrect. The absorbed adjective is what caused the match.
505
- # This phrase is absorbing non-hyphenated adjective(s), which changes its variable
575
+ # This phrase is absorbing non-hyphenated adjective(s), which changes its binding
506
576
  #phrase.leading_adjective =
507
577
  @phrases.slice!(side_effect.num-side_effect.absorbed_precursors, side_effect.absorbed_precursors)*' '
508
578
  changed = true
@@ -540,10 +610,10 @@ module ActiveFacts
540
610
  reading_words.map! do |phrase|
541
611
  if phrase.respond_to?(:player)
542
612
  # phrase.role will be set if this reading was used to make_fact_type.
543
- # Otherwise we have to find the existing role via the Variable. This is pretty ugly.
613
+ # Otherwise we have to find the existing role via the Binding. This is pretty ugly.
544
614
  unless phrase.role
545
- # Find another variable for this phrase which already has a role_ref to the same fact type:
546
- ref = phrase.variable.refs.detect{|ref| ref.role_ref && ref.role_ref.role.fact_type == fact_type}
615
+ # Find another binding for this phrase which already has a role_ref to the same fact type:
616
+ ref = phrase.binding.refs.detect{|ref| ref.role_ref && ref.role_ref.role.fact_type == fact_type}
547
617
  role_ref = ref.role_ref
548
618
  phrase.role = role_ref.role
549
619
  end
@@ -578,7 +648,8 @@ module ActiveFacts
578
648
  existing
579
649
  #raise "Reading '#{existing.expand}' already exists, so why are we creating a duplicate?"
580
650
  end
581
- constellation.Reading(@fact_type, @fact_type.all_reading.size, :role_sequence => @role_sequence, :text => reading_words*" ")
651
+ r = constellation.Reading(@fact_type, @fact_type.all_reading.size, :role_sequence => @role_sequence, :text => reading_words*" ", :is_negative => (certainty == false))
652
+ r
582
653
  end
583
654
  end
584
655
 
@@ -649,16 +720,16 @@ module ActiveFacts
649
720
  end
650
721
  # raise "Reading '#{@reading.expand}' already exists, so why are we creating a duplicate (with #{extra_adjectives.inspect})?"
651
722
  else
652
- constellation.Reading(@fact_type, @fact_type.all_reading.size, :role_sequence => @role_sequence, :text => reading_text)
723
+ constellation.Reading(@fact_type, @fact_type.all_reading.size, :role_sequence => @role_sequence, :text => reading_text, :is_negative => (certainty == false))
653
724
  end
654
725
  @role_sequence
655
726
  end
656
727
 
657
728
  def make_embedded_constraints vocabulary
658
- var_refs.each do |var_ref|
659
- next unless var_ref.quantifier
660
- # puts "Quantifier #{var_ref.inspect} not implemented as a presence constraint"
661
- var_ref.make_embedded_presence_constraint vocabulary
729
+ refs.each do |ref|
730
+ next unless ref.quantifier
731
+ # puts "Quantifier #{ref.inspect} not implemented as a presence constraint"
732
+ ref.make_embedded_presence_constraint vocabulary
662
733
  end
663
734
 
664
735
  if @qualifiers && @qualifiers.size > 0
@@ -675,12 +746,12 @@ module ActiveFacts
675
746
  end
676
747
 
677
748
  # REVISIT: Check maybe and other qualifiers:
678
- debug :constraint, "Need to make constraints for #{@qualifiers*', '}" if @qualifiers.size > 0
749
+ debug :constraint, "Need to make constraints for #{@qualifiers.inspect}" if @qualifiers.size > 0 or @certainty != true
679
750
  end
680
751
  end
681
752
 
682
753
  def is_naked_object_type
683
- @phrases.size == 1 && var_refs.size == 1
754
+ @phrases.size == 1 && refs.size == 1
684
755
  end
685
756
 
686
757
  end
@@ -699,7 +770,7 @@ module ActiveFacts
699
770
  @common_supertype = common_supertype
700
771
  @residual_adjectives = residual_adjectives
701
772
  @cancelled_cost = 0
702
- debug :matching_fails, "Saving side effects for #{@phrase.term}, absorbs #{@absorbed_precursors}/#{@absorbed_followers}#{@common_supertype ? ', join over supertype '+ @common_supertype.name : ''}" if @absorbed_precursors+@absorbed_followers+(@common_supertype ? 1 : 0) > 0
773
+ debug :matching_fails, "Saving side effects for #{@phrase.term}, absorbs #{@absorbed_precursors}/#{@absorbed_followers}#{@common_supertype ? ', step over supertype '+ @common_supertype.name : ''}" if @absorbed_precursors+@absorbed_followers+(@common_supertype ? 1 : 0) > 0
703
774
  end
704
775
 
705
776
  def cost
@@ -718,19 +789,23 @@ module ActiveFacts
718
789
  class ClauseMatchSideEffects
719
790
  attr_reader :residual_adjectives
720
791
  attr_reader :fact_type
721
- attr_reader :role_side_effects # One array of values per VarRef matched, in order
792
+ attr_reader :role_side_effects # One array of values per Reference matched, in order
793
+ attr_reader :negated
794
+ attr_reader :optional
722
795
 
723
- def initialize fact_type, clause, residual_adjectives, role_side_effects
796
+ def initialize fact_type, clause, residual_adjectives, role_side_effects, negated = false
724
797
  @fact_type = fact_type
725
798
  @clause = clause
726
799
  @residual_adjectives = residual_adjectives
727
800
  @role_side_effects = role_side_effects
801
+ @negated = negated
728
802
  end
729
803
 
730
804
  def to_s
731
805
  'side-effects are [' +
732
806
  @role_side_effects.map{|r| r.to_s}*', ' +
733
807
  ']' +
808
+ "#{@negated ? ' negated' : ''}" +
734
809
  "#{@residual_adjectives ? ' with residual adjectives' : ''}"
735
810
  end
736
811
 
@@ -743,29 +818,32 @@ module ActiveFacts
743
818
  @role_side_effects.each do |side_effect|
744
819
  c += side_effect.cost
745
820
  end
746
- c + (@residual_adjectives ? 1 : 0)
821
+ c += 1 if @residual_adjectives
822
+ c += 2 if @negated
823
+ c
747
824
  end
748
825
 
749
826
  def describe
750
827
  actual_effects =
751
828
  @role_side_effects.map do |side_effect|
752
- ( [side_effect.common_supertype ? "supertype join over #{side_effect.common_supertype.name}" : nil] +
829
+ ( [side_effect.common_supertype ? "supertype step over #{side_effect.common_supertype.name}" : nil] +
753
830
  [side_effect.absorbed_precursors > 0 ? "absorbs #{side_effect.absorbed_precursors} preceding words" : nil] +
754
- [side_effect.absorbed_followers > 0 ? "absorbs #{side_effect.absorbed_followers} following words" : nil]
831
+ [side_effect.absorbed_followers > 0 ? "absorbs #{side_effect.absorbed_followers} following words" : nil] +
832
+ [@negated ? 'implicitly negated' : nil]
755
833
  )
756
834
  end.flatten.compact*','
757
835
  actual_effects.empty? ? "no side effects" : actual_effects
758
836
  end
759
837
  end
760
838
 
761
- class VarRef
839
+ class Reference
762
840
  attr_reader :term, :quantifier, :function_call, :value_constraint, :literal, :nested_clauses
763
841
  attr_accessor :leading_adjective, :trailing_adjective, :role_name
764
- attr_accessor :player # What ObjectType does the Variable denote
765
- attr_accessor :variable # What Variable for that ObjectType
842
+ attr_accessor :player # What ObjectType does the Binding denote
843
+ attr_accessor :binding # What Binding for that ObjectType
766
844
  attr_accessor :role # Which Role of this ObjectType
767
845
  attr_accessor :role_ref # Which RoleRef to that Role
768
- attr_accessor :clause # The clause that this VarRef is part of
846
+ attr_accessor :clause # The clause that this Reference is part of
769
847
  attr_accessor :objectification_of # If nested_clauses is set, this is the fact type it objectifies
770
848
  attr_reader :embedded_presence_constraint # This refers to the ActiveFacts::Metamodel::PresenceConstraint
771
849
 
@@ -870,19 +948,19 @@ module ActiveFacts
870
948
  role_name = @term
871
949
  end
872
950
  end
873
- @variable = (context.variables[key] ||= Variable.new(@player, role_name))
874
- @variable.refs << self
875
- @variable
951
+ @binding = (context.bindings[key] ||= Binding.new(@player, role_name))
952
+ @binding.refs << self
953
+ @binding
876
954
  end
877
955
 
878
956
  def unbind context
879
957
  # The key has changed.
880
- @variable.refs.delete(self)
881
- if @variable.refs.empty?
882
- # Remove the variable from the context if this was the last reference
883
- context.variables.delete_if {|k,v| v == @variable }
958
+ @binding.refs.delete(self)
959
+ if @binding.refs.empty?
960
+ # Remove the binding from the context if this was the last reference
961
+ context.bindings.delete_if {|k,v| v == @binding }
884
962
  end
885
- @variable = nil
963
+ @binding = nil
886
964
  end
887
965
 
888
966
  def rebind(context)
@@ -890,18 +968,18 @@ module ActiveFacts
890
968
  bind context
891
969
  end
892
970
 
893
- def rebind_to(context, other_var_ref)
894
- debug :binding, "Rebinding #{inspect} to #{other_var_ref.inspect}"
971
+ def rebind_to(context, other_ref)
972
+ debug :binding, "Rebinding #{inspect} to #{other_ref.inspect}"
895
973
 
896
- old_variable = variable # Remember to move all refs across
974
+ old_binding = binding # Remember to move all refs across
897
975
  unbind(context)
898
976
 
899
- new_variable = other_var_ref.variable
900
- [self, *old_variable.refs].each do |ref|
901
- ref.variable = new_variable
902
- new_variable.refs << ref
977
+ new_binding = other_ref.binding
978
+ [self, *old_binding.refs].each do |ref|
979
+ ref.binding = new_binding
980
+ new_binding.refs << ref
903
981
  end
904
- old_variable.rebound_to = new_variable
982
+ old_binding.rebound_to = new_binding
905
983
  end
906
984
 
907
985
  # These are called when we successfully match a fact type reading that has relevant adjectives:
@@ -931,17 +1009,18 @@ module ActiveFacts
931
1009
 
932
1010
  debug :constraint, "Processing embedded constraint #{@quantifier.inspect} on #{@role_ref.role.object_type.name} in #{fact_type.describe}" do
933
1011
  # Preserve the role order of the clause, excluding this role:
934
- constrained_roles = (@clause.var_refs-[self]).map{|vr| vr.role_ref.role}
1012
+ constrained_roles = (@clause.refs-[self]).map{|vr| vr.role_ref.role}
935
1013
  if constrained_roles.empty?
936
1014
  debug :constraint, "Quantifier over unary role has no effect"
937
1015
  return
938
1016
  end
939
1017
  constraint = find_pc_over_roles(constrained_roles)
940
1018
  if constraint
941
- debug :constraint, "Setting max frequency to #{@quantifier.max} for existing constraint #{constraint.object_id} over #{constraint.role_sequence.describe} in #{fact_type.describe}"
942
1019
  raise "Conflicting maximum frequency for constraint" if constraint.max_frequency && constraint.max_frequency != @quantifier.max
1020
+ debug :constraint, "Setting max frequency to #{@quantifier.max} for existing constraint #{constraint.object_id} over #{constraint.role_sequence.describe} in #{fact_type.describe}" unless constraint.max_frequency
943
1021
  constraint.max_frequency = @quantifier.max
944
1022
  raise "Conflicting minimum frequency for constraint" if constraint.min_frequency && constraint.min_frequency != @quantifier.min
1023
+ debug :constraint, "Setting min frequency to #{@quantifier.min} for existing constraint #{constraint.object_id} over #{constraint.role_sequence.describe} in #{fact_type.describe}" unless constraint.min_frequency
945
1024
  constraint.min_frequency = @quantifier.min
946
1025
  else
947
1026
  role_sequence = constellation.RoleSequence(:new)
@@ -956,7 +1035,7 @@ module ActiveFacts
956
1035
  :max_frequency => @quantifier.max,
957
1036
  :min_frequency => @quantifier.min
958
1037
  )
959
- debug :constraint, "Made new PC min=#{@quantifier.min.inspect} max=#{@quantifier.max.inspect} constraint #{constraint.object_id} over #{(e = fact_type.entity_type) ? e.name : role_sequence.describe} in #{fact_type.describe}"
1038
+ debug :constraint, "Made new embedded PC GUID=#{constraint.guid} min=#{@quantifier.min.inspect} max=#{@quantifier.max.inspect} over #{(e = fact_type.entity_type) ? e.name : role_sequence.describe} in #{fact_type.describe}"
960
1039
  @quantifier.enforcement.compile(constellation, constraint) if @quantifier.enforcement
961
1040
  @embedded_presence_constraint = constraint
962
1041
  end
@@ -982,6 +1061,18 @@ module ActiveFacts
982
1061
  @context_note = context_note
983
1062
  end
984
1063
 
1064
+ def is_unique
1065
+ @max and @max == 1
1066
+ end
1067
+
1068
+ def is_mandatory
1069
+ @min and @min >= 1
1070
+ end
1071
+
1072
+ def is_zero
1073
+ @min == 0 and @max == 0
1074
+ end
1075
+
985
1076
  def inspect
986
1077
  "[#{@min}..#{@max}]#{
987
1078
  @context_note && ' ' + @context_note.inspect