activefacts 0.7.0 → 0.7.1

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 (60) hide show
  1. data/README.rdoc +0 -3
  2. data/Rakefile +7 -5
  3. data/bin/afgen +5 -2
  4. data/bin/cql +3 -2
  5. data/examples/CQL/Genealogy.cql +3 -3
  6. data/examples/CQL/Metamodel.cql +10 -7
  7. data/examples/CQL/MultiInheritance.cql +2 -1
  8. data/examples/CQL/OilSupply.cql +4 -4
  9. data/examples/CQL/Orienteering.cql +2 -2
  10. data/lib/activefacts.rb +2 -1
  11. data/lib/activefacts/api.rb +21 -2
  12. data/lib/activefacts/api/concept.rb +52 -39
  13. data/lib/activefacts/api/constellation.rb +8 -6
  14. data/lib/activefacts/api/entity.rb +41 -37
  15. data/lib/activefacts/api/instance.rb +5 -3
  16. data/lib/activefacts/api/numeric.rb +28 -21
  17. data/lib/activefacts/api/role.rb +29 -43
  18. data/lib/activefacts/api/standard_types.rb +8 -3
  19. data/lib/activefacts/api/support.rb +4 -4
  20. data/lib/activefacts/api/value.rb +9 -3
  21. data/lib/activefacts/api/vocabulary.rb +17 -7
  22. data/lib/activefacts/cql.rb +10 -7
  23. data/lib/activefacts/cql/CQLParser.treetop +6 -0
  24. data/lib/activefacts/cql/Concepts.treetop +32 -26
  25. data/lib/activefacts/cql/DataTypes.treetop +6 -0
  26. data/lib/activefacts/cql/Expressions.treetop +6 -0
  27. data/lib/activefacts/cql/FactTypes.treetop +6 -0
  28. data/lib/activefacts/cql/Language/English.treetop +9 -3
  29. data/lib/activefacts/cql/LexicalRules.treetop +6 -0
  30. data/lib/activefacts/cql/Rakefile +8 -0
  31. data/lib/activefacts/cql/parser.rb +4 -2
  32. data/lib/activefacts/generate/absorption.rb +20 -28
  33. data/lib/activefacts/generate/cql.rb +28 -16
  34. data/lib/activefacts/generate/cql/html.rb +327 -321
  35. data/lib/activefacts/generate/null.rb +7 -3
  36. data/lib/activefacts/generate/oo.rb +19 -15
  37. data/lib/activefacts/generate/ordered.rb +457 -461
  38. data/lib/activefacts/generate/ruby.rb +12 -4
  39. data/lib/activefacts/generate/sql/server.rb +42 -10
  40. data/lib/activefacts/generate/text.rb +7 -3
  41. data/lib/activefacts/input/cql.rb +55 -28
  42. data/lib/activefacts/input/orm.rb +32 -22
  43. data/lib/activefacts/persistence.rb +5 -0
  44. data/lib/activefacts/persistence/columns.rb +66 -32
  45. data/lib/activefacts/persistence/foreignkey.rb +29 -5
  46. data/lib/activefacts/persistence/index.rb +57 -25
  47. data/lib/activefacts/persistence/reference.rb +65 -30
  48. data/lib/activefacts/persistence/tables.rb +28 -17
  49. data/lib/activefacts/support.rb +8 -0
  50. data/lib/activefacts/version.rb +7 -1
  51. data/lib/activefacts/vocabulary.rb +4 -2
  52. data/lib/activefacts/vocabulary/extensions.rb +12 -10
  53. data/lib/activefacts/vocabulary/metamodel.rb +24 -23
  54. data/spec/api/autocounter.rb +2 -2
  55. data/spec/api/entity_type.rb +2 -2
  56. data/spec/api/instance.rb +61 -30
  57. data/spec/api/roles.rb +9 -9
  58. data/spec/cql_parse_spec.rb +1 -0
  59. data/spec/norma_tables_spec.rb +3 -3
  60. metadata +8 -4
@@ -1,13 +1,19 @@
1
1
  #
2
- # Generate CQL from an ActiveFacts vocabulary.
3
- # Copyright (c) 2008 Clifford Heath. Read the LICENSE file.
2
+ # ActiveFacts Generators.
3
+ # Generate CQL from an ActiveFacts vocabulary.
4
+ #
5
+ # Copyright (c) 2009 Clifford Heath. Read the LICENSE file.
4
6
  #
5
7
  require 'activefacts/vocabulary'
6
8
  require 'activefacts/generate/ordered'
7
9
 
8
10
  module ActiveFacts
9
11
  module Generate #:nodoc:
12
+ # Generate CQL for an ActiveFacts vocabulary.
13
+ # Invoke as
14
+ # afgen --cql <file>.cql
10
15
  class CQL < OrderedDumper
16
+ private
11
17
  include Metamodel
12
18
 
13
19
  def vocabulary_start(vocabulary)
@@ -42,7 +48,10 @@ module ActiveFacts
42
48
 
43
49
  puts "#{o.name} is defined as #{o.supertype.name}#{ parameters }#{
44
50
  o.value_restriction ? " restricted to {#{
45
- o.value_restriction.all_allowed_range.map{|ar|
51
+ o.value_restriction.all_allowed_range.sort_by{|ar|
52
+ ((min = ar.value_range.minimum_bound) && min.value) ||
53
+ ((max = ar.value_range.maximum_bound) && max.value)
54
+ }.map{|ar|
46
55
  # REVISIT: Need to display as string or numeric according to type here...
47
56
  min = ar.value_range.minimum_bound
48
57
  max = ar.value_range.maximum_bound
@@ -89,9 +98,10 @@ module ActiveFacts
89
98
  # Detect standard reference-mode scenarios
90
99
  ft = identifying_facts[0]
91
100
  fact_constraints = nil
101
+ ftr = ft.all_role.sort_by{|role| role.ordinal}
92
102
  if identifying_facts.size == 1 and
93
- entity_role = ft.all_role[n = (ft.all_role[0].concept == entity_type ? 0 : 1)] and
94
- value_role = ft.all_role[1-n] and
103
+ entity_role = ftr[n = (ftr[0].concept == entity_type ? 0 : 1)] and
104
+ value_role = ftr[1-n] and
95
105
  value_name = value_role.concept.name and
96
106
  residual = value_name.gsub(%r{#{entity_role.concept.name}},'') and
97
107
  residual != '' and
@@ -104,13 +114,13 @@ module ActiveFacts
104
114
  forward_reading = reverse_reading = nil
105
115
  ft.all_reading.each do |reading|
106
116
  if reading.reading_text =~ /^\{(\d)\} has \{\d\}$/
107
- if reading.role_sequence.all_role_ref[$1.to_i].role == entity_role
117
+ if reading.role_sequence.all_role_ref.detect{|rr| rr.ordinal == $1.to_i}.role == entity_role
108
118
  forward_reading = reading
109
119
  else
110
120
  reverse_reading = reading
111
121
  end
112
122
  elsif reading.reading_text =~ /^\{(\d)\} is of \{\d\}$/
113
- if reading.role_sequence.all_role_ref[$1.to_i].role == value_role
123
+ if reading.role_sequence.all_role_ref.detect{|rr| rr.ordinal == $1.to_i}.role == value_role
114
124
  reverse_reading = reading
115
125
  else
116
126
  forward_reading = reading
@@ -192,7 +202,7 @@ module ActiveFacts
192
202
  @identifying_fact_text = nil
193
203
  if (o = fact_type.entity_type)
194
204
  print "#{o.name} is"
195
- if !o.all_type_inheritance_by_subtype.empty?
205
+ if !o.all_type_inheritance_as_subtype.empty?
196
206
  print " a kind of #{ o.supertypes.map(&:name)*", " }"
197
207
  end
198
208
 
@@ -314,7 +324,7 @@ module ActiveFacts
314
324
  # Each constraint involves two or more occurrences of one or more players.
315
325
  # For each player, a subtype may be involved in the occurrences.
316
326
  # Find the common supertype of each player.
317
- scrs = c.all_set_comparison_roles
327
+ scrs = c.all_set_comparison_roles.sort_by{|scr| scr.ordinal}
318
328
  player_count = scrs[0].role_sequence.all_role_ref.size
319
329
  role_seq_count = scrs.size
320
330
 
@@ -325,9 +335,9 @@ module ActiveFacts
325
335
  players_differ = [] # Record which players are also played by subclasses
326
336
  players = (0...player_count).map do |pi|
327
337
  # Find the common supertype of the players of the pi'th role in each sequence
328
- concepts = scrs.map{|r| r.role_sequence.all_role_ref[pi].role.concept }
338
+ concepts = scrs.map{|r| r.role_sequence.all_role_ref.sort_by{|rr| rr.ordinal}[pi].role.concept }
329
339
  player, players_differ[pi] = common_supertype(concepts)
330
- raise "Role sequences of #{c.class.basename} must have concepts matching #{concept.name} in position #{pi}" unless player
340
+ raise "Role sequences of #{c.class.basename} must have concepts matching #{concepts.map(&:name)*","} in position #{pi}" unless player
331
341
  player
332
342
  end
333
343
  #puts "#{c.class.basename} has players #{players.map{|p| p.name}*", "}"
@@ -387,15 +397,17 @@ module ActiveFacts
387
397
 
388
398
  def dump_subset_constraint(c)
389
399
  # If the role players are identical and not duplicated, we can simply say "reading1 only if reading2"
390
- subset_roles = c.subset_role_sequence.all_role_ref.map{|rr| rr.role}
391
- superset_roles = c.superset_role_sequence.all_role_ref.map{|rr| rr.role}
400
+ subset_roles, subset_fact_types =
401
+ c.subset_role_sequence.all_role_ref.sort_by{|rr| rr.ordinal}.map{|rr| [rr.role, rr.role.fact_type]}.transpose
402
+ superset_roles, superset_fact_types =
403
+ c.superset_role_sequence.all_role_ref.sort_by{|rr| rr.ordinal}.map{|rr| [rr.role, rr.role.fact_type]}.transpose
404
+
405
+ subset_fact_types.uniq!
406
+ superset_fact_types.uniq!
392
407
 
393
408
  subset_players = subset_roles.map(&:concept)
394
409
  superset_players = superset_roles.map(&:concept)
395
410
 
396
- subset_fact_types = c.subset_role_sequence.all_role_ref.map{|rr| rr.role.fact_type }.uniq
397
- superset_fact_types = c.superset_role_sequence.all_role_ref.map{|rr| rr.role.fact_type }.uniq
398
-
399
411
  # We need to ensure that if the player of any constrained role also exists
400
412
  # as the player of a role that's not a constrained role, there are different
401
413
  # adjectives or other qualifiers qualifier applied to distinguish that role.
@@ -1,8 +1,10 @@
1
1
  #
2
- # Generate HTML-highlighted CQL from an ActiveFacts vocabulary.
3
- # Copyright (c) 2008 Clifford Heath. Read the LICENSE file.
2
+ # ActiveFacts Generators.
3
+ # Generate HTML-highlighted CQL from an ActiveFacts vocabulary.
4
4
  #
5
- # The text generated here is pre-formatted, and in spans haing the following styles:
5
+ # Copyright (c) 2009 Clifford Heath. Read the LICENSE file.
6
+ #
7
+ # The text generated here is pre-formatted, and it spans haing the following styles:
6
8
  # keyword: ORM2 standard colour is #00C (blue)
7
9
  # concept: ORM2 standard concept is #808 (purple)
8
10
  # copula: ORM2 standard concept is #060 (green)
@@ -13,384 +15,388 @@ require 'activefacts/generate/ordered'
13
15
  require 'activefacts/generate/cql'
14
16
 
15
17
  module ActiveFacts
16
-
17
18
  module Generate
18
19
  class CQL
19
- class HTML < CQL
20
+ # Generate CQL with HTML syntax-highlighting for an ActiveFacts vocabulary.
21
+ # Invoke as
22
+ # afgen --cql/html <file>.cql
23
+ class HTML < CQL
24
+ private
25
+
26
+ def initialize(vocabulary, *options)
27
+ super
28
+ end
20
29
 
21
- def initialize(vocabulary, *options)
22
- super
23
- end
30
+ def puts s
31
+ super(s.gsub(/[,;]/) do |p| keyword p; end)
32
+ end
24
33
 
25
- def puts s
26
- super(s.gsub(/[,;]/) do |p| keyword p; end)
27
- end
34
+ def keyword(str)
35
+ "<span class='keyword'>#{str}</span>"
36
+ end
28
37
 
29
- def keyword(str)
30
- "<span class='keyword'>#{str}</span>"
31
- end
38
+ def concept(str)
39
+ "<span class='concept'>#{str}</span>"
40
+ end
32
41
 
33
- def concept(str)
34
- "<span class='concept'>#{str}</span>"
35
- end
42
+ def copula(str)
43
+ "<span class='copula'>#{str}</span>"
44
+ end
36
45
 
37
- def copula(str)
38
- "<span class='copula'>#{str}</span>"
39
- end
46
+ def vocabulary_start(vocabulary)
47
+ puts %q{<head>
48
+ <link rel="stylesheet" href="css/orm2.css" type="text/css"/>
49
+ </head>
50
+ <pre class="copula">}
51
+ puts "#{keyword "vocabulary"} #{concept(vocabulary.name)};\n\n"
52
+ end
40
53
 
41
- def vocabulary_start(vocabulary)
42
- puts %q{<head>
43
- <link rel="stylesheet" href="css/orm2.css" type="text/css"/>
44
- </head>
45
- <pre class="copula">}
46
- puts "#{keyword "vocabulary"} #{concept(vocabulary.name)};\n\n"
47
- end
54
+ def vocabulary_end
55
+ puts %q{</pre>}
56
+ end
48
57
 
49
- def vocabulary_end
50
- puts %q{</pre>}
51
- end
58
+ def value_type_banner
59
+ puts "/*\n * Value Types\n */"
60
+ end
52
61
 
53
- def value_type_banner
54
- puts "/*\n * Value Types\n */"
55
- end
62
+ def value_type_dump(o)
63
+ return unless o.supertype # An imported type
64
+ if o.name == o.supertype.name
65
+ # In ActiveFacts, parameterising a ValueType will create a new datatype
66
+ # throw Can't handle parameterized value type of same name as its datatype" if ...
67
+ end
56
68
 
57
- def value_type_dump(o)
58
- return unless o.supertype # An imported type
59
- if o.name == o.supertype.name
60
- # In ActiveFacts, parameterising a ValueType will create a new datatype
61
- # throw Can't handle parameterized value type of same name as its datatype" if ...
69
+ parameters =
70
+ [ o.length != 0 || o.scale != 0 ? o.length : nil,
71
+ o.scale != 0 ? o.scale : nil
72
+ ].compact
73
+ parameters = parameters.length > 0 ? "("+parameters.join(",")+")" : "()"
74
+
75
+ puts "#{concept o.name} #{keyword "is defined as"} #{concept o.supertype.name + parameters }#{
76
+ if (o.value_restriction)
77
+ keyword("restricted to")+
78
+ o.value_restriction.all_allowed_range.map{|ar|
79
+ # REVISIT: Need to display as string or numeric according to type here...
80
+ min = ar.value_range.minimum_bound
81
+ max = ar.value_range.maximum_bound
82
+
83
+ range = (min ? min.value : "") +
84
+ (min.value != (max&&max.value) ? (".." + (max ? max.value : "")) : "")
85
+ keyword range
86
+ }*", "
87
+ else
88
+ ""
89
+ end
90
+ };"
62
91
  end
63
92
 
64
- parameters =
65
- [ o.length != 0 || o.scale != 0 ? o.length : nil,
66
- o.scale != 0 ? o.scale : nil
67
- ].compact
68
- parameters = parameters.length > 0 ? "("+parameters.join(",")+")" : "()"
69
-
70
- puts "#{concept o.name} #{keyword "is defined as"} #{concept o.supertype.name + parameters }#{
71
- if (o.value_restriction)
72
- keyword("restricted to")+
73
- o.value_restriction.all_allowed_range.map{|ar|
74
- # REVISIT: Need to display as string or numeric according to type here...
75
- min = ar.value_range.minimum_bound
76
- max = ar.value_range.maximum_bound
77
-
78
- range = (min ? min.value : "") +
79
- (min.value != (max&&max.value) ? (".." + (max ? max.value : "")) : "")
80
- keyword range
81
- }*", "
82
- else
83
- ""
84
- end
85
- };"
86
- end
87
-
88
- def append_ring_to_reading(reading, ring)
89
- reading << keyword(" [#{(ring.ring_type.scan(/[A-Z][a-z]*/)*", ").downcase}]")
90
- end
93
+ def append_ring_to_reading(reading, ring)
94
+ reading << keyword(" [#{(ring.ring_type.scan(/[A-Z][a-z]*/)*", ").downcase}]")
95
+ end
91
96
 
92
- def identified_by_roles_and_facts(entity_type, identifying_roles, identifying_facts, preferred_readings)
93
- identifying_role_names = identifying_roles.map{|role|
94
- preferred_role_ref = preferred_readings[role.fact_type].role_sequence.all_role_ref.detect{|reading_rr|
95
- reading_rr.role == role
96
- }
97
- role_words = []
98
- # REVISIT: Consider whether NOT to use the adjective if it's a prefix of the role_name
99
-
100
- role_name = role.role_name
101
- role_name = nil if role_name == ""
102
- # debug "concept.name=#{preferred_role_ref.role.concept.name}, role_name=#{role_name.inspect}, preferred_role_name=#{preferred_role_ref.role.role_name.inspect}"
103
-
104
- if (role.fact_type.all_role.size == 1)
105
- # REVISIT: Guard against unary reading containing the illegal words "and" and "where".
106
- role.fact_type.default_reading # Need whole reading for a unary.
107
- elsif (role_name)
108
- role_name
109
- else
110
- role_words << preferred_role_ref.leading_adjective if preferred_role_ref.leading_adjective != ""
111
- role_words << preferred_role_ref.role.concept.name
112
- role_words << preferred_role_ref.trailing_adjective if preferred_role_ref.trailing_adjective != ""
113
- role_words.compact*"-"
114
- end
115
- }
116
-
117
- # REVISIT: Consider emitting extra fact types here, instead of in entity_type_dump?
118
- # Just beware that readings having the same players will be considered to be of the same fact type, even if they're not.
119
-
120
- # Detect standard reference-mode scenarios
121
- ft = identifying_facts[0]
122
- fact_constraints = nil
123
- if identifying_facts.size == 1 and
124
- entity_role = ft.all_role[n = (ft.all_role[0].concept == entity_type ? 0 : 1)] and
125
- value_role = ft.all_role[1-n] and
126
- value_name = value_role.concept.name and
127
- residual = value_name.gsub(%r{#{entity_role.concept.name}},'') and
128
- residual != '' and
129
- residual != value_name
130
-
131
- # The EntityType is identified by its association with a single ValueType
132
- # whose name is an extension (the residual) of the EntityType's name.
133
-
134
- # Detect standard reference-mode readings:
135
- forward_reading = reverse_reading = nil
136
- ft.all_reading.each do |reading|
137
- if reading.reading_text =~ /^\{(\d)\} has \{\d\}$/
138
- if reading.role_sequence.all_role_ref[$1.to_i].role == entity_role
139
- forward_reading = reading
97
+ def identified_by_roles_and_facts(entity_type, identifying_roles, identifying_facts, preferred_readings)
98
+ identifying_role_names = identifying_roles.map{|role|
99
+ preferred_role_ref = preferred_readings[role.fact_type].role_sequence.all_role_ref.detect{|reading_rr|
100
+ reading_rr.role == role
101
+ }
102
+ role_words = []
103
+ # REVISIT: Consider whether NOT to use the adjective if it's a prefix of the role_name
104
+
105
+ role_name = role.role_name
106
+ role_name = nil if role_name == ""
107
+ # debug "concept.name=#{preferred_role_ref.role.concept.name}, role_name=#{role_name.inspect}, preferred_role_name=#{preferred_role_ref.role.role_name.inspect}"
108
+
109
+ if (role.fact_type.all_role.size == 1)
110
+ # REVISIT: Guard against unary reading containing the illegal words "and" and "where".
111
+ role.fact_type.default_reading # Need whole reading for a unary.
112
+ elsif (role_name)
113
+ role_name
140
114
  else
141
- reverse_reading = reading
115
+ role_words << preferred_role_ref.leading_adjective if preferred_role_ref.leading_adjective != ""
116
+ role_words << preferred_role_ref.role.concept.name
117
+ role_words << preferred_role_ref.trailing_adjective if preferred_role_ref.trailing_adjective != ""
118
+ role_words.compact*"-"
142
119
  end
143
- elsif reading.reading_text =~ /^\{(\d)\} is of \{\d\}$/
144
- if reading.role_sequence.all_role_ref[$1.to_i].role == value_role
145
- reverse_reading = reading
146
- else
147
- forward_reading = reading
120
+ }
121
+
122
+ # REVISIT: Consider emitting extra fact types here, instead of in entity_type_dump?
123
+ # Just beware that readings having the same players will be considered to be of the same fact type, even if they're not.
124
+
125
+ # Detect standard reference-mode scenarios
126
+ ft = identifying_facts[0]
127
+ fact_constraints = nil
128
+ if identifying_facts.size == 1 and
129
+ entity_role = ft.all_role[n = (ft.all_role[0].concept == entity_type ? 0 : 1)] and
130
+ value_role = ft.all_role[1-n] and
131
+ value_name = value_role.concept.name and
132
+ residual = value_name.gsub(%r{#{entity_role.concept.name}},'') and
133
+ residual != '' and
134
+ residual != value_name
135
+
136
+ # The EntityType is identified by its association with a single ValueType
137
+ # whose name is an extension (the residual) of the EntityType's name.
138
+
139
+ # Detect standard reference-mode readings:
140
+ forward_reading = reverse_reading = nil
141
+ ft.all_reading.each do |reading|
142
+ if reading.reading_text =~ /^\{(\d)\} has \{\d\}$/
143
+ if reading.role_sequence.all_role_ref[$1.to_i].role == entity_role
144
+ forward_reading = reading
145
+ else
146
+ reverse_reading = reading
147
+ end
148
+ elsif reading.reading_text =~ /^\{(\d)\} is of \{\d\}$/
149
+ if reading.role_sequence.all_role_ref[$1.to_i].role == value_role
150
+ reverse_reading = reading
151
+ else
152
+ forward_reading = reading
153
+ end
148
154
  end
149
155
  end
150
- end
151
156
 
152
- debug :mode, "------------------- Didn't find standard forward reading" unless forward_reading
153
- debug :mode, "------------------- Didn't find standard reverse reading" unless reverse_reading
154
-
155
- # If we didn't find at least one of the standard readings, don't use a refmode:
156
- if (forward_reading || reverse_reading)
157
- # Elide the constraints that would have been emitted on those readings.
158
- # If there is a UC that's not in the standard form for a reference mode,
159
- # we have to emit the standard reading anyhow.
160
- fact_constraints = @presence_constraints_by_fact[ft]
161
- fact_constraints.each do |pc|
162
- if (pc.role_sequence.all_role_ref.size == 1 and pc.max_frequency == 1)
163
- # It's a uniqueness constraint, and will be regenerated
164
- @constraints_used[pc] = true
157
+ debug :mode, "------------------- Didn't find standard forward reading" unless forward_reading
158
+ debug :mode, "------------------- Didn't find standard reverse reading" unless reverse_reading
159
+
160
+ # If we didn't find at least one of the standard readings, don't use a refmode:
161
+ if (forward_reading || reverse_reading)
162
+ # Elide the constraints that would have been emitted on those readings.
163
+ # If there is a UC that's not in the standard form for a reference mode,
164
+ # we have to emit the standard reading anyhow.
165
+ fact_constraints = @presence_constraints_by_fact[ft]
166
+ fact_constraints.each do |pc|
167
+ if (pc.role_sequence.all_role_ref.size == 1 and pc.max_frequency == 1)
168
+ # It's a uniqueness constraint, and will be regenerated
169
+ @constraints_used[pc] = true
170
+ end
165
171
  end
166
- end
167
172
 
168
- @fact_types_dumped[ft] = true
173
+ @fact_types_dumped[ft] = true
169
174
 
170
- # Figure out whether any non-standard readings exist:
171
- other_readings = ft.all_reading - [forward_reading] - [reverse_reading]
172
- debug :mode, "--- other_readings.size now = #{other_readings.size}" if other_readings.size > 0
175
+ # Figure out whether any non-standard readings exist:
176
+ other_readings = ft.all_reading - [forward_reading] - [reverse_reading]
177
+ debug :mode, "--- other_readings.size now = #{other_readings.size}" if other_readings.size > 0
173
178
 
174
- fact_text = other_readings.map do |reading|
175
- expanded_reading(reading, fact_constraints, true)
176
- end*",\n\t"
177
- return keyword(" identified by its ") +
178
- concept(residual) +
179
- (fact_text != "" ? keyword(" where\n\t") + fact_text : "")
179
+ fact_text = other_readings.map do |reading|
180
+ expanded_reading(reading, fact_constraints, true)
181
+ end*",\n\t"
182
+ return keyword(" identified by its ") +
183
+ concept(residual) +
184
+ (fact_text != "" ? keyword(" where\n\t") + fact_text : "")
185
+ end
180
186
  end
181
- end
182
187
 
183
- identifying_facts.each{|f| @fact_types_dumped[f] = true }
184
- @identifying_fact_text =
185
- identifying_facts.map{|f|
186
- fact_readings_with_constraints(f, fact_constraints)
187
- }.flatten*",\n\t"
188
+ identifying_facts.each{|f| @fact_types_dumped[f] = true }
189
+ @identifying_fact_text =
190
+ identifying_facts.map{|f|
191
+ fact_readings_with_constraints(f, fact_constraints)
192
+ }.flatten*",\n\t"
188
193
 
189
- keyword(" identified by ") +
190
- identifying_role_names.map{|n| concept n} * keyword(" and ") +
191
- keyword(" where\n\t") +
192
- @identifying_fact_text
193
- end
194
+ keyword(" identified by ") +
195
+ identifying_role_names.map{|n| concept n} * keyword(" and ") +
196
+ keyword(" where\n\t") +
197
+ @identifying_fact_text
198
+ end
194
199
 
195
- def show_frequency role, constraint
196
- # REVISIT: Need to also colorize the adjectives here:
197
- [ constraint ? keyword(constraint.frequency) : nil, concept(role.concept.name) ]
198
- end
200
+ def show_frequency role, constraint
201
+ # REVISIT: Need to also colorize the adjectives here:
202
+ [ constraint ? keyword(constraint.frequency) : nil, concept(role.concept.name) ]
203
+ end
199
204
 
200
- def entity_type_banner
201
- puts(keyword("/*\n * Entity Types\n */"))
202
- end
205
+ def entity_type_banner
206
+ puts(keyword("/*\n * Entity Types\n */"))
207
+ end
203
208
 
204
- def fact_readings(fact_type)
205
- constrained_fact_readings = fact_readings_with_constraints(fact_type)
206
- constrained_fact_readings*",\n\t"
207
- end
209
+ def fact_readings(fact_type)
210
+ constrained_fact_readings = fact_readings_with_constraints(fact_type)
211
+ constrained_fact_readings*",\n\t"
212
+ end
208
213
 
209
- def subtype_dump(o, supertypes, pi)
210
- print "#{concept o.name} #{keyword "is a kind of"} #{ o.supertypes.map(&:name).map{|n| concept n}*keyword(", ") }"
211
- if pi
212
- print identified_by(o, pi)
214
+ def subtype_dump(o, supertypes, pi)
215
+ print "#{concept o.name} #{keyword "is a kind of"} #{ o.supertypes.map(&:name).map{|n| concept n}*keyword(", ") }"
216
+ if pi
217
+ print identified_by(o, pi)
218
+ end
219
+ # If there's a preferred_identifier for this subtype, identifying readings were emitted
220
+ if o.fact_type
221
+ print(
222
+ (pi ? "," : keyword(" where")) +
223
+ "\n\t" +
224
+ fact_readings(o.fact_type)
225
+ )
226
+ end
227
+ puts ";\n"
213
228
  end
214
- # If there's a preferred_identifier for this subtype, identifying readings were emitted
215
- if o.fact_type
216
- print(
217
- (pi ? "," : keyword(" where")) +
218
- "\n\t" +
219
- fact_readings(o.fact_type)
220
- )
229
+
230
+ def non_subtype_dump(o, pi)
231
+ print "#{concept(o.name)} #{keyword "is"}" +
232
+ identified_by(o, pi)
233
+ print(keyword(" where\n\t") + fact_readings(o.fact_type)) if o.fact_type
234
+ puts ";\n"
221
235
  end
222
- puts ";\n"
223
- end
224
236
 
225
- def non_subtype_dump(o, pi)
226
- print "#{concept(o.name)} #{keyword "is"}" +
227
- identified_by(o, pi)
228
- print(keyword(" where\n\t") + fact_readings(o.fact_type)) if o.fact_type
229
- puts ";\n"
230
- end
237
+ def fact_type_dump(fact_type, name)
231
238
 
232
- def fact_type_dump(fact_type, name)
239
+ @identifying_fact_text = nil
240
+ if (o = fact_type.entity_type)
241
+ print "#{concept o.name} #{keyword "is"}"
242
+ if !o.all_type_inheritance_as_subtype.empty?
243
+ print(keyword(" a kind of ") + o.supertypes.map(&:name).map{|n| concept n}*", ")
244
+ end
233
245
 
234
- @identifying_fact_text = nil
235
- if (o = fact_type.entity_type)
236
- print "#{concept o.name} #{keyword "is"}"
237
- if !o.all_type_inheritance_by_subtype.empty?
238
- print(keyword(" a kind of ") + o.supertypes.map(&:name).map{|n| concept n}*", ")
246
+ # Alternate identification of objectified fact type?
247
+ primary_supertype = o.supertypes[0]
248
+ pi = fact_type.entity_type.preferred_identifier
249
+ if pi && primary_supertype && primary_supertype.preferred_identifier != pi
250
+ print identified_by(o, pi)
251
+ print ";\n"
252
+ end
239
253
  end
240
254
 
241
- # Alternate identification of objectified fact type?
242
- primary_supertype = o.supertypes[0]
243
- pi = fact_type.entity_type.preferred_identifier
244
- if pi && primary_supertype && primary_supertype.preferred_identifier != pi
245
- print identified_by(o, pi)
246
- print ";\n"
255
+ unless @identifying_fact_text
256
+ print(keyword(" where\n\t")) if o
257
+ puts(fact_readings(fact_type)+";")
247
258
  end
248
259
  end
249
260
 
250
- unless @identifying_fact_text
251
- print(keyword(" where\n\t")) if o
252
- puts(fact_readings(fact_type)+";")
261
+ def fact_type_banner
262
+ puts keyword("/*\n * Fact Types\n */")
253
263
  end
254
- end
255
264
 
256
- def fact_type_banner
257
- puts keyword("/*\n * Fact Types\n */")
258
- end
265
+ def constraint_banner
266
+ puts keyword("/*\n * Constraints:\n */")
267
+ end
259
268
 
260
- def constraint_banner
261
- puts keyword("/*\n * Constraints:\n */")
262
- end
269
+ def dump_presence_constraint(c)
270
+ roles = c.role_sequence.all_role_ref.map{|rr| rr.role }
263
271
 
264
- def dump_presence_constraint(c)
265
- roles = c.role_sequence.all_role_ref.map{|rr| rr.role }
272
+ # REVISIT: If only one role is covered and it's mandatory >=1 constraint, use SOME/THAT form:
273
+ # each Bug SOME Tester logged THAT Bug;
274
+ players = c.role_sequence.all_role_ref.map{|rr| rr.role.concept.name}.uniq
266
275
 
267
- # REVISIT: If only one role is covered and it's mandatory >=1 constraint, use SOME/THAT form:
268
- # each Bug SOME Tester logged THAT Bug;
269
- players = c.role_sequence.all_role_ref.map{|rr| rr.role.concept.name}.uniq
276
+ fact_types = c.role_sequence.all_role_ref.map{|rr| rr.role.fact_type}.uniq
277
+ puts \
278
+ "#{keyword "each #{players.size > 1 ? "combination " : ""}"}"+
279
+ "#{players.map{|n| concept n}*", "} "+
280
+ "#{keyword "occurs #{c.frequency} time in"}\n\t"+
281
+ "#{fact_types.map{|ft| ft.default_reading([], nil)}*",\n\t"}" +
282
+ ";"
283
+ end
270
284
 
271
- fact_types = c.role_sequence.all_role_ref.map{|rr| rr.role.fact_type}.uniq
272
- puts \
273
- "#{keyword "each #{players.size > 1 ? "combination " : ""}"}"+
274
- "#{players.map{|n| concept n}*", "} "+
275
- "#{keyword "occurs #{c.frequency} time in"}\n\t"+
276
- "#{fact_types.map{|ft| ft.default_reading([], nil)}*",\n\t"}" +
277
- ";"
278
- end
285
+ def dump_set_constraint(c)
286
+ # REVISIT exclusion: every <player-list> must<?> either reading1, reading2, ...
287
+
288
+ # Each constraint involves two or more occurrences of one or more players.
289
+ # For each player, a subtype may be involved in the occurrences.
290
+ # Find the common supertype of each player.
291
+ scrs = c.all_set_comparison_roles
292
+ player_count = scrs[0].role_sequence.all_role_ref.size
293
+ role_seq_count = scrs.size
294
+
295
+ #raise "Can't verbalise constraint over many players and facts" if player_count > 1 and role_seq_count > 1
296
+
297
+ players_differ = [] # Record which players are also played by subclasses
298
+ players = (0...player_count).map do |pi|
299
+ # Find the common supertype of the players of the pi'th role in each sequence
300
+ concepts = scrs.map{|r| r.role_sequence.all_role_ref[pi].role.concept }
301
+ player, players_differ[pi] = common_supertype(concepts)
302
+ raise "Role sequences of #{c.class.basename} must have concepts matching #{c.name} in position #{pi}" unless player
303
+ player
304
+ end
305
+ #puts "#{c.class.basename} has players #{players.map{|p| p.name}*", "}"
306
+
307
+ if (SetEqualityConstraint === c)
308
+ # REVISIT: Need a proper approach to some/that and adjective disambiguation:
309
+ puts \
310
+ scrs.map{|scr|
311
+ scr.role_sequence.all_role_ref.map{|rr|
312
+ rr.role.fact_type.default_reading([], nil)
313
+ }*keyword(" and ")
314
+ } * keyword("\n\tif and only if\n\t") + ";"
315
+ return
316
+ end
279
317
 
280
- def dump_set_constraint(c)
281
- # REVISIT exclusion: every <player-list> must<?> either reading1, reading2, ...
282
-
283
- # Each constraint involves two or more occurrences of one or more players.
284
- # For each player, a subtype may be involved in the occurrences.
285
- # Find the common supertype of each player.
286
- scrs = c.all_set_comparison_roles
287
- player_count = scrs[0].role_sequence.all_role_ref.size
288
- role_seq_count = scrs.size
289
-
290
- #raise "Can't verbalise constraint over many players and facts" if player_count > 1 and role_seq_count > 1
291
-
292
- players_differ = [] # Record which players are also played by subclasses
293
- players = (0...player_count).map do |pi|
294
- # Find the common supertype of the players of the pi'th role in each sequence
295
- concepts = scrs.map{|r| r.role_sequence.all_role_ref[pi].role.concept }
296
- player, players_differ[pi] = common_supertype(concepts)
297
- raise "Role sequences of #{c.class.basename} must have concepts matching #{c.name} in position #{pi}" unless player
298
- player
299
- end
300
- #puts "#{c.class.basename} has players #{players.map{|p| p.name}*", "}"
318
+ mode = c.is_mandatory ? "exactly one" : "at most one"
319
+ puts "#{keyword "for each"} #{players.map{|p| concept p.name}*", "} #{keyword(mode + " of these holds")}:\n\t" +
320
+ (scrs.map do |scr|
321
+ constrained_roles = scr.role_sequence.all_role_ref.map{|rr| rr.role }
322
+ fact_types = constrained_roles.map{|r| r.fact_type }.uniq
301
323
 
302
- if (SetEqualityConstraint === c)
303
- # REVISIT: Need a proper approach to some/that and adjective disambiguation:
304
- puts \
305
- scrs.map{|scr|
306
- scr.role_sequence.all_role_ref.map{|rr|
307
- rr.role.fact_type.default_reading([], nil)
308
- }*keyword(" and ")
309
- } * keyword("\n\tif and only if\n\t") + ";"
310
- return
311
- end
324
+ fact_types.map do |fact_type|
325
+ # REVISIT: future: Use "THAT" and "SOME" only when:
326
+ # - the role player occurs twice in the reading, or
327
+ # - is a subclass of the constrained concept, or
328
+ reading = fact_type.preferred_reading
329
+ expand_constrained(reading, constrained_roles, players, players_differ)
330
+ end * keyword(" and ")
312
331
 
313
- mode = c.is_mandatory ? "exactly one" : "at most one"
314
- puts "#{keyword "for each"} #{players.map{|p| concept p.name}*", "} #{keyword(mode + " of these holds")}:\n\t" +
315
- (scrs.map do |scr|
316
- constrained_roles = scr.role_sequence.all_role_ref.map{|rr| rr.role }
317
- fact_types = constrained_roles.map{|r| r.fact_type }.uniq
318
-
319
- fact_types.map do |fact_type|
320
- # REVISIT: future: Use "THAT" and "SOME" only when:
321
- # - the role player occurs twice in the reading, or
322
- # - is a subclass of the constrained concept, or
323
- reading = fact_type.preferred_reading
324
- expand_constrained(reading, constrained_roles, players, players_differ)
325
- end * keyword(" and ")
326
-
327
- end*",\n\t"
328
- )+';'
329
- end
332
+ end*",\n\t"
333
+ )+';'
334
+ end
330
335
 
331
- # Expand this reading using (in)definite articles where needed
332
- # Handle any roles in constrained_roles specially.
333
- def expand_constrained(reading, constrained_roles, players, players_differ)
334
- frequency_constraints = reading.role_sequence.all_role_ref.map {|role_ref|
335
- i = constrained_roles.index(role_ref.role)
336
- if !i
337
- v = [ "some", role_ref.role.concept.name]
338
- elsif players_differ[i]
339
- v = [ "that", players[i].name ] # Make sure to use the superclass name
340
- else
341
- if reading.fact_type.all_role.select{|r| r.concept == role_ref.role.concept }.size > 1
342
- v = [ "that", role_ref.role.concept.name ]
336
+ # Expand this reading using (in)definite articles where needed
337
+ # Handle any roles in constrained_roles specially.
338
+ def expand_constrained(reading, constrained_roles, players, players_differ)
339
+ frequency_constraints = reading.role_sequence.all_role_ref.map {|role_ref|
340
+ i = constrained_roles.index(role_ref.role)
341
+ if !i
342
+ v = [ "some", role_ref.role.concept.name]
343
+ elsif players_differ[i]
344
+ v = [ "that", players[i].name ] # Make sure to use the superclass name
343
345
  else
344
- v = [ "some", role_ref.role.concept.name ]
346
+ if reading.fact_type.all_role.select{|r| r.concept == role_ref.role.concept }.size > 1
347
+ v = [ "that", role_ref.role.concept.name ]
348
+ else
349
+ v = [ "some", role_ref.role.concept.name ]
350
+ end
345
351
  end
346
- end
347
352
 
348
- v[0] = keyword(v[0])
349
- v[1] = concept(v[1])
350
- v
351
- }
352
- frequency_constraints = [] unless frequency_constraints.detect{|fc| fc[0] =~ /some/ }
353
+ v[0] = keyword(v[0])
354
+ v[1] = concept(v[1])
355
+ v
356
+ }
357
+ frequency_constraints = [] unless frequency_constraints.detect{|fc| fc[0] =~ /some/ }
353
358
 
354
- #$stderr.puts "fact_type roles (#{fact_type.all_role.map{|r| r.concept.name}*","}) default_reading '#{fact_type.preferred_reading.reading_text}' roles (#{fact_type.preferred_reading.role_sequence.all_role_ref.map{|rr| rr.role.concept.name}*","}) #{frequency_constraints.inspect}"
359
+ #$stderr.puts "fact_type roles (#{fact_type.all_role.map{|r| r.concept.name}*","}) default_reading '#{fact_type.preferred_reading.reading_text}' roles (#{fact_type.preferred_reading.role_sequence.all_role_ref.map{|rr| rr.role.concept.name}*","}) #{frequency_constraints.inspect}"
355
360
 
356
- # REVISIT: Make sure that we refer to the constrained players by their common supertype
361
+ # REVISIT: Make sure that we refer to the constrained players by their common supertype
357
362
 
358
- reading.expand(frequency_constraints, nil)
359
- end
363
+ reading.expand(frequency_constraints, nil)
364
+ end
360
365
 
361
- def dump_subset_constraint(c)
362
- # If the role players are identical and not duplicated, we can simply say "reading1 only if reading2"
363
- subset_roles = c.subset_role_sequence.all_role_ref.map{|rr| rr.role}
364
- superset_roles = c.superset_role_sequence.all_role_ref.map{|rr| rr.role}
365
-
366
- subset_players = subset_roles.map(&:concept)
367
- superset_players = superset_roles.map(&:concept)
368
-
369
- subset_fact_types = c.subset_role_sequence.all_role_ref.map{|rr| rr.role.fact_type }.uniq
370
- superset_fact_types = c.superset_role_sequence.all_role_ref.map{|rr| rr.role.fact_type }.uniq
371
-
372
- # We need to ensure that if the player of any constrained role also exists
373
- # as the player of a role that's not a constrained role, there are different
374
- # adjectives or other qualifiers qualifier applied to distinguish that role.
375
- fact_type_roles = (subset_fact_types+superset_fact_types).map{|ft| ft.all_role }.flatten
376
- non_constrained_roles = fact_type_roles - subset_roles - superset_roles
377
- if (r = non_constrained_roles.detect{|r| (subset_roles+superset_roles).include?(r) })
378
- # REVISIT: Find a way to deal with this problem, should it arise.
379
-
380
- # It would help, but not entirely fix it, to use SOME/THAT to identify the constrained roles.
381
- # See ServiceDirector's DataStore<->Client fact types for example
382
- # Use SOME on the subset, THAT on the superset.
383
- raise "Critical ambiguity, #{r.concept.name} occurs both constrained and unconstrained in #{c.name}"
366
+ def dump_subset_constraint(c)
367
+ # If the role players are identical and not duplicated, we can simply say "reading1 only if reading2"
368
+ subset_roles = c.subset_role_sequence.all_role_ref.map{|rr| rr.role}
369
+ superset_roles = c.superset_role_sequence.all_role_ref.map{|rr| rr.role}
370
+
371
+ subset_players = subset_roles.map(&:concept)
372
+ superset_players = superset_roles.map(&:concept)
373
+
374
+ subset_fact_types = c.subset_role_sequence.all_role_ref.map{|rr| rr.role.fact_type }.uniq
375
+ superset_fact_types = c.superset_role_sequence.all_role_ref.map{|rr| rr.role.fact_type }.uniq
376
+
377
+ # We need to ensure that if the player of any constrained role also exists
378
+ # as the player of a role that's not a constrained role, there are different
379
+ # adjectives or other qualifiers qualifier applied to distinguish that role.
380
+ fact_type_roles = (subset_fact_types+superset_fact_types).map{|ft| ft.all_role }.flatten
381
+ non_constrained_roles = fact_type_roles - subset_roles - superset_roles
382
+ if (r = non_constrained_roles.detect{|r| (subset_roles+superset_roles).include?(r) })
383
+ # REVISIT: Find a way to deal with this problem, should it arise.
384
+
385
+ # It would help, but not entirely fix it, to use SOME/THAT to identify the constrained roles.
386
+ # See ServiceDirector's DataStore<->Client fact types for example
387
+ # Use SOME on the subset, THAT on the superset.
388
+ raise "Critical ambiguity, #{r.concept.name} occurs both constrained and unconstrained in #{c.name}"
389
+ end
390
+
391
+ puts \
392
+ "#{subset_fact_types.map{|ft| ft.default_reading([], nil)}*" and "}" +
393
+ "\n\t#{keyword "only if"} " +
394
+ "#{superset_fact_types.map{|ft| ft.default_reading([], nil)}*" and "}" +
395
+ ";"
384
396
  end
385
397
 
386
- puts \
387
- "#{subset_fact_types.map{|ft| ft.default_reading([], nil)}*" and "}" +
388
- "\n\t#{keyword "only if"} " +
389
- "#{superset_fact_types.map{|ft| ft.default_reading([], nil)}*" and "}" +
390
- ";"
391
398
  end
392
-
393
- end
399
+ end
394
400
  end
395
401
  end
396
402
  end