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
@@ -11,80 +11,147 @@ require 'treetop'
11
11
  require 'activefacts/cql/LexicalRules'
12
12
  require 'activefacts/cql/Language/English'
13
13
  require 'activefacts/cql/Expressions'
14
+ require 'activefacts/cql/Terms'
14
15
  require 'activefacts/cql/Concepts'
15
- require 'activefacts/cql/DataTypes'
16
+ require 'activefacts/cql/ValueTypes'
16
17
  require 'activefacts/cql/FactTypes'
18
+ require 'activefacts/cql/Context'
17
19
  require 'activefacts/cql/CQLParser'
18
20
 
19
21
  module ActiveFacts
20
- # Extend the generated parser:
21
- class CQLParser
22
- include ActiveFacts
23
-
24
- # Repeatedly parse rule_name until all input is consumed,
25
- # returning an array of syntax trees for each definition.
26
- def parse_all(input, rule_name = nil, &block)
27
- self.root = rule_name if rule_name
28
-
29
- @index = 0 # Byte offset to start next parse
30
- self.consume_all_input = false
31
- results = []
32
- begin
33
- node = parse(input, :index => @index)
34
- return nil unless node
35
- node = block.call(node) if block
36
- results << node if node
37
- end until self.index == @input_length
38
- results
39
- end
22
+ module CQL
23
+ # Extend the generated parser:
24
+ class Parser < CQLParser
25
+ include ActiveFacts
26
+
27
+ class BlackHole
28
+ def method_missing(m, *p, &b)
29
+ self # Make all calls vanish
30
+ end
31
+ end
32
+
33
+ class InputProxy < Object
34
+ attr_reader :context
35
+
36
+ def initialize(input, context)
37
+ @input = input
38
+ @context = context
39
+ end
40
+
41
+ def length
42
+ @input.length
43
+ end
44
+
45
+ def size
46
+ length
47
+ end
48
+
49
+ def [](*a)
50
+ @input[*a]
51
+ end
52
+
53
+ def index(*a)
54
+ @input.index(*a)
55
+ end
56
+ end
57
+
58
+ def context
59
+ @context ||= BlackHole.new
60
+ end
40
61
 
41
- def definition(node)
42
- name, ast = *node.value
43
- kind, *value = *ast
44
-
45
- begin
46
- debug "CQL: Processing definition #{[kind, name].compact*" "}" do
47
- case kind
48
- when :vocabulary
49
- [kind, name]
50
- when :data_type
51
- data_type(name, value)
52
- when :entity_type
53
- supertypes = value.shift
54
- entity_type(name, supertypes, value)
55
- when :fact_type
56
- f = fact_type(name, value)
57
- when :constraint
58
- ast
62
+ def parse(input, options = {})
63
+ input = InputProxy.new(input, context) unless input.respond_to?(:context)
64
+ super(input, options)
65
+ end
66
+
67
+ # Repeatedly parse rule_name until all input is consumed,
68
+ # returning an array of syntax trees for each definition.
69
+ def parse_all(input, rule_name = nil, &block)
70
+ self.root = rule_name if rule_name
71
+
72
+ @index = 0 # Byte offset to start next parse
73
+ self.consume_all_input = false
74
+ results = []
75
+ begin
76
+ node = parse(InputProxy.new(input, context), :index => @index)
77
+ return nil unless node
78
+ node = block.call(node) if block
79
+ results << node if node
80
+ end until self.index == @input_length
81
+ results
82
+ end
83
+
84
+ def definition(node)
85
+ name, ast = *node.value
86
+ kind, *value = *ast
87
+
88
+ begin
89
+ debug "CQL: Processing definition #{[kind, name].compact*" "}" do
90
+ case kind
91
+ when :vocabulary
92
+ [kind, name]
93
+ when :value_type
94
+ value_type_ast(name, value)
95
+ when :entity_type
96
+ supertypes = value.shift
97
+ entity_type_ast(name, supertypes, value)
98
+ when :fact_type
99
+ f = fact_type_ast(name, value)
100
+ when :unit
101
+ ast
102
+ when :constraint
103
+ ast
104
+ else
105
+ raise "CQL: internal error, unknown definition kind"
106
+ end
59
107
  end
60
108
  end
109
+ rescue => e
110
+ raise "in #{kind.to_s.camelcase(true)} definition, #{e.message}:\n\t#{node.text_value}" +
111
+ (ENV['DEBUG'] =~ /\bexception\b/ ? "\nfrom\t"+e.backtrace*"\n\t" : "")
61
112
  end
62
- rescue => e
63
- raise "in #{kind.to_s.camelcase(true)} definition, #{e.message}:\n\t#{node.text_value}"
64
- end
65
113
 
66
- def data_type(name, value)
67
- # REVISIT: Massage/check data type here?
68
- [:data_type, name, *value]
69
- end
114
+ def value_type_ast(name, value)
115
+ # REVISIT: Massage/check value type here?
116
+ [:value_type, name, *value]
117
+ end
70
118
 
71
- def entity_type(name, supertypes, value)
72
- #print "entity_type parameters for #{name}: "; p value
73
- identification, clauses = *value
74
- clauses ||= []
119
+ def entity_type_ast(name, supertypes, value)
120
+ #print "entity_type parameters for #{name}: "; p value
121
+ identification, mapping_pragmas, clauses = *value
122
+ clauses ||= []
75
123
 
76
- # raise "Entity type clauses must all be fact types" if clauses.detect{|c| c[0] != :fact_clause }
124
+ # raise "Entity type clauses must all be fact types" if clauses.detect{|c| c[0] != :fact_clause }
77
125
 
78
- [:entity_type, name, supertypes, identification, clauses]
79
- end
126
+ [:entity_type, name, supertypes, identification, mapping_pragmas, clauses]
127
+ end
128
+
129
+ def fact_type_ast(name, value)
130
+ clauses, conditions = value
131
+
132
+ if conditions.empty? && includes_literals(clauses)
133
+ [:fact, nil, clauses]
134
+ elsif clauses.size == 1 &&
135
+ (popname = clauses[0][2]).size == 1 &&
136
+ popname[0].keys == [:word] &&
137
+ includes_literals(conditions)
138
+ [:fact, popname[0][:word], conditions]
139
+ else
140
+ [:fact_type, name, clauses, conditions]
141
+ end
142
+ end
80
143
 
81
- def fact_type(name, value)
82
- defined_readings, *clauses = value
144
+ def includes_literals(clauses)
145
+ clauses.detect do |clause|
146
+ raise "alternate clauses are not yet supported" if clause[0] == :"||"
147
+ fc, qualifiers, phrases, context_note = *clause
148
+ phrases.detect{|w| w[:literal]}
149
+ end
150
+ end
83
151
 
84
- [:fact_type, name, defined_readings, clauses]
85
152
  end
86
153
 
87
154
  end
88
155
 
89
- Polyglot.register('cql', CQLParser)
156
+ Polyglot.register('cql', CQL::Parser)
90
157
  end
@@ -34,7 +34,7 @@ module ActiveFacts
34
34
  multi_absorption_vts = 0
35
35
  multi_absorption_ets = 0
36
36
  @vocabulary.tables
37
- @vocabulary.all_feature.sort_by{|c| c.name}.each do |o|
37
+ @vocabulary.all_concept.sort_by{|c| c.name}.each do |o|
38
38
  next if !o.is_table
39
39
  show(o)
40
40
  end
@@ -31,33 +31,22 @@ module ActiveFacts
31
31
 
32
32
  def value_type_dump(o)
33
33
  return unless o.supertype # An imported type
34
+
35
+ # REVISIT: A ValueType that is only used as a reference mode need not be emitted here. We haven't detected this situation yet however...
36
+
34
37
  if o.name == o.supertype.name
35
- # In ActiveFacts, parameterising a ValueType will create a new datatype
36
- # throw Can't handle parameterized value type of same name as its datatype" if ...
38
+ # In ActiveFacts, parameterising a ValueType will create a new ValueType
39
+ # throw Can't handle parameterized value type of same name as its ValueType" if ...
37
40
  end
38
41
 
39
42
  parameters =
40
43
  [ o.length != 0 || o.scale != 0 ? o.length : nil,
41
44
  o.scale != 0 ? o.scale : nil
42
45
  ].compact
43
- parameters = parameters.length > 0 ? "("+parameters.join(",")+")" : "()"
44
-
45
- #" restricted to {#{(allowed_values.map{|r| r.inspect}*", ").gsub('"',"'")}}")
46
+ parameters = parameters.length > 0 ? "("+parameters.join(",")+")" : ""
46
47
 
47
48
  puts "#{o.name} is written as #{o.supertype.name}#{ parameters }#{
48
- o.value_restriction ? " restricted to {#{
49
- o.value_restriction.all_allowed_range.sort_by{|ar|
50
- ((min = ar.value_range.minimum_bound) && min.value) ||
51
- ((max = ar.value_range.maximum_bound) && max.value)
52
- }.map{|ar|
53
- # REVISIT: Need to display as string or numeric according to type here...
54
- min = ar.value_range.minimum_bound
55
- max = ar.value_range.maximum_bound
56
-
57
- (min ? min.value : "") +
58
- (min.value != (max&&max.value) ? (".." + (max ? max.value : "")) : "")
59
- }*", "
60
- }}" : ""
49
+ o.value_restriction && " "+o.value_restriction.describe
61
50
  };"
62
51
  end
63
52
 
@@ -65,6 +54,18 @@ module ActiveFacts
65
54
  reading << " [#{(ring.ring_type.scan(/[A-Z][a-z]*/)*", ").downcase}]"
66
55
  end
67
56
 
57
+ def mapping_pragma(entity_type)
58
+ ti = entity_type.all_type_inheritance_as_subtype
59
+ assimilation = ti.map{|t| t.assimilation }.compact[0]
60
+ return "" unless entity_type.is_independent || assimilation
61
+ " [" +
62
+ [
63
+ entity_type.is_independent ? "independent" : nil,
64
+ assimilation || nil
65
+ ].compact*", " +
66
+ "]"
67
+ end
68
+
68
69
  def identified_by_roles_and_facts(entity_type, identifying_roles, identifying_facts, preferred_readings)
69
70
  identifying_role_names = identifying_roles.map{|role|
70
71
  preferred_role_ref = preferred_readings[role.fact_type].role_sequence.all_role_ref.detect{|reading_rr|
@@ -94,13 +95,16 @@ module ActiveFacts
94
95
  # Just beware that readings having the same players will be considered to be of the same fact type, even if they're not.
95
96
 
96
97
  # Detect standard reference-mode scenarios
97
- ft = identifying_facts[0]
98
+ external_identifying_facts = identifying_facts - [entity_type.fact_type]
99
+ ft = external_identifying_facts[0]
98
100
  fact_constraints = nil
99
- ftr = ft.all_role.sort_by{|role| role.ordinal}
100
- if identifying_facts.size == 1 and
101
+ ftr = ft && ft.all_role.sort_by{|role| role.ordinal}
102
+ if external_identifying_facts.size == 1 and
101
103
  entity_role = ftr[n = (ftr[0].concept == entity_type ? 0 : 1)] and
102
104
  value_role = ftr[1-n] and
103
- value_name = value_role.concept.name and
105
+ value_player = value_role.concept and
106
+ value_player.is_a?(ActiveFacts::Metamodel::ValueType) and
107
+ value_name = value_player.name and
104
108
  residual = value_name.gsub(%r{#{entity_role.concept.name}},'') and
105
109
  residual != '' and
106
110
  residual != value_name
@@ -111,13 +115,13 @@ module ActiveFacts
111
115
  # Detect standard reference-mode readings:
112
116
  forward_reading = reverse_reading = nil
113
117
  ft.all_reading.each do |reading|
114
- if reading.reading_text =~ /^\{(\d)\} has \{\d\}$/
118
+ if reading.text =~ /^\{(\d)\} has \{\d\}$/
115
119
  if reading.role_sequence.all_role_ref.detect{|rr| rr.ordinal == $1.to_i}.role == entity_role
116
120
  forward_reading = reading
117
121
  else
118
122
  reverse_reading = reading
119
123
  end
120
- elsif reading.reading_text =~ /^\{(\d)\} is of \{\d\}$/
124
+ elsif reading.text =~ /^\{(\d)\} is of \{\d\}$/
121
125
  if reading.role_sequence.all_role_ref.detect{|rr| rr.ordinal == $1.to_i}.role == value_role
122
126
  reverse_reading = reading
123
127
  else
@@ -141,6 +145,7 @@ module ActiveFacts
141
145
  @constraints_used[pc] = true
142
146
  end
143
147
  end
148
+ fact_constraints += Array(@presence_constraints_by_fact[entity_type.fact_type])
144
149
 
145
150
  @fact_types_dumped[ft] = true
146
151
 
@@ -148,10 +153,19 @@ module ActiveFacts
148
153
  other_readings = ft.all_reading - [forward_reading] - [reverse_reading]
149
154
  debug :mode, "--- other_readings.size now = #{other_readings.size}" if other_readings.size > 0
150
155
 
151
- fact_text = other_readings.map do |reading|
152
- expanded_reading(reading, fact_constraints, true)
153
- end*",\n\t"
154
- return " identified by its #{residual}" +
156
+ fact_text = (
157
+ other_readings.map do |reading|
158
+ expanded_reading(reading, fact_constraints, true)
159
+ end +
160
+ (entity_type.fact_type ?
161
+ fact_readings_with_constraints(entity_type.fact_type, fact_constraints) : []
162
+ )
163
+ )*",\n\t"
164
+
165
+ restriction = value_role.role_value_restriction || value_player.value_restriction
166
+ # REVISIT: If both restrictions apply and differ, we can't use a reference mode
167
+ restriction_text = restriction ? " "+restriction.describe : ""
168
+ return " identified by its #{residual}#{restriction_text}#{mapping_pragma(entity_type)}" +
155
169
  (fact_text != "" ? " where\n\t" + fact_text : "")
156
170
  end
157
171
  end
@@ -163,6 +177,7 @@ module ActiveFacts
163
177
  }.flatten*",\n\t"
164
178
 
165
179
  " identified by #{ identifying_role_names*" and " }" +
180
+ mapping_pragma(entity_type) +
166
181
  " where\n\t"+@identifying_fact_text
167
182
  end
168
183
 
@@ -183,7 +198,10 @@ module ActiveFacts
183
198
  print "#{o.name} is a kind of #{ o.supertypes.map(&:name)*", " }"
184
199
  if pi
185
200
  print identified_by(o, pi)
201
+ else
202
+ print mapping_pragma(o)
186
203
  end
204
+
187
205
  # If there's a preferred_identifier for this subtype, identifying readings were emitted
188
206
  print((pi ? "," : " where") + "\n\t" + fact_readings(o.fact_type)) if o.fact_type
189
207
  puts ";\n"
@@ -191,7 +209,7 @@ module ActiveFacts
191
209
 
192
210
  def non_subtype_dump(o, pi)
193
211
  print "#{o.name} is" + identified_by(o, pi)
194
- print(" where\n\t"+ fact_readings(o.fact_type)) if o.fact_type
212
+ # print(" where\n\t"+ fact_readings(o.fact_type)) if o.fact_type
195
213
  puts ";\n"
196
214
  end
197
215
 
@@ -209,6 +227,7 @@ module ActiveFacts
209
227
  pi = fact_type.entity_type.preferred_identifier
210
228
  if pi && primary_supertype && primary_supertype.preferred_identifier != pi
211
229
  print identified_by(o, pi)
230
+ # REVISIT: This *has* to be wrong. When you fix it, remember mapping_pragmas!
212
231
  print ";\n"
213
232
  end
214
233
  end
@@ -244,55 +263,25 @@ module ActiveFacts
244
263
  end
245
264
 
246
265
  def dump_presence_constraint(c)
247
- roles = c.role_sequence.all_role_ref.map{|rr| rr.role }
248
-
249
- # REVISIT: If only one role is covered and it's mandatory >=1 constraint, use SOME/THAT form:
250
- # each Bug SOME Tester logged THAT Bug;
251
- players = c.role_sequence.all_role_ref.map{|rr| rr.role.concept.name}.uniq
252
-
253
- fact_types = c.role_sequence.all_role_ref.map{|rr| rr.role.fact_type}.uniq
254
- puts \
255
- "each #{players.size > 1 ? "combination " : ""}#{players*", "} occurs #{c.frequency} time in\n\t"+
256
- "#{fact_types.map{|ft| ft.default_reading([], nil)}*",\n\t"}" +
257
- ";"
258
-
259
- =begin
260
- # More than one fact type involved, an external constraint.
261
- fact_type = rr.role.fact_type
262
- # or all facts are binary and the counterparts of the roles are.
263
- puts "// REVISIT: " +
264
- if (player = roleplayer_subclass(roles))
265
- "#{player.name} must play #{c.frequency} of "
266
- else
267
- counterparts = roles.map{|r|
268
- r.fact_type.all_role[r.fact_type.all_role[0] != r ? 0 : -1]
269
- }
270
- player = roleplayer_subclass(counterparts)
271
- "#{c.frequency} #{player ? player.name : "UNKNOWN" } exists for each "
272
- end +
273
- "#{
274
- c.role_sequence.all_role_ref.map{|rr|
275
- "'#{rr.role.fact_type.default_reading([], nil)}'"
276
- }*", "
277
- }"
278
- =end
279
-
280
- =begin
281
- puts \
282
- "FOR each #{players*", "}" +
283
- (c.role_sequence.all_role_ref.size > 1 ? " "+c.frequency+" of these holds" : "") + "\n\t"+
284
- "#{c.role_sequence.all_role_ref.map{|rr|
285
- role = rr.role
286
- fact_type = role.fact_type
287
- some_that = Array.new(fact_type.all_role.size, "some")
288
- c.role_sequence.all_role_ref.each{|rr2|
289
- next if rr2.role.fact_type != fact_type
290
- some_that[fact_type.all_role.index(role)] = "that"
291
- }
292
- rr.role.fact_type.default_reading(some_that, nil)
293
- }*",\n\t"}" +
294
- ";"
295
- =end
266
+ if c.min_frequency == 1 && c.max_frequency == nil and c.role_sequence.all_role_ref.size == 2
267
+ # REVISIT: Implement the "either... or" syntax for a simple external mandatory constraint
268
+ puts \
269
+ "either #{
270
+ c.role_sequence.all_role_ref.map { |rr|
271
+ rr.role.fact_type.default_reading([], nil)
272
+ }*" or "
273
+ };"
274
+ else
275
+ # REVISIT: If only one role is covered and it's mandatory >=1 constraint, use SOME/THAT form:
276
+ # for each Bug SOME Tester logged THAT Bug;
277
+ roles = c.role_sequence.all_role_ref.map{|rr| rr.role }
278
+ players = c.role_sequence.all_role_ref.map{|rr| rr.role.concept.name}.uniq
279
+ fact_types = c.role_sequence.all_role_ref.map{|rr| rr.role.fact_type}.uniq
280
+ puts \
281
+ "each #{players.size > 1 ? "combination " : ""}#{players*", "} occurs #{c.frequency} time in\n\t"+
282
+ "#{fact_types.map{|ft| ft.default_reading([], nil)}*",\n\t"}" +
283
+ ";"
284
+ end
296
285
  end
297
286
 
298
287
  # Find the common supertype of these concepts.
@@ -331,11 +320,13 @@ module ActiveFacts
331
320
  # puts "#{c.class.basename} has #{role_seq_count} scr's: #{scrs.map{|scr| "("+scr.role_sequence.all_role_ref.map{|rr| rr.role.concept.name}*", "+")"}*", "}"
332
321
 
333
322
  players_differ = [] # Record which players are also played by subclasses
334
- players = (0...player_count).map do |pi|
335
- # Find the common supertype of the players of the pi'th role in each sequence
336
- concepts = scrs.map{|r| r.role_sequence.all_role_ref.sort_by{|rr| rr.ordinal}[pi].role.concept }
337
- player, players_differ[pi] = common_supertype(concepts)
338
- raise "Role sequences of #{c.class.basename} must have concepts matching #{concepts.map(&:name)*","} in position #{pi}" unless player
323
+ players = (0...player_count).map do |pindex|
324
+ # Find the common supertype of the players of the pindex'th role in each sequence
325
+ concepts = scrs.map do |r|
326
+ r.role_sequence.all_role_ref.sort_by{|rr| rr.ordinal}[pindex].role.concept
327
+ end
328
+ player, players_differ[pindex] = common_supertype(concepts)
329
+ raise "Role sequences of #{c.class.basename} must have concepts matching #{concepts.map(&:name)*","} in position #{pindex}" unless player
339
330
  player
340
331
  end
341
332
  #puts "#{c.class.basename} has players #{players.map{|p| p.name}*", "}"
@@ -349,22 +340,42 @@ module ActiveFacts
349
340
  return
350
341
  end
351
342
 
352
- mode = c.is_mandatory ? "exactly one" : "at most one"
353
- puts "for each #{players.map{|p| p.name}*", "} #{mode} of these holds:\n\t" +
354
- (scrs.map do |scr|
355
- constrained_roles = scr.role_sequence.all_role_ref.map{|rr| rr.role }
356
- fact_types = constrained_roles.map{|r| r.fact_type }.uniq
357
-
358
- fact_types.map do |fact_type|
359
- # REVISIT: future: Use "THAT" and "SOME" only when:
360
- # - the role player occurs twice in the reading, or
361
- # - is a subclass of the constrained concept, or
362
- reading = fact_type.preferred_reading
363
- expand_constrained(reading, constrained_roles, players, players_differ)
364
- end * " and "
365
-
366
- end*",\n\t"
367
- )+';'
343
+ if scrs.size == 2 && c.is_mandatory
344
+ puts "either " +
345
+ ( scrs.map do |scr|
346
+ constrained_roles = scr.role_sequence.all_role_ref.map{|rr| rr.role }
347
+ fact_types = constrained_roles.map{|r| r.fact_type }.uniq
348
+
349
+ fact_types.map do |fact_type|
350
+ # Choose a reading that starts with the input role (constrained role if none)
351
+ reading = fact_type.all_reading.sort_by{|r| r.ordinal}.detect do |r|
352
+ first_reading_role = r.role_sequence.all_role_ref.detect{|rr| rr.ordinal == 0}.role
353
+ constrained_roles.include?(first_reading_role)
354
+ end
355
+ reading ||= fact_type.preferred_reading
356
+ expand_constrained(reading, constrained_roles, players, players_differ)
357
+ end * " and "
358
+ end*" or "
359
+ ) +
360
+ " but not both;"
361
+ else
362
+ mode = c.is_mandatory ? "exactly one" : "at most one"
363
+ puts "for each #{players.map{|p| p.name}*", "} #{mode} of these holds:\n\t" +
364
+ (scrs.map do |scr|
365
+ constrained_roles = scr.role_sequence.all_role_ref.map{|rr| rr.role }
366
+ fact_types = constrained_roles.map{|r| r.fact_type }.uniq
367
+
368
+ fact_types.map do |fact_type|
369
+ # REVISIT: future: Use "THAT" and "SOME" only when:
370
+ # - the role player occurs twice in the reading, or
371
+ # - is a subclass of the constrained concept, or
372
+ reading = fact_type.preferred_reading
373
+ expand_constrained(reading, constrained_roles, players, players_differ)
374
+ end * " and "
375
+
376
+ end*",\n\t"
377
+ )+';'
378
+ end
368
379
  end
369
380
 
370
381
  # Expand this reading using (in)definite articles where needed
@@ -386,7 +397,7 @@ module ActiveFacts
386
397
  }
387
398
  frequency_constraints = [] unless frequency_constraints.detect{|fc| fc[0] != "some" }
388
399
 
389
- #$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}"
400
+ #$stderr.puts "fact_type roles (#{fact_type.all_role.map{|r| r.concept.name}*","}) default_reading '#{fact_type.preferred_reading.text}' roles (#{fact_type.preferred_reading.role_sequence.all_role_ref.map{|rr| rr.role.concept.name}*","}) #{frequency_constraints.inspect}"
390
401
 
391
402
  # REVISIT: Make sure that we refer to the constrained players by their common supertype
392
403