activefacts 0.7.0 → 0.7.1

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