activefacts 0.7.3 → 0.8.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. data/LICENSE +19 -0
  2. data/Manifest.txt +24 -2
  3. data/Rakefile +25 -3
  4. data/bin/afgen +1 -1
  5. data/bin/cql +13 -2
  6. data/css/offline.css +3 -0
  7. data/css/orm2.css +24 -0
  8. data/css/print.css +8 -0
  9. data/css/style-print.css +357 -0
  10. data/css/style.css +387 -0
  11. data/download.html +85 -0
  12. data/examples/CQL/Address.cql +3 -3
  13. data/examples/CQL/Blog.cql +13 -14
  14. data/examples/CQL/CompanyDirectorEmployee.cql +4 -4
  15. data/examples/CQL/Death.cql +3 -2
  16. data/examples/CQL/Genealogy.cql +13 -11
  17. data/examples/CQL/Marriage.cql +2 -2
  18. data/examples/CQL/Metamodel.cql +136 -93
  19. data/examples/CQL/MultiInheritance.cql +2 -2
  20. data/examples/CQL/OilSupply.cql +14 -10
  21. data/examples/CQL/Orienteering.cql +22 -19
  22. data/examples/CQL/PersonPlaysGame.cql +3 -2
  23. data/examples/CQL/SchoolActivities.cql +4 -2
  24. data/examples/CQL/SimplestUnary.cql +1 -1
  25. data/examples/CQL/SubtypePI.cql +6 -7
  26. data/examples/CQL/Warehousing.cql +16 -19
  27. data/examples/CQL/unit.cql +584 -0
  28. data/examples/index.html +276 -0
  29. data/examples/intro.html +497 -0
  30. data/examples/local.css +20 -0
  31. data/index.html +96 -0
  32. data/lib/activefacts/api/concept.rb +48 -46
  33. data/lib/activefacts/api/constellation.rb +43 -23
  34. data/lib/activefacts/api/entity.rb +2 -2
  35. data/lib/activefacts/api/instance.rb +6 -2
  36. data/lib/activefacts/api/instance_index.rb +5 -0
  37. data/lib/activefacts/api/value.rb +8 -2
  38. data/lib/activefacts/api/vocabulary.rb +15 -10
  39. data/lib/activefacts/cql/CQLParser.treetop +109 -88
  40. data/lib/activefacts/cql/Concepts.treetop +32 -10
  41. data/lib/activefacts/cql/Context.treetop +34 -0
  42. data/lib/activefacts/cql/Expressions.treetop +9 -9
  43. data/lib/activefacts/cql/FactTypes.treetop +30 -31
  44. data/lib/activefacts/cql/Language/English.treetop +50 -0
  45. data/lib/activefacts/cql/LexicalRules.treetop +2 -1
  46. data/lib/activefacts/cql/Terms.treetop +117 -0
  47. data/lib/activefacts/cql/ValueTypes.treetop +152 -0
  48. data/lib/activefacts/cql/compiler.rb +1718 -0
  49. data/lib/activefacts/cql/parser.rb +124 -57
  50. data/lib/activefacts/generate/absorption.rb +1 -1
  51. data/lib/activefacts/generate/cql.rb +111 -100
  52. data/lib/activefacts/generate/cql/html.rb +5 -5
  53. data/lib/activefacts/generate/oo.rb +3 -3
  54. data/lib/activefacts/generate/ordered.rb +51 -19
  55. data/lib/activefacts/generate/ruby.rb +10 -8
  56. data/lib/activefacts/generate/sql/mysql.rb +14 -10
  57. data/lib/activefacts/generate/sql/server.rb +29 -24
  58. data/lib/activefacts/input/cql.rb +9 -1264
  59. data/lib/activefacts/input/orm.rb +213 -200
  60. data/lib/activefacts/persistence/columns.rb +11 -10
  61. data/lib/activefacts/persistence/index.rb +15 -18
  62. data/lib/activefacts/persistence/reference.rb +17 -17
  63. data/lib/activefacts/persistence/tables.rb +50 -51
  64. data/lib/activefacts/version.rb +1 -1
  65. data/lib/activefacts/vocabulary/extensions.rb +79 -8
  66. data/lib/activefacts/vocabulary/metamodel.rb +183 -114
  67. data/spec/absorption_ruby_spec.rb +99 -0
  68. data/spec/absorption_spec.rb +3 -4
  69. data/spec/api/constellation.rb +1 -1
  70. data/spec/api/entity_type.rb +3 -1
  71. data/spec/api/instance.rb +4 -2
  72. data/spec/api/roles.rb +8 -6
  73. data/spec/api_spec.rb +1 -2
  74. data/spec/cql/context_spec.rb +71 -0
  75. data/spec/cql/samples_spec.rb +154 -0
  76. data/spec/cql/unit_spec.rb +375 -0
  77. data/spec/cql_cql_spec.rb +31 -21
  78. data/spec/cql_mysql_spec.rb +70 -0
  79. data/spec/cql_parse_spec.rb +15 -9
  80. data/spec/cql_ruby_spec.rb +27 -13
  81. data/spec/cql_sql_spec.rb +42 -16
  82. data/spec/cql_symbol_tables_spec.rb +2 -3
  83. data/spec/cqldump_spec.rb +7 -7
  84. data/spec/helpers/file_matcher.rb +39 -0
  85. data/spec/norma_cql_spec.rb +20 -12
  86. data/spec/norma_ruby_spec.rb +6 -3
  87. data/spec/norma_sql_spec.rb +6 -3
  88. data/spec/norma_tables_spec.rb +6 -4
  89. data/spec/spec_helper.rb +27 -8
  90. data/status.html +69 -0
  91. data/why.html +60 -0
  92. metadata +34 -11
  93. data/lib/activefacts/cql/DataTypes.treetop +0 -81
  94. data/spec/cql_unit_spec.rb +0 -330
@@ -4,6 +4,7 @@
4
4
  #
5
5
  require 'activefacts/vocabulary'
6
6
  require 'activefacts/cql/parser'
7
+ require 'activefacts/cql/compiler'
7
8
 
8
9
  module ActiveFacts
9
10
  module Input #:nodoc:
@@ -11,1281 +12,25 @@ module ActiveFacts
11
12
  # Invoke as
12
13
  # afgen --<generator> <file>.cql
13
14
  class CQL
14
- private
15
- include ActiveFacts
16
- include ActiveFacts::Metamodel
17
-
18
- class SymbolTable; end #:nodoc:
19
-
20
- RingTypes = %w{acyclic intransitive symmetric asymmetric transitive antisymmetric irreflexive reflexive}
21
- RingPairs = {
22
- :intransitive => [:acyclic, :asymmetric, :symmetric],
23
- :irreflexive => [:symmetric]
24
- }
25
-
26
- # Open the specified file and read it:
15
+ # Read the specified file
27
16
  def self.readfile(filename)
28
17
  File.open(filename) {|file|
29
- self.read(file, filename)
18
+ read(file, filename)
30
19
  }
31
20
  rescue => e
32
21
  puts e.message+"\n\t"+e.backtrace*"\n\t" if debug :exception
33
22
  raise "In #{filename} #{e.message.strip}"
34
23
  end
35
24
 
36
- # Read the specified input stream:
25
+ # Read the specified input stream
37
26
  def self.read(file, filename = "stdin")
38
- CQL.new(file.read, filename).read
27
+ readstring(file.read, filename)
39
28
  end
40
29
 
41
- def initialize(file, filename = "stdin")
42
- @file = file
43
- @filename = filename
44
- end
45
-
46
- public
47
- # Read the input, returning a new Vocabulary:
48
- def read #:nodoc:
49
- @constellation = ActiveFacts::API::Constellation.new(ActiveFacts::Metamodel)
50
-
51
- @parser = ActiveFacts::CQLParser.new
52
-
53
- # The syntax tree created from each parsed CQL statement gets passed to the block.
54
- # parse_all returns an array of the block's non-nil return values.
55
- result = @parser.parse_all(@file, :definition) do |node|
56
- begin
57
- kind, *value = @parser.definition(node)
58
- #print "Parsed '#{node.text_value}'"
59
- #print " to "; p value
60
- raise "Definition of #{kind} must be in a vocabulary" if kind != :vocabulary and !@vocabulary
61
- case kind
62
- when :vocabulary
63
- @vocabulary = @constellation.Vocabulary(value[0])
64
- when :data_type
65
- value_type *value
66
- when :entity_type
67
- entity_type *value
68
- when :fact_type
69
- fact_type *value
70
- when :constraint
71
- constraint *value
72
- else
73
- print "="*20+" unhandled declaration type: "; p kind, value
74
- end
75
- rescue => e
76
- puts e.message+"\n\t"+e.backtrace*"\n\t" if debug :exception
77
- start_line = @file.line_of(node.interval.first)
78
- end_line = @file.line_of(node.interval.last-1)
79
- lines = start_line != end_line ? "s #{start_line}-#{end_line}" : " #{start_line.to_s}"
80
- raise "at line#{lines} #{e.message.strip}"
81
- end
82
-
83
- nil
84
- end
85
- raise @parser.failure_reason unless result
86
- @vocabulary
87
- end
88
-
89
- private
90
- def value_type(name, base_type_name, parameters, unit, ranges)
91
- length, scale = *parameters
92
-
93
- # Create the base type:
94
- base_type = nil
95
- if (base_type_name != name)
96
- unless base_type = @constellation.ValueType[[@vocabulary, @constellation.Name(base_type_name)]]
97
- #puts "REVISIT: Creating base ValueType #{base_type_name} in #{@vocabulary.inspect}"
98
- base_type = @constellation.ValueType(@vocabulary, base_type_name)
99
- return if base_type_name == name
100
- end
101
- end
102
-
103
- # Create and initialise the ValueType:
104
- vt = @constellation.ValueType(@vocabulary, name)
105
- vt.supertype = base_type if base_type
106
- vt.length = length if length
107
- vt.scale = scale if scale
108
-
109
- # REVISIT: Find and apply the units
110
-
111
- if ranges.size != 0
112
- vt.value_restriction = @constellation.ValueRestriction(:new)
113
- ranges.each do |range|
114
- min, max = Array === range ? range : [range, range]
115
- v_range = @constellation.ValueRange(
116
- min ? [min.to_s, true] : nil,
117
- max ? [max.to_s, true] : nil
118
- )
119
- ar = @constellation.AllowedRange(vt.value_restriction, v_range)
120
- end
121
- end
122
- end
123
-
124
- def entity_type(name, supertypes, identification, clauses)
125
- #puts "Entity Type #{name}, supertypes #{supertypes.inspect}, id #{identification.inspect}, clauses = #{clauses.inspect}"
126
- debug :entity, "Defining Entity Type #{name}" do
127
- # Assert the entity:
128
- # If this entity was forward referenced, this won't be a new object, and will subsume its roles
129
- entity_type = @constellation.EntityType(@vocabulary, name)
130
-
131
- # Set up its supertypes:
132
- supertypes.each do |supertype_name|
133
- add_supertype(entity_type, supertype_name, !identification && supertype_name == supertypes[0])
134
- end
135
-
136
- # Use a two-pass algorithm for entity fact types...
137
- # The first step is to find all role references and definitions in the clauses
138
- # After bind_roles, each item in the reading part of each clause is either:
139
- # * a string, which is a linking word, or
140
- # * the phrase hash augmented with a :binding=>Binding
141
- @symbols = SymbolTable.new(@constellation, @vocabulary)
142
- @symbols.bind_roles_in_clauses(clauses, identification ? identification[:roles] : nil)
143
-
144
- # Next arrange the readings according to what fact they belong to,
145
- # then process each fact type using normal fact type processing.
146
- # That way if we find a fact type here having none of the players being the
147
- # entity type, we know it's an objectified fact type. The CQL syntax might make
148
- # us come here with such a case when the fact type is a subtype of some entity type,
149
- # such as occurs in the Metamodel with TypeInheritance.
150
-
151
- # N.B. This doesn't allow forward identification by roles with adjectives (see the i[0]):
152
- @symbols.allowed_forward = (ir = identification && identification[:roles]) ? ir.inject({}){|h, i| h[i[0]] = true; h} : {}
153
-
154
- # If we're using a common identification mode, find or create the necessary ValueTypes first:
155
- vt_name = vt = nil
156
- if identification && identification[:mode]
157
- mode = identification[:mode] # An identification mode
158
-
159
- # Find or Create an appropriate ValueType called "#{name}#{mode}", of the supertype "#{mode}"
160
- vt_name = "#{name}#{mode}"
161
- unless vt = @constellation.ValueType[[@vocabulary, vt_name]]
162
- base_vt = @constellation.ValueType(@vocabulary, mode)
163
- vt = @constellation.ValueType(@vocabulary, vt_name, :supertype => base_vt)
164
- end
165
- end
166
-
167
- identifying_fact_types = {}
168
- clauses_by_fact_type(clauses).each do |clauses_for_fact_type|
169
- fact_type = nil
170
- @symbols.embedded_presence_constraints = [] # Clear embedded_presence_constraints for each fact type
171
- debug :entity, "New Fact Type for entity #{name}" do
172
- clauses_for_fact_type.each do |clause|
173
- type, qualifiers, reading = *clause
174
- debug :reading, "Clause: #{clause.inspect}" do
175
- f = bind_fact_reading(fact_type, qualifiers, reading)
176
- identifying_fact_types[f] = true
177
- fact_type ||= f
178
- end
179
- end
180
- end
181
-
182
- # Find the role that this entity type plays in the fact type, if any:
183
- debug :reading, "Roles are: #{fact_type.all_role.map{|role| (role.concept == entity_type ? "*" : "") + role.concept.name }*", "}"
184
- player_roles = fact_type.all_role.select{|role| role.concept == entity_type }
185
- raise "#{role.concept.name} may only play one role in each identifying fact type" if player_roles.size > 1
186
- if player_role = player_roles[0]
187
- non_player_roles = fact_type.all_role-[player_role]
188
-
189
- raise "#{name} cannot be identified by a role in a non-binary fact type" if non_player_roles.size > 1
190
- elsif identification
191
- # This situation occurs when an objectified fact type has an entity identifier
192
- #raise "Entity type #{name} cannot objectify fact type #{fact_type.describe}, it already objectifies #{entity_type.fact_type.describe}" if entity_type.fact_type
193
- raise "Entity type #{name} cannot objectify fact type #{identification.inspect}, it already objectifies #{entity_type.fact_type.describe}" if entity_type.fact_type
194
- debug :entity, "Entity type #{name} objectifies fact type #{fact_type.describe} with distinct identifier"
195
-
196
- entity_type.fact_type = fact_type
197
- fact_type_identification(fact_type, name, false)
198
- else
199
- debug :entity, "Entity type #{name} objectifies fact type #{fact_type.describe}"
200
- # it's an objectified fact type, such as a subtype
201
- entity_type.fact_type = fact_type
202
- end
203
- end
204
-
205
- # Finally, create the identifying uniqueness constraint, or mark it as preferred
206
- # if it's already been created. The identifying roles have been defined already.
207
-
208
- if identification
209
- debug :identification, "Handling identification" do
210
- if id_role_names = identification[:roles] # A list of identifying roles
211
- debug "Identifying roles: #{id_role_names.inspect}"
212
-
213
- # Pick out the identifying_roles in the order they were declared,
214
- # not the order the fact types were defined:
215
- identifying_roles = id_role_names.map do |names|
216
- unless (role = bind_unary_fact_type(entity_type, names))
217
- player, binding = @symbols.bind(names)
218
- role = @symbols.roles_by_binding[binding]
219
- raise "identifying role #{names*"-"} not found in fact types for #{name}" unless role
220
- end
221
- role
222
- end
223
-
224
- # Find a uniqueness constraint as PI, or make one
225
- pc = find_pc_over_roles(identifying_roles)
226
- if (pc)
227
- debug "Existing PC #{pc.verbalise} is now PK for #{name} #{pc.class.roles.keys.map{|k|"#{k} => "+pc.send(k).verbalise}*", "}"
228
- pc.is_preferred_identifier = true
229
- pc.name = "#{name}PK" unless pc.name
230
- else
231
- debug "Adding PK for #{name} using #{identifying_roles.map{|r| r.concept.name}.inspect}"
232
-
233
- role_sequence = @constellation.RoleSequence(:new)
234
- # REVISIT: Need to sort the identifying_roles to match the identification parameter array
235
- identifying_roles.each_with_index do |identifying_role, index|
236
- @constellation.RoleRef(role_sequence, index, :role => identifying_role)
237
- end
238
-
239
- # Add a unique constraint over all identifying roles
240
- pc = @constellation.PresenceConstraint(
241
- :new,
242
- :vocabulary => @vocabulary,
243
- :name => "#{name}PK", # Is this a useful name?
244
- :role_sequence => role_sequence,
245
- :is_preferred_identifier => true,
246
- :max_frequency => 1 # Unique
247
- #:is_mandatory => true,
248
- #:min_frequency => 1,
249
- )
250
- end
251
-
252
- elsif identification[:mode]
253
- mode = identification[:mode] # An identification mode
254
-
255
- raise "Entity definition using reference mode may only have one identifying fact type" if identifying_fact_types.size > 1
256
- mode_fact_type = identifying_fact_types.keys[0]
257
-
258
- # If the entity type is an objectified fact type, don't use the objectified fact type!
259
- mode_fact_type = nil if mode_fact_type && mode_fact_type.entity_type == entity_type
260
-
261
- debug :mode, "Processing Reference Mode for #{name}#{mode_fact_type ? " with existing '#{mode_fact_type.default_reading}'" : ""}"
262
-
263
- # Fact Type:
264
- if (ft = mode_fact_type)
265
- entity_role, value_role = ft.all_role.partition{|role| role.concept == entity_type}.flatten
266
- else
267
- ft = @constellation.FactType(:new)
268
- entity_role = @constellation.Role(ft, 0, entity_type)
269
- value_role = @constellation.Role(ft, 1, vt)
270
- debug :mode, "Creating new fact type to identify #{name}"
271
- end
272
-
273
- # Forward reading, if it doesn't already exist:
274
- rss = entity_role.all_role_ref.map{|rr| rr.role_sequence.all_role_ref.size == 2 ? rr.role_sequence : nil }.compact
275
- # Find or create RoleSequences for the forward and reverse readings:
276
- rs01 = rss.select{|rs| rs.all_role_ref.sort_by{|rr| rr.ordinal}.map(&:role) == [entity_role, value_role] }[0]
277
- if !rs01
278
- rs01 = @constellation.RoleSequence(:new)
279
- @constellation.RoleRef(rs01, 0, :role => entity_role)
280
- @constellation.RoleRef(rs01, 1, :role => value_role)
281
- end
282
- if rs01.all_reading.empty?
283
- @constellation.Reading(ft, ft.all_reading.size, :role_sequence => rs01, :reading_text => "{0} has {1}")
284
- debug :mode, "Creating new forward reading '#{name} has #{vt.name}'"
285
- else
286
- debug :mode, "Using existing forward reading"
287
- end
288
-
289
- # Reverse reading:
290
- rs10 = rss.select{|rs| rs.all_role_ref.sort_by{|rr| rr.ordinal}.map(&:role) == [value_role, entity_role] }[0]
291
- if !rs10
292
- rs10 = @constellation.RoleSequence(:new)
293
- @constellation.RoleRef(rs10, 0, :role => value_role)
294
- @constellation.RoleRef(rs10, 1, :role => entity_role)
295
- end
296
- if rs10.all_reading.empty?
297
- @constellation.Reading(ft, ft.all_reading.size, :role_sequence => rs10, :reading_text => "{0} is of {1}")
298
- debug :mode, "Creating new reverse reading '#{vt.name} is of #{name}'"
299
- else
300
- debug :mode, "Using existing reverse reading"
301
- end
302
-
303
- # Entity Type must have a value type. Find or create the role sequence, then create a PC if necessary
304
- debug :mode, "entity_role has #{entity_role.all_role_ref.size} attached sequences"
305
- debug :mode, "entity_role has #{entity_role.all_role_ref.select{|rr| rr.role_sequence.all_role_ref.size == 1}.size} unary sequences"
306
- rs0 = entity_role.all_role_ref.select{|rr| rr.role_sequence.all_role_ref.size == 1 ? rr.role_sequence : nil }.compact[0]
307
- if !rs0
308
- rs0 = @constellation.RoleSequence(:new)
309
- @constellation.RoleRef(rs0, 0, :role => entity_role)
310
- debug :mode, "Creating new EntityType role sequence"
311
- else
312
- rs0 = rs0.role_sequence
313
- debug :mode, "Using existing EntityType role sequence"
314
- end
315
- if (rs0.all_presence_constraint.size == 0)
316
- @constellation.PresenceConstraint(
317
- :new,
318
- :name => '',
319
- :enforcement => '',
320
- :vocabulary => @vocabulary,
321
- :role_sequence => rs0,
322
- :min_frequency => 1,
323
- :max_frequency => 1,
324
- :is_preferred_identifier => false,
325
- :is_mandatory => true
326
- )
327
- debug :mode, "Creating new EntityType PresenceConstraint"
328
- else
329
- debug :mode, "Using existing EntityType PresenceConstraint"
330
- end
331
-
332
- # Value Type must have a value type. Find or create the role sequence, then create a PC if necessary
333
- debug :mode, "value_role has #{value_role.all_role_ref.size} attached sequences"
334
- debug :mode, "value_role has #{value_role.all_role_ref.select{|rr| rr.role_sequence.all_role_ref.size == 1}.size} unary sequences"
335
- rs1 = value_role.all_role_ref.select{|rr| rr.role_sequence.all_role_ref.size == 1 ? rr.role_sequence : nil }.compact[0]
336
- if (!rs1)
337
- rs1 = @constellation.RoleSequence(:new)
338
- @constellation.RoleRef(rs1, 0, :role => value_role)
339
- debug :mode, "Creating new ValueType role sequence"
340
- else
341
- rs1 = rs1.role_sequence
342
- debug :mode, "Using existing ValueType role sequence"
343
- end
344
- if (rs1.all_presence_constraint.size == 0)
345
- @constellation.PresenceConstraint(
346
- :new,
347
- :name => '',
348
- :enforcement => '',
349
- :vocabulary => @vocabulary,
350
- :role_sequence => rs1,
351
- :min_frequency => 0,
352
- :max_frequency => 1,
353
- :is_preferred_identifier => true,
354
- :is_mandatory => false
355
- )
356
- debug :mode, "Creating new ValueType PresenceConstraint"
357
- else
358
- debug :mode, "Marking existing ValueType PresenceConstraint as preferred"
359
- rs1.all_presence_constraint[0].is_preferred_identifier = true
360
- end
361
- end
362
- end
363
- else
364
- # identification must be inherited.
365
- debug "Identification is inherited"
366
- end
367
- end
368
- end
369
-
370
- def add_supertype(entity_type, supertype_name, identifying_supertype)
371
- debug :supertype, "Supertype #{supertype_name}"
372
- supertype = @constellation.EntityType(@vocabulary, supertype_name)
373
- inheritance_fact = @constellation.TypeInheritance(entity_type, supertype, :fact_type_id => :new)
374
-
375
- # Create a reading:
376
- sub_role = @constellation.Role(inheritance_fact, 0, entity_type)
377
- super_role = @constellation.Role(inheritance_fact, 1, supertype)
378
- rs = @constellation.RoleSequence(:new)
379
- @constellation.RoleRef(rs, 0, :role => sub_role)
380
- @constellation.RoleRef(rs, 1, :role => super_role)
381
- @constellation.Reading(inheritance_fact, 0, :role_sequence => rs, :reading_text => "{0} is a subtype of {1}")
382
-
383
- if identifying_supertype
384
- inheritance_fact.provides_identification = true
385
- end
386
-
387
- # Create uniqueness constraints over the subtyping fact type
388
- p1rs = @constellation.RoleSequence(:new)
389
- @constellation.RoleRef(p1rs, 0).role = sub_role
390
- pc1 = @constellation.PresenceConstraint(:new)
391
- pc1.name = "#{entity_type.name}MustHaveSupertype#{supertype.name}"
392
- pc1.vocabulary = @vocabulary
393
- pc1.role_sequence = p1rs
394
- pc1.is_mandatory = true # A subtype instance must have a supertype instance
395
- pc1.min_frequency = 1
396
- pc1.max_frequency = 1
397
- pc1.is_preferred_identifier = false
398
-
399
- # The supertype role often identifies the subtype:
400
- p2rs = @constellation.RoleSequence(:new)
401
- @constellation.RoleRef(p2rs, 0).role = super_role
402
- pc2 = @constellation.PresenceConstraint(:new)
403
- pc2.name = "#{supertype.name}MayBeA#{entity_type.name}"
404
- pc2.vocabulary = @vocabulary
405
- pc2.role_sequence = p2rs
406
- pc2.is_mandatory = false
407
- pc2.min_frequency = 0
408
- pc2.max_frequency = 1
409
- pc2.is_preferred_identifier = inheritance_fact.provides_identification
410
- end
411
-
412
- # If one of the words is the name of the entity type, and the other
413
- # words consist of a unary fact type reading, return the role it plays.
414
- def bind_unary_fact_type(entity_type, words)
415
- return nil unless i = words.index(entity_type.name)
416
-
417
- to_match = words.clone
418
- to_match[i] = '{0}'
419
- to_match = to_match*' '
420
-
421
- # See if any unary fact type of this or any supertype matches these words:
422
- entity_type.supertypes_transitive.each do |supertype|
423
- supertype.all_role.each do |role|
424
- role.fact_type.all_role.size == 1 &&
425
- role.fact_type.all_reading.each do |reading|
426
- if reading.reading_text == to_match
427
- debug :identification, "Bound identification to unary role '#{to_match.sub(/\{0\}/, entity_type.name)}'"
428
- return role
429
- end
430
- end
431
- end
432
- end
433
- nil
434
- end
435
-
436
- def fact_type(name, clauses, conditions)
437
- debug "Processing clauses for fact type" do
438
- fact_type = nil
439
-
440
- #
441
- # The first step is to find all role references and definitions in the reading clauses.
442
- # This also:
443
- # * deletes any adjectives that were used but not hyphenated
444
- # * changes each linking word phrase into a simple String
445
- # * adds a :binding key to each bound role
446
- #
447
- @symbols = SymbolTable.new(@constellation, @vocabulary)
448
- @symbols.bind_roles_in_clauses(clauses)
449
-
450
- clauses.each do |clause|
451
- kind, qualifiers, reading = *clause
452
-
453
- fact_type = bind_fact_reading(fact_type, qualifiers, reading)
454
- end
455
-
456
- # The fact type has a name iff it's objectified as an entity type
457
- #puts "============= Creating entity #{name} to nominalize fact type #{fact_type.default_reading} ======================" if name
458
- fact_type.entity_type = @constellation.EntityType(@vocabulary, name) if name
459
-
460
- # Add the identifying PresenceConstraint for this fact type:
461
- if fact_type.all_role.size == 1 && !fact_type.entity_type
462
- # All is well, unaries don't need an identifying PC unless objectified
463
- else
464
- fact_type_identification(fact_type, name, true)
465
- end
466
-
467
- # REVISIT: Process the fact derivation conditions, if any
468
- end
469
- end
470
-
471
- def constraint *value
472
- case type = value.shift
473
- when :presence
474
- presence_constraint *value
475
- when :subset
476
- subset_constraint *value
477
- when :set
478
- set_constraint *value
479
- when :equality
480
- equality_constraint *value
481
- else
482
- $stderr.puts "REVISIT: external #{type} constraints aren't yet handled:\n\t"+value.map{|a| a.inspect }*"\n\t"
483
- end
484
- end
485
-
486
- # The readings list is an array of an array of fact types.
487
- # The fact types contain roles played by concepts, where each
488
- # concept plays more than one role. In fact, a concept may
489
- # occur in more than one binding, and each binding plays more
490
- # than one role. The bindings that are common to all fact types
491
- # in each array in the readings list form the constrained role
492
- # sequences. Each binding that isn't common at this top level
493
- # must occur more than once in each group of fact types where
494
- # it appears, and it forms a join between those fact types.
495
- def bind_joins_as_role_sequences(readings_list)
496
- @symbols = SymbolTable.new(@constellation, @vocabulary)
497
- fact_roles_list = []
498
- bindings_list = []
499
- readings_list.each_with_index do |readings, index|
500
- # readings is an array of readings
501
- @symbols.bind_roles_in_readings(readings)
502
-
503
- # Was:
504
- # fact_roles_list[index] = invoked_fact_roles(readings[0])
505
- # raise "Fact type reading not found for #{readings[0].inspect}" unless fact_roles_list[index]
506
- # bindings_list[index] = readings[0].map{|phrase| Hash === phrase ? phrase[:binding] : nil}.compact
507
-
508
- fact_roles_list << readings.map do |reading|
509
- ifr = invoked_fact_roles(reading)
510
- raise "Fact type reading not found for #{reading.inspect}" unless ifr
511
- ifr
512
- end
513
- bindings_list << readings.map do |reading|
514
- reading.map{ |phrase| Hash === phrase ? phrase[:binding] : nil}.compact
515
- end
516
- end
517
-
518
- # Each set of binding arrays in the list must share at least one common binding
519
- bindings_by_join = bindings_list.map{|join| join.flatten}
520
- common_bindings = bindings_by_join[1..-1].inject(bindings_by_join[0]) { |c, b| c & b }
521
- # Was:
522
- # common_bindings = bindings_list.inject(bindings_list[0]) { |common, bindings| common & bindings }
523
- raise "Set constraints must have at least one common role between the sets" unless common_bindings.size > 0
524
-
525
- # REVISIT: Do we need to constrain things such that each join path only includes *one* instance of each common binding?
526
-
527
- # For each set of binding arrays, if there's more than one binding array in the set,
528
- # it represents a join path. Here we check that each join path is complete, i.e. linked up.
529
- # Each element of a join path is the array of bindings for a fact type invocation.
530
- # Each invocation must share a binding (not one of the globally common ones) with
531
- # another invocation in that join path.
532
- bindings_list.each_with_index do |join, jpnum|
533
- # Check that this bindings array creates a complete join path:
534
- join.each_with_index do |bindings, i|
535
- fact_type_roles = fact_roles_list[jpnum][i]
536
- fact_type = fact_type_roles[0].fact_type
537
-
538
- # The bindings are for one fact type invocation.
539
- # These bindings must be joined to some later fact type by a common binding that isn't a globally-common one:
540
- local_bindings = bindings-common_bindings
541
- next if local_bindings.size == 0 # No join path is required, as only one fact type is invoked.
542
- next if i == join.size-1 # We already checked that the last fact type invocation is joined
543
- ok = local_bindings.detect do |local_binding|
544
- j = i+1
545
- join[j..-1].detect do |other_bindings|
546
- other_fact_type_roles = fact_roles_list[jpnum][j]
547
- other_fact_type = other_fact_type_roles[0].fact_type
548
- j += 1
549
- # These next two lines allow joining from/to an objectified fact type:
550
- fact_type_roles.detect{|r| r.concept == other_fact_type.entity_type } ||
551
- other_fact_type_roles.detect{|r| r.concept == fact_type.entity_type } ||
552
- other_bindings.include?(local_binding)
553
- end
554
- end
555
- raise "Incomplete join path; one of the bindings #{local_bindings.inspect} must re-occur to establish a join" unless ok
556
- end
557
- end
558
-
559
- # Create the role sequences and their role references.
560
- # Each role sequence contain one RoleRef for each common binding
561
- # REVISIT: This results in ordering all RoleRefs according to the order of the common_bindings.
562
- # This for example means that a set constraint having joins might have the join order changed so they all match.
563
- # When you create e.g. a subset constraint in NORMA, make sure that the subset roles are created in the order of the preferred readings.
564
- role_sequences = readings_list.map{|r| @constellation.RoleSequence(:new) }
565
- common_bindings.each_with_index do |binding, index|
566
- role_sequences.each_with_index do |rs, rsi|
567
- join = bindings_list[rsi]
568
- fact_pos = nil
569
- join_pos = (0...join.size).detect do |i|
570
- fact_pos = join[i].index(binding)
571
- end
572
- @constellation.RoleRef(rs, index).role = fact_roles_list[rsi][join_pos][fact_pos]
573
- end
574
- end
575
-
576
- role_sequences
577
- end
578
-
579
- def subset_constraint(subset_readings, superset_readings)
580
- role_sequences = bind_joins_as_role_sequences([subset_readings, superset_readings])
581
-
582
- #puts "subset_constraint:\n\t#{subset_readings.inspect}\n\t#{superset_readings.inspect}"
583
- #puts "\t#{role_sequences.map{|rs| rs.describe}.inspect}"
584
- #puts "subset_role_sequence = #{role_sequences[0].describe}"
585
- #puts "superset_role_sequence = #{role_sequences[1].describe}"
586
-
587
- # create the constraint:
588
- constraint = @constellation.SubsetConstraint(:new)
589
- constraint.vocabulary = @vocabulary
590
- #constraint.name = nil
591
- #constraint.enforcement =
592
- constraint.subset_role_sequence = role_sequences[0]
593
- constraint.superset_role_sequence = role_sequences[1]
594
- end
595
-
596
- def set_constraint(constrained_roles, quantifier, *readings_list)
597
- # Exactly one or at most one, nothing else will do
598
- raise "Set comparison constraint must use 'at most' or 'exactly' one" if quantifier[1] != 1
599
-
600
- role_sequences = bind_joins_as_role_sequences(readings_list)
601
-
602
- # Create the constraint:
603
- constraint = @constellation.SetExclusionConstraint(:new)
604
- constraint.vocabulary = @vocabulary
605
- role_sequences.each_with_index do |rs, i|
606
- @constellation.SetComparisonRoles(constraint, i, :role_sequence => rs)
607
- end
608
- constraint.is_mandatory = quantifier[0] == 1
609
- end
610
-
611
- def equality_constraint(*readings_list)
612
- #puts "REVISIT: equality\n\t#{readings_list.map{|rl| rl.inspect}*"\n\tif and only if\n\t"}"
613
-
614
- role_sequences = bind_joins_as_role_sequences(readings_list)
615
-
616
- # Create the constraint:
617
- constraint = @constellation.SetEqualityConstraint(:new)
618
- constraint.vocabulary = @vocabulary
619
- role_sequences.each_with_index do |rs, i|
620
- @constellation.SetComparisonRoles(constraint, i, :role_sequence => rs)
621
- end
622
- end
623
-
624
- def presence_constraint(constrained_role_names, quantifier, readings)
625
- raise "REVISIT: Join presence constraints not supported yet" if readings[0].size > 1
626
- readings = readings.map{|r| r[0] }
627
- #p readings
628
-
629
- @symbols = SymbolTable.new(@constellation, @vocabulary)
630
-
631
- # Find players for all constrained_role_names. These may use leading or trailing adjective forms...
632
- constrained_players = []
633
- constrained_bindings = []
634
- constrained_role_names.each do |role_name|
635
- player, binding = @symbols.bind(role_name)
636
- constrained_players << player
637
- constrained_bindings << binding
638
- end
639
- #puts "Constrained bindings are #{constrained_bindings.inspect}"
640
- #puts "Constrained bindings object_id's are #{constrained_bindings.map{|b|b.object_id.to_s}*","}"
641
-
642
- # Find players for all the concepts in all readings:
643
- @symbols.bind_roles_in_readings(readings)
644
-
645
- constrained_roles = []
646
- unmatched_roles = constrained_role_names.clone
647
- readings.each do |reading|
648
- # puts reading.inspect
649
-
650
- # If this succeeds, the reading found matches the roles in our phrases
651
- fact_roles = invoked_fact_roles(reading)
652
- raise "Fact type reading not found for #{reading.inspect}" unless fact_roles
653
-
654
- # Look for the constrained role(s); the bindings will be the same
655
- matched_bindings = reading.select{|p| Hash === p}.map{|p| p[:binding]}
656
- #puts "matched_bindings = #{matched_bindings.inspect}"
657
- #puts "matched_bindings object_id's are #{matched_bindings.map{|b|b.object_id.to_s}*","}}"
658
- matched_bindings.each_with_index{|b, pos|
659
- i = constrained_bindings.index(b)
660
- next unless i
661
- unmatched_roles[i] = nil
662
- #puts "found #{constrained_bindings[i].inspect} found as #{b.inspect} in position #{i.inspect}"
663
- role = fact_roles[pos]
664
- constrained_roles << role unless constrained_roles.include?(role)
665
- }
666
- end
667
-
668
- # Check that all constrained roles were matched at least once:
669
- unmatched_roles.compact!
670
- raise "Constrained roles #{unmatched_roles.map{|ur| ur*"-"}*", "} not found in fact types" if unmatched_roles.size != 0
671
-
672
- rs = @constellation.RoleSequence(:new)
673
- #puts "constrained_roles: #{constrained_roles.map{|r| r.concept.name}.inspect}"
674
- constrained_roles.each_with_index do |role, index|
675
- raise "Constrained role #{constrained_role_names[index]} not found" unless role
676
- rr = @constellation.RoleRef(rs, index)
677
- rr.role = role
678
- end
679
- #puts "New external PresenceConstraint with quantifier = #{quantifier.inspect} over #{rs.describe}"
680
-
681
- # REVISIT: Check that no existing PC spans the same roles (nor a superset nor subset?)
682
-
683
- @constellation.PresenceConstraint(
684
- :new,
685
- :name => '',
686
- :enforcement => '',
687
- :vocabulary => @vocabulary,
688
- :role_sequence => rs,
689
- :min_frequency => quantifier[0],
690
- :max_frequency => quantifier[1],
691
- :is_preferred_identifier => false,
692
- :is_mandatory => quantifier[0] && quantifier[0] > 0
693
- )
694
- end
695
-
696
- # Search the supertypes of 'subtype' looking for an inheritance path to 'supertype',
697
- # and returning the array of TypeInheritance fact types from supertype to subtype.
698
- def inheritance_path(subtype, supertype)
699
- direct_inheritance = subtype.all_supertype_inheritance.select{|ti| ti.supertype == supertype}
700
- return direct_inheritance if (direct_inheritance[0])
701
- subtype.all_supertype_inheritance.each{|ti|
702
- ip = inheritance_path(ti.supertype, supertype)
703
- return ip+[ti] if (ip)
704
- }
705
- return nil
706
- end
707
-
708
- # For a given reading from the parser, find the matching declared reading, and return
709
- # the array of Role object in the same order as they occur in the reading.
710
- def invoked_fact_roles(reading)
711
- # REVISIT: Possibly this special reading from the parser can be removed now?
712
- if (reading[0] == "!SUBTYPE!")
713
- subtype = reading[1][:binding].concept
714
- supertype = reading[2][:binding].concept
715
- raise "#{subtype.name} is not a subtype of #{supertype.name}" unless subtype.supertypes_transitive.include?(supertype)
716
- ip = inheritance_path(subtype, supertype)
717
- return [
718
- ip[-1].all_role.detect{|r| r.concept == subtype},
719
- ip[0].all_role.detect{|r| r.concept == supertype}
720
- ]
721
- end
722
-
723
- bindings = reading.select{|p| Hash === p}
724
- players = bindings.map{|p| p[:binding].concept }
725
- invoked_fact_roles_by_players(reading, players)
726
- end
727
-
728
- def invoked_fact_roles_by_players(reading, players)
729
- players[0].all_role.each do |role|
730
- # Does this fact type have the right number of roles?
731
- next if role.fact_type.all_role.size != players.size
732
-
733
- # Does this fact type include the correct other players?
734
- # REVISIT: Might need subtype/supertype matching here, with an implied subtyping join invocation
735
- next if role.fact_type.all_role.detect{|r| !players.include?(r.concept)}
736
-
737
- # Oooh, a real candidate. Check the reading words.
738
- debug "Considering "+role.fact_type.describe do
739
- next unless role.fact_type.all_reading.detect do |candidate_reading|
740
- debug "Considering reading"+candidate_reading.reading_text do
741
- to_match = reading.clone
742
- players_to_match = players.clone
743
- candidate_reading.words_and_role_refs.each do |wrr|
744
- if (wrr.is_a?(RoleRef))
745
- break unless Hash === to_match.first
746
- break unless binding = to_match[0][:binding]
747
- # REVISIT: May need to match super- or sub-types here too!
748
- break unless players_to_match[0] == wrr.role.concept
749
- break if wrr.leading_adjective && binding.leading_adjective != wrr.leading_adjective
750
- break if wrr.trailing_adjective && binding.trailing_adjective != wrr.trailing_adjective
751
-
752
- # All matched.
753
- to_match.shift
754
- players_to_match.shift
755
- # elsif # REVISIT: Match "not" and "none" here as negating the fact type invocation
756
- else
757
- break unless String === to_match[0]
758
- break unless to_match[0] == wrr
759
- to_match.shift
760
- end
761
- end
762
-
763
- # This is the first matching candidate.
764
- # REVISIT: Since we do sub/supertype matching (and will do more!),
765
- # we need to accumulate all possible matches to be sure
766
- # there's only one, or the match is exact, or risk ambiguity.
767
- debug "Reading match was #{to_match.size == 0 ? "ok" : "bad"}"
768
- return candidate_reading.role_sequence.all_role_ref.sort_by{|rr| rr.ordinal}.map{|rr| rr.role} if to_match.size == 0
769
- end
770
- end
771
- end
772
- end
773
-
774
- # Hmm, that didn't work, try the subtypes of the first player.
775
- # When a fact type matches like this, there is an implied join to the subtype.
776
- players[0].subtypes.each do |subtype|
777
- players[0] = subtype
778
- fr = invoked_fact_roles_by_players(reading, players)
779
- return fr if fr
780
- end
781
-
782
- # REVISIT: Do we need to do this again for the supertypes of the first player?
783
-
784
- nil
785
- end
786
-
787
- def bind_fact_reading(fact_type, qualifiers, reading)
788
- debug :reading, "Processing reading #{reading.inspect}" do
789
- role_phrases = reading.select do |phrase|
790
- Hash === phrase && phrase[:binding]
791
- end
792
-
793
- # All readings for a fact type must have the same number of roles.
794
- # This might be relaxed later for fact clauses, where readings might
795
- # be concatenated if the adjacent items are the same concept.
796
- if (fact_type && fact_type.all_reading.size > 0 && role_phrases.size != fact_type.all_role.size)
797
- raise "#{
798
- role_phrases.size > fact_type.all_role.size ? "Too many" : "Not all"
799
- } roles found for non-initial reading of #{fact_type.describe}"
800
- end
801
-
802
- # REVISIT: If the first reading is a re-iteration of an existing fact type, find and use the existing fact type
803
- # This will require loading the @symbols.roles_by_binding using a SymbolTable
804
-
805
- fact_type ||= @constellation.FactType(:new)
806
-
807
- # Create the roles on the first reading, or look them up on subsequent readings.
808
- # If the player occurs twice, we must find one with matching adjectives.
809
-
810
- role_sequence = @constellation.RoleSequence(:new) # RoleSequence for RoleRefs of this reading
811
- roles = []
812
- role_phrases.each_with_index do |role_phrase, index|
813
- binding = role_phrase[:binding]
814
- role_name = role_phrase[:role_name]
815
- player = binding.concept
816
- if (fact_type.all_reading.size == 0) # First reading
817
- # Assert this role of the fact type:
818
- role = @constellation.Role(fact_type, fact_type.all_role.size, player)
819
- role.role_name = role_name if role_name
820
- debug "Concept #{player.name} found, created role #{role.describe} by binding #{binding.inspect}"
821
- @symbols.roles_by_binding[binding] = role
822
- else # Subsequent readings
823
- #debug "Looking for role #{binding.inspect} in bindings #{@symbols.roles_by_binding.inspect}"
824
- role = @symbols.roles_by_binding[binding]
825
- raise "Role #{binding.inspect} not found in prior readings" if !role
826
- player = role.concept
827
- end
828
- roles << role
829
-
830
- # Create the RoleRefs for the RoleSequence
831
-
832
- role_ref = @constellation.RoleRef(role_sequence, index, :role => roles[index])
833
- leading_adjective = role_phrase[:leading_adjective]
834
- role_ref.leading_adjective = leading_adjective if leading_adjective
835
- trailing_adjective = role_phrase[:trailing_adjective]
836
- role_ref.trailing_adjective = trailing_adjective if trailing_adjective
837
- end
838
-
839
- # Create any embedded constraints:
840
- debug "Creating embedded presence constraints for #{fact_type.describe}" do
841
- create_embedded_presence_constraints(fact_type, role_phrases, roles)
842
- end
843
-
844
- process_qualifiers(role_sequence, qualifiers)
845
-
846
- # Save the first role sequence to be used for a default PresenceConstraint
847
- add_reading(fact_type, role_sequence, reading)
848
- end
849
- fact_type
850
- end
851
-
852
- def fact_type_identification(fact_type, name, prefer)
853
- if !@symbols.embedded_presence_constraints.detect{|pc| pc.max_frequency == 1}
854
- # Provide a default identifier for a fact type that's lacking one (over all roles):
855
- first_role_sequence = fact_type.preferred_reading.role_sequence
856
- #puts "Creating PC for #{name}: #{fact_type.describe}"
857
- identifier = @constellation.PresenceConstraint(
858
- :new,
859
- :vocabulary => @vocabulary,
860
- :name => "#{name}PK", # Is this a useful name?
861
- :role_sequence => first_role_sequence,
862
- :is_preferred_identifier => prefer,
863
- :max_frequency => 1 # Unique
864
- )
865
- # REVISIT: The UC might be provided later as an external constraint, relax this rule:
866
- #raise "'#{fact_type.default_reading}': non-unary fact types having no uniqueness constraints must be objectified (named)" unless fact_type.entity_type
867
- debug "Made default fact type identifier #{identifier.object_id} over #{first_role_sequence.describe} in #{fact_type.describe}"
868
- elsif prefer
869
- #debug "Made fact type identifier #{identifier.object_id} preferred over #{@symbols.embedded_presence_constraints[0].role_sequence.describe} in #{fact_type.describe}"
870
- @symbols.embedded_presence_constraints[0].is_preferred_identifier = true
871
- end
872
- end
873
-
874
- # Categorise the fact type clauses according to the set of role player names
875
- # Return an array where each element is an array of clauses, the clauses having
876
- # matching players, and otherwise preserving the order of definition.
877
- def clauses_by_fact_type(clauses)
878
- clause_group_by_role_players = {}
879
- clauses.inject([]) do |clause_groups, clause|
880
- type, qualifiers, reading = *clause
881
-
882
- debug "Clause: #{clause.inspect}"
883
- roles = reading.map do |phrase|
884
- Hash === phrase ? phrase[:binding] : nil
885
- end.compact
886
-
887
- # Look for an existing clause group involving these players, or make one:
888
- clause_group = clause_group_by_role_players[key = roles.sort]
889
- if clause_group # Another clause for an existing clause group
890
- clause_group << clause
891
- else # A new clause group
892
- clause_groups << (clause_group_by_role_players[key] = [clause])
893
- end
894
- clause_groups
895
- end
896
- end
897
-
898
- # For each fact reading there may be embedded mandatory, uniqueness or frequency constraints:
899
- def create_embedded_presence_constraints(fact_type, role_phrases, roles)
900
- embedded_presence_constraints = []
901
- role_phrases.zip(roles).each_with_index do |role_pair, index|
902
- role_phrase, role = *role_pair
903
-
904
- next unless quantifier = role_phrase[:quantifier]
905
-
906
- debug "Processing embedded constraint #{quantifier.inspect} on #{role.concept.name} in #{fact_type.describe}" do
907
- constrained_roles = roles.clone
908
- constrained_roles.delete_at(index)
909
- constraint = find_pc_over_roles(constrained_roles)
910
- if constraint
911
- debug "Setting max frequency to #{quantifier[1]} for existing constraint #{constraint.object_id} over #{constraint.role_sequence.describe} in #{fact_type.describe}"
912
- raise "Conflicting maximum frequency for constraint" if constraint.max_frequency && constraint.max_frequency != quantifier[1]
913
- constraint.max_frequency = quantifier[1]
914
- else
915
- role_sequence = @constellation.RoleSequence(:new)
916
- constrained_roles.each_with_index do |constrained_role, i|
917
- role_ref = @constellation.RoleRef(role_sequence, i, :role => constrained_role)
918
- end
919
- constraint = @constellation.PresenceConstraint(
920
- :new,
921
- :vocabulary => @vocabulary,
922
- :role_sequence => role_sequence,
923
- :is_mandatory => quantifier[0] && quantifier[0] > 0, # REVISIT: Check "maybe" qualifier?
924
- :max_frequency => quantifier[1],
925
- :min_frequency => quantifier[0]
926
- )
927
- embedded_presence_constraints << constraint
928
- debug "Made new PC min=#{quantifier[0].inspect} max=#{quantifier[1].inspect} constraint #{constraint.object_id} over #{(e = fact_type.entity_type) ? e.name : role_sequence.describe} in #{fact_type.describe}"
929
- end
930
- end
931
- end
932
- @symbols.embedded_presence_constraints += embedded_presence_constraints
933
- end
934
-
935
- def process_qualifiers(role_sequence, qualifiers)
936
- return unless qualifiers.size > 0
937
- qualifiers.sort!
938
-
939
- # Process the ring constraints:
940
- ring_constraints, qualifiers = qualifiers.partition{|q| RingTypes.include?(q) }
941
- unless ring_constraints.empty?
942
- # A Ring may be over a supertype/subtype pair, and this won't find that.
943
- role_refs = Array(role_sequence.all_role_ref)
944
- role_pairs = []
945
- player_supertypes_by_role = role_refs.map{|rr|
946
- concept = rr.role.concept
947
- concept.is_a?(EntityType) ? supertypes(concept) : [concept]
948
- }
949
- role_refs.each_with_index{|rr1, i|
950
- player1 = rr1.role.concept
951
- (i+1...role_refs.size).each{|j|
952
- rr2 = role_refs[j]
953
- player2 = rr2.role.concept
954
- if player_supertypes_by_role[i] - player_supertypes_by_role[j] != player_supertypes_by_role[i]
955
- role_pairs << [rr1.role, rr2.role]
956
- end
957
- }
958
- }
959
- raise "ring constraint (#{ring_constraints*" "}) role pair not found" if role_pairs.size == 0
960
- raise "ring constraint (#{ring_constraints*" "}) is ambiguous over roles of #{role_pairs.map{|rp| rp.map{|r| r.concept.name}}.inspect}" if role_pairs.size > 1
961
- roles = role_pairs[0]
962
-
963
- # Ensure that the keys in RingPairs follow others:
964
- ring_constraints = ring_constraints.partition{|rc| !RingPairs.keys.include?(rc.downcase.to_sym) }.flatten
965
-
966
- if ring_constraints.size > 1 and !RingPairs[ring_constraints[-1].to_sym].include?(ring_constraints[0].to_sym)
967
- raise "incompatible ring constraint types (#{ring_constraints*", "})"
968
- end
969
- ring_type = ring_constraints.map{|c| c.capitalize}*""
970
-
971
- ring = @constellation.RingConstraint(
972
- :new,
973
- :vocabulary => @vocabulary,
974
- # :name => name, # REVISIT: Create a name for Ring Constraints?
975
- :role => roles[0],
976
- :other_role => roles[1],
977
- :ring_type => ring_type
978
- )
979
-
980
- debug "Added #{ring.verbalise} #{ring.class.roles.keys.map{|k|"#{k} => "+ring.send(k).verbalise}*", "}"
981
- end
982
-
983
- return unless qualifiers.size > 0
984
-
985
- # Process the remaining qualifiers:
986
- puts "REVISIT: Qualifiers #{qualifiers.inspect} over #{role_sequence.describe}"
987
- end
988
-
989
- def find_pc_over_roles(roles)
990
- return nil if roles.size == 0 # Safeguard; this would chuck an exception otherwise
991
- roles[0].all_role_ref.each do |role_ref|
992
- next if role_ref.role_sequence.all_role_ref.map(&:role) != roles
993
- pc = role_ref.role_sequence.all_presence_constraint.single # Will return nil if there's more than one.
994
- #puts "Existing PresenceConstraint matches those roles!" if pc
995
- return pc if pc
996
- end
997
- nil
998
- end
999
-
1000
- def add_reading(fact_type, role_sequence, reading)
1001
- ordinal = (fact_type.all_reading.map(&:ordinal).max||-1) + 1 # Use the next unused ordinal
1002
- defined_reading = @constellation.Reading(fact_type, ordinal, :role_sequence => role_sequence)
1003
- role_num = -1
1004
- defined_reading.reading_text = reading.map {|phrase|
1005
- Hash === phrase ? "{#{role_num += 1}}" : phrase
1006
- }*" "
1007
- raise "Wrong number of players (#{role_num+1}) found in reading #{defined_reading.reading_text} over #{fact_type.describe}" if role_num+1 != fact_type.all_role.size
1008
- debug "Added reading #{defined_reading.reading_text}"
1009
- end
1010
-
1011
- # Return an array of this entity type and all its supertypes, transitively:
1012
- def supertypes(o)
1013
- ([o] + o.all_supertype_inheritance.map{|ti| supertypes(ti.supertype)}.flatten).uniq
1014
- end
1015
-
1016
- def concept_by_name(name)
1017
- player = @constellation.Concept[[@vocabulary.identifying_role_values, name]]
1018
-
1019
- # REVISIT: Hack to allow facts to refer to standard types that will be imported from standard vocabulary:
1020
- if !player && %w{Date DateAndTime Time}.include?(name)
1021
- player = @constellation.ValueType(@vocabulary.identifying_role_values, name)
1022
- end
1023
-
1024
- if (!player && @symbols.allowed_forward[name])
1025
- player = @constellation.EntityType(@vocabulary, name)
1026
- end
1027
- player
1028
- end
1029
-
1030
- class SymbolTable #:nodoc:all
1031
- # Externally built tables used in this binding context:
1032
- attr_reader :roles_by_binding
1033
- attr_accessor :embedded_presence_constraints
1034
- attr_accessor :allowed_forward
1035
- attr_reader :constellation
1036
- attr_reader :vocabulary
1037
- attr_reader :bindings_by_concept
1038
- attr_reader :role_names
1039
-
1040
- # A Binding here is a form of reference to a concept, being a name and optional adjectives, possibly designated by a role name:
1041
- Binding = Struct.new("Binding", :concept, :name, :leading_adjective, :trailing_adjective, :role_name)
1042
- class Binding
1043
- def inspect
1044
- "Binding(#{concept.class.basename} #{concept.name}, #{[leading_adjective, name, trailing_adjective].compact*"-"}#{role_name ? " (as #{role_name})" : ""})"
1045
- end
1046
-
1047
- # Any ordering works to allow a hash to be keyed by a set (unordered array) of Bindings:
1048
- def <=>(other)
1049
- object_id <=> other.object_id
1050
- end
1051
- end
1052
-
1053
- def initialize(constellation, vocabulary)
1054
- @constellation = constellation
1055
- @vocabulary = vocabulary
1056
- @bindings_by_concept = Hash.new {|h, k| h[k] = [] } # Indexed by Binding#name, maybe multiple entries for each name
1057
- @role_names = {}
1058
-
1059
- @embedded_presence_constraints = []
1060
- @roles_by_binding = {} # Build a hash of allowed bindings on first reading (check against it on subsequent ones)
1061
- @allowed_forward = {} # No roles may be forward-referenced
1062
- end
1063
-
1064
- #
1065
- # This method is the guts of role matching.
1066
- # "words" may be a single word (and then the adjectives may also be used) or two words.
1067
- # In either case a word is expected to be a defined concept or role name.
1068
- # If a role_name is provided here, that's a *definition* and will only be accepted if legal
1069
- # If allowed_forward is true, words is a single word and is not defined, create a forward Entity
1070
- # If leading_speculative or trailing_speculative is true, the adjectives may not apply. If they do apply, use them.
1071
- # If loose_binding_except is true, it's a hash containing names that may *not* be loose-bound... else none may.
1072
- #
1073
- # Loose binding is when a word without an adjective matches a role with, or vice verse.
1074
- #
1075
- def bind(words, leading_adjective = nil, trailing_adjective = nil, role_name = nil, allowed_forward = false, leading_speculative = false, trailing_speculative = false, loose_binding_except = nil)
1076
- words = Array(words)
1077
- if (words.size > 2 or words.size == 2 && (leading_adjective or trailing_adjective or allowed_forward))
1078
- raise "role has too many adjectives '#{[leading_adjective, words, trailing_adjective].flatten.compact*" "}'"
1079
- end
1080
-
1081
- # Check for use of a role name, valid if they haven't used any adjectives or tried to define a role_name:
1082
- binding = @role_names[words[0]]
1083
- if binding && words.size == 1 # If ok, this is it.
1084
- raise "May not use existing role name '#{words[0]}' to define a new role name" if role_name
1085
- if (leading_adjective && !leading_speculative) || (trailing_adjective && !trailing_speculative)
1086
- raise "May not use existing role name '#{words[0]}' with adjectives"
1087
- end
1088
- return binding.concept, binding
1089
- end
1090
-
1091
- # Look for an existing definition
1092
- # If we have more than one word that might be the concept name, find which it is:
1093
- words.each do |w|
1094
- # Find the existing defined binding that matches this one:
1095
- bindings = @bindings_by_concept[w]
1096
- best_match = nil
1097
- matched_adjectives = 0
1098
- bindings.each do |binding|
1099
- # Adjectives defined on the binding must be matched unless loose binding is allowed.
1100
- loose_ok = loose_binding_except and !loose_binding_except[binding.concept.name]
1101
-
1102
- # Don't allow binding a new role name to an existing one:
1103
- next if role_name and role_name != binding.role_name
1104
-
1105
- quality = 0
1106
- if binding.leading_adjective != leading_adjective
1107
- next if binding.leading_adjective && leading_adjective # Both set, but different
1108
- next if !loose_ok && (!leading_speculative || !leading_adjective)
1109
- quality += 1
1110
- end
1111
-
1112
- if binding.trailing_adjective != trailing_adjective
1113
- next if binding.trailing_adjective && trailing_adjective # Both set, but different
1114
- next if !loose_ok && (!trailing_speculative || !trailing_adjective)
1115
- quality += 1
1116
- end
1117
-
1118
- quality += 1 unless binding.role_name # A role name that was not matched... better if there wasn't one
1119
-
1120
- if (quality > matched_adjectives || !best_match)
1121
- best_match = binding # A better match than we had before
1122
- matched_adjectives = quality
1123
- break unless loose_ok || leading_speculative || trailing_speculative
1124
- end
1125
- end
1126
-
1127
- if best_match
1128
- # We've found the best existing definition
1129
-
1130
- # Indicate which speculative adjectives were used so the clauses can be deleted:
1131
- leading_adjective.replace("") if best_match.leading_adjective and leading_adjective and leading_speculative
1132
- trailing_adjective.replace("") if best_match.trailing_adjective and trailing_adjective and trailing_speculative
1133
-
1134
- return best_match.concept, best_match
1135
- end
1136
-
1137
- # No existing defined binding. Look up an existing concept of this name:
1138
- player = concept(w, allowed_forward)
1139
- next unless player
1140
-
1141
- # Found a new binding for this player, save it.
1142
-
1143
- # Check that a trailing adjective isn't an existing role name or concept:
1144
- trailing_word = words[1] if w == words[0]
1145
- if trailing_word
1146
- raise "May not use existing role name '#{trailing_word}' with a new name or with adjectives" if @role_names[trailing_word]
1147
- raise "ambiguous concept reference #{words*" '"}'" if concept(trailing_word)
1148
- end
1149
- leading_word = words[0] if w != words[0]
1150
-
1151
- raise "may not redefine existing concept '#{role_name}' as a role name" if role_name and concept(role_name)
1152
-
1153
- binding = Binding.new(
1154
- player,
1155
- w,
1156
- (!leading_speculative && leading_adjective) || leading_word,
1157
- (!trailing_speculative && trailing_adjective) || trailing_word,
1158
- role_name
1159
- )
1160
- @bindings_by_concept[binding.name] << binding
1161
- @role_names[binding.role_name] = binding if role_name
1162
- return binding.concept, binding
1163
- end
1164
-
1165
- # Not found.
1166
- return nil
1167
- end
1168
-
1169
- # return the EntityType or ValueType this name refers to:
1170
- def concept(name, allowed_forward = false)
1171
- # See if the name is a defined concept in this vocabulary:
1172
- player = @constellation.Concept[[virv = @vocabulary.identifying_role_values, name]]
1173
-
1174
- # REVISIT: Hack to allow facts to refer to standard types that will be imported from standard vocabulary:
1175
- if !player && %w{Date DateAndTime Time}.include?(name)
1176
- player = @constellation.ValueType(virv, name)
1177
- end
1178
-
1179
- if !player && allowed_forward
1180
- player = @constellation.EntityType(@vocabulary, name)
1181
- end
1182
-
1183
- player
1184
- end
1185
-
1186
- def bind_roles_in_clauses(clauses, identification = [])
1187
- identification ||= []
1188
- bind_roles_in_readings(
1189
- clauses.map{|clause| clause[2]}, # Extract the readings
1190
- single_word_identifiers = identification.map{|i| i.size == 1 ? i[0] : nil}.compact.uniq
1191
- )
1192
- end
1193
-
1194
- #
1195
- # Walk through all phrases of all readings identifying role players.
1196
- # Each role player phrase gets a :binding key added to it.
1197
- #
1198
- # Any adjectives that the parser didn't recognise are merged with their players here,
1199
- # as long as they're indicated as adjectives of that player somewhere in the readings.
1200
- #
1201
- # Other words are turned from phrases (hashes) into simple strings.
1202
- #
1203
- def bind_roles_in_readings(readings, allowed_forwards = [])
1204
- disallow_loose_binding = allowed_forwards.inject({}) { |h, v| h[v] = true; h }
1205
- readings.each do |reading|
1206
- debug :bind, "Binding a reading"
1207
-
1208
- if (reading.size == 1 && reading[0][:subtype])
1209
- # REVISIT: Handle a subtype reading here
1210
- subtype_name = reading[0][:subtype]
1211
- supertype_name = reading[0][:supertype]
1212
- subtype, subtype_binding = bind(subtype_name)
1213
- supertype, supertype_binding = bind(supertype_name)
1214
- reading.replace([
1215
- "!SUBTYPE!",
1216
- {:word => subtype, :binding => subtype_binding },
1217
- {:word => supertype, :binding => supertype_binding }
1218
- ]
1219
- )
1220
- next
1221
- end
1222
-
1223
- phrase_numbers_used_speculatively = []
1224
- disallow_loose_binding_this_reading = disallow_loose_binding.clone
1225
- reading.each_with_index do |phrase, index|
1226
- la = phrase[:leading_adjective]
1227
- player_name = phrase[:word]
1228
- ta = phrase[:trailing_adjective]
1229
- role_name = phrase[:role_name]
1230
-
1231
- # We use the preceeding phrase and/or following phrase speculatively if they're simple words:
1232
- preceeding_phrase = nil
1233
- following_phrase = nil
1234
- if !la && index > 0 && (preceeding_phrase = reading[index-1])
1235
- preceeding_phrase = nil unless String === preceeding_phrase || preceeding_phrase.keys == [:word]
1236
- la = preceeding_phrase[:word] if Hash === preceeding_phrase
1237
- end
1238
- if !ta && (following_phrase = reading[index+1])
1239
- following_phrase = nil unless following_phrase.keys == [:word]
1240
- ta = following_phrase[:word] if following_phrase
1241
- end
1242
-
1243
- # If the identification includes this player name as a single word, it's allowed to be forward referenced:
1244
- allowed_forward = allowed_forwards.include?(player_name)
1245
-
1246
- debug :bind, "Binding a role: #{[player_name, la, ta, role_name, allowed_forward, !!preceeding_phrase, !!following_phrase].inspect}"
1247
- player, binding = bind(
1248
- player_name,
1249
- la, ta,
1250
- role_name,
1251
- allowed_forward,
1252
- !!preceeding_phrase, !!following_phrase,
1253
- reading == readings[0] ? nil : disallow_loose_binding_this_reading # Never allow loose binding on the first reading
1254
- )
1255
- disallow_loose_binding_this_reading[player.name] = true if player
1256
-
1257
- # Arrange to delete the speculative adjectives that were used:
1258
- if preceeding_phrase && preceeding_phrase[:word] == ""
1259
- debug :bind, "binding consumed a speculative leading_adjective #{la}"
1260
- # The numbers are adjusted to allow for prior deletions.
1261
- phrase_numbers_used_speculatively << index-1-phrase_numbers_used_speculatively.size
1262
- end
1263
- if following_phrase && following_phrase[:word] == ""
1264
- debug :bind, "binding consumed a speculative trailing_adjective #{ta}"
1265
- phrase_numbers_used_speculatively << index+1-phrase_numbers_used_speculatively.size
1266
- end
1267
-
1268
- if player
1269
- # Replace the words used to identify the role by a reference to the role itself,
1270
- # leaving :quantifier, :function, :restriction and :literal intact
1271
- phrase[:binding] = binding
1272
- binding
1273
- else
1274
- raise "Internal error; role #{phrase.inspect} not matched" unless phrase.keys == [:word]
1275
- # Just a linking word
1276
- reading[index] = phrase[:word]
1277
- end
1278
- debug :bind, "Bound phrase: #{phrase.inspect}" + " -> " + (player ? player.name+", "+binding.inspect : phrase[:word].inspect)
1279
-
1280
- end
1281
- phrase_numbers_used_speculatively.each do |index|
1282
- reading.delete_at(index)
1283
- end
1284
- debug :bind, "Bound reading: #{reading.inspect}"
1285
- end
1286
- end
1287
- end # of SymbolTable class
1288
-
30
+ # Read the specified input string
31
+ def self.readstring(str, filename = "string")
32
+ ActiveFacts::CQL::Compiler.new(str, filename).vocabulary
33
+ end
1289
34
  end
1290
35
  end
1291
36
  end