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,14 +1,18 @@
|
|
1
1
|
#
|
2
|
-
#
|
2
|
+
# ActiveFacts Generators.
|
3
|
+
# Generate *no* output for ActiveFacts vocabularies; i.e. just a stub
|
3
4
|
#
|
4
|
-
# Copyright (c)
|
5
|
-
# Author: Clifford Heath.
|
5
|
+
# Copyright (c) 2009 Clifford Heath. Read the LICENSE file.
|
6
6
|
#
|
7
7
|
require 'activefacts/persistence'
|
8
8
|
|
9
9
|
module ActiveFacts
|
10
10
|
module Generate
|
11
|
+
# Generate nothing from an ActiveFacts vocabulary. This is useful to check the file can be read ok.
|
12
|
+
# Invoke as
|
13
|
+
# afgen --null <file>.cql
|
11
14
|
class NULL
|
15
|
+
private
|
12
16
|
def initialize(vocabulary, *options)
|
13
17
|
@vocabulary = vocabulary
|
14
18
|
@vocabulary = @vocabulary.Vocabulary.values[0] if ActiveFacts::API::Constellation === @vocabulary
|
@@ -1,14 +1,16 @@
|
|
1
1
|
#
|
2
|
-
#
|
3
|
-
#
|
2
|
+
# ActiveFacts Generators.
|
3
|
+
# Base class for generators of class libraries in any object-oriented language that supports the ActiveFacts API.
|
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
|
-
|
10
11
|
module Generate
|
11
|
-
class
|
12
|
+
# Base class for generators of object-oriented class libraries for an ActiveFacts vocabulary.
|
13
|
+
class OO < OrderedDumper #:nodoc:
|
12
14
|
include Metamodel
|
13
15
|
|
14
16
|
def constraints_dump(constraints_used)
|
@@ -23,13 +25,12 @@ module ActiveFacts
|
|
23
25
|
|
24
26
|
def roles_dump(o)
|
25
27
|
o.all_role.
|
28
|
+
select{|role|
|
29
|
+
role.fact_type.all_role.size <= 2
|
30
|
+
}.
|
26
31
|
sort_by{|role|
|
27
|
-
|
28
|
-
other_role ? preferred_role_name(other_role) : ""
|
29
|
-
#puts "\t#{role.fact_type.describe(other_role)} by #{p}"
|
32
|
+
preferred_role_name(role.fact_type.all_role.select{|r2| r2 != role}[0] || role)
|
30
33
|
}.each{|role|
|
31
|
-
fact_type = role.fact_type
|
32
|
-
next if fact_type.all_role.size > 2
|
33
34
|
role_dump(role)
|
34
35
|
}
|
35
36
|
end
|
@@ -49,8 +50,7 @@ module ActiveFacts
|
|
49
50
|
return
|
50
51
|
end
|
51
52
|
|
52
|
-
|
53
|
-
other_role = fact_type.all_role[other_role_number]
|
53
|
+
other_role = fact_type.all_role.select{|r| r != role}[0]
|
54
54
|
other_role_name = preferred_role_name(other_role)
|
55
55
|
other_player = other_role.concept
|
56
56
|
|
@@ -80,9 +80,13 @@ module ActiveFacts
|
|
80
80
|
|
81
81
|
# Find role name:
|
82
82
|
role_method = preferred_role_name(role)
|
83
|
-
|
83
|
+
as = other_role_name != other_player.name.snakecase ? "_as_#{other_role_name}" : ""
|
84
84
|
other_role_method = one_to_one ? role_method : "all_"+role_method
|
85
|
-
|
85
|
+
# puts "---"+role.role_name if role.role_name
|
86
|
+
if other_role_name != other_player.name.snakecase and
|
87
|
+
role_method == role.concept.name.snakecase
|
88
|
+
other_role_method += "_as_#{other_role_name}"
|
89
|
+
end
|
86
90
|
|
87
91
|
role_name = role_method
|
88
92
|
role_name = nil if role_name == role.concept.name.snakecase
|
@@ -134,9 +138,9 @@ module ActiveFacts
|
|
134
138
|
preferred_role_name(role)
|
135
139
|
}.each{|role|
|
136
140
|
role_name = preferred_role_name(role)
|
137
|
-
|
141
|
+
as = role_name != role.concept.name.snakecase ? "_as_#{role_name}" : ""
|
138
142
|
raise "Fact #{fact.describe} type is not objectified" unless fact.entity_type
|
139
|
-
other_role_method = "all_"+fact.entity_type.name.snakecase+
|
143
|
+
other_role_method = "all_"+fact.entity_type.name.snakecase+as
|
140
144
|
binary_dump(role, role_name, role.concept, false, nil, nil, other_role_method)
|
141
145
|
}
|
142
146
|
end
|
@@ -1,557 +1,553 @@
|
|
1
1
|
#
|
2
|
-
#
|
2
|
+
# ActiveFacts Generators.
|
3
|
+
# Generation support superclass that sequences entity types to avoid forward references.
|
3
4
|
#
|
4
|
-
# Copyright (c)
|
5
|
+
# Copyright (c) 2009 Clifford Heath. Read the LICENSE file.
|
5
6
|
#
|
6
7
|
require 'activefacts/api'
|
7
8
|
|
8
9
|
module ActiveFacts
|
10
|
+
module Generate #:nodoc:
|
11
|
+
class OrderedDumper #:nodoc:
|
12
|
+
# Base class for generators of object-oriented class libraries for an ActiveFacts vocabulary.
|
13
|
+
include Metamodel
|
14
|
+
|
15
|
+
def initialize(vocabulary, *options)
|
16
|
+
@vocabulary = vocabulary
|
17
|
+
@vocabulary = @vocabulary.Vocabulary.values[0] if ActiveFacts::API::Constellation === @vocabulary
|
18
|
+
options.each{|option| set_option(option) }
|
19
|
+
end
|
9
20
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
def initialize(vocabulary, *options)
|
14
|
-
@vocabulary = vocabulary
|
15
|
-
@vocabulary = @vocabulary.Vocabulary.values[0] if ActiveFacts::API::Constellation === @vocabulary
|
16
|
-
options.each{|option| set_option(option) }
|
17
|
-
end
|
18
|
-
|
19
|
-
def set_option(option)
|
20
|
-
end
|
21
|
+
def set_option(option)
|
22
|
+
end
|
21
23
|
|
22
|
-
|
23
|
-
|
24
|
-
|
24
|
+
def puts(*a)
|
25
|
+
@out.puts *a
|
26
|
+
end
|
25
27
|
|
26
|
-
|
27
|
-
|
28
|
-
|
28
|
+
def print(*a)
|
29
|
+
@out.print *a
|
30
|
+
end
|
29
31
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
32
|
+
def generate(out = $>)
|
33
|
+
@out = out
|
34
|
+
vocabulary_start(@vocabulary)
|
35
|
+
|
36
|
+
build_indices
|
37
|
+
@concept_types_dumped = {}
|
38
|
+
@fact_types_dumped = {}
|
39
|
+
value_types_dump()
|
40
|
+
entity_types_dump()
|
41
|
+
fact_types_dump()
|
42
|
+
constraints_dump(@constraints_used)
|
43
|
+
vocabulary_end
|
44
|
+
end
|
43
45
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
46
|
+
def build_indices
|
47
|
+
@presence_constraints_by_fact = Hash.new{ |h, k| h[k] = [] }
|
48
|
+
@ring_constraints_by_fact = Hash.new{ |h, k| h[k] = [] }
|
49
|
+
|
50
|
+
@vocabulary.all_constraint.each { |c|
|
51
|
+
case c
|
52
|
+
when PresenceConstraint
|
53
|
+
fact_types = c.role_sequence.all_role_ref.map{|rr| rr.role.fact_type}.uniq # All fact types spanned by this constraint
|
54
|
+
if fact_types.size == 1 # There's only one, save it:
|
55
|
+
# debug "Single-fact constraint on #{fact_types[0].fact_type_id}: #{c.name}"
|
56
|
+
(@presence_constraints_by_fact[fact_types[0]] ||= []) << c
|
57
|
+
end
|
58
|
+
when RingConstraint
|
59
|
+
(@ring_constraints_by_fact[c.role.fact_type] ||= []) << c
|
60
|
+
else
|
61
|
+
# debug "Found unhandled constraint #{c.class} #{c.name}"
|
55
62
|
end
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
# debug "Found unhandled constraint #{c.class} #{c.name}"
|
60
|
-
end
|
61
|
-
}
|
62
|
-
@constraints_used = {}
|
63
|
-
end
|
64
|
-
|
65
|
-
def value_types_dump
|
66
|
-
done_banner = false
|
67
|
-
@vocabulary.all_feature.sort_by{|o| o.name}.each{|o|
|
68
|
-
next unless ValueType === o
|
63
|
+
}
|
64
|
+
@constraints_used = {}
|
65
|
+
end
|
69
66
|
|
70
|
-
|
71
|
-
|
67
|
+
def value_types_dump
|
68
|
+
done_banner = false
|
69
|
+
@vocabulary.all_feature.sort_by{|o| o.name}.each{|o|
|
70
|
+
next unless ValueType === o
|
72
71
|
|
73
|
-
|
74
|
-
|
75
|
-
}
|
76
|
-
value_type_end if done_banner
|
77
|
-
end
|
72
|
+
value_type_banner unless done_banner
|
73
|
+
done_banner = true
|
78
74
|
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
precursors, followers = *build_entity_dependencies
|
85
|
-
|
86
|
-
done_banner = false
|
87
|
-
sorted = @vocabulary.all_feature.select{|o| EntityType === o and !o.fact_type }.sort_by{|o| o.name}
|
88
|
-
panic = nil
|
89
|
-
while true do
|
90
|
-
count_this_pass = 0
|
91
|
-
skipped_this_pass = 0
|
92
|
-
sorted.each{|o|
|
93
|
-
next if @concept_types_dumped[o] # Already done
|
94
|
-
|
95
|
-
# Can we do this yet?
|
96
|
-
if (o != panic and # We don't *have* to do it (panic mode)
|
97
|
-
(p = precursors[o]) and # There might be...
|
98
|
-
p.size > 0) # precursors - still blocked
|
99
|
-
skipped_this_pass += 1
|
100
|
-
next
|
101
|
-
end
|
75
|
+
value_type_dump(o)
|
76
|
+
@concept_types_dumped[o] = true
|
77
|
+
}
|
78
|
+
value_type_end if done_banner
|
79
|
+
end
|
102
80
|
|
103
|
-
|
104
|
-
|
81
|
+
# Try to dump entity types in order of name, but we need
|
82
|
+
# to dump ETs before they're referenced in preferred ids
|
83
|
+
# if possible (it's not always, there may be loops!)
|
84
|
+
def entity_types_dump
|
85
|
+
# Build hash tables of precursors and followers to use:
|
86
|
+
precursors, followers = *build_entity_dependencies
|
87
|
+
|
88
|
+
done_banner = false
|
89
|
+
sorted = @vocabulary.all_feature.select{|o| EntityType === o and !o.fact_type }.sort_by{|o| o.name}
|
90
|
+
panic = nil
|
91
|
+
while true do
|
92
|
+
count_this_pass = 0
|
93
|
+
skipped_this_pass = 0
|
94
|
+
sorted.each{|o|
|
95
|
+
next if @concept_types_dumped[o] # Already done
|
96
|
+
|
97
|
+
# Can we do this yet?
|
98
|
+
if (o != panic and # We don't *have* to do it (panic mode)
|
99
|
+
(p = precursors[o]) and # There might be...
|
100
|
+
p.size > 0) # precursors - still blocked
|
101
|
+
skipped_this_pass += 1
|
102
|
+
next
|
103
|
+
end
|
104
|
+
|
105
|
+
entity_type_banner unless done_banner
|
106
|
+
done_banner = true
|
105
107
|
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
108
|
+
# We're going to emit o - remove it from precursors of others:
|
109
|
+
(followers[o]||[]).each{|f|
|
110
|
+
precursors[f] -= [o]
|
111
|
+
}
|
112
|
+
count_this_pass += 1
|
113
|
+
panic = nil
|
112
114
|
|
113
|
-
|
114
|
-
|
115
|
+
entity_type_dump(o)
|
116
|
+
released_fact_types_dump(o)
|
115
117
|
|
116
|
-
|
117
|
-
|
118
|
+
entity_type_group_end
|
119
|
+
}
|
118
120
|
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
121
|
+
# Check that we made progress if there's any to make:
|
122
|
+
if count_this_pass == 0 && skipped_this_pass > 0
|
123
|
+
if panic # We were already panicing... what to do now?
|
124
|
+
# This won't happen again unless the above code is changed to decide it can't dump "panic".
|
125
|
+
raise "Unresolvable cycle of forward references: " +
|
126
|
+
(bad = sorted.select{|o| EntityType === o && !@concept_types_dumped[o]}).map{|o| o.name }.inspect +
|
127
|
+
":\n\t" + bad.map{|o|
|
128
|
+
o.name +
|
129
|
+
": " +
|
130
|
+
precursors[o].map{|p| p.name}.uniq.inspect
|
131
|
+
} * "\n\t" + "\n"
|
132
|
+
else
|
133
|
+
# Find the object that has the most followers and no fwd-ref'd supertypes:
|
134
|
+
# This selection might be better if we allow PI roles to be fwd-ref'd...
|
135
|
+
panic = sorted.
|
136
|
+
select{|o| !@concept_types_dumped[o] }.
|
137
|
+
sort_by{|o|
|
138
|
+
f = followers[o] || [];
|
139
|
+
o.supertypes.detect{|s| !@concept_types_dumped[s] } ? 0 : -f.size
|
140
|
+
}[0]
|
141
|
+
# debug "Panic mode, selected #{panic.name} next"
|
142
|
+
end
|
140
143
|
end
|
141
|
-
end
|
142
144
|
|
143
|
-
|
145
|
+
break if skipped_this_pass == 0 # All done.
|
144
146
|
|
147
|
+
end
|
145
148
|
end
|
146
|
-
end
|
147
149
|
|
148
|
-
|
149
|
-
|
150
|
-
|
150
|
+
def entity_type_dump(o)
|
151
|
+
@concept_types_dumped[o] = true
|
152
|
+
pi = o.preferred_identifier
|
151
153
|
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
154
|
+
supers = o.supertypes
|
155
|
+
if (supers.size > 0)
|
156
|
+
# Ignore identification by a supertype:
|
157
|
+
pi = nil if pi && pi.role_sequence.all_role_ref.detect{|rr| rr.role.fact_type.is_a?(TypeInheritance) }
|
158
|
+
subtype_dump(o, supers, pi)
|
159
|
+
else
|
160
|
+
non_subtype_dump(o, pi)
|
161
|
+
end
|
162
|
+
@constraints_used[pi] = true
|
159
163
|
end
|
160
|
-
@constraints_used[pi] = true
|
161
|
-
end
|
162
164
|
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
165
|
+
def identified_by(o, pi)
|
166
|
+
# Different adjectives might be used for different readings.
|
167
|
+
# Here, we must find the role_ref containing the adjectives that we need for each identifier,
|
168
|
+
# which will be attached to the uniqueness constraint on this object in the binary FT that
|
169
|
+
# attaches that identifying role.
|
170
|
+
role_refs = pi.role_sequence.all_role_ref.sort_by{|role_ref| role_ref.ordinal}
|
171
|
+
|
172
|
+
# We need to get the adjectives for the roles from the identifying fact's preferred readings:
|
173
|
+
identifying_facts = role_refs.map{|rr| rr.role.fact_type }.uniq
|
174
|
+
preferred_readings = identifying_facts.inject({}){|reading_hash, fact_type|
|
175
|
+
pr = fact_type.preferred_reading
|
176
|
+
reading_hash[fact_type] = pr
|
177
|
+
reading_hash
|
178
|
+
}
|
179
|
+
#p identifying_facts.map{|f| f.preferred_reading }
|
178
180
|
|
179
|
-
|
180
|
-
|
181
|
-
|
181
|
+
identifying_roles = role_refs.map(&:role)
|
182
|
+
identification = identified_by_roles_and_facts(o, identifying_roles, identifying_facts, preferred_readings)
|
183
|
+
#identifying_facts.each{|f| @fact_types_dumped[f] = true }
|
182
184
|
|
183
|
-
|
184
|
-
|
185
|
+
identification
|
186
|
+
end
|
185
187
|
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
188
|
+
def fact_readings_with_constraints(fact_type, fact_constraints = nil)
|
189
|
+
define_role_names = true
|
190
|
+
fact_constraints ||= @presence_constraints_by_fact[fact_type]
|
191
|
+
readings = fact_type.all_reading_by_ordinal.inject([]) do |reading_array, reading|
|
192
|
+
reading_array << expanded_reading(reading, fact_constraints, define_role_names)
|
191
193
|
|
192
|
-
|
194
|
+
define_role_names = false # No need to define role names in subsequent readings
|
193
195
|
|
194
|
-
|
195
|
-
|
196
|
+
reading_array
|
197
|
+
end
|
196
198
|
|
197
|
-
|
198
|
-
|
199
|
+
readings
|
200
|
+
end
|
199
201
|
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
202
|
+
def expanded_reading(reading, fact_constraints, define_role_names)
|
203
|
+
# Find all role numbers in order of occurrence in this reading:
|
204
|
+
role_refs = reading.role_sequence.all_role_ref.sort_by{|role_ref| role_ref.ordinal}
|
205
|
+
role_numbers = reading.reading_text.scan(/\{(\d)\}/).flatten.map{|m| Integer(m) }
|
206
|
+
roles = role_numbers.map{|m| role_refs[m].role }
|
207
|
+
# debug "Considering #{reading.reading_text} having #{role_numbers.inspect}"
|
208
|
+
|
209
|
+
# Find the constraints that constrain frequency over each role we can verbalise:
|
210
|
+
frequency_constraints = []
|
211
|
+
roles.each do |role|
|
212
|
+
# Find a mandatory constraint that's *not* unique; this will need an extra reading
|
213
|
+
role_is_first_in = reading.fact_type.all_reading.detect{|r|
|
214
|
+
role == r.role_sequence.all_role_ref.sort_by{|role_ref|
|
215
|
+
role_ref.ordinal
|
216
|
+
}[0].role
|
217
|
+
}
|
216
218
|
|
217
|
-
|
218
|
-
|
219
|
-
|
219
|
+
if (role == roles.last) # First role of the reading?
|
220
|
+
# REVISIT: With a ternary, doing this on other than the last role can be ambiguous,
|
221
|
+
# in case both the 2nd and 3rd roles have frequencies. Think some more!
|
220
222
|
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
223
|
+
constraint = fact_constraints.find{|c| # Find a UC that spans all other Roles
|
224
|
+
# internal uniqueness constraints span all roles but one, the residual:
|
225
|
+
PresenceConstraint === c &&
|
226
|
+
!@constraints_used[c] && # Already verbalised
|
227
|
+
roles-c.role_sequence.all_role_ref.map(&:role) == [role]
|
228
|
+
}
|
229
|
+
# Index the frequency implied by the constraint under the role position in the reading
|
230
|
+
if constraint # Mark this constraint as "verbalised" so we don't do it again:
|
231
|
+
@constraints_used[constraint] = true
|
232
|
+
end
|
233
|
+
frequency_constraints << show_frequency(role, constraint)
|
234
|
+
else
|
235
|
+
frequency_constraints << show_frequency(role, nil)
|
230
236
|
end
|
231
|
-
frequency_constraints << show_frequency(role, constraint)
|
232
|
-
else
|
233
|
-
frequency_constraints << show_frequency(role, nil)
|
234
237
|
end
|
235
|
-
end
|
236
238
|
|
237
|
-
|
239
|
+
expanded = reading.expand(frequency_constraints, define_role_names)
|
238
240
|
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
241
|
+
if (ft_rings = @ring_constraints_by_fact[reading.fact_type]) &&
|
242
|
+
(ring = ft_rings.detect{|rc| !@constraints_used[rc]})
|
243
|
+
@constraints_used[ring] = true
|
244
|
+
append_ring_to_reading(expanded, ring)
|
245
|
+
end
|
246
|
+
expanded
|
243
247
|
end
|
244
|
-
expanded
|
245
|
-
end
|
246
248
|
|
247
|
-
|
248
|
-
|
249
|
-
|
249
|
+
def show_frequency role, constraint
|
250
|
+
constraint ? constraint.frequency : nil
|
251
|
+
end
|
250
252
|
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
253
|
+
def describe_fact_type(fact_type, highlight = nil)
|
254
|
+
(fact_type.entity_type ? fact_type.entity_type.name : "")+
|
255
|
+
describe_roles(fact_type.all_role, highlight)
|
256
|
+
end
|
255
257
|
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
258
|
+
def describe_roles(roles, highlight = nil)
|
259
|
+
"("+
|
260
|
+
roles.map{|role| role.concept.name + (role == highlight ? "*" : "")}*", "+
|
261
|
+
")"
|
262
|
+
end
|
261
263
|
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
264
|
+
def describe_role_sequence(role_sequence)
|
265
|
+
"("+
|
266
|
+
role_sequence.all_role_ref.map{|role_ref| role_ref.role.concept.name }*", "+
|
267
|
+
")"
|
268
|
+
end
|
267
269
|
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
270
|
+
# This returns an array of two hash tables each keyed by an EntityType.
|
271
|
+
# The values of each hash entry are the precursors and followers (respectively) of that entity.
|
272
|
+
def build_entity_dependencies
|
273
|
+
@vocabulary.all_feature.inject([{},{}]) { |a, o|
|
274
|
+
if EntityType === o && !o.fact_type
|
275
|
+
precursor = a[0]
|
276
|
+
follower = a[1]
|
277
|
+
blocked = false
|
278
|
+
pi = o.preferred_identifier
|
279
|
+
if pi
|
280
|
+
pi.role_sequence.all_role_ref.each{|rr|
|
281
|
+
role = rr.role
|
282
|
+
player = role.concept
|
283
|
+
next unless EntityType === player
|
284
|
+
# player is a precursor of o
|
285
|
+
(precursor[o] ||= []) << player if (player != o)
|
286
|
+
(follower[player] ||= []) << o if (player != o)
|
287
|
+
}
|
288
|
+
end
|
289
|
+
# Supertypes are precursors too:
|
290
|
+
subtyping = o.all_type_inheritance_as_supertype
|
291
|
+
next a if subtyping.size == 0
|
292
|
+
subtyping.each{|ti|
|
293
|
+
# debug ti.class.roles.verbalise; debug "all_type_inheritance_as_supertype"; exit
|
294
|
+
s = ti.subtype
|
295
|
+
(precursor[s] ||= []) << o
|
296
|
+
(follower[o] ||= []) << s
|
285
297
|
}
|
286
298
|
end
|
287
|
-
|
288
|
-
subtyping = o.all_type_inheritance_by_supertype
|
289
|
-
next a if subtyping.size == 0
|
290
|
-
subtyping.each{|ti|
|
291
|
-
# debug ti.class.roles.verbalise; debug "all_type_inheritance_by_supertype"; exit
|
292
|
-
s = ti.subtype
|
293
|
-
(precursor[s] ||= []) << o
|
294
|
-
(follower[o] ||= []) << s
|
295
|
-
}
|
296
|
-
end
|
297
|
-
a
|
298
|
-
}
|
299
|
-
end
|
300
|
-
|
301
|
-
# Dump all fact types for which all precursors (of which "o" is one) have been emitted:
|
302
|
-
def released_fact_types_dump(o)
|
303
|
-
roles = o.all_role
|
304
|
-
begin
|
305
|
-
progress = false
|
306
|
-
roles.map(&:fact_type).uniq.select{|fact_type|
|
307
|
-
# The fact type hasn't already been dumped but all its role players have
|
308
|
-
!@fact_types_dumped[fact_type] &&
|
309
|
-
!fact_type.all_role.detect{|r| !@concept_types_dumped[r.concept] }
|
310
|
-
}.sort_by{|fact_type|
|
311
|
-
fact_type_key(fact_type)
|
312
|
-
}.each{|fact_type|
|
313
|
-
fact_type_dump_with_dependents(fact_type)
|
314
|
-
# Objectified Fact Types may release additional fact types
|
315
|
-
roles += fact_type.entity_type.all_role if fact_type.entity_type
|
316
|
-
progress = true
|
299
|
+
a
|
317
300
|
}
|
318
|
-
end
|
319
|
-
end
|
320
|
-
|
321
|
-
def skip_fact_type(f)
|
322
|
-
# REVISIT: There might be constraints we have to merge into the nested entity or subtype.
|
323
|
-
# These will come up as un-handled constraints:
|
324
|
-
pcs = @presence_constraints_by_fact[f]
|
325
|
-
TypeInheritance === f ||
|
326
|
-
(pcs && pcs.size > 0 && !pcs.detect{|c| !@constraints_used[c] })
|
327
|
-
end
|
301
|
+
end
|
328
302
|
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
303
|
+
# Dump all fact types for which all precursors (of which "o" is one) have been emitted:
|
304
|
+
def released_fact_types_dump(o)
|
305
|
+
roles = o.all_role
|
306
|
+
begin
|
307
|
+
progress = false
|
308
|
+
roles.map(&:fact_type).uniq.select{|fact_type|
|
309
|
+
# The fact type hasn't already been dumped but all its role players have
|
310
|
+
!@fact_types_dumped[fact_type] &&
|
311
|
+
!fact_type.all_role.detect{|r| !@concept_types_dumped[r.concept] }
|
312
|
+
}.sort_by{|fact_type|
|
313
|
+
fact_type_key(fact_type)
|
314
|
+
}.each{|fact_type|
|
315
|
+
fact_type_dump_with_dependents(fact_type)
|
316
|
+
# Objectified Fact Types may release additional fact types
|
317
|
+
roles += fact_type.entity_type.all_role.sort_by{|role| role.ordinal} if fact_type.entity_type
|
318
|
+
progress = true
|
319
|
+
}
|
320
|
+
end while progress
|
321
|
+
end
|
335
322
|
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
return
|
323
|
+
def skip_fact_type(f)
|
324
|
+
# REVISIT: There might be constraints we have to merge into the nested entity or subtype.
|
325
|
+
# These will come up as un-handled constraints:
|
326
|
+
pcs = @presence_constraints_by_fact[f]
|
327
|
+
TypeInheritance === f ||
|
328
|
+
(pcs && pcs.size > 0 && !pcs.detect{|c| !@constraints_used[c] })
|
343
329
|
end
|
344
330
|
|
345
|
-
|
331
|
+
# Dump one fact type.
|
332
|
+
# Include as many as possible internal constraints in the fact type readings.
|
333
|
+
def fact_type_dump_with_dependents(fact_type)
|
334
|
+
@fact_types_dumped[fact_type] = true
|
335
|
+
# debug "Trying to dump FT again" if @fact_types_dumped[fact_type]
|
336
|
+
return if skip_fact_type(fact_type)
|
337
|
+
|
338
|
+
if (et = fact_type.entity_type) &&
|
339
|
+
(pi = et.preferred_identifier) &&
|
340
|
+
pi.role_sequence.all_role_ref.detect{|rr| rr.role.fact_type != fact_type }
|
341
|
+
# debug "Dumping objectified FT #{et.name} as an entity, non-fact PI"
|
342
|
+
entity_type_dump(et)
|
343
|
+
released_fact_types_dump(et)
|
344
|
+
return
|
345
|
+
end
|
346
346
|
|
347
|
-
|
348
|
-
# debug "#{fact_type.name} has readings:\n\t#{fact_type.readings.map(&:name)*"\n\t"}"
|
349
|
-
# debug "Dumping #{fact_type.fact_type_id} as a fact type"
|
347
|
+
fact_constraints = @presence_constraints_by_fact[fact_type]
|
350
348
|
|
351
|
-
|
352
|
-
|
349
|
+
# debug "for fact type #{fact_type.to_s}, considering\n\t#{fact_constraints.map(&:to_s)*",\n\t"}"
|
350
|
+
# debug "#{fact_type.name} has readings:\n\t#{fact_type.readings.map(&:name)*"\n\t"}"
|
351
|
+
# debug "Dumping #{fact_type.fact_type_id} as a fact type"
|
353
352
|
|
354
|
-
|
353
|
+
# Fact types that aren't nested have no names
|
354
|
+
name = fact_type.entity_type && fact_type.entity_type.name
|
355
355
|
|
356
|
-
|
356
|
+
fact_type_dump(fact_type, name)
|
357
357
|
|
358
|
-
|
359
|
-
@concept_types_dumped[fact_type.entity_type] = true if fact_type.entity_type
|
360
|
-
end
|
358
|
+
# REVISIT: Go through the residual constraints and re-process appropriate readings to show them
|
361
359
|
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
def fact_type_key(fact_type)
|
366
|
-
role_names =
|
367
|
-
if (pr = fact_type.preferred_reading)
|
368
|
-
pr.role_sequence.
|
369
|
-
all_role_ref.
|
370
|
-
sort_by{|role_ref| role_ref.ordinal}.
|
371
|
-
map{|role_ref| [ role_ref.leading_adjective, role_ref.role.concept.name, role_ref.trailing_adjective ].compact*"-" } +
|
372
|
-
[pr.reading_text]
|
373
|
-
else
|
374
|
-
fact_type.all_role.map{|role| role.concept.name }
|
375
|
-
end
|
360
|
+
@fact_types_dumped[fact_type] = true
|
361
|
+
@concept_types_dumped[fact_type.entity_type] = true if fact_type.entity_type
|
362
|
+
end
|
376
363
|
|
377
|
-
|
378
|
-
|
364
|
+
# Arrange for objectified fact types to appear in order of name, after other fact types.
|
365
|
+
# Facts are ordered alphabetically by the names of their role players,
|
366
|
+
# then by preferred_reading (subtyping fact types have no preferred_reading).
|
367
|
+
def fact_type_key(fact_type)
|
368
|
+
role_names =
|
369
|
+
if (pr = fact_type.preferred_reading)
|
370
|
+
pr.role_sequence.
|
371
|
+
all_role_ref.
|
372
|
+
sort_by{|role_ref| role_ref.ordinal}.
|
373
|
+
map{|role_ref| [ role_ref.leading_adjective, role_ref.role.concept.name, role_ref.trailing_adjective ].compact*"-" } +
|
374
|
+
[pr.reading_text]
|
375
|
+
else
|
376
|
+
fact_type.all_role.map{|role| role.concept.name }
|
377
|
+
end
|
379
378
|
|
380
|
-
|
381
|
-
|
382
|
-
end
|
379
|
+
(fact_type.entity_type ? [fact_type.entity_type.name] : [""]) + role_names
|
380
|
+
end
|
383
381
|
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
# The only fact types that can be remaining are those involving only value types,
|
389
|
-
# since we dumped every fact type as soon as all relevant entities were dumped.
|
390
|
-
# Iterate over all fact types of all value types, looking for these strays.
|
391
|
-
|
392
|
-
done_banner = false
|
393
|
-
fact_collection = @vocabulary.constellation.FactType
|
394
|
-
fact_collection.keys.select{|fact_id|
|
395
|
-
fact_type = fact_collection[fact_id]
|
396
|
-
!(TypeInheritance === fact_type) and
|
397
|
-
!@fact_types_dumped[fact_type] and
|
398
|
-
!skip_fact_type(fact_type) and
|
399
|
-
!fact_type.all_role.detect{|r| EntityType === r.concept }
|
400
|
-
}.sort_by{|fact_id|
|
401
|
-
fact_type = fact_collection[fact_id]
|
402
|
-
fact_type_key(fact_type)
|
403
|
-
}.each{|fact_id|
|
404
|
-
fact_type = fact_collection[fact_id]
|
382
|
+
def role_ref_key(role_ref)
|
383
|
+
[ role_ref.leading_adjective, role_ref.role.concept.name, role_ref.trailing_adjective ].compact*"-"
|
384
|
+
end
|
405
385
|
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
|
386
|
+
# Dump fact types.
|
387
|
+
def fact_types_dump
|
388
|
+
# REVISIT: Uniqueness on the LHS of a binary can be coded using "distinct"
|
389
|
+
|
390
|
+
# The only fact types that can be remaining are those involving only value types,
|
391
|
+
# since we dumped every fact type as soon as all relevant entities were dumped.
|
392
|
+
# Iterate over all fact types of all value types, looking for these strays.
|
393
|
+
|
394
|
+
done_banner = false
|
395
|
+
fact_collection = @vocabulary.constellation.FactType
|
396
|
+
fact_collection.keys.select{|fact_id|
|
397
|
+
fact_type = fact_collection[fact_id] and
|
398
|
+
!(TypeInheritance === fact_type) and
|
399
|
+
!@fact_types_dumped[fact_type] and
|
400
|
+
!skip_fact_type(fact_type) and
|
401
|
+
!fact_type.all_role.detect{|r| r.concept.is_a?(EntityType) }
|
402
|
+
}.sort_by{|fact_id|
|
403
|
+
fact_type = fact_collection[fact_id]
|
404
|
+
fact_type_key(fact_type)
|
405
|
+
}.each{|fact_id|
|
406
|
+
fact_type = fact_collection[fact_id]
|
407
|
+
|
408
|
+
fact_type_banner unless done_banner
|
409
|
+
done_banner = true
|
410
|
+
fact_type_dump_with_dependents(fact_type)
|
411
|
+
}
|
410
412
|
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
|
421
|
-
|
422
|
-
|
413
|
+
# REVISIT: Find out why some fact types are missed during entity dumping:
|
414
|
+
@vocabulary.constellation.FactType.values.select{|fact_type|
|
415
|
+
!(TypeInheritance === fact_type)
|
416
|
+
}.sort_by{|fact_type|
|
417
|
+
fact_type_key(fact_type)
|
418
|
+
}.each{|fact_type|
|
419
|
+
next if @fact_types_dumped[fact_type]
|
420
|
+
# debug "Not dumped #{fact_type.verbalise}(#{fact_type.all_role.map{|r| r.concept.name}*", "})"
|
421
|
+
fact_type_banner unless done_banner
|
422
|
+
done_banner = true
|
423
|
+
fact_type_dump_with_dependents(fact_type)
|
424
|
+
}
|
423
425
|
|
424
|
-
|
425
|
-
|
426
|
-
|
426
|
+
fact_type_end if done_banner
|
427
|
+
# unused = constraints - @constraints_used.keys
|
428
|
+
# debug "residual constraints are\n\t#{unused.map(&:to_s)*",\n\t"}"
|
427
429
|
|
428
|
-
|
429
|
-
|
430
|
+
@constraints_used
|
431
|
+
end
|
430
432
|
|
431
|
-
|
432
|
-
|
433
|
-
|
434
|
-
|
435
|
-
|
436
|
-
|
437
|
-
|
438
|
-
|
439
|
-
|
433
|
+
def fact_instances_dump
|
434
|
+
@vocabulary.fact_types.each{|f|
|
435
|
+
# Dump the instances:
|
436
|
+
f.facts.each{|i|
|
437
|
+
raise "REVISIT: Not dumping fact instances"
|
438
|
+
debug "\t\t"+i.to_s
|
439
|
+
}
|
440
|
+
}
|
441
|
+
end
|
440
442
|
|
441
|
-
|
442
|
-
|
443
|
-
|
444
|
-
|
445
|
-
|
446
|
-
|
447
|
-
|
448
|
-
|
449
|
-
|
450
|
-
|
443
|
+
def constraint_sort_key(c)
|
444
|
+
case c
|
445
|
+
when RingConstraint
|
446
|
+
[1, c.ring_type, c.role.concept.name, c.other_role.concept.name, c.name||""]
|
447
|
+
when SetComparisonConstraint
|
448
|
+
[2, c.all_set_comparison_roles.map{|scrs| scrs.role_sequence.all_role_ref.map{|rr| role_ref_key(rr)}}, c.name||""]
|
449
|
+
when SubsetConstraint
|
450
|
+
[3, [c.superset_role_sequence, c.subset_role_sequence].map{|rs| rs.all_role_ref.map{|rr| role_ref_key(rr)}}, c.name||""]
|
451
|
+
when PresenceConstraint
|
452
|
+
[4, c.role_sequence.all_role_ref.map{|rr| role_ref_key(rr)}, c.name||""]
|
453
|
+
end
|
451
454
|
end
|
452
|
-
end
|
453
455
|
|
454
|
-
|
455
|
-
|
456
|
-
|
457
|
-
|
458
|
-
|
459
|
-
|
460
|
-
|
461
|
-
|
462
|
-
|
463
|
-
|
464
|
-
|
465
|
-
|
456
|
+
def constraints_dump(except = {})
|
457
|
+
heading = false
|
458
|
+
@vocabulary.all_constraint.reject{|c| except[c]}.sort_by{ |c| constraint_sort_key(c) }.each do|c|
|
459
|
+
# Skip some PresenceConstraints:
|
460
|
+
if PresenceConstraint === c
|
461
|
+
# Skip uniqueness constraints that cover all roles of a fact type, they're implicit
|
462
|
+
fact_types = c.role_sequence.all_role_ref.map{|rr| rr.role.fact_type}.uniq
|
463
|
+
next if fact_types.size == 1 &&
|
464
|
+
c.max_frequency == 1 && # Uniqueness
|
465
|
+
fact_types[0].all_role.size == c.role_sequence.all_role_ref.size
|
466
|
+
|
467
|
+
# Skip internal PresenceConstraints over TypeInheritances:
|
468
|
+
next if c.role_sequence.all_role_ref.size == 1 &&
|
469
|
+
TypeInheritance === fact_types[0]
|
466
470
|
end
|
467
|
-
|
468
|
-
|
469
|
-
|
470
|
-
|
471
|
-
|
472
|
-
#
|
473
|
-
|
474
|
-
|
471
|
+
|
472
|
+
constraint_banner unless heading
|
473
|
+
heading = true
|
474
|
+
|
475
|
+
# Skip presence constraints on value types:
|
476
|
+
# next if ActiveFacts::PresenceConstraint === c &&
|
477
|
+
# ActiveFacts::ValueType === c.concept
|
478
|
+
constraint_dump(c)
|
475
479
|
end
|
480
|
+
constraint_end if heading
|
481
|
+
end
|
476
482
|
|
477
|
-
|
478
|
-
|
483
|
+
def vocabulary_start(vocabulary)
|
484
|
+
debug "Should override vocabulary_start"
|
485
|
+
end
|
479
486
|
|
480
|
-
|
481
|
-
|
482
|
-
# ActiveFacts::ValueType === c.concept
|
483
|
-
constraint_dump(c)
|
487
|
+
def vocabulary_end
|
488
|
+
debug "Should override vocabulary_end"
|
484
489
|
end
|
485
|
-
constraint_end if heading
|
486
|
-
end
|
487
490
|
|
488
|
-
|
489
|
-
|
490
|
-
|
491
|
+
def value_type_banner
|
492
|
+
debug "Should override value_type_banner"
|
493
|
+
end
|
491
494
|
|
492
|
-
|
493
|
-
|
494
|
-
|
495
|
+
def value_type_end
|
496
|
+
debug "Should override value_type_end"
|
497
|
+
end
|
495
498
|
|
496
|
-
|
497
|
-
|
498
|
-
|
499
|
+
def value_type_dump(o)
|
500
|
+
debug "Should override value_type_dump"
|
501
|
+
end
|
499
502
|
|
500
|
-
|
501
|
-
|
502
|
-
|
503
|
+
def entity_type_banner
|
504
|
+
debug "Should override entity_type_banner"
|
505
|
+
end
|
503
506
|
|
504
|
-
|
505
|
-
|
506
|
-
|
507
|
+
def entity_type_group_end
|
508
|
+
debug "Should override entity_type_group_end"
|
509
|
+
end
|
507
510
|
|
508
|
-
|
509
|
-
|
510
|
-
|
511
|
+
def non_subtype_dump(o, pi)
|
512
|
+
debug "Should override non_subtype_dump"
|
513
|
+
end
|
511
514
|
|
512
|
-
|
513
|
-
|
514
|
-
|
515
|
+
def subtype_dump(o, supertypes, pi = nil)
|
516
|
+
debug "Should override subtype_dump"
|
517
|
+
end
|
515
518
|
|
516
|
-
|
517
|
-
|
518
|
-
|
519
|
+
def append_ring_to_reading(reading, ring)
|
520
|
+
debug "Should override append_ring_to_reading"
|
521
|
+
end
|
519
522
|
|
520
|
-
|
521
|
-
|
522
|
-
|
523
|
+
def fact_type_banner
|
524
|
+
debug "Should override fact_type_banner"
|
525
|
+
end
|
523
526
|
|
524
|
-
|
525
|
-
|
526
|
-
|
527
|
+
def fact_type_end
|
528
|
+
debug "Should override fact_type_end"
|
529
|
+
end
|
527
530
|
|
528
|
-
|
529
|
-
|
530
|
-
|
531
|
+
def fact_type_dump(fact_type, name)
|
532
|
+
debug "Should override fact_type_dump"
|
533
|
+
end
|
531
534
|
|
532
|
-
|
533
|
-
|
534
|
-
|
535
|
+
def constraint_banner
|
536
|
+
debug "Should override constraint_banner"
|
537
|
+
end
|
535
538
|
|
536
|
-
|
537
|
-
|
538
|
-
|
539
|
+
def constraint_end
|
540
|
+
debug "Should override constraint_end"
|
541
|
+
end
|
539
542
|
|
540
|
-
|
541
|
-
|
542
|
-
|
543
|
+
def constraint_dump(c)
|
544
|
+
debug "Should override constraint_dump"
|
545
|
+
end
|
543
546
|
|
544
|
-
def constraint_end
|
545
|
-
debug "Should override constraint_end"
|
546
547
|
end
|
547
548
|
|
548
|
-
def
|
549
|
-
|
549
|
+
def dump(vocabulary, out = $>)
|
550
|
+
OrderedDumper.new(vocabulary).dump(out)
|
550
551
|
end
|
551
|
-
|
552
|
-
end
|
553
|
-
|
554
|
-
def dump(vocabulary, out = $>)
|
555
|
-
OrderedDumper.new(vocabulary).dump(out)
|
556
552
|
end
|
557
553
|
end
|