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,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
|