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