activefacts 0.7.3 → 0.8.5

Sign up to get free protection for your applications and to get access to all the features.
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