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.
- data/README.rdoc +0 -3
- data/Rakefile +7 -5
- data/bin/afgen +5 -2
- data/bin/cql +3 -2
- data/examples/CQL/Genealogy.cql +3 -3
- data/examples/CQL/Metamodel.cql +10 -7
- data/examples/CQL/MultiInheritance.cql +2 -1
- data/examples/CQL/OilSupply.cql +4 -4
- data/examples/CQL/Orienteering.cql +2 -2
- data/lib/activefacts.rb +2 -1
- data/lib/activefacts/api.rb +21 -2
- data/lib/activefacts/api/concept.rb +52 -39
- data/lib/activefacts/api/constellation.rb +8 -6
- data/lib/activefacts/api/entity.rb +41 -37
- data/lib/activefacts/api/instance.rb +5 -3
- data/lib/activefacts/api/numeric.rb +28 -21
- data/lib/activefacts/api/role.rb +29 -43
- data/lib/activefacts/api/standard_types.rb +8 -3
- data/lib/activefacts/api/support.rb +4 -4
- data/lib/activefacts/api/value.rb +9 -3
- data/lib/activefacts/api/vocabulary.rb +17 -7
- data/lib/activefacts/cql.rb +10 -7
- data/lib/activefacts/cql/CQLParser.treetop +6 -0
- data/lib/activefacts/cql/Concepts.treetop +32 -26
- data/lib/activefacts/cql/DataTypes.treetop +6 -0
- data/lib/activefacts/cql/Expressions.treetop +6 -0
- data/lib/activefacts/cql/FactTypes.treetop +6 -0
- data/lib/activefacts/cql/Language/English.treetop +9 -3
- data/lib/activefacts/cql/LexicalRules.treetop +6 -0
- data/lib/activefacts/cql/Rakefile +8 -0
- data/lib/activefacts/cql/parser.rb +4 -2
- data/lib/activefacts/generate/absorption.rb +20 -28
- data/lib/activefacts/generate/cql.rb +28 -16
- data/lib/activefacts/generate/cql/html.rb +327 -321
- data/lib/activefacts/generate/null.rb +7 -3
- data/lib/activefacts/generate/oo.rb +19 -15
- data/lib/activefacts/generate/ordered.rb +457 -461
- data/lib/activefacts/generate/ruby.rb +12 -4
- data/lib/activefacts/generate/sql/server.rb +42 -10
- data/lib/activefacts/generate/text.rb +7 -3
- data/lib/activefacts/input/cql.rb +55 -28
- data/lib/activefacts/input/orm.rb +32 -22
- data/lib/activefacts/persistence.rb +5 -0
- data/lib/activefacts/persistence/columns.rb +66 -32
- data/lib/activefacts/persistence/foreignkey.rb +29 -5
- data/lib/activefacts/persistence/index.rb +57 -25
- data/lib/activefacts/persistence/reference.rb +65 -30
- data/lib/activefacts/persistence/tables.rb +28 -17
- data/lib/activefacts/support.rb +8 -0
- data/lib/activefacts/version.rb +7 -1
- data/lib/activefacts/vocabulary.rb +4 -2
- data/lib/activefacts/vocabulary/extensions.rb +12 -10
- data/lib/activefacts/vocabulary/metamodel.rb +24 -23
- data/spec/api/autocounter.rb +2 -2
- data/spec/api/entity_type.rb +2 -2
- data/spec/api/instance.rb +61 -30
- data/spec/api/roles.rb +9 -9
- data/spec/cql_parse_spec.rb +1 -0
- data/spec/norma_tables_spec.rb +3 -3
- metadata +8 -4
@@ -1,13 +1,19 @@
|
|
1
1
|
#
|
2
|
-
#
|
3
|
-
#
|
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.
|
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 =
|
94
|
-
value_role =
|
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
|
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
|
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.
|
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 #{
|
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 =
|
391
|
-
|
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
|
-
#
|
3
|
-
#
|
2
|
+
# ActiveFacts Generators.
|
3
|
+
# Generate HTML-highlighted CQL from an ActiveFacts vocabulary.
|
4
4
|
#
|
5
|
-
#
|
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
|
-
|
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
|
-
|
22
|
-
|
23
|
-
|
30
|
+
def puts s
|
31
|
+
super(s.gsub(/[,;]/) do |p| keyword p; end)
|
32
|
+
end
|
24
33
|
|
25
|
-
|
26
|
-
|
27
|
-
|
34
|
+
def keyword(str)
|
35
|
+
"<span class='keyword'>#{str}</span>"
|
36
|
+
end
|
28
37
|
|
29
|
-
|
30
|
-
|
31
|
-
|
38
|
+
def concept(str)
|
39
|
+
"<span class='concept'>#{str}</span>"
|
40
|
+
end
|
32
41
|
|
33
|
-
|
34
|
-
|
35
|
-
|
42
|
+
def copula(str)
|
43
|
+
"<span class='copula'>#{str}</span>"
|
44
|
+
end
|
36
45
|
|
37
|
-
|
38
|
-
|
39
|
-
|
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
|
-
|
42
|
-
|
43
|
-
|
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
|
-
|
50
|
-
|
51
|
-
|
58
|
+
def value_type_banner
|
59
|
+
puts "/*\n * Value Types\n */"
|
60
|
+
end
|
52
61
|
|
53
|
-
|
54
|
-
|
55
|
-
|
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
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
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
|
-
|
65
|
-
|
66
|
-
|
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
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
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
|
-
|
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
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
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
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
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
|
-
|
173
|
+
@fact_types_dumped[ft] = true
|
169
174
|
|
170
|
-
|
171
|
-
|
172
|
-
|
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
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
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
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
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
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
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
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
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
|
-
|
201
|
-
|
202
|
-
|
205
|
+
def entity_type_banner
|
206
|
+
puts(keyword("/*\n * Entity Types\n */"))
|
207
|
+
end
|
203
208
|
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
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
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
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
|
-
|
215
|
-
|
216
|
-
print(
|
217
|
-
(
|
218
|
-
|
219
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
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
|
-
|
242
|
-
|
243
|
-
|
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
|
-
|
251
|
-
|
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
|
-
|
257
|
-
|
258
|
-
|
265
|
+
def constraint_banner
|
266
|
+
puts keyword("/*\n * Constraints:\n */")
|
267
|
+
end
|
259
268
|
|
260
|
-
|
261
|
-
|
262
|
-
end
|
269
|
+
def dump_presence_constraint(c)
|
270
|
+
roles = c.role_sequence.all_role_ref.map{|rr| rr.role }
|
263
271
|
|
264
|
-
|
265
|
-
|
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
|
-
|
268
|
-
|
269
|
-
|
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
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
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
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
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
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
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
|
-
|
314
|
-
|
315
|
-
|
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
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
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
|
-
|
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
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
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
|
-
|
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
|
-
|
361
|
+
# REVISIT: Make sure that we refer to the constrained players by their common supertype
|
357
362
|
|
358
|
-
|
359
|
-
|
363
|
+
reading.expand(frequency_constraints, nil)
|
364
|
+
end
|
360
365
|
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
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
|