activefacts 1.6.0 → 1.7.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (169) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +14 -0
  3. data/.rspec +2 -0
  4. data/.travis.yml +4 -0
  5. data/Gemfile +14 -0
  6. data/LICENSE.txt +21 -0
  7. data/README.md +60 -0
  8. data/Rakefile +3 -80
  9. data/activefacts.gemspec +36 -0
  10. data/bin/afgen +4 -2
  11. data/bin/cql +5 -1
  12. data/lib/activefacts.rb +3 -12
  13. data/lib/activefacts/{vocabulary/query_evaluator.rb → query/evaluator.rb} +0 -0
  14. data/lib/activefacts/version.rb +2 -2
  15. metadata +48 -296
  16. data/History.txt +0 -4
  17. data/LICENSE +0 -19
  18. data/Manifest.txt +0 -165
  19. data/README.rdoc +0 -81
  20. data/css/offline.css +0 -3
  21. data/css/orm2.css +0 -124
  22. data/css/print.css +0 -8
  23. data/css/style-print.css +0 -357
  24. data/css/style.css +0 -387
  25. data/download.html +0 -110
  26. data/examples/CQL/Address.cql +0 -44
  27. data/examples/CQL/Blog.cql +0 -54
  28. data/examples/CQL/CompanyDirectorEmployee.cql +0 -56
  29. data/examples/CQL/Death.cql +0 -17
  30. data/examples/CQL/Diplomacy.cql +0 -48
  31. data/examples/CQL/Genealogy.cql +0 -98
  32. data/examples/CQL/Insurance.cql +0 -320
  33. data/examples/CQL/Marriage.cql +0 -18
  34. data/examples/CQL/Metamodel.cql +0 -493
  35. data/examples/CQL/Monogamy.cql +0 -24
  36. data/examples/CQL/MultiInheritance.cql +0 -22
  37. data/examples/CQL/NonRoleId.cql +0 -14
  38. data/examples/CQL/OddIdentifier.cql +0 -18
  39. data/examples/CQL/OilSupply.cql +0 -53
  40. data/examples/CQL/OneToOnes.cql +0 -17
  41. data/examples/CQL/Orienteering.cql +0 -111
  42. data/examples/CQL/PersonPlaysGame.cql +0 -18
  43. data/examples/CQL/RedundantDependency.cql +0 -34
  44. data/examples/CQL/SchoolActivities.cql +0 -33
  45. data/examples/CQL/SeparateSubtype.cql +0 -30
  46. data/examples/CQL/ServiceDirector.cql +0 -276
  47. data/examples/CQL/SimplestUnary.cql +0 -12
  48. data/examples/CQL/Supervision.cql +0 -34
  49. data/examples/CQL/WaiterTips.cql +0 -33
  50. data/examples/CQL/Warehousing.cql +0 -101
  51. data/examples/CQL/WindowInRoomInBldg.cql +0 -28
  52. data/examples/CQL/unit.cql +0 -474
  53. data/examples/index.html +0 -420
  54. data/examples/intro.html +0 -327
  55. data/examples/local.css +0 -24
  56. data/index.html +0 -111
  57. data/lib/activefacts/cql.rb +0 -35
  58. data/lib/activefacts/cql/CQLParser.treetop +0 -158
  59. data/lib/activefacts/cql/Context.treetop +0 -48
  60. data/lib/activefacts/cql/Expressions.treetop +0 -67
  61. data/lib/activefacts/cql/FactTypes.treetop +0 -358
  62. data/lib/activefacts/cql/Language/English.treetop +0 -315
  63. data/lib/activefacts/cql/LexicalRules.treetop +0 -253
  64. data/lib/activefacts/cql/ObjectTypes.treetop +0 -210
  65. data/lib/activefacts/cql/Rakefile +0 -14
  66. data/lib/activefacts/cql/Terms.treetop +0 -183
  67. data/lib/activefacts/cql/ValueTypes.treetop +0 -202
  68. data/lib/activefacts/cql/compiler.rb +0 -156
  69. data/lib/activefacts/cql/compiler/clause.rb +0 -1137
  70. data/lib/activefacts/cql/compiler/constraint.rb +0 -581
  71. data/lib/activefacts/cql/compiler/entity_type.rb +0 -457
  72. data/lib/activefacts/cql/compiler/expression.rb +0 -443
  73. data/lib/activefacts/cql/compiler/fact.rb +0 -390
  74. data/lib/activefacts/cql/compiler/fact_type.rb +0 -421
  75. data/lib/activefacts/cql/compiler/query.rb +0 -106
  76. data/lib/activefacts/cql/compiler/shared.rb +0 -161
  77. data/lib/activefacts/cql/compiler/value_type.rb +0 -174
  78. data/lib/activefacts/cql/nodes.rb +0 -49
  79. data/lib/activefacts/cql/parser.rb +0 -241
  80. data/lib/activefacts/dependency_analyser.rb +0 -182
  81. data/lib/activefacts/generate/absorption.rb +0 -70
  82. data/lib/activefacts/generate/composition.rb +0 -118
  83. data/lib/activefacts/generate/cql.rb +0 -714
  84. data/lib/activefacts/generate/dm.rb +0 -279
  85. data/lib/activefacts/generate/help.rb +0 -64
  86. data/lib/activefacts/generate/helpers/inject.rb +0 -16
  87. data/lib/activefacts/generate/helpers/oo.rb +0 -162
  88. data/lib/activefacts/generate/helpers/ordered.rb +0 -605
  89. data/lib/activefacts/generate/helpers/rails.rb +0 -57
  90. data/lib/activefacts/generate/html/glossary.rb +0 -461
  91. data/lib/activefacts/generate/json.rb +0 -337
  92. data/lib/activefacts/generate/null.rb +0 -32
  93. data/lib/activefacts/generate/rails/models.rb +0 -246
  94. data/lib/activefacts/generate/rails/schema.rb +0 -216
  95. data/lib/activefacts/generate/records.rb +0 -46
  96. data/lib/activefacts/generate/ruby.rb +0 -133
  97. data/lib/activefacts/generate/sql/mysql.rb +0 -280
  98. data/lib/activefacts/generate/sql/server.rb +0 -273
  99. data/lib/activefacts/generate/stats.rb +0 -69
  100. data/lib/activefacts/generate/text.rb +0 -27
  101. data/lib/activefacts/generate/topics.rb +0 -265
  102. data/lib/activefacts/generate/traits/datavault.rb +0 -241
  103. data/lib/activefacts/generate/traits/oo.rb +0 -73
  104. data/lib/activefacts/generate/traits/ordered.rb +0 -33
  105. data/lib/activefacts/generate/traits/ruby.rb +0 -210
  106. data/lib/activefacts/generate/transform/datavault.rb +0 -266
  107. data/lib/activefacts/generate/transform/surrogate.rb +0 -214
  108. data/lib/activefacts/generate/version.rb +0 -26
  109. data/lib/activefacts/input/cql.rb +0 -43
  110. data/lib/activefacts/input/orm.rb +0 -1636
  111. data/lib/activefacts/mapping/rails.rb +0 -132
  112. data/lib/activefacts/persistence.rb +0 -15
  113. data/lib/activefacts/persistence/columns.rb +0 -446
  114. data/lib/activefacts/persistence/foreignkey.rb +0 -187
  115. data/lib/activefacts/persistence/index.rb +0 -240
  116. data/lib/activefacts/persistence/object_type.rb +0 -198
  117. data/lib/activefacts/persistence/reference.rb +0 -434
  118. data/lib/activefacts/persistence/tables.rb +0 -380
  119. data/lib/activefacts/registry.rb +0 -11
  120. data/lib/activefacts/support.rb +0 -132
  121. data/lib/activefacts/vocabulary.rb +0 -9
  122. data/lib/activefacts/vocabulary/extensions.rb +0 -1348
  123. data/lib/activefacts/vocabulary/metamodel.rb +0 -570
  124. data/lib/activefacts/vocabulary/verbaliser.rb +0 -804
  125. data/script/txt2html +0 -71
  126. data/spec/absorption_spec.rb +0 -95
  127. data/spec/cql/comparison_spec.rb +0 -89
  128. data/spec/cql/context_spec.rb +0 -94
  129. data/spec/cql/contractions_spec.rb +0 -224
  130. data/spec/cql/deontic_spec.rb +0 -88
  131. data/spec/cql/entity_type_spec.rb +0 -320
  132. data/spec/cql/expressions_spec.rb +0 -66
  133. data/spec/cql/fact_type_matching_spec.rb +0 -338
  134. data/spec/cql/french_spec.rb +0 -21
  135. data/spec/cql/parser/bad_literals_spec.rb +0 -86
  136. data/spec/cql/parser/constraints_spec.rb +0 -19
  137. data/spec/cql/parser/entity_types_spec.rb +0 -106
  138. data/spec/cql/parser/expressions_spec.rb +0 -199
  139. data/spec/cql/parser/fact_types_spec.rb +0 -44
  140. data/spec/cql/parser/literals_spec.rb +0 -312
  141. data/spec/cql/parser/pragmas_spec.rb +0 -89
  142. data/spec/cql/parser/value_types_spec.rb +0 -42
  143. data/spec/cql/role_matching_spec.rb +0 -148
  144. data/spec/cql/samples_spec.rb +0 -244
  145. data/spec/cql_cql_spec.rb +0 -73
  146. data/spec/cql_dm_spec.rb +0 -136
  147. data/spec/cql_mysql_spec.rb +0 -69
  148. data/spec/cql_parse_spec.rb +0 -34
  149. data/spec/cql_ruby_spec.rb +0 -73
  150. data/spec/cql_sql_spec.rb +0 -72
  151. data/spec/cql_symbol_tables_spec.rb +0 -261
  152. data/spec/cqldump_spec.rb +0 -170
  153. data/spec/helpers/array_matcher.rb +0 -23
  154. data/spec/helpers/ctrl_c_support.rb +0 -52
  155. data/spec/helpers/diff_matcher.rb +0 -39
  156. data/spec/helpers/file_matcher.rb +0 -34
  157. data/spec/helpers/parse_to_ast_matcher.rb +0 -80
  158. data/spec/helpers/string_matcher.rb +0 -30
  159. data/spec/helpers/test_parser.rb +0 -15
  160. data/spec/norma_cql_spec.rb +0 -66
  161. data/spec/norma_ruby_spec.rb +0 -62
  162. data/spec/norma_ruby_sql_spec.rb +0 -107
  163. data/spec/norma_sql_spec.rb +0 -57
  164. data/spec/norma_tables_spec.rb +0 -95
  165. data/spec/ruby_api_spec.rb +0 -23
  166. data/spec/spec_helper.rb +0 -35
  167. data/spec/transform_surrogate_spec.rb +0 -59
  168. data/status.html +0 -138
  169. data/why.html +0 -60
@@ -1,1137 +0,0 @@
1
- module ActiveFacts
2
- module CQL
3
- class Compiler < ActiveFacts::CQL::Parser
4
-
5
- class Clause
6
- attr_reader :phrases
7
- attr_accessor :qualifiers, :context_note
8
- attr_accessor :certainty # nil, true, false -> maybe, definitely, not
9
- attr_accessor :conjunction # one of {nil, 'and', ',', 'or', 'where'}
10
- attr_accessor :fact_type
11
- attr_reader :reading, :role_sequence # These are the Metamodel objects
12
- attr_reader :side_effects # How to adjust the phrases if this fact_type match is accepted
13
- attr_accessor :fact # When binding fact instances the fact goes here
14
- attr_accessor :objectified_as # The Reference which objectified this fact type
15
-
16
- def initialize phrases, qualifiers = [], context_note = nil
17
- @phrases = phrases
18
- refs.each { |ref| ref.clause = self }
19
- @certainty = true
20
- @qualifiers = qualifiers
21
- @context_note = context_note
22
- end
23
-
24
- def refs
25
- @phrases.select{|r| r.respond_to?(:player)}
26
- end
27
-
28
- # A clause that contains only the name of a ObjectType and no literal or reading text
29
- # refers only to the existence of that ObjectType (as opposed to an instance of the object_type).
30
- def is_existential_type
31
- @phrases.size == 1 and
32
- @phrases[0].is_a?(Reference) and
33
- !@phrases[0].literal
34
- end
35
-
36
- def display
37
- to_s
38
- end
39
-
40
- def inspect
41
- to_s
42
- end
43
-
44
- def to_s phrases = nil
45
- phrases ||= @phrases
46
- "#{
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
54
- }#{
55
- (
56
- phrases.map do |phrase|
57
- case phrase
58
- when String
59
- '"' + phrase.to_s + '"'
60
- when Reference
61
- phrase.to_s +
62
- if phrase.nested_clauses
63
- ' (in which ' +
64
- phrase.nested_clauses.map do |c|
65
- ((j = c.conjunction) ? j+' ' : '') +
66
- c.to_s
67
- end*' ' +
68
- ')'
69
- else
70
- ''
71
- end
72
- when Operation
73
- phrase.inspect
74
- when Literal
75
- phrase.inspect
76
- #when FunctionCallChain # REVISIT: Add something here when I re-add functions
77
- # phrase.inspect
78
- else
79
- raise "Unexpected phrase type in clause: #{phrase.class}"
80
- end
81
- end * ' '
82
- ).gsub(/" "/, ' ')
83
- }#{
84
- @context_note && ' ' + @context_note.inspect
85
- }"
86
- end
87
-
88
- def identify_players_with_role_name context
89
- refs.each do |ref|
90
- ref.identify_players_with_role_name(context)
91
- end
92
- end
93
-
94
- def identify_other_players context
95
- refs.each do |ref|
96
- ref.identify_other_players(context)
97
- # Include players in nested clauses, if any
98
- ref.nested_clauses.each{|clause| clause.identify_other_players(context)} if ref.nested_clauses
99
- end
100
- end
101
-
102
- def includes_literals
103
- refs.detect{|ref| ref.literal || (ja = ref.nested_clauses and ja.detect{|jr| jr.includes_literals })}
104
- end
105
-
106
- def is_equality_comparison
107
- false
108
- end
109
-
110
- def bind context
111
- role_names = refs.map{ |ref| ref.role_name }.compact
112
-
113
- # Check uniqueness of role names and subscripts within this clause:
114
- role_names.each do |rn|
115
- next if role_names.select{|rn2| rn2 == rn}.size == 1
116
- raise "Duplicate role #{rn.is_a?(Integer) ? "subscript" : "name"} '#{rn}' in clause"
117
- end
118
-
119
- refs.each do |ref|
120
- ref.bind context
121
- end
122
- end
123
-
124
- # This method is used in matching unary fact types in entity identification
125
- # It disregards literals, which are not allowed in this context.
126
- def phrases_match(phrases)
127
- @phrases.zip(phrases).each do |mine, theirs|
128
- return false if mine.is_a?(Reference) != theirs.is_a?(Reference)
129
- if mine.is_a?(Reference)
130
- return false unless mine.key == theirs.key
131
- else
132
- return false unless mine == theirs
133
- end
134
- end
135
- true
136
- end
137
-
138
- # This method chooses the existing fact type which matches most closely.
139
- # It returns nil if there is none, or a ClauseMatchSideEffects object if matched.
140
- #
141
- # As this match may not necessarily be used (depending on the side effects),
142
- # no change is made to this Clause object - those will be done later.
143
- #
144
- def match_existing_fact_type context, options = {}
145
- raise "Cannot match a clause that contains no object types" if refs.size == 0
146
- raise "Internal error, clause already matched, should not match again" if @fact_type
147
-
148
- if is_naked_object_type
149
- ref = refs[0] # "There can be only one"
150
- return true unless ref.nested_clauses
151
- ref.nested_clauses.each do |nested|
152
- ft = nested.match_existing_fact_type(context)
153
- raise "Unrecognised fact type #{nested.display} nested under #{inspect}" unless ft
154
- if (ft.entity_type == ref.player)
155
- ref.objectification_of = ft
156
- nested.objectified_as = ref
157
- end
158
- end
159
- raise "#{ref.inspect} contains objectification steps that do not objectify it" unless ref.objectification_of
160
- return true
161
- end
162
-
163
- # If we fail to match, try a left contraction (or save this for a subsequent left contraction):
164
- left_contract_this_onto = context.left_contractable_clause
165
- new_conjunction = (conjunction == nil || conjunction == ',')
166
- changed_conjunction = (lcc = context.left_contraction_conjunction) && lcc != conjunction
167
- if context.left_contraction_allowed && (new_conjunction || changed_conjunction)
168
- # Conjunctions are that/who, where, comparison-operator, ','
169
- trace :matching, "A left contraction will be against #{self.inspect}, conjunction is #{conjunction.inspect}"
170
- context.left_contractable_clause = self
171
- left_contract_this_onto = nil # Can't left-contract this clause
172
- end
173
- context.left_contraction_conjunction = new_conjunction ? nil : @conjunction
174
-
175
- phrases = @phrases
176
- vrs = []+refs
177
-
178
- # A left contraction is where the first player in the previous clause continues as first player of this clause
179
- contracted_left = false
180
- can_contract_right = false
181
- left_insertion = nil
182
- right_insertion = nil
183
- supposed_roles = [] # Arrange to unbind incorrect references supposed due to contraction
184
- contract_left = proc do
185
- contracted_from = left_contract_this_onto.refs[0]
186
- contraction_player = contracted_from.player
187
- contracted_role = Reference.new(contraction_player.name)
188
- supposed_roles << contracted_role
189
- left_insertion = contracted_role.inspect+' '
190
- contracted_role.player = contracted_from.player
191
- contracted_role.role_name = contracted_from.role_name
192
- contracted_role.bind(context)
193
- vrs.unshift contracted_role
194
- contracted_left = true
195
- phrases = [contracted_role]+phrases
196
- trace :matching, "Failed to match #{inspect}. Trying again using left contraction onto #{contraction_player.name}"
197
- end
198
-
199
- contract_right = proc do
200
- contracted_from = left_contract_this_onto.refs[-1]
201
- contraction_player = contracted_from.player
202
- contracted_role = Reference.new(contraction_player.name)
203
- supposed_roles << contracted_role
204
- right_insertion = ' '+contracted_role.inspect
205
- contracted_role.player = contracted_from.player
206
- contracted_role.role_name = contracted_from.role_name
207
- contracted_role.bind(context)
208
- vrs.push contracted_role
209
- phrases = phrases+[contracted_role]
210
- trace :matching, "Failed to match #{inspect}. Trying again using right contraction onto #{contraction_player.name}"
211
- end
212
-
213
- begin
214
- players = vrs.map{|vr| vr.player}
215
-
216
- if players.size == 0
217
- can_contract_right = left_contract_this_onto.refs.size == 2
218
- contract_left.call
219
- redo
220
- end
221
-
222
- raise "Must identify players before matching fact types" if players.include? nil
223
- 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
224
-
225
- player_names = players.map{|p| p.name}
226
-
227
- trace :matching, "Looking for existing #{players.size}-ary fact types matching '#{inspect}'" do
228
- trace :matching, "Players are '#{player_names.inspect}'"
229
-
230
- # Match existing fact types in nested clauses first:
231
- # (not for contractions) REVISIT: Why not?
232
- if !contracted_left
233
- vrs.each do |ref|
234
- next if ref.is_a?(Operation)
235
- next unless steps = ref.nested_clauses and !steps.empty?
236
- ref.nested_clauses.each do |nested|
237
- ft = nested.match_existing_fact_type(context)
238
- raise "Unrecognised fact type #{nested.display}" unless ft
239
- if (ft && ft.entity_type == ref.player)
240
- ref.objectification_of = ft
241
- nested.objectified_as = ref
242
- end
243
- end
244
- raise "#{ref.inspect} contains objectification steps that do not objectify it" unless ref.objectification_of
245
- end
246
- end
247
-
248
- # For each role player, find the compatible types (the set of all subtypes and supertypes).
249
- # For a player that's an objectification, we don't allow implicit supertype steps
250
- player_related_types =
251
- vrs.zip(players).map do |ref, player|
252
- disallow_subtyping = ref && ref.objectification_of || options[:exact_type]
253
- ((disallow_subtyping ? [] : player.supertypes_transitive) +
254
- player.subtypes_transitive).uniq
255
- end
256
-
257
- trace :matching, "Players must match '#{player_related_types.map{|pa| pa.map{|p|p.name}}.inspect}'"
258
-
259
- start_obj = player_related_types[0] || [left_contract_this_onto.refs[-1].player]
260
- # The candidate fact types have the right number of role players of related types.
261
- # If any role is played by a supertype or subtype of the required type, there's an implicit subtyping steps
262
- # REVISIT: A double contraction results in player_related_types being empty here
263
- candidate_fact_types =
264
- start_obj.map do |related_type|
265
- related_type.all_role.select do |role|
266
- # next if role.fact_type.all_reading.size == 0
267
- next if role.fact_type.is_a?(ActiveFacts::Metamodel::LinkFactType)
268
- next if role.fact_type.all_role.size != players.size # Wrong number of players
269
-
270
- compatible_readings = role.fact_type.compatible_readings(player_related_types)
271
- next unless compatible_readings.size > 0
272
- trace :matching_fails, "These readings are compatible: #{compatible_readings.map(&:expand).inspect}"
273
- true
274
- end.
275
- map{ |role| role.fact_type}
276
- end.flatten.uniq
277
-
278
- # If there is more than one possible exact match (same adjectives) with different subyping, the implicit query is ambiguous and is not allowed
279
-
280
- trace :matching, "Looking amongst #{candidate_fact_types.size} existing fact types for one matching #{left_insertion}'#{inspect}'#{right_insertion}" do
281
- matches = {}
282
- candidate_fact_types.map do |fact_type|
283
- fact_type.all_reading.map do |reading|
284
- next unless side_effects = clause_matches(fact_type, reading, phrases)
285
- matches[reading] = side_effects if side_effects
286
- end
287
- end
288
-
289
- # REVISIT: Side effects that leave extra adjectives should only be allowed if the
290
- # same extra adjectives exist in some other clause in the same declaration.
291
- # The extra adjectives are then necessary to associate the two role players
292
- # when consumed adjectives were required to bind to the underlying fact types.
293
- # This requires the final decision on fact type matching to be postponed until
294
- # the whole declaration has been processed and the extra adjectives can be matched.
295
-
296
- best_matches = matches.keys.sort_by{|match|
297
- # Between equivalents, prefer the one without steps on the first role
298
- (m = matches[match]).cost*2 + ((!(e = m.role_side_effects[0]) || e.cost) == 0 ? 0 : 1)
299
- }
300
- trace :matching_fails, "Found #{matches.size} valid matches#{matches.size > 0 ? ', best is '+best_matches[0].expand : ''}"
301
-
302
- if matches.size > 1
303
- first = matches[best_matches[0]]
304
- cost = first.cost
305
- equal_best = matches.select{|k,m| m.cost == cost}
306
-
307
- if equal_best.size > 1 and equal_best.detect{|k,m| !m.fact_type.is_a?(Metamodel::TypeInheritance)}
308
- # Complain if there's more than one equivalent cost match (unless all are TypeInheritance):
309
- raise "#{@phrases.inspect} could match any of the following:\n\t"+
310
- best_matches.map { |reading| reading.expand + " with " + matches[reading].describe } * "\n\t"
311
- end
312
- end
313
-
314
- if matches.size >= 1
315
- @reading = best_matches[0]
316
- @side_effects = matches[@reading]
317
- @fact_type = @side_effects.fact_type
318
- trace :matching, "Matched '#{@fact_type.default_reading}'"
319
- @phrases = phrases
320
- apply_side_effects(context, @side_effects)
321
- return @fact_type
322
- end
323
-
324
- end
325
- trace :matching, "No fact type matched, candidates were '#{candidate_fact_types.map{|ft| ft.default_reading}*"', '"}'"
326
- end
327
- if left_contract_this_onto
328
- if !contracted_left
329
- contract_left.call
330
- redo
331
- elsif can_contract_right
332
- contract_right.call
333
- can_contract_right = false
334
- redo
335
- end
336
- end
337
- end until true # Once through, unless we hit a redo
338
- supposed_roles.each do |role|
339
- role.unbind context
340
- end
341
- @fact_type = nil
342
- end
343
-
344
- # The Reading passed has the same players as this Clause. Does it match?
345
- # Twisty curves. This is a complex bit of code!
346
- # Find whether the phrases of this clause match the fact type reading,
347
- # which may require absorbing unmarked adjectives.
348
- #
349
- # If it does match, make the required changes and set @ref to the matching role ref.
350
- # Adjectives that were used to match are removed (and leaving any additional adjectives intact).
351
- #
352
- # Approach:
353
- # Match each element where element means:
354
- # a role player phrase (perhaps with adjectives)
355
- # Our phrase must either be
356
- # a player that contains the same adjectives as in the reading.
357
- # a word (unmarked leading adjective) that introduces a sequence
358
- # of adjectives leading up to a matching player
359
- # trailing adjectives, both marked and unmarked, are absorbed too.
360
- # a word that matches the reading's
361
- #
362
- def clause_matches(fact_type, reading, phrases = @phrases)
363
- implicitly_negated = false
364
- side_effects = [] # An array of items for each role, describing any side-effects of the match.
365
- intervening_words = nil
366
- residual_adjectives = false
367
-
368
- # The following form of negation is, e.g., where "Person was invited to no Party",
369
- # as opposed to where "Person was not invited to that Party". Quite different meaning,
370
- # because a free Party variable is required, but the join step is still disallowed.
371
- # REVISIT: I'll create the free variable when I implement some/that binding
372
- # REVISIT: the verbaliser will need to know about a negated step to a free variable
373
- implicitly_negated = true if refs.detect{|ref| q = ref.quantifier and q.is_zero }
374
-
375
- trace :matching_fails, "Does '#{phrases.inspect}' match '#{reading.expand}'" do
376
- phrase_num = 0
377
- reading_parts = reading.text.split(/\s+/)
378
- reading_parts.each do |element|
379
- phrase = phrases[phrase_num]
380
- if phrase == 'not'
381
- raise "Stop playing games with your double negatives: #{phrases.inspect}" if implicitly_negated
382
- trace :matching, "Negation detected"
383
- implicitly_negated = true
384
- phrase = phrases[phrase_num += 1]
385
- end
386
- if element !~ /\{(\d+)\}/
387
- # Just a word; it must match
388
- unless phrase == element
389
- trace :matching_fails, "Mismatched ordinary word #{phrases[phrase_num].inspect} (wanted #{element})"
390
- return nil
391
- end
392
- phrase_num += 1
393
- next
394
- else
395
- role_ref = reading.role_sequence.all_role_ref.sort_by{|rr| rr.ordinal}[$1.to_i]
396
- end
397
-
398
- player = role_ref.role.object_type
399
-
400
- # Figure out what's next in this phrase (the next player and the words leading up to it)
401
- next_player_phrase = nil
402
- intervening_words = []
403
- while (phrase = phrases[phrase_num])
404
- phrase_num += 1
405
- if phrase.respond_to?(:player)
406
- next_player_phrase = phrase
407
- next_player_phrase_num = phrase_num-1
408
- break
409
- else
410
- intervening_words << phrase
411
- end
412
- end
413
- return nil unless next_player_phrase # reading has more players than we do.
414
- next_player = next_player_phrase.player
415
-
416
- # The next player must match:
417
- common_supertype = nil
418
- if next_player != player
419
- # This relies on the supertypes being in breadth-first order:
420
- common_supertype = (next_player.supertypes_transitive & player.supertypes_transitive)[0]
421
- if !common_supertype
422
- trace :matching_fails, "Reading discounted because next player #{player.name} doesn't match #{next_player.name}"
423
- return nil
424
- end
425
-
426
- trace :matching_fails, "Subtype step is required between #{player.name} and #{next_player_phrase.player.name} via common supertype #{common_supertype.name}"
427
- else
428
- if !next_player_phrase
429
- next # Contraction succeeded so far
430
- end
431
- end
432
-
433
- # It's the right player. Do the adjectives match? This must include the intervening_words, if any.
434
-
435
- role_has_residual_adjectives = false
436
- absorbed_precursors = 0
437
- if la = role_ref.leading_adjective and !la.empty?
438
- # The leading adjectives must match, one way or another
439
- la = la.split(/\s+/)
440
- if (la[0, intervening_words.size] == intervening_words) # Exact match
441
- iw = intervening_words
442
- else
443
- # We may have hyphenated adjectives. Break them up to check:
444
- iw = intervening_words.map{|w| w.split(/-/)}.flatten
445
- return nil unless la[0,iw.size] == iw
446
- end
447
-
448
- # Any intervening_words matched, see what remains
449
- la.slice!(0, iw.size)
450
-
451
- # If there were intervening_words, the remaining reading adjectives must match the phrase's leading_adjective exactly.
452
- phrase_la = (next_player_phrase.leading_adjective||'').split(/\s+/)
453
- return nil if !iw.empty? && la != phrase_la
454
- # If not, the phrase's leading_adjectives must *end* with the reading's
455
- return nil if phrase_la[-la.size..-1] != la
456
- role_has_residual_adjectives = true if phrase_la.size > la.size
457
- # The leading adjectives and the player matched! Check the trailing adjectives.
458
- absorbed_precursors = intervening_words.size
459
- intervening_words = []
460
- elsif intervening_words.size > 0 || next_player_phrase.leading_adjective
461
- role_has_residual_adjectives = true
462
- end
463
-
464
- absorbed_followers = 0
465
- if ta = role_ref.trailing_adjective and !ta.empty?
466
- ta = ta.split(/\s+/) # These are the trailing adjectives to match
467
-
468
- phrase_ta = (next_player_phrase.trailing_adjective||'').split(/\s+/)
469
- i = 0 # Pad the phrases up to the size of the trailing_adjectives
470
- while phrase_ta.size < ta.size
471
- break unless (word = phrases[phrase_num+i]).is_a?(String)
472
- phrase_ta << word
473
- i += 1
474
- end
475
- # ta is the adjectives in the fact type being matched
476
- # phrase_ta is the explicit adjectives augmented with implicit ones to the same size
477
- return nil if ta != phrase_ta[0,ta.size]
478
- role_has_residual_adjectives = true if phrase_ta.size > ta.size
479
- absorbed_followers = i
480
- phrase_num += i # Skip following words that were consumed as trailing adjectives
481
- elsif next_player_phrase.trailing_adjective
482
- role_has_residual_adjectives = true
483
- end
484
-
485
- # REVISIT: I'm not even sure I should be caring about role names here.
486
- # Role names are on roles, and are only useful within the fact type definition.
487
- # At some point, we need to worry about role names on clauses within fact type derivations,
488
- # which means they'll move to the Role Ref class; but even then they only match within the
489
- # definition that creates that Role Ref.
490
- =begin
491
- if a = (!phrase.role_name.is_a?(Integer) && phrase.role_name) and
492
- e = role_ref.role.role_name and
493
- a != e
494
- trace :matching, "Role names #{e.inspect} for #{player.name} and #{a.inspect} for #{next_player_phrase.player.name} don't match"
495
- return nil
496
- end
497
- =end
498
-
499
- residual_adjectives ||= role_has_residual_adjectives
500
- if residual_adjectives && next_player_phrase.binding.refs.size == 1
501
- # This makes matching order-dependent, because there may be no "other purpose"
502
- # until another reading has been matched and the roles rebound.
503
- trace :matching_fails, "Residual adjectives have no other purpose, so this match fails"
504
- return nil
505
- end
506
-
507
- # The phrases matched this reading's next role_ref, save data to apply the side-effects:
508
- side_effects << ClauseMatchSideEffect.new(next_player_phrase, role_ref, next_player_phrase_num, absorbed_precursors, absorbed_followers, common_supertype, role_has_residual_adjectives)
509
- end
510
-
511
- if phrase_num != phrases.size || !intervening_words.empty?
512
- trace :matching_fails, "Extra words #{(intervening_words + phrases[phrase_num..-1]).inspect}"
513
- return nil
514
- end
515
-
516
- if fact_type.is_a?(Metamodel::TypeInheritance)
517
- # There may be only one subtyping step on a TypeInheritance fact type.
518
- ti_steps = side_effects.select{|side_effect| side_effect.common_supertype}
519
- if ti_steps.size > 1 # Not allowed
520
- trace :matching_fails, "Can't have more than one subtyping step on a TypeInheritance fact type"
521
- return nil
522
- end
523
-
524
- if ti = ti_steps[0]
525
- # The Type Inheritance step must continue in the same direction as this reading.
526
- allowed = fact_type.supertype == ti.role_ref.role.object_type ?
527
- fact_type.subtype.supertypes_transitive :
528
- fact_type.supertype.subtypes_transitive
529
- if !allowed.include?(ti.common_supertype)
530
- trace :matching_fails, "Implicit subtyping step extends in the wrong direction"
531
- return nil
532
- end
533
- end
534
- end
535
-
536
- trace :matching, "Matched reading '#{reading.expand}' (with #{
537
- side_effects.map{|side_effect|
538
- side_effect.absorbed_precursors+side_effect.absorbed_followers + (side_effect.common_supertype ? 1 : 0)
539
- }.inspect
540
- } side effects)#{residual_adjectives ? ' and residual adjectives' : ''}"
541
- end
542
- # There will be one side_effects for each role player
543
- @certainty = !@certainty if implicitly_negated
544
- @certainty = !@certainty if reading.is_negative
545
- ClauseMatchSideEffects.new(fact_type, self, residual_adjectives, side_effects, implicitly_negated)
546
- end
547
-
548
- def apply_side_effects(context, side_effects)
549
- @applied_side_effects = side_effects
550
- # Enact the side-effects of this match (delete the consumed adjectives):
551
- # Since this deletes words from the phrases, we do it in reverse order.
552
- trace :matching, "Apply side-effects" do
553
- side_effects.apply_all do |side_effect|
554
- phrase = side_effect.phrase
555
-
556
- # We re-use the role_ref if possible (no extra adjectives were used, no rolename or step, etc).
557
- trace :matching, "side-effect means binding #{phrase.inspect} matches role ref #{side_effect.role_ref.role.object_type.name}"
558
- phrase.role_ref = side_effect.role_ref
559
-
560
- changed = false
561
-
562
- # Where this phrase has leading or trailing adjectives that are in excess of those of
563
- # the role_ref, those must be local, and we'll need to extract them.
564
-
565
- if rra = side_effect.role_ref.trailing_adjective
566
- trace :matching, "Deleting matched trailing adjective '#{rra}'#{side_effect.absorbed_followers>0 ? " in #{side_effect.absorbed_followers} followers" : ""}, cost is #{side_effect.cost}"
567
- side_effect.cancel_cost side_effect.absorbed_followers
568
-
569
- # These adjective(s) matched either an adjective here, or a follower word, or both.
570
- if a = phrase.trailing_adjective
571
- if a.size >= rra.size
572
- a = a[rra.size+1..-1]
573
- phrase.trailing_adjective = a == '' ? nil : a
574
- changed = true
575
- end
576
- elsif side_effect.absorbed_followers > 0
577
- # The following statement is incorrect. The absorbed adjective is what caused the match.
578
- # This phrase is absorbing non-hyphenated adjective(s), which changes its binding
579
- # phrase.trailing_adjective =
580
- @phrases.slice!(side_effect.num+1, side_effect.absorbed_followers)*' '
581
- changed = true
582
- end
583
- end
584
-
585
- if rra = side_effect.role_ref.leading_adjective
586
- trace :matching, "Deleting matched leading adjective '#{rra}'#{side_effect.absorbed_precursors>0 ? " in #{side_effect.absorbed_precursors} precursors" : ""}, cost is #{side_effect.cost}"
587
- side_effect.cancel_cost side_effect.absorbed_precursors
588
-
589
- # These adjective(s) matched either an adjective here, or a precursor word, or both.
590
- if a = phrase.leading_adjective
591
- if a.size >= rra.size
592
- a = a[0...-rra.size]
593
- phrase.leading_adjective = a == '' ? nil : a
594
- changed = true
595
- end
596
- elsif side_effect.absorbed_precursors > 0
597
- # The following statement is incorrect. The absorbed adjective is what caused the match.
598
- # This phrase is absorbing non-hyphenated adjective(s), which changes its binding
599
- #phrase.leading_adjective =
600
- @phrases.slice!(side_effect.num-side_effect.absorbed_precursors, side_effect.absorbed_precursors)*' '
601
- changed = true
602
- end
603
- end
604
- if changed
605
- phrase.rebind context
606
- end
607
-
608
- end
609
- end
610
- end
611
-
612
- # Make a new fact type with roles for this clause.
613
- # Don't assign @fact_type; that will happen when the reading is added
614
- def make_fact_type vocabulary
615
- fact_type = vocabulary.constellation.FactType(:new)
616
- trace :matching, "Making new fact type for #{@phrases.inspect}" do
617
- @phrases.each do |phrase|
618
- next unless phrase.respond_to?(:player)
619
- phrase.role = vocabulary.constellation.Role(fact_type, fact_type.all_role.size, :object_type => phrase.player, :concept => :new)
620
- phrase.role.role_name = phrase.role_name if phrase.role_name && phrase.role_name.is_a?(String)
621
- end
622
- end
623
- fact_type
624
- end
625
-
626
- def make_reading vocabulary, fact_type
627
- @fact_type = fact_type
628
- constellation = vocabulary.constellation
629
- @role_sequence = constellation.RoleSequence(:new)
630
- reading_words = @phrases.clone
631
- index = 0
632
- trace :matching, "Making new reading for #{@phrases.inspect}" do
633
- reading_words.map! do |phrase|
634
- if phrase.respond_to?(:player)
635
- # phrase.role will be set if this reading was used to make_fact_type.
636
- # Otherwise we have to find the existing role via the Binding. This is pretty ugly.
637
- unless phrase.role
638
- # Find another binding for this phrase which already has a role_ref to the same fact type:
639
- ref = phrase.binding.refs.detect{|ref| ref.role_ref && ref.role_ref.role.fact_type == fact_type}
640
- role_ref = ref.role_ref
641
- phrase.role = role_ref.role
642
- end
643
- rr = constellation.RoleRef(@role_sequence, index, :role => phrase.role)
644
- phrase.role_ref = rr
645
-
646
- if la = phrase.leading_adjective
647
- # If we have used one or more adjective to match an existing reading, that has already been removed.
648
- rr.leading_adjective = la
649
- end
650
- if ta = phrase.trailing_adjective
651
- rr.trailing_adjective = ta
652
- end
653
-
654
- if phrase.value_constraint
655
- raise "The role #{rr.inspect} already has a value constraint" if rr.role.role_value_constraint
656
- phrase.value_constraint.constellation = fact_type.constellation
657
- rr.role.role_value_constraint = phrase.value_constraint.compile
658
- end
659
-
660
- index += 1
661
- "{#{index-1}}"
662
- else
663
- phrase
664
- end
665
- end
666
- if existing = @fact_type.all_reading.detect{|r|
667
- r.text == reading_words*' ' and
668
- r.role_sequence.all_role_ref_in_order.map{|rr| rr.role.object_type} ==
669
- role_sequence.all_role_ref_in_order.map{|rr| rr.role.object_type}
670
- }
671
- existing
672
- #raise "Reading '#{existing.expand}' already exists, so why are we creating a duplicate?"
673
- end
674
- r = constellation.Reading(@fact_type, @fact_type.all_reading.size, :role_sequence => @role_sequence, :text => reading_words*" ", :is_negative => (certainty == false))
675
- r
676
- end
677
- end
678
-
679
- # When we match an existing reading, we might have matched using additional adjectives.
680
- # These adjectives have been removed from the phrases. If there are any remaining adjectives,
681
- # we need to make a new RoleSequence, otherwise we can use the existing one.
682
- def adjust_for_match
683
- return unless @applied_side_effects
684
- new_role_sequence_needed = @applied_side_effects.residual_adjectives
685
-
686
- role_phrases = []
687
- reading_words = []
688
- new_role_sequence_needed = false
689
- @phrases.each do |phrase|
690
- if phrase.respond_to?(:player)
691
- role_phrases << phrase
692
- reading_words << "{#{phrase.role_ref.ordinal}}"
693
- if phrase.role_name != phrase.role_ref.role.role_name ||
694
- phrase.leading_adjective ||
695
- phrase.trailing_adjective
696
- trace :matching, "phrase in matched clause has residual adjectives or role name, so needs a new role_sequence" if @fact_type.all_reading.size > 0
697
- new_role_sequence_needed = true
698
- end
699
- else
700
- reading_words << phrase
701
- false
702
- end
703
- end
704
-
705
- trace :matching, "Clause '#{reading_words*' '}' #{new_role_sequence_needed ? 'requires' : 'does not require'} a new Role Sequence"
706
-
707
- constellation = @fact_type.constellation
708
- reading_text = reading_words*" "
709
- if new_role_sequence_needed
710
- @role_sequence = constellation.RoleSequence(:new)
711
- extra_adjectives = []
712
- role_phrases.each_with_index do |rp, i|
713
- role_ref = constellation.RoleRef(@role_sequence, i, :role => rp.role_ref.role)
714
- if a = rp.leading_adjective
715
- role_ref.leading_adjective = a
716
- extra_adjectives << a+"-"
717
- end
718
- if a = rp.trailing_adjective
719
- role_ref.trailing_adjective = a
720
- extra_adjectives << "-"+a
721
- end
722
- if (a = rp.role_name) && (e = rp.role_ref.role.role_name) && a != e
723
- raise "Can't create new reading '#{reading_text}' for '#{reading.expand}' with alternate role name #{a}"
724
- extra_adjectives << "(as #{a})"
725
- end
726
- end
727
- trace :matching, "Making new role sequence for new reading #{reading_text} due to #{extra_adjectives.inspect}"
728
- else
729
- # Use existing RoleSequence
730
- @role_sequence = role_phrases[0].role_ref.role_sequence
731
- if @role_sequence.all_reading.detect{|r| r.text == reading_text }
732
- trace :matching, "No need to re-create identical reading for #{reading_text}"
733
- return @role_sequence
734
- else
735
- trace :matching, "Using existing role sequence for new reading '#{reading_text}'"
736
- end
737
- end
738
- if @fact_type.all_reading.
739
- detect do |r|
740
- r.text == reading_text and
741
- r.role_sequence.all_role_ref_in_order.map{|rr| rr.role.object_type} ==
742
- @role_sequence.all_role_ref_in_order.map{|rr| rr.role.object_type}
743
- end
744
- # raise "Reading '#{@reading.expand}' already exists, so why are we creating a duplicate (with #{extra_adjectives.inspect})?"
745
- else
746
- constellation.Reading(@fact_type, @fact_type.all_reading.size, :role_sequence => @role_sequence, :text => reading_text, :is_negative => (certainty == false))
747
- end
748
- @role_sequence
749
- end
750
-
751
- def make_embedded_constraints vocabulary
752
- refs.each do |ref|
753
- next unless ref.quantifier
754
- # puts "Quantifier #{ref.inspect} not implemented as a presence constraint"
755
- ref.make_embedded_presence_constraint vocabulary
756
- end
757
-
758
- if @qualifiers && @qualifiers.size > 0
759
- # We shouldn't make a new ring constraint if there's already one over this ring.
760
- existing_rcs =
761
- @role_sequence.all_role_ref.map{|rr| rr.role.all_ring_constraint.to_a }.flatten.uniq
762
- unless existing_rcs[0]
763
- rc = RingConstraint.new(@role_sequence, @qualifiers)
764
- rc.vocabulary = vocabulary
765
- rc.constellation = vocabulary.constellation
766
- rc.compile
767
- else
768
- # Ignore the fact that the ring might be of a different type.
769
- end
770
-
771
- # REVISIT: Check maybe and other qualifiers:
772
- trace :constraint, "Need to make constraints for #{@qualifiers.inspect}" if @qualifiers.size > 0 or @certainty != true
773
- end
774
- end
775
-
776
- def is_naked_object_type
777
- @phrases.size == 1 && refs.size == 1
778
- end
779
-
780
- end
781
-
782
- # An instance of ClauseMatchSideEffects is created when the compiler matches an existing fact type.
783
- # It captures the details that have to be adjusted for the match to be regarded a success.
784
- class ClauseMatchSideEffect
785
- attr_reader :phrase, :role_ref, :num, :absorbed_precursors, :absorbed_followers, :common_supertype, :residual_adjectives
786
-
787
- def initialize phrase, role_ref, num, absorbed_precursors, absorbed_followers, common_supertype, residual_adjectives
788
- @phrase = phrase
789
- @role_ref = role_ref
790
- @num = num
791
- @absorbed_precursors = absorbed_precursors
792
- @absorbed_followers = absorbed_followers
793
- @common_supertype = common_supertype
794
- @residual_adjectives = residual_adjectives
795
- @cancelled_cost = 0
796
- trace :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
797
- end
798
-
799
- def cost
800
- absorbed_precursors + absorbed_followers + (common_supertype ? 1 : 0) - @cancelled_cost
801
- end
802
-
803
- def cancel_cost c
804
- @cancelled_cost += c
805
- end
806
-
807
- def to_s
808
- "#{@phrase.inspect} absorbs #{@absorbed_precursors||0}/#{@absorbed_followers||0} at #{@num}#{@common_supertype && ' super '+@common_supertype.name}#{@residual_adjectives ? ' with residual adjectives' : ''}"
809
- end
810
- end
811
-
812
- class ClauseMatchSideEffects
813
- attr_reader :residual_adjectives
814
- attr_reader :fact_type
815
- attr_reader :role_side_effects # One array of values per Reference matched, in order
816
- attr_reader :negated
817
- attr_reader :optional
818
-
819
- def initialize fact_type, clause, residual_adjectives, role_side_effects, negated = false
820
- @fact_type = fact_type
821
- @clause = clause
822
- @residual_adjectives = residual_adjectives
823
- @role_side_effects = role_side_effects
824
- @negated = negated
825
- end
826
-
827
- def inspect
828
- 'side-effects are [' +
829
- @role_side_effects.map{|r| r.to_s}*', ' +
830
- ']' +
831
- "#{@negated ? ' negated' : ''}" +
832
- "#{@residual_adjectives ? ' with residual adjectives' : ''}"
833
- end
834
-
835
- def apply_all &b
836
- @role_side_effects.reverse.each{ |role_side_effect| b.call(*role_side_effect) }
837
- end
838
-
839
- def cost
840
- c = 0
841
- @role_side_effects.each do |side_effect|
842
- c += side_effect.cost
843
- end
844
- c += 1 if @residual_adjectives
845
- c += 2 if @negated
846
- c
847
- end
848
-
849
- def describe
850
- actual_effects =
851
- @role_side_effects.map do |side_effect|
852
- ( [side_effect.common_supertype ? "supertype step over #{side_effect.common_supertype.name}" : nil] +
853
- [side_effect.absorbed_precursors > 0 ? "absorbs #{side_effect.absorbed_precursors} preceding words" : nil] +
854
- [side_effect.absorbed_followers > 0 ? "absorbs #{side_effect.absorbed_followers} following words" : nil] +
855
- [@negated ? 'implicitly negated' : nil]
856
- )
857
- end.flatten.compact*','
858
- actual_effects.empty? ? "no side effects" : actual_effects
859
- end
860
- end
861
-
862
- class Reference
863
- attr_reader :term, :quantifier, :function_call, :value_constraint, :literal, :nested_clauses
864
- attr_accessor :leading_adjective, :trailing_adjective, :role_name
865
- attr_accessor :player # What ObjectType does the Binding denote
866
- attr_accessor :binding # What Binding for that ObjectType
867
- attr_accessor :role # Which Role of this ObjectType
868
- attr_accessor :role_ref # Which RoleRef to that Role
869
- attr_accessor :clause # The clause that this Reference is part of
870
- attr_accessor :objectification_of # If nested_clauses is set, this is the fact type it objectifies
871
- attr_reader :embedded_presence_constraint # This refers to the ActiveFacts::Metamodel::PresenceConstraint
872
-
873
- def initialize term, leading_adjective = nil, trailing_adjective = nil, quantifier = nil, function_call = nil, role_name = nil, value_constraint = nil, literal = nil, nested_clauses = nil
874
- @term = term
875
- @leading_adjective = leading_adjective
876
- @trailing_adjective = trailing_adjective
877
- @quantifier = quantifier
878
- # @function_call = function_call # Not used or implemented
879
- @role_name = role_name
880
- @value_constraint = value_constraint
881
- @literal = literal
882
- @nested_clauses = nested_clauses
883
- end
884
-
885
- def inspect
886
- to_s
887
- end
888
-
889
- def to_s
890
- "{#{
891
- @quantifier && @quantifier.inspect+' '
892
- }#{
893
- @leading_adjective && @leading_adjective.sub(/ |$/,'- ').sub(/ *$/,' ')
894
- }#{
895
- @term
896
- }#{
897
- @trailing_adjective && ' '+@trailing_adjective.sub(/(.* |^)/, '\1-')
898
- }#{
899
- @role_name and @role_name.is_a?(Integer) ? "(#{@role_name})" : " (as #{@role_name})"
900
- }#{
901
- @literal && ' '+@literal.inspect
902
- }#{
903
- @value_constraint && ' '+@value_constraint.to_s
904
- }}"
905
- end
906
-
907
- def <=>(other)
908
- ( 4*(@term <=> other.term) +
909
- 2*((@leading_adjective||'') <=> (other.leading_adjective||'')) +
910
- 1*((@trailing_adjective||'') <=> (other.trailing_adjective||''))
911
- ) <=> 0
912
- end
913
-
914
- def includes_literals
915
- @nested_clauses && @nested_clauses.detect{|nested| nested.includes_literals}
916
- end
917
-
918
- # We create value types for the results of arithmetic expressions, and they get assigned here:
919
- def player=(player)
920
- @player = player
921
- end
922
-
923
- def identify_players_with_role_name(context)
924
- identify_player(context) if role_name
925
- # Include players in nested clauses, if any
926
- nested_clauses.each{|clause| clause.identify_players_with_role_name(context)} if nested_clauses
927
- end
928
-
929
- def identify_other_players context
930
- identify_player context
931
- end
932
-
933
- def identify_player context
934
- @player || begin
935
- @player = context.object_type @term
936
- raise "ObjectType #{@term} unrecognised" unless @player
937
- context.player_by_role_name[@role_name] = player if @role_name
938
- @player
939
- end
940
- end
941
-
942
- def uses_role_name?
943
- @term != @player.name
944
- end
945
-
946
- def key
947
- if @role_name
948
- key = [@term, @role_name] # Defines a role name
949
- elsif uses_role_name?
950
- key = [@player.name, @term] # Uses a role name
951
- else
952
- l = @leading_adjective
953
- t = @trailing_adjective
954
- key = [!l || l.empty? ? nil : l, @term, !t || t.empty? ? nil : t]
955
- end
956
- key += [:literal, literal.literal] if @literal
957
- key
958
- end
959
-
960
- def bind context
961
- @nested_clauses.each{|c| c.bind context} if @nested_clauses
962
- if role_name = @role_name
963
- # Omit these tests to see if anything evil eventuates:
964
- #if @leading_adjective || @trailing_adjective
965
- # raise "Role reference may not have adjectives if it defines a role name or uses a subscript: #{inspect}"
966
- #end
967
- else
968
- if uses_role_name?
969
- if @leading_adjective || @trailing_adjective
970
- raise "Role reference may not have adjectives if it uses a role name: #{inspect}"
971
- end
972
- role_name = @term
973
- end
974
- end
975
- k = key
976
- @binding = context.bindings[k]
977
- if !@binding
978
- if !literal
979
- # Find a binding that has a literal, and bind to it if it's the only one
980
- candidates = context.bindings.map do |binding_key, binding|
981
- binding_key[0...k.size] == k &&
982
- binding_key[-2] == :literal ? binding : nil
983
- end.compact
984
- raise "Uncertain binding reference for #{to_s}, could be any of #{candidates.inspect}" if candidates.size > 1
985
- @binding = candidates[0]
986
- else
987
- # New binding has a literal, look for one without:
988
- @binding = context.bindings[k[0...-2]]
989
- end
990
- end
991
-
992
- if !@binding
993
- @binding = Binding.new(@player, role_name)
994
- context.bindings[k] = @binding
995
- end
996
- @binding.add_ref self
997
- @binding
998
- end
999
-
1000
- def unbind context
1001
- # The key has changed.
1002
- @binding.delete_ref self
1003
- if @binding.refs.empty?
1004
- # Remove the binding from the context if this was the last reference
1005
- context.bindings.delete_if {|k,v| v == @binding }
1006
- end
1007
- @binding = nil
1008
- end
1009
-
1010
- def rebind(context)
1011
- unbind context
1012
- bind context
1013
- end
1014
-
1015
- def rebind_to(context, other_ref)
1016
- trace :binding, "Rebinding #{inspect} to #{other_ref.inspect}"
1017
-
1018
- old_binding = binding # Remember to move all refs across
1019
- unbind(context)
1020
-
1021
- new_binding = other_ref.binding
1022
- [self, *old_binding.refs].each do |ref|
1023
- ref.binding = new_binding
1024
- new_binding.add_ref ref
1025
- end
1026
- old_binding.rebound_to = new_binding
1027
- end
1028
-
1029
- # These are called when we successfully match a fact type reading that has relevant adjectives:
1030
- def wipe_leading_adjective
1031
- @leading_adjective = nil
1032
- end
1033
-
1034
- def wipe_trailing_adjective
1035
- @trailing_adjective = nil
1036
- end
1037
-
1038
- def find_pc_over_roles(roles)
1039
- return nil if roles.size == 0 # Safeguard; this would chuck an exception otherwise
1040
- roles[0].all_role_ref.each do |role_ref|
1041
- next if role_ref.role_sequence.all_role_ref.map(&:role) != roles
1042
- pc = role_ref.role_sequence.all_presence_constraint.single # Will return nil if there's more than one.
1043
- #puts "Existing PresenceConstraint matches those roles!" if pc
1044
- return pc if pc
1045
- end
1046
- nil
1047
- end
1048
-
1049
- def make_embedded_presence_constraint vocabulary
1050
- raise "No Role for embedded_presence_constraint" unless @role_ref
1051
- fact_type = @role_ref.role.fact_type
1052
- constellation = vocabulary.constellation
1053
-
1054
- trace :constraint, "Processing embedded constraint #{@quantifier.inspect} on #{@role_ref.role.object_type.name} in #{fact_type.describe}" do
1055
- # Preserve the role order of the clause, excluding this role:
1056
- constrained_roles = (@clause.refs-[self]).map{|vr| vr.role_ref.role}
1057
- if constrained_roles.empty?
1058
- trace :constraint, "Quantifier over unary role has no effect"
1059
- return
1060
- end
1061
- constraint = find_pc_over_roles(constrained_roles)
1062
- if constraint
1063
- raise "Conflicting maximum frequency for constraint" if constraint.max_frequency && constraint.max_frequency != @quantifier.max
1064
- trace :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
1065
- constraint.max_frequency = @quantifier.max
1066
- raise "Conflicting minimum frequency for constraint" if constraint.min_frequency && constraint.min_frequency != @quantifier.min
1067
- trace :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
1068
- constraint.min_frequency = @quantifier.min
1069
- else
1070
- role_sequence = constellation.RoleSequence(:new)
1071
- constrained_roles.each_with_index do |constrained_role, i|
1072
- role_ref = constellation.RoleRef(role_sequence, i, :role => constrained_role)
1073
- end
1074
- constraint = constellation.PresenceConstraint(
1075
- :new,
1076
- :vocabulary => vocabulary,
1077
- :role_sequence => role_sequence,
1078
- :is_mandatory => @quantifier.min && @quantifier.min > 0, # REVISIT: Check "maybe" qualifier?
1079
- :max_frequency => @quantifier.max,
1080
- :min_frequency => @quantifier.min
1081
- )
1082
- if @quantifier.pragmas
1083
- @quantifier.pragmas.each do |p|
1084
- constellation.ConceptAnnotation(:concept => constraint.concept, :mapping_annotation => p)
1085
- end
1086
- end
1087
- trace :constraint, "Made new embedded PC GUID=#{constraint.concept.guid} min=#{@quantifier.min.inspect} max=#{@quantifier.max.inspect} over #{(e = fact_type.entity_type) ? e.name : role_sequence.describe} in #{fact_type.describe}"
1088
- @quantifier.enforcement.compile(constellation, constraint) if @quantifier.enforcement
1089
- @embedded_presence_constraint = constraint
1090
- end
1091
- constraint
1092
- end
1093
-
1094
- end
1095
-
1096
- def result(context = nil)
1097
- self
1098
- end
1099
- end
1100
-
1101
- # REVISIT: This needs to handle annotations for some/that/which, etc.
1102
- class Quantifier
1103
- attr_accessor :enforcement
1104
- attr_accessor :context_note
1105
- attr_accessor :pragmas
1106
- attr_reader :min, :max
1107
-
1108
- def initialize min, max, enforcement = nil, context_note = nil, pragmas = nil
1109
- @min = min
1110
- @max = max
1111
- @enforcement = enforcement
1112
- @context_note = context_note
1113
- @pragmas = pragmas
1114
- end
1115
-
1116
- def is_unique
1117
- @max and @max == 1
1118
- end
1119
-
1120
- def is_mandatory
1121
- @min and @min >= 1
1122
- end
1123
-
1124
- def is_zero
1125
- @min == 0 and @max == 0
1126
- end
1127
-
1128
- def inspect
1129
- "[#{@min}..#{@max}]#{
1130
- @context_note && ' ' + @context_note.inspect
1131
- }"
1132
- end
1133
- end
1134
-
1135
- end
1136
- end
1137
- end