activefacts 1.6.0 → 1.7.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (169) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +14 -0
  3. data/.rspec +2 -0
  4. data/.travis.yml +4 -0
  5. data/Gemfile +14 -0
  6. data/LICENSE.txt +21 -0
  7. data/README.md +60 -0
  8. data/Rakefile +3 -80
  9. data/activefacts.gemspec +36 -0
  10. data/bin/afgen +4 -2
  11. data/bin/cql +5 -1
  12. data/lib/activefacts.rb +3 -12
  13. data/lib/activefacts/{vocabulary/query_evaluator.rb → query/evaluator.rb} +0 -0
  14. data/lib/activefacts/version.rb +2 -2
  15. metadata +48 -296
  16. data/History.txt +0 -4
  17. data/LICENSE +0 -19
  18. data/Manifest.txt +0 -165
  19. data/README.rdoc +0 -81
  20. data/css/offline.css +0 -3
  21. data/css/orm2.css +0 -124
  22. data/css/print.css +0 -8
  23. data/css/style-print.css +0 -357
  24. data/css/style.css +0 -387
  25. data/download.html +0 -110
  26. data/examples/CQL/Address.cql +0 -44
  27. data/examples/CQL/Blog.cql +0 -54
  28. data/examples/CQL/CompanyDirectorEmployee.cql +0 -56
  29. data/examples/CQL/Death.cql +0 -17
  30. data/examples/CQL/Diplomacy.cql +0 -48
  31. data/examples/CQL/Genealogy.cql +0 -98
  32. data/examples/CQL/Insurance.cql +0 -320
  33. data/examples/CQL/Marriage.cql +0 -18
  34. data/examples/CQL/Metamodel.cql +0 -493
  35. data/examples/CQL/Monogamy.cql +0 -24
  36. data/examples/CQL/MultiInheritance.cql +0 -22
  37. data/examples/CQL/NonRoleId.cql +0 -14
  38. data/examples/CQL/OddIdentifier.cql +0 -18
  39. data/examples/CQL/OilSupply.cql +0 -53
  40. data/examples/CQL/OneToOnes.cql +0 -17
  41. data/examples/CQL/Orienteering.cql +0 -111
  42. data/examples/CQL/PersonPlaysGame.cql +0 -18
  43. data/examples/CQL/RedundantDependency.cql +0 -34
  44. data/examples/CQL/SchoolActivities.cql +0 -33
  45. data/examples/CQL/SeparateSubtype.cql +0 -30
  46. data/examples/CQL/ServiceDirector.cql +0 -276
  47. data/examples/CQL/SimplestUnary.cql +0 -12
  48. data/examples/CQL/Supervision.cql +0 -34
  49. data/examples/CQL/WaiterTips.cql +0 -33
  50. data/examples/CQL/Warehousing.cql +0 -101
  51. data/examples/CQL/WindowInRoomInBldg.cql +0 -28
  52. data/examples/CQL/unit.cql +0 -474
  53. data/examples/index.html +0 -420
  54. data/examples/intro.html +0 -327
  55. data/examples/local.css +0 -24
  56. data/index.html +0 -111
  57. data/lib/activefacts/cql.rb +0 -35
  58. data/lib/activefacts/cql/CQLParser.treetop +0 -158
  59. data/lib/activefacts/cql/Context.treetop +0 -48
  60. data/lib/activefacts/cql/Expressions.treetop +0 -67
  61. data/lib/activefacts/cql/FactTypes.treetop +0 -358
  62. data/lib/activefacts/cql/Language/English.treetop +0 -315
  63. data/lib/activefacts/cql/LexicalRules.treetop +0 -253
  64. data/lib/activefacts/cql/ObjectTypes.treetop +0 -210
  65. data/lib/activefacts/cql/Rakefile +0 -14
  66. data/lib/activefacts/cql/Terms.treetop +0 -183
  67. data/lib/activefacts/cql/ValueTypes.treetop +0 -202
  68. data/lib/activefacts/cql/compiler.rb +0 -156
  69. data/lib/activefacts/cql/compiler/clause.rb +0 -1137
  70. data/lib/activefacts/cql/compiler/constraint.rb +0 -581
  71. data/lib/activefacts/cql/compiler/entity_type.rb +0 -457
  72. data/lib/activefacts/cql/compiler/expression.rb +0 -443
  73. data/lib/activefacts/cql/compiler/fact.rb +0 -390
  74. data/lib/activefacts/cql/compiler/fact_type.rb +0 -421
  75. data/lib/activefacts/cql/compiler/query.rb +0 -106
  76. data/lib/activefacts/cql/compiler/shared.rb +0 -161
  77. data/lib/activefacts/cql/compiler/value_type.rb +0 -174
  78. data/lib/activefacts/cql/nodes.rb +0 -49
  79. data/lib/activefacts/cql/parser.rb +0 -241
  80. data/lib/activefacts/dependency_analyser.rb +0 -182
  81. data/lib/activefacts/generate/absorption.rb +0 -70
  82. data/lib/activefacts/generate/composition.rb +0 -118
  83. data/lib/activefacts/generate/cql.rb +0 -714
  84. data/lib/activefacts/generate/dm.rb +0 -279
  85. data/lib/activefacts/generate/help.rb +0 -64
  86. data/lib/activefacts/generate/helpers/inject.rb +0 -16
  87. data/lib/activefacts/generate/helpers/oo.rb +0 -162
  88. data/lib/activefacts/generate/helpers/ordered.rb +0 -605
  89. data/lib/activefacts/generate/helpers/rails.rb +0 -57
  90. data/lib/activefacts/generate/html/glossary.rb +0 -461
  91. data/lib/activefacts/generate/json.rb +0 -337
  92. data/lib/activefacts/generate/null.rb +0 -32
  93. data/lib/activefacts/generate/rails/models.rb +0 -246
  94. data/lib/activefacts/generate/rails/schema.rb +0 -216
  95. data/lib/activefacts/generate/records.rb +0 -46
  96. data/lib/activefacts/generate/ruby.rb +0 -133
  97. data/lib/activefacts/generate/sql/mysql.rb +0 -280
  98. data/lib/activefacts/generate/sql/server.rb +0 -273
  99. data/lib/activefacts/generate/stats.rb +0 -69
  100. data/lib/activefacts/generate/text.rb +0 -27
  101. data/lib/activefacts/generate/topics.rb +0 -265
  102. data/lib/activefacts/generate/traits/datavault.rb +0 -241
  103. data/lib/activefacts/generate/traits/oo.rb +0 -73
  104. data/lib/activefacts/generate/traits/ordered.rb +0 -33
  105. data/lib/activefacts/generate/traits/ruby.rb +0 -210
  106. data/lib/activefacts/generate/transform/datavault.rb +0 -266
  107. data/lib/activefacts/generate/transform/surrogate.rb +0 -214
  108. data/lib/activefacts/generate/version.rb +0 -26
  109. data/lib/activefacts/input/cql.rb +0 -43
  110. data/lib/activefacts/input/orm.rb +0 -1636
  111. data/lib/activefacts/mapping/rails.rb +0 -132
  112. data/lib/activefacts/persistence.rb +0 -15
  113. data/lib/activefacts/persistence/columns.rb +0 -446
  114. data/lib/activefacts/persistence/foreignkey.rb +0 -187
  115. data/lib/activefacts/persistence/index.rb +0 -240
  116. data/lib/activefacts/persistence/object_type.rb +0 -198
  117. data/lib/activefacts/persistence/reference.rb +0 -434
  118. data/lib/activefacts/persistence/tables.rb +0 -380
  119. data/lib/activefacts/registry.rb +0 -11
  120. data/lib/activefacts/support.rb +0 -132
  121. data/lib/activefacts/vocabulary.rb +0 -9
  122. data/lib/activefacts/vocabulary/extensions.rb +0 -1348
  123. data/lib/activefacts/vocabulary/metamodel.rb +0 -570
  124. data/lib/activefacts/vocabulary/verbaliser.rb +0 -804
  125. data/script/txt2html +0 -71
  126. data/spec/absorption_spec.rb +0 -95
  127. data/spec/cql/comparison_spec.rb +0 -89
  128. data/spec/cql/context_spec.rb +0 -94
  129. data/spec/cql/contractions_spec.rb +0 -224
  130. data/spec/cql/deontic_spec.rb +0 -88
  131. data/spec/cql/entity_type_spec.rb +0 -320
  132. data/spec/cql/expressions_spec.rb +0 -66
  133. data/spec/cql/fact_type_matching_spec.rb +0 -338
  134. data/spec/cql/french_spec.rb +0 -21
  135. data/spec/cql/parser/bad_literals_spec.rb +0 -86
  136. data/spec/cql/parser/constraints_spec.rb +0 -19
  137. data/spec/cql/parser/entity_types_spec.rb +0 -106
  138. data/spec/cql/parser/expressions_spec.rb +0 -199
  139. data/spec/cql/parser/fact_types_spec.rb +0 -44
  140. data/spec/cql/parser/literals_spec.rb +0 -312
  141. data/spec/cql/parser/pragmas_spec.rb +0 -89
  142. data/spec/cql/parser/value_types_spec.rb +0 -42
  143. data/spec/cql/role_matching_spec.rb +0 -148
  144. data/spec/cql/samples_spec.rb +0 -244
  145. data/spec/cql_cql_spec.rb +0 -73
  146. data/spec/cql_dm_spec.rb +0 -136
  147. data/spec/cql_mysql_spec.rb +0 -69
  148. data/spec/cql_parse_spec.rb +0 -34
  149. data/spec/cql_ruby_spec.rb +0 -73
  150. data/spec/cql_sql_spec.rb +0 -72
  151. data/spec/cql_symbol_tables_spec.rb +0 -261
  152. data/spec/cqldump_spec.rb +0 -170
  153. data/spec/helpers/array_matcher.rb +0 -23
  154. data/spec/helpers/ctrl_c_support.rb +0 -52
  155. data/spec/helpers/diff_matcher.rb +0 -39
  156. data/spec/helpers/file_matcher.rb +0 -34
  157. data/spec/helpers/parse_to_ast_matcher.rb +0 -80
  158. data/spec/helpers/string_matcher.rb +0 -30
  159. data/spec/helpers/test_parser.rb +0 -15
  160. data/spec/norma_cql_spec.rb +0 -66
  161. data/spec/norma_ruby_spec.rb +0 -62
  162. data/spec/norma_ruby_sql_spec.rb +0 -107
  163. data/spec/norma_sql_spec.rb +0 -57
  164. data/spec/norma_tables_spec.rb +0 -95
  165. data/spec/ruby_api_spec.rb +0 -23
  166. data/spec/spec_helper.rb +0 -35
  167. data/spec/transform_surrogate_spec.rb +0 -59
  168. data/status.html +0 -138
  169. data/why.html +0 -60
@@ -1,11 +0,0 @@
1
- module ActiveFacts
2
- module Registry
3
- def self.generator(name, klass)
4
- generators[name] = klass
5
- end
6
-
7
- def self.generators
8
- @@generators ||= {}
9
- end
10
- end
11
- end
@@ -1,132 +0,0 @@
1
- #
2
- # ActiveFacts Support code.
3
- #
4
- # Copyright (c) 2009 Clifford Heath. Read the LICENSE file.
5
- #
6
-
7
- # Return all duplicate objects in the array (using hash-equality)
8
- class Array
9
- def duplicates(&b)
10
- inject({}) do |h,e|
11
- h[e] ||= 0
12
- h[e] += 1
13
- h
14
- end.reject do |k,v|
15
- v == 1
16
- end.keys
17
- end
18
-
19
- if RUBY_VERSION =~ /^1\.8/
20
- # Fake up Ruby 1.9's Array#index method, mostly
21
- alias_method :__orig_index, :index
22
- def index *a, &b
23
- if a.size == 0
24
- raise "Not faking Enumerator for #{RUBY_VERSION}" if !b
25
- (0...size).detect{|i| return i if b.call(self[i]) }
26
- else
27
- __orig_index(*a, &b)
28
- end
29
- end
30
- end
31
-
32
- # If any element, or sequence of elements, repeats immediately, delete the repetition.
33
- # Note that this doesn't remove all re-occurrences of a subsequence, only consecutive ones.
34
- # The compare_block allows a custom equality comparison.
35
- def elide_repeated_subsequences &compare_block
36
- compare_block ||= lambda{|a,b| a == b}
37
- i = 0
38
- while i < size # Need to re-evaluate size on each loop - the array shrinks.
39
- j = i
40
- #puts "Looking for repetitions of #{self[i]}@[#{i}]"
41
- while tail = self[j+1..-1] and k = tail.index {|e| compare_block.call(e, self[i]) }
42
- length = j+1+k-i
43
- #puts "Found at #{j+1+k} (subsequence of length #{j+1+k-i}), will need to repeat to #{j+k+length}"
44
- if j+k+1+length <= size && compare_block[self[i, length], self[j+k+1, length]]
45
- #puts "Subsequence from #{i}..#{j+k} repeats immediately at #{j+k+1}..#{j+k+length}"
46
- slice!(j+k+1, length)
47
- j = i
48
- else
49
- j += k+1
50
- end
51
- end
52
- i += 1
53
- end
54
- self
55
- end
56
- end
57
-
58
- class String
59
- class Words
60
- def initialize words
61
- @words = words
62
- end
63
-
64
- def map(&b)
65
- @words.map(&b)
66
- end
67
-
68
- def to_s
69
- titlecase
70
- end
71
-
72
- def titlewords
73
- @words.map do |word|
74
- word[0].upcase+word[1..-1].downcase
75
- end
76
- end
77
-
78
- def titlecase
79
- titlewords.join('')
80
- end
81
-
82
- def capwords
83
- @words.map do |word|
84
- word[0].upcase+word[1..-1]
85
- end
86
- end
87
-
88
- def capcase
89
- capwords.join('')
90
- end
91
-
92
- def camelwords
93
- count = 0
94
- @words.map do |word|
95
- if (count += 1) == 1
96
- word
97
- else
98
- word[0].upcase+word[1..-1].downcase
99
- end
100
- end
101
- end
102
-
103
- def camelcase
104
- camelwords.join('')
105
- end
106
-
107
- def snakewords
108
- @words.map do |w|
109
- w.downcase
110
- end
111
- end
112
-
113
- def snakecase
114
- snakewords.join('_')
115
- end
116
-
117
- def to_a
118
- @words
119
- end
120
-
121
- def +(words)
122
- Words.new(@words + Array(words))
123
- end
124
- end
125
-
126
- def words
127
- Words.new(
128
- self.split(/(?:[^[:alnum:]]+|(?<=[[:alnum:]])(?=[[:upper:]][[:lower:]]))/).reject{|w| w == '' }
129
- )
130
- end
131
- end
132
-
@@ -1,9 +0,0 @@
1
- #
2
- # ActiveFacts Vocabulary Metamodel.
3
- # The ActiveFacts Vocabulary API is generated from Metamodel.orm with extensions:
4
- #
5
- # Copyright (c) 2009 Clifford Heath. Read the LICENSE file.
6
- #
7
- require 'activefacts/vocabulary/metamodel'
8
- require 'activefacts/vocabulary/extensions'
9
- require 'activefacts/vocabulary/verbaliser'
@@ -1,1348 +0,0 @@
1
- #
2
- # ActiveFacts Vocabulary Metamodel.
3
- # Extensions to the ActiveFacts Vocabulary classes (which are generated from the Metamodel)
4
- #
5
- # Copyright (c) 2009 Clifford Heath. Read the LICENSE file.
6
- #
7
- module ActiveFacts
8
- module Metamodel
9
- class Vocabulary
10
- def finalise
11
- constellation.FactType.values.each do |fact_type|
12
- if c = fact_type.check_and_add_spanning_uniqueness_constraint
13
- trace :constraint, "Checking for existence of at least one uniqueness constraint over the roles of #{fact_type.default_reading.inspect}"
14
- fact_type.check_and_add_spanning_uniqueness_constraint = nil
15
- c.call
16
- end
17
- end
18
- end
19
-
20
- # This name does not yet exist (at least not as we expect it to).
21
- # If it in fact does exist (but as the wrong type), complain.
22
- # If it doesn't exist, but its name would cause existing fact type
23
- # readings to be re-interpreted to a different meaning, complain.
24
- # Otherwise return nil.
25
- def check_valid_nonexistent_object_type_name name
26
- if ot = valid_object_type_name(name)
27
- raise "Cannot redefine #{ot.class.basename} #{name}"
28
- end
29
- end
30
-
31
- def valid_object_type_name name
32
- # Raise an exception if adding this name to the vocabulary would create anomalies
33
- anomaly = constellation.Reading.detect do |r_key, reading|
34
- expanded = reading.expand do |role_ref, *words|
35
- words.map! do |w|
36
- case
37
- when w == nil
38
- w
39
- when w[0...name.size] == name
40
- '_ok_'+w
41
- when w[-name.size..-1] == name
42
- w[-1]+'_ok_'
43
- else
44
- w
45
- end
46
- end
47
-
48
- words
49
- end
50
- expanded =~ %r{\b#{name}\b}
51
- end
52
- raise "Adding new term '#{name}' would create anomalous re-interpretation of '#{anomaly.expand}'" if anomaly
53
- @constellation.ObjectType[[identifying_role_values, name]]
54
- end
55
-
56
- # If this entity type exists, ok, otherwise check it's ok to add it
57
- def valid_entity_type_name name
58
- @constellation.EntityType[[identifying_role_values, name]] or
59
- check_valid_nonexistent_object_type_name name
60
- end
61
-
62
- # If this entity type exists, ok, otherwise check it's ok to add it
63
- def valid_value_type_name name
64
- @constellation.ValueType[[identifying_role_values, name]] or
65
- check_valid_nonexistent_object_type_name name
66
- end
67
- end
68
-
69
- class Concept
70
- def describe
71
- case
72
- when object_type; "#{object_type.class.basename} #{object_type.name.inspect}"
73
- when fact_type; "FactType #{fact_type.default_reading.inspect}"
74
- when role; "Role in #{role.fact_type.describe(role)}"
75
- when constraint; constraint.describe
76
- when instance; "Instance #{instance.verbalise}"
77
- when fact; "Fact #{fact.verbalise}"
78
- when query; query.describe
79
- when context_note; "ContextNote#{context_note.verbalise}"
80
- when unit; "Unit #{unit.describe}"
81
- when population; "Population: #{population.name}"
82
- else
83
- raise "ROGUE CONCEPT OF NO TYPE"
84
- end
85
- end
86
-
87
- def embodied_as
88
- case
89
- when object_type; object_type
90
- when fact_type; fact_type
91
- when role; role
92
- when constraint; constraint
93
- when instance; instance
94
- when fact; fact
95
- when query; query
96
- when context_note; context_note
97
- when unit; unit
98
- when population; population
99
- else
100
- raise "ROGUE CONCEPT OF NO TYPE"
101
- end
102
- end
103
-
104
- # Return an array of all Concepts that must be defined before this concept can be defined:
105
- def precursors
106
- case body = embodied_as
107
- when ActiveFacts::Metamodel::ValueType
108
- [ body.supertype, body.unit ] +
109
- body.all_value_type_parameter.map{|f| f.facet_value_type } +
110
- body.all_value_type_parameter_restriction.map{|vr| vr.value.unit}
111
- when ActiveFacts::Metamodel::EntityType
112
- # You can't define the preferred_identifier fact types until you define the entity type,
113
- # but the objects which play the identifying roles must be defined:
114
- body.preferred_identifier.role_sequence.all_role_ref.map {|rr| rr.role.object_type } +
115
- # You can't define the objectified fact type until you define the entity type:
116
- # [ body.fact_type ] # If it's an objectification
117
- body.all_type_inheritance_as_subtype.map{|ti| ti.supertype} # If it's a subtype
118
- when FactType
119
- body.all_role.map(&:object_type)
120
- when Role # We don't consider roles as they cannot be separately defined
121
- []
122
- when ActiveFacts::Metamodel::PresenceConstraint
123
- body.role_sequence.all_role_ref.map do |rr|
124
- rr.role.fact_type
125
- end
126
- when ActiveFacts::Metamodel::ValueConstraint
127
- [ body.role ? body.role.fact_type : nil, body.value_type ] +
128
- body.all_allowed_range.map do |ar|
129
- [ ar.value_range.minimum_bound, ar.value_range.maximum_bound ].compact.map{|b| b.value.unit}
130
- end
131
- when ActiveFacts::Metamodel::SubsetConstraint
132
- body.subset_role_sequence.all_role_ref.map{|rr| rr.role.fact_type } +
133
- body.superset_role_sequence.all_role_ref.map{|rr| rr.role.fact_type }
134
- when ActiveFacts::Metamodel::SetComparisonConstraint
135
- body.all_set_comparison_roles.map{|scr| scr.role_sequence.all_role_ref.map{|rr| rr.role.fact_type } }
136
- when ActiveFacts::Metamodel::RingConstraint
137
- [ body.role.fact_type, body.other_role.fact_type ]
138
- when Instance
139
- [ body.population, body.object_type, body.value ? body.value.unit : nil ]
140
- when Fact
141
- [ body.population, body.fact_type ]
142
- when Query
143
- body.all_variable.map do |v|
144
- [ v.object_type,
145
- v.value ? v.value.unit : nil,
146
- v.step ? v.step.fact_type : nil
147
- ] +
148
- v.all_play.map{|p| p.role.fact_type }
149
- end
150
- when ContextNote
151
- []
152
- when Unit
153
- body.all_derivation_as_derived_unit.map{|d| d.base_unit }
154
- when Population
155
- []
156
- else
157
- raise "ROGUE CONCEPT OF NO TYPE"
158
- end.flatten.compact.uniq.map{|c| c.concept }
159
- end
160
- end
161
-
162
- class Topic
163
- def precursors
164
- # Precursors of a topic are the topics of all precursors of items in this topic
165
- all_concept.map{|c| c.precursors }.flatten.uniq.map{|c| c.topic}.uniq-[self]
166
- end
167
- end
168
-
169
- class Unit
170
- def describe
171
- 'Unit' +
172
- name +
173
- (plural_name ? '/'+plural_name : '') +
174
- '=' +
175
- coefficient.to_s+'*' +
176
- all_derivation_as_derived_unit.map do |derivation|
177
- derivation.base_unit.name +
178
- (derivation.exponent != 1 ? derivation.exponent.to_s : '')
179
- end.join('') +
180
- (offset ? ' + '+offset.to_s : '')
181
- end
182
- end
183
-
184
- class Coefficient
185
- def to_s
186
- numerator.to_s +
187
- (denominator != 1 ? '/' + denominator.to_s : '')
188
- end
189
- end
190
-
191
- class FactType
192
- attr_accessor :check_and_add_spanning_uniqueness_constraint
193
-
194
- def all_reading_by_ordinal
195
- all_reading.sort_by{|reading| reading.ordinal}
196
- end
197
-
198
- def preferred_reading negated = false
199
- pr = all_reading_by_ordinal.detect{|r| !r.is_negative == !negated }
200
- raise "No reading for (#{all_role.map{|r| r.object_type.name}*", "})" unless pr || negated
201
- pr
202
- end
203
-
204
- def describe(highlight = nil)
205
- (entity_type ? entity_type.name : "")+
206
- '('+all_role.map{|role| role.describe(highlight) }*", "+')'
207
- end
208
-
209
- def default_reading(frequency_constraints = [], define_role_names = nil)
210
- preferred_reading.expand(frequency_constraints, define_role_names)
211
- end
212
-
213
- # Does any role of this fact type participate in a preferred identifier?
214
- def is_existential
215
- return false if all_role.size > 2
216
- all_role.detect do |role|
217
- role.all_role_ref.detect do |rr|
218
- rr.role_sequence.all_presence_constraint.detect do |pc|
219
- pc.is_preferred_identifier
220
- end
221
- end
222
- end
223
- end
224
-
225
- def internal_presence_constraints
226
- all_role.map do |r|
227
- r.all_role_ref.map do |rr|
228
- !rr.role_sequence.all_role_ref.detect{|rr1| rr1.role.fact_type != self } ?
229
- rr.role_sequence.all_presence_constraint.to_a :
230
- []
231
- end
232
- end.flatten.compact.uniq
233
- end
234
-
235
- def implicit_boolean_type vocabulary
236
- @constellation.ImplicitBooleanValueType[[vocabulary.identifying_role_values, "_ImplicitBooleanValueType"]] or
237
- @constellation.ImplicitBooleanValueType(vocabulary.identifying_role_values, "_ImplicitBooleanValueType", :concept => [:new, :implication_rule => 'unary'])
238
- end
239
-
240
- # This entity type has just objectified a fact type. Create the necessary ImplicitFactTypes with phantom roles
241
- def create_implicit_fact_type_for_unary
242
- role = all_role.single
243
- return if role.link_fact_type # Already exists
244
- # NORMA doesn't create an implicit fact type here, rather the fact type has an implicit extra role, so looks like a binary
245
- # We only do it when the unary fact type is not objectified
246
- link_fact_type = @constellation.LinkFactType(:new, :implying_role => role)
247
- link_fact_type.concept.implication_rule = 'unary'
248
- entity_type = @entity_type || implicit_boolean_type(role.object_type.vocabulary)
249
- phantom_role = @constellation.Role(link_fact_type, 0, :object_type => entity_type, :concept => :new)
250
- end
251
-
252
- def reading_preferably_starting_with_role role, negated = false
253
- all_reading_by_ordinal.detect do |reading|
254
- reading.text =~ /\{\d\}/ and
255
- reading.role_sequence.all_role_ref_in_order[$1.to_i].role == role and
256
- reading.is_negative == !!negated
257
- end || preferred_reading(negated)
258
- end
259
-
260
- def all_role_in_order
261
- all_role.sort_by{|r| r.ordinal}
262
- end
263
-
264
- def compatible_readings types_array
265
- all_reading.select do |reading|
266
- ok = true
267
- reading.role_sequence.all_role_ref_in_order.each_with_index do |rr, i|
268
- ok = false unless types_array[i].include?(rr.role.object_type)
269
- end
270
- ok
271
- end
272
- end
273
- end
274
-
275
- class Role
276
- def describe(highlight = nil)
277
- object_type.name + (self == highlight ? "*" : "")
278
- end
279
-
280
- # Is there are internal uniqueness constraint on this role only?
281
- def unique
282
- all_role_ref.detect{|rr|
283
- rs = rr.role_sequence
284
- rs.all_role_ref.size == 1 and
285
- rs.all_presence_constraint.detect{|pc|
286
- pc.max_frequency == 1
287
- }
288
- } ? true : false
289
- end
290
-
291
- def is_mandatory
292
- return fact_type.implying_role.is_mandatory if fact_type.is_a?(LinkFactType)
293
- all_role_ref.detect{|rr|
294
- rs = rr.role_sequence
295
- rs.all_role_ref.size == 1 and
296
- rs.all_presence_constraint.detect{|pc|
297
- pc.min_frequency and pc.min_frequency >= 1 and pc.is_mandatory
298
- }
299
- } ? true : false
300
- end
301
-
302
- def preferred_reference
303
- fact_type.preferred_reading.role_sequence.all_role_ref.detect{|rr| rr.role == self }
304
- end
305
-
306
- # Return true if this role is functional (has only one instance wrt its player)
307
- # A role in an objectified fact type is deemed to refer to the implicit role of the objectification.
308
- def is_functional
309
- fact_type.entity_type or
310
- fact_type.all_role.size != 2 or
311
- is_unique
312
- end
313
-
314
- def is_unique
315
- all_role_ref.detect do |rr|
316
- rr.role_sequence.all_role_ref.size == 1 and
317
- rr.role_sequence.all_presence_constraint.detect do |pc|
318
- pc.max_frequency == 1 and !pc.enforcement # Alethic uniqueness constraint
319
- end
320
- end
321
- end
322
-
323
- def name
324
- role_name || object_type.name
325
- end
326
-
327
- end
328
-
329
- class RoleRef
330
- def describe
331
- role_name
332
- end
333
-
334
- def preferred_reference
335
- role.preferred_reference
336
- end
337
-
338
- def role_name(separator = "-")
339
- return 'UNKNOWN' unless role
340
- name_array =
341
- if role.fact_type.all_role.size == 1
342
- if role.fact_type.is_a?(LinkFactType)
343
- "#{role.object_type.name} phantom for #{role.fact_type.role.object_type.name}"
344
- else
345
- role.fact_type.preferred_reading.text.gsub(/\{[0-9]\}/,'').strip.split(/\s/)
346
- end
347
- else
348
- role.role_name || [leading_adjective, role.object_type.name, trailing_adjective].compact.map{|w| w.split(/\s/)}.flatten
349
- end
350
- return separator ? Array(name_array)*separator : Array(name_array)
351
- end
352
-
353
- def cql_leading_adjective
354
- if leading_adjective
355
- # 'foo' => "foo-"
356
- # 'foo bar' => "foo- bar "
357
- # 'foo-bar' => "foo-- bar "
358
- # 'foo-bar baz' => "foo-- bar baz "
359
- # 'bat foo-bar baz' => "bat- foo-bar baz "
360
- leading_adjective.strip.
361
- sub(/[- ]|$/, '-\0 ').sub(/ /, ' ').sub(/[^-]$/, '\0 ').sub(/- $/,'-')
362
- else
363
- ''
364
- end
365
- end
366
-
367
- def cql_trailing_adjective
368
- if trailing_adjective
369
- # 'foo' => "-foo"
370
- # 'foo bar' => " foo -bar"
371
- # 'foo-bar' => " foo --bar"
372
- # 'foo-bar baz' => " foo-bar -baz"
373
- # 'bat foo-bar baz' => " bat foo-bar -baz"
374
- trailing_adjective.
375
- strip.
376
- sub(/(?<a>.*) (?<b>[^- ]+$)|(?<a>.*)(?<b>-[^- ]*)$|(?<a>)(?<b>.*)/) {
377
- " #{$~[:a]} -#{$~[:b]}"
378
- }.
379
- sub(/^ *-/, '-') # A leading space is not needed if the hyphen is at the start
380
- else
381
- ''
382
- end
383
- end
384
-
385
- def cql_name
386
- if role.fact_type.all_role.size == 1
387
- role_name
388
- elsif role.role_name
389
- role.role_name
390
- else
391
- # Where an adjective has multiple words, the hyphen is inserted outside the outermost space, leaving the space
392
- cql_leading_adjective +
393
- role.object_type.name+
394
- cql_trailing_adjective
395
- end
396
- end
397
- end
398
-
399
- class RoleSequence
400
- def describe(highlighted_role_ref = nil)
401
- "("+
402
- all_role_ref.sort_by{|rr| rr.ordinal}.map{|rr| rr.describe + (highlighted_role_ref == rr ? '*' : '') }*", "+
403
- ")"
404
- end
405
-
406
- def all_role_ref_in_order
407
- all_role_ref.sort_by{|rr| rr.ordinal}
408
- end
409
- end
410
-
411
- class ObjectType
412
- # Placeholder for the surrogate transform
413
- attr_reader :injected_surrogate_role
414
-
415
- def is_separate
416
- is_independent or concept.all_concept_annotation.detect{|ca| ca.mapping_annotation == 'separate'}
417
- end
418
- end
419
-
420
- class ValueType
421
- def supertypes_transitive
422
- [self] + (supertype ? supertype.supertypes_transitive : [])
423
- end
424
-
425
- def subtypes
426
- all_value_type_as_supertype
427
- end
428
-
429
- def subtypes_transitive
430
- [self] + subtypes.map{|st| st.subtypes_transitive}.flatten
431
- end
432
-
433
- def common_supertype(other)
434
- return nil unless other.is_?(ActiveFacts::Metamodel::ValueType)
435
- return self if other.supertypes_transitive.include?(self)
436
- return other if supertypes_transitive.include(other)
437
- nil
438
- end
439
- end
440
-
441
- class EntityType
442
- def identification_is_inherited
443
- preferred_identifier and
444
- preferred_identifier.role_sequence.all_role_ref.detect{|rr| rr.role.fact_type.is_a?(ActiveFacts::Metamodel::TypeInheritance) }
445
- end
446
-
447
- def assimilation
448
- if rr = identification_is_inherited
449
- rr.role.fact_type.assimilation
450
- else
451
- nil
452
- end
453
- end
454
-
455
- def preferred_identifier
456
- return @preferred_identifier if @preferred_identifier
457
- if fact_type
458
- # When compiling a fact instance, the delayed creation of a preferred identifier might be necessary
459
- if c = fact_type.check_and_add_spanning_uniqueness_constraint
460
- fact_type.check_and_add_spanning_uniqueness_constraint = nil
461
- c.call
462
- end
463
-
464
- # For a nested fact type, the PI is a unique constraint over N or N-1 roles
465
- fact_roles = Array(fact_type.all_role)
466
- trace :pi, "Looking for PI on nested fact type #{name}" do
467
- pi = catch :pi do
468
- fact_roles[0,2].each{|r| # Try the first two roles of the fact type, that's enough
469
- r.all_role_ref.map{|rr| # All role sequences that reference this role
470
- role_sequence = rr.role_sequence
471
-
472
- # The role sequence is only interesting if it cover only this fact's roles
473
- # or roles of the objectification
474
- next if role_sequence.all_role_ref.size < fact_roles.size-1 # Not enough roles
475
- next if role_sequence.all_role_ref.size > fact_roles.size # Too many roles
476
- next if role_sequence.all_role_ref.detect do |rsr|
477
- if (of = rsr.role.fact_type) != fact_type
478
- case of.all_role.size
479
- when 1 # A unary FT must be played by the objectification of this fact type
480
- next rsr.role.object_type != fact_type.entity_type
481
- when 2 # A binary FT must have the objectification of this FT as the other player
482
- other_role = (of.all_role-[rsr.role])[0]
483
- next other_role.object_type != fact_type.entity_type
484
- else
485
- next true # A role in a ternary (or higher) cannot be usd in our identifier
486
- end
487
- end
488
- rsr.role.fact_type != fact_type
489
- end
490
-
491
- # This role sequence is a candidate
492
- pc = role_sequence.all_presence_constraint.detect{|c|
493
- c.max_frequency == 1 && c.is_preferred_identifier
494
- }
495
- throw :pi, pc if pc
496
- }
497
- }
498
- throw :pi, nil
499
- end
500
- trace :pi, "Got PI #{pi.name||pi.object_id} for nested #{name}" if pi
501
- trace :pi, "Looking for PI on entity that nests this fact" unless pi
502
- raise "Oops, pi for nested fact is #{pi.class}" unless !pi || pi.is_a?(ActiveFacts::Metamodel::PresenceConstraint)
503
- return @preferred_identifier = pi if pi
504
- end
505
- end
506
-
507
- trace :pi, "Looking for PI for ordinary entity #{name} with #{all_role.size} roles:" do
508
- trace :pi, "Roles are in fact types #{all_role.map{|r| r.fact_type.describe(r)}*", "}"
509
- pi = catch :pi do
510
- all_supertypes = supertypes_transitive
511
- trace :pi, "PI roles must be played by one of #{all_supertypes.map(&:name)*", "}" if all_supertypes.size > 1
512
- all_role.each{|role|
513
- next unless role.unique || fact_type
514
- ftroles = Array(role.fact_type.all_role)
515
-
516
- # Skip roles in ternary and higher fact types, they're objectified, and in unaries, they can't identify us.
517
- next if ftroles.size != 2
518
-
519
- trace :pi, "Considering role in #{role.fact_type.describe(role)}"
520
-
521
- # Find the related role which must be included in any PI:
522
- # Note this works with unary fact types:
523
- pi_role = ftroles[ftroles[0] != role ? 0 : -1]
524
-
525
- next if ftroles.size == 2 && pi_role.object_type == self
526
- trace :pi, " Considering #{pi_role.object_type.name} as a PI role"
527
-
528
- # If this is an identifying role, the PI is a PC whose role_sequence spans the role.
529
- # Walk through all role_sequences that span this role, and test each:
530
- pi_role.all_role_ref.each{|rr|
531
- role_sequence = rr.role_sequence # A role sequence that includes a possible role
532
-
533
- trace :pi, " Considering role sequence #{role_sequence.describe}"
534
-
535
- # All roles in this role_sequence must be in fact types which
536
- # (apart from that role) only have roles played by the original
537
- # entity type or a supertype.
538
- #trace :pi, " All supertypes #{all_supertypes.map{|st| "#{st.object_id}=>#{st.name}"}*", "}"
539
- if role_sequence.all_role_ref.detect{|rsr|
540
- fact_type = rsr.role.fact_type
541
- trace :pi, " Role Sequence touches #{fact_type.describe(pi_role)}"
542
-
543
- fact_type_roles = fact_type.all_role
544
- trace :pi, " residual is #{fact_type_roles.map{|r| r.object_type.name}.inspect} minus #{rsr.role.object_type.name}"
545
- residual_roles = fact_type_roles-[rsr.role]
546
- residual_roles.detect{|rfr|
547
- trace :pi, " Checking residual role #{rfr.object_type.object_id}=>#{rfr.object_type.name}"
548
- # This next line looks right, but breaks things. Find out what and why:
549
- # !rfr.unique or
550
- !all_supertypes.include?(rfr.object_type)
551
- }
552
- }
553
- trace :pi, " Discounting this role_sequence because it includes alien roles"
554
- next
555
- end
556
-
557
- # Any presence constraint over this role sequence is a candidate
558
- rr.role_sequence.all_presence_constraint.detect{|pc|
559
- # Found it!
560
- if pc.is_preferred_identifier
561
- trace :pi, "found PI #{pc.name||pc.object_id}, is_preferred_identifier=#{pc.is_preferred_identifier.inspect} over #{pc.role_sequence.describe}"
562
- throw :pi, pc
563
- end
564
- }
565
- }
566
- }
567
- throw :pi, nil
568
- end
569
- raise "Oops, pi for entity is #{pi.class}" if pi && !pi.is_a?(ActiveFacts::Metamodel::PresenceConstraint)
570
- trace :pi, "Got PI #{pi.name||pi.object_id} for #{name}" if pi
571
-
572
- if !pi
573
- if (supertype = identifying_supertype)
574
- # This shouldn't happen now, as an identifying supertype is connected by a fact type
575
- # that has a uniqueness constraint marked as the preferred identifier.
576
- #trace :pi, "PI not found for #{name}, looking in supertype #{supertype.name}"
577
- #pi = supertype.preferred_identifier
578
- #return nil
579
- elsif fact_type
580
- possible_pi = nil
581
- fact_type.all_role.each{|role|
582
- role.all_role_ref.each{|role_ref|
583
- # Discount role sequences that contain roles not in this fact type:
584
- next if role_ref.role_sequence.all_role_ref.detect{|rr| rr.role.fact_type != fact_type }
585
- role_ref.role_sequence.all_presence_constraint.each{|pc|
586
- next unless pc.max_frequency == 1
587
- possible_pi = pc
588
- next unless pc.is_preferred_identifier
589
- pi = pc
590
- break
591
- }
592
- break if pi
593
- }
594
- break if pi
595
- }
596
- if !pi && possible_pi
597
- trace :pi, "Using existing PC as PI for #{name}"
598
- pi = possible_pi
599
- end
600
- else
601
- byebug
602
- trace :pi, "No PI found for #{name}"
603
- end
604
- end
605
- raise "No PI found for #{name}" unless pi
606
- @preferred_identifier = pi
607
- end
608
- end
609
-
610
- # An array of all direct subtypes:
611
- def subtypes
612
- # REVISIT: There's no sorting here. Should there be?
613
- all_type_inheritance_as_supertype.map{|ti| ti.subtype }
614
- end
615
-
616
- def subtypes_transitive
617
- [self] + subtypes.map{|st| st.subtypes_transitive}.flatten.uniq
618
- end
619
-
620
- def all_supertype_inheritance
621
- all_type_inheritance_as_subtype.sort_by{|ti|
622
- [ti.provides_identification ? 0 : 1, ti.supertype.name]
623
- }
624
- end
625
-
626
- # An array all direct supertypes
627
- def supertypes
628
- all_supertype_inheritance.map{|ti|
629
- ti.supertype
630
- }
631
- end
632
-
633
- # An array of self followed by all supertypes in order:
634
- def supertypes_transitive
635
- ([self] + all_type_inheritance_as_subtype.map{|ti|
636
- ti.supertype.supertypes_transitive
637
- }).flatten.uniq
638
- end
639
-
640
- # A subtype does not have a identifying_supertype if it defines its own identifier
641
- def identifying_supertype
642
- trace "Looking for identifying_supertype of #{name}"
643
- all_type_inheritance_as_subtype.detect{|ti|
644
- trace "considering supertype #{ti.supertype.name}"
645
- next unless ti.provides_identification
646
- trace "found identifying supertype of #{name}, it's #{ti.supertype.name}"
647
- return ti.supertype
648
- }
649
- trace "Failed to find identifying supertype of #{name}"
650
- return nil
651
- end
652
-
653
- def common_supertype(other)
654
- return nil unless other.is_?(ActiveFacts::Metamodel::EntityType)
655
- candidates = supertypes_transitive & other.supertypes_transitive
656
- return candidates[0] if candidates.size <= 1
657
- candidates[0] # REVISIT: This might not be the closest supertype
658
- end
659
-
660
- # This entity type has just objectified a fact type. Create the necessary ImplicitFactTypes with phantom roles
661
- def create_implicit_fact_types
662
- fact_type.all_role.map do |role|
663
- next if role.link_fact_type # Already exists
664
- link_fact_type = @constellation.LinkFactType(:new, :implying_role => role)
665
- link_fact_type.concept.implication_rule = 'objectification'
666
- phantom_role = @constellation.Role(link_fact_type, 0, :object_type => self, :concept => :new)
667
- # We could create a copy of the visible external role here, but there's no need yet...
668
- # Nor is there a need for a presence constraint, readings, etc.
669
- link_fact_type
670
- end
671
- end
672
- end
673
-
674
- class Reading
675
- # The frequency_constraints array here, if supplied, may provide for each role either:
676
- # * a PresenceConstraint to be verbalised against the relevant role, or
677
- # * a String, used as a definite or indefinite article on the relevant role, or
678
- # * an array containing two strings (an article and a super-type name)
679
- # The order in the array is the same as the reading's role-sequence.
680
- # REVISIT: This should probably be changed to be the fact role sequence.
681
- #
682
- # define_role_names here is false (use defined names), true (define names) or nil (neither)
683
- def expand(frequency_constraints = [], define_role_names = nil, literals = [], &subscript_block)
684
- expanded = "#{text}"
685
- role_refs = role_sequence.all_role_ref.sort_by{|role_ref| role_ref.ordinal}
686
- (0...role_refs.size).each{|i|
687
- role_ref = role_refs[i]
688
- role = role_ref.role
689
- l_adj = "#{role_ref.leading_adjective}".sub(/(\b-\b|.\b|.\Z)/, '\1-').sub(/\b--\b/,'-- ').sub(/- /,'- ')
690
- l_adj = nil if l_adj == ""
691
- # Double the space to compensate for space removed below
692
- # REVISIT: hyphenated trailing adjectives are not correctly represented here
693
- t_adj = "#{role_ref.trailing_adjective}".sub(/(\b.|\A.)/, '-\1').sub(/ -/,' -')
694
- t_adj = nil if t_adj == ""
695
-
696
- expanded.gsub!(/\{#{i}\}/) do
697
- role_ref = role_refs[i]
698
- if role_ref.role
699
- player = role_ref.role.object_type
700
- role_name = role.role_name
701
- role_name = nil if role_name == ""
702
- if role_name && define_role_names == false
703
- l_adj = t_adj = nil # When using role names, don't add adjectives
704
- end
705
-
706
- freq_con = frequency_constraints[i]
707
- freq_con = freq_con.frequency if freq_con && freq_con.is_a?(ActiveFacts::Metamodel::PresenceConstraint)
708
- if freq_con.is_a?(Array)
709
- freq_con, player_name = *freq_con
710
- else
711
- player_name = player.name
712
- end
713
- else
714
- # We have an unknown role. The reading cannot be correctly expanded
715
- player_name = "UNKNOWN"
716
- role_name = nil
717
- freq_con = nil
718
- end
719
-
720
- literal = literals[i]
721
- words = [
722
- freq_con ? freq_con : nil,
723
- l_adj,
724
- define_role_names == false && role_name ? role_name : player_name,
725
- t_adj,
726
- define_role_names && role_name && player_name != role_name ? "(as #{role_name})" : nil,
727
- # Can't have both a literal and a value constraint, but we don't enforce that here:
728
- literal ? literal : nil
729
- ]
730
- if (subscript_block)
731
- words = subscript_block.call(role_ref, *words)
732
- end
733
- words.compact*" "
734
- end
735
- }
736
- expanded.gsub!(/ ?- ?/, '-') # Remove single spaces around adjectives
737
- #trace "Expanded '#{expanded}' using #{frequency_constraints.inspect}"
738
- expanded
739
- end
740
-
741
- def words_and_role_refs
742
- text.
743
- scan(/(?: |\{[0-9]+\}|[^{} ]+)/). # split up the text into words
744
- reject{|s| s==' '}. # Remove white space
745
- map do |frag| # and go through the bits
746
- if frag =~ /\{([0-9]+)\}/
747
- role_sequence.all_role_ref.detect{|rr| rr.ordinal == $1.to_i}
748
- else
749
- frag
750
- end
751
- end
752
- end
753
-
754
- # Return the array of the numbers of the RoleRefs inserted into this reading from the role_sequence
755
- def role_numbers
756
- text.scan(/\{(\d)\}/).flatten.map{|m| Integer(m) }
757
- end
758
-
759
- def expand_with_final_presence_constraint &b
760
- # Arrange the roles in order they occur in this reading:
761
- role_refs = role_sequence.all_role_ref_in_order
762
- role_numbers = text.scan(/\{(\d)\}/).flatten.map{|m| Integer(m) }
763
- roles = role_numbers.map{|m| role_refs[m].role }
764
- fact_constraints = fact_type.internal_presence_constraints
765
-
766
- # Find the constraints that constrain frequency over each role we can verbalise:
767
- frequency_constraints = []
768
- roles.each do |role|
769
- frequency_constraints <<
770
- if (role == roles.last) # On the last role of the reading, emit any presence constraint
771
- constraint = fact_constraints.
772
- detect do |c| # Find a UC that spans all other Roles
773
- c.is_a?(ActiveFacts::Metamodel::PresenceConstraint) &&
774
- roles-c.role_sequence.all_role_ref.map(&:role) == [role]
775
- end
776
- constraint && constraint.frequency
777
- else
778
- nil
779
- end
780
- end
781
-
782
- expand(frequency_constraints) { |*a| b && b.call(*a) }
783
- end
784
- end
785
-
786
- class ValueConstraint
787
- def describe
788
- as_cql
789
- end
790
-
791
- def as_cql
792
- "restricted to "+
793
- ( if regular_expression
794
- '/' + regular_expression + '/'
795
- else
796
- '{' + all_allowed_range_sorted.map{|ar| ar.to_s(false) }*', ' + '}'
797
- end
798
- )
799
- end
800
-
801
- def all_allowed_range_sorted
802
- all_allowed_range.sort_by{|ar|
803
- ((min = ar.value_range.minimum_bound) && min.value.literal) ||
804
- ((max = ar.value_range.maximum_bound) && max.value.literal)
805
- }
806
- end
807
-
808
- def to_s
809
- if all_allowed_range.size > 1
810
- "[" +
811
- all_allowed_range_sorted.map { |ar| ar.to_s(true) }*", " +
812
- "]"
813
- else
814
- all_allowed_range.single.to_s
815
- end
816
- end
817
- end
818
-
819
- class AllowedRange
820
- def to_s(infinity = true)
821
- min = value_range.minimum_bound
822
- max = value_range.maximum_bound
823
- # Open-ended string ranges will fail in Ruby
824
-
825
- if min = value_range.minimum_bound
826
- min = min.value
827
- if min.is_literal_string
828
- min_literal = min.literal.inspect.gsub(/\A"|"\Z/,"'") # Escape string characters
829
- else
830
- min_literal = min.literal
831
- end
832
- else
833
- min_literal = infinity ? "-Infinity" : ""
834
- end
835
- if max = value_range.maximum_bound
836
- max = max.value
837
- if max.is_literal_string
838
- max_literal = max.literal.inspect.gsub(/\A"|"\Z/,"'") # Escape string characters
839
- else
840
- max_literal = max.literal
841
- end
842
- else
843
- max_literal = infinity ? "Infinity" : ""
844
- end
845
-
846
- min_literal +
847
- (min_literal != (max&&max_literal) ? (".." + max_literal) : "")
848
- end
849
- end
850
-
851
- class Value
852
- def to_s
853
- if is_literal_string
854
- "'"+
855
- literal.
856
- inspect. # Use Ruby's inspect to generate necessary escapes
857
- gsub(/\A"|"\Z/,''). # Remove surrounding quotes
858
- gsub(/'/, "\\'") + # Escape any single quotes
859
- "'"
860
- else
861
- literal
862
- end +
863
- (unit ? " " + unit.name : "")
864
- end
865
-
866
- def inspect
867
- to_s
868
- end
869
- end
870
-
871
- class PresenceConstraint
872
- def frequency
873
- min = min_frequency
874
- max = max_frequency
875
- [
876
- ((min && min > 0 && min != max) ? "at least #{min == 1 ? "one" : min.to_s}" : nil),
877
- ((max && min != max) ? "at most #{max == 1 ? "one" : max.to_s}" : nil),
878
- ((max && min == max) ? "#{max == 1 ? "one" : "exactly "+max.to_s}" : nil)
879
- ].compact * " and "
880
- end
881
-
882
- def describe
883
- min = min_frequency
884
- max = max_frequency
885
- 'PresenceConstraint over '+role_sequence.describe + " occurs " + frequency + " time#{(min&&min>1)||(max&&max>1) ? 's' : ''}"
886
- end
887
- end
888
-
889
- class SubsetConstraint
890
- def describe
891
- 'SubsetConstraint(' +
892
- subset_role_sequence.describe
893
- ' < ' +
894
- superset_role_sequence.describe +
895
- ')'
896
- end
897
- end
898
-
899
- class SetComparisonConstraint
900
- def describe
901
- self.class.basename+'(' +
902
- all_set_comparison_roles.map do |scr|
903
- scr.role_sequence.describe
904
- end*',' +
905
- ')'
906
- end
907
- end
908
-
909
- class RingConstraint
910
- def describe
911
- 'RingConstraint(' +
912
- ring_type.to_s+': ' +
913
- role.describe+', ' +
914
- other_role.describe+' in ' +
915
- role.fact_type.default_reading +
916
- ')'
917
- end
918
- end
919
-
920
- class TypeInheritance
921
- def describe(role = nil)
922
- "#{subtype.name} is a kind of #{supertype.name}"
923
- end
924
-
925
- def supertype_role
926
- (roles = all_role.to_a)[0].object_type == supertype ? roles[0] : roles[1]
927
- end
928
-
929
- def subtype_role
930
- (roles = all_role.to_a)[0].object_type == subtype ? roles[0] : roles[1]
931
- end
932
- end
933
-
934
- class Step
935
- def describe
936
- "Step " +
937
- "#{is_optional ? 'maybe ' : ''}" +
938
- (is_unary_step ? '(unary) ' : "from #{input_play.describe} ") +
939
- "#{is_disallowed ? 'not ' : ''}" +
940
- "to #{output_plays.map(&:describe)*', '}" +
941
- (objectification_variable ? ", objectified as #{objectification_variable.describe}" : '') +
942
- " '#{fact_type.default_reading}'"
943
- end
944
-
945
- def input_play
946
- all_play.detect{|p| p.is_input}
947
- end
948
-
949
- def output_plays
950
- all_play.reject{|p| p.is_input}
951
- end
952
-
953
- def is_unary_step
954
- # Preserve this in case we have to use a real variable for the phantom
955
- all_play.size == 1
956
- end
957
-
958
- def is_objectification_step
959
- !!objectification_variable
960
- end
961
-
962
- def external_fact_type
963
- fact_type.is_a?(LinkFactType) ? fact_type.role.fact_type : fact_type
964
- end
965
- end
966
-
967
- class Variable
968
- def describe
969
- object_type.name +
970
- (subscript ? "(#{subscript})" : '') +
971
- " Var#{ordinal}" +
972
- (value ? ' = '+value.to_s : '')
973
- end
974
-
975
- def all_step
976
- all_play.map(&:step).uniq
977
- end
978
- end
979
-
980
- class Play
981
- def describe
982
- "#{role.object_type.name} Var#{variable.ordinal}" +
983
- (role_ref ? " (projected)" : "")
984
- end
985
- end
986
-
987
- class Query
988
- def describe
989
- steps_shown = {}
990
- 'Query(' +
991
- all_variable.sort_by{|var| var.ordinal}.map do |variable|
992
- variable.describe + ': ' +
993
- variable.all_step.map do |step|
994
- next if steps_shown[step]
995
- steps_shown[step] = true
996
- step.describe
997
- end.compact.join(',')
998
- end.join('; ') +
999
- ')'
1000
- end
1001
-
1002
- def show
1003
- steps_shown = {}
1004
- trace :query, "Displaying full contents of Query #{concept.guid}" do
1005
- all_variable.sort_by{|var| var.ordinal}.each do |variable|
1006
- trace :query, "#{variable.describe}" do
1007
- variable.all_step.
1008
- each do |step|
1009
- next if steps_shown[step]
1010
- steps_shown[step] = true
1011
- trace :query, "#{step.describe}"
1012
- end
1013
- variable.all_play.each do |play|
1014
- trace :query, "role of #{play.describe} in '#{play.role.fact_type.default_reading}'"
1015
- end
1016
- end
1017
- end
1018
- end
1019
- end
1020
-
1021
- def all_step
1022
- all_variable.map{|var| var.all_step.to_a}.flatten.uniq
1023
- end
1024
-
1025
- # Check all parts of this query for validity
1026
- def validate
1027
- show
1028
- return
1029
-
1030
- # Check the variables:
1031
- steps = []
1032
- variables = all_variable.sort_by{|var| var.ordinal}
1033
- variables.each_with_index do |variable, i|
1034
- raise "Variable #{i} should have ordinal #{variable.ordinal}" unless variable.ordinal == i
1035
- raise "Variable #{i} has missing object_type" unless variable.object_type
1036
- variable.all_play do |play|
1037
- raise "Variable for #{object_type.name} includes role played by #{play.object_type.name}" unless play.object_type == object_type
1038
- end
1039
- steps += variable.all_step
1040
- end
1041
- steps.uniq!
1042
-
1043
- # Check the steps:
1044
- steps.each do |step|
1045
- raise "Step has missing fact type" unless step.fact_type
1046
- raise "Step has missing input node" unless step.input_play
1047
- raise "Step has missing output node" unless step.output_play
1048
- if (role = input_play).role.fact_type != fact_type or
1049
- (role = output_play).role.fact_type != fact_type
1050
- raise "Step has role #{role.describe} which doesn't belong to the fact type '#{fact_type.default_reading}' it traverses"
1051
- end
1052
- end
1053
-
1054
- # REVISIT: Do a connectivity check
1055
- end
1056
- end
1057
-
1058
- class LinkFactType
1059
- def default_reading
1060
- # There are two cases, where role is in a unary fact type, and where the fact type is objectified
1061
- # If a unary fact type is objectified, only the LinkFactType for the objectification is asserted
1062
- if objectification = implying_role.fact_type.entity_type
1063
- "#{objectification.name} involves #{implying_role.object_type.name}"
1064
- else
1065
- implying_role.fact_type.default_reading+" Boolean" # Must be a unary FT
1066
- end
1067
- end
1068
-
1069
- def add_reading implicit_reading
1070
- @readings ||= []
1071
- @readings << implicit_reading
1072
- end
1073
-
1074
- def all_reading
1075
- @readings ||=
1076
- [ ImplicitReading.new(
1077
- self,
1078
- implying_role.fact_type.entity_type ? "{0} involves {1}" : implying_role.fact_type.default_reading+" Boolean"
1079
- )
1080
- ] +
1081
- Array(implying_role.fact_type.entity_type ? ImplicitReading.new(self, "{1} is involved in {0}") : nil)
1082
- end
1083
-
1084
- def reading_preferably_starting_with_role role, negated = false
1085
- all_reading[role == implying_role ? 1 : 0]
1086
- end
1087
-
1088
- # This is only used for debugging, from RoleRef#describe
1089
- class ImplicitReading
1090
- attr_accessor :fact_type, :text
1091
- attr_reader :is_negative # Never true
1092
-
1093
- def initialize(fact_type, text)
1094
- @fact_type = fact_type
1095
- @text = text
1096
- end
1097
-
1098
- class ImplicitReadingRoleSequence
1099
- class ImplicitReadingRoleRef
1100
- attr_reader :role
1101
- attr_reader :role_sequence
1102
- def initialize(role, role_sequence)
1103
- @role = role
1104
- @role_sequence = role_sequence
1105
- end
1106
- def variable; nil; end
1107
- def play; nil; end
1108
- def leading_adjective; nil; end
1109
- def trailing_adjective; nil; end
1110
- def describe
1111
- @role.object_type.name
1112
- end
1113
- end
1114
-
1115
- def initialize roles
1116
- @role_refs = roles.map{|role| ImplicitReadingRoleRef.new(role, self) }
1117
- end
1118
-
1119
- def all_role_ref
1120
- @role_refs
1121
- end
1122
- def all_role_ref_in_order
1123
- @role_refs
1124
- end
1125
- def describe
1126
- '('+@role_refs.map(&:describe)*', '+')'
1127
- end
1128
- def all_reading
1129
- []
1130
- end
1131
- end
1132
-
1133
- def role_sequence
1134
- ImplicitReadingRoleSequence.new([@fact_type.implying_role, @fact_type.all_role.single])
1135
- end
1136
-
1137
- def ordinal; 0; end
1138
-
1139
- def expand
1140
- text.gsub(/\{([01])\}/) do
1141
- if $1 == '0'
1142
- fact_type.all_role[0].object_type.name
1143
- else
1144
- fact_type.implying_role.object_type.name
1145
- end
1146
- end
1147
- end
1148
- end
1149
- end
1150
-
1151
- # Some queries must be over the proximate roles, some over the counterpart roles.
1152
- # Return the common superclass of the appropriate roles, and the actual roles
1153
- def self.plays_over roles, options = :both # Or :proximate, :counterpart
1154
- # If we can stay inside this objectified FT, there's no query:
1155
- roles = Array(roles) # To be safe, in case we get a role collection proxy
1156
- return nil if roles.size == 1 or
1157
- options != :counterpart && roles.map{|role| role.fact_type}.uniq.size == 1
1158
- proximate_sups, counterpart_sups, obj_sups, counterpart_roles, objectification_roles =
1159
- *roles.inject(nil) do |d_c_o, role|
1160
- object_type = role.object_type
1161
- fact_type = role.fact_type
1162
-
1163
- proximate_role_supertypes = object_type.supertypes_transitive
1164
-
1165
- # A role in an objectified fact type may indicate either the objectification or the counterpart player.
1166
- # This could be ambiguous. Figure out both and prefer the counterpart over the objectification.
1167
- counterpart_role_supertypes =
1168
- if fact_type.all_role.size > 2
1169
- possible_roles = fact_type.all_role.select{|r| d_c_o && d_c_o[1].include?(r.object_type) }
1170
- if possible_roles.size == 1 # Only one candidate matches the types of the possible variables
1171
- counterpart_role = possible_roles[0]
1172
- d_c_o[1] # No change
1173
- else
1174
- # puts "#{constraint_type} #{name}: Awkward, try counterpart-role query on a >2ary '#{fact_type.default_reading}'"
1175
- # Try all roles; hopefully we don't have two roles with a matching candidate here:
1176
- # Find which role is compatible with the existing supertypes, if any
1177
- if d_c_o
1178
- st = nil
1179
- counterpart_role =
1180
- fact_type.all_role.detect{|r| ((st = r.object_type.supertypes_transitive) & d_c_o[1]).size > 0}
1181
- st
1182
- else
1183
- counterpart_role = nil # This can't work, we don't have any basis for a decision (must be objectification)
1184
- []
1185
- end
1186
- #fact_type.all_role.map{|r| r.object_type.supertypes_transitive}.flatten.uniq
1187
- end
1188
- else
1189
- # Get the supertypes of the counterpart role (care with unaries):
1190
- ftr = role.fact_type.all_role.to_a
1191
- (counterpart_role = ftr[0] == role ? ftr[-1] : ftr[0]).object_type.supertypes_transitive
1192
- end
1193
-
1194
- if fact_type.entity_type
1195
- objectification_role_supertypes =
1196
- fact_type.entity_type.supertypes_transitive+object_type.supertypes_transitive
1197
- objectification_role = role.link_fact_type.all_role.single # Find the phantom role here
1198
- else
1199
- objectification_role_supertypes = counterpart_role_supertypes
1200
- objectification_role = counterpart_role
1201
- end
1202
-
1203
- if !d_c_o
1204
- d_c_o = [proximate_role_supertypes, counterpart_role_supertypes, objectification_role_supertypes, [counterpart_role], [objectification_role]]
1205
- #puts "role player supertypes starts #{d_c_o.map{|dco| dco.map(&:name).inspect}*' or '}"
1206
- else
1207
- #puts "continues #{[proximate_role_supertypes, counterpart_role_supertypes, objectification_role_supertypes]map{|dco| dco.map(&:name).inspect}*' or '}"
1208
- d_c_o[0] &= proximate_role_supertypes
1209
- d_c_o[1] &= counterpart_role_supertypes
1210
- d_c_o[2] &= objectification_role_supertypes
1211
- d_c_o[3] << (counterpart_role || objectification_role)
1212
- d_c_o[4] << (objectification_role || counterpart_role)
1213
- end
1214
- d_c_o
1215
- end # inject
1216
-
1217
- # Discount a subtype step over an object type that's not a player here,
1218
- # if we can use an objectification step to an object type that is:
1219
- if counterpart_sups.size > 0 && obj_sups.size > 0 && counterpart_sups[0] != obj_sups[0]
1220
- trace :query, "ambiguous query, could be over #{counterpart_sups[0].name} or #{obj_sups[0].name}"
1221
- if !roles.detect{|r| r.object_type == counterpart_sups[0]} and roles.detect{|r| r.object_type == obj_sups[0]}
1222
- trace :query, "discounting #{counterpart_sups[0].name} in favour of direct objectification"
1223
- counterpart_sups = []
1224
- end
1225
- end
1226
-
1227
- # Choose the first entry in the first non-empty supertypes list:
1228
- if options != :counterpart && proximate_sups[0]
1229
- [ proximate_sups[0], roles ]
1230
- elsif !counterpart_sups.empty?
1231
- [ counterpart_sups[0], counterpart_roles ]
1232
- else
1233
- [ obj_sups[0], objectification_roles ]
1234
- end
1235
- end
1236
-
1237
- class Fact
1238
- def verbalise(context = nil)
1239
- reading = fact_type.preferred_reading
1240
- reading_roles = reading.role_sequence.all_role_ref.sort_by{|rr| rr.ordinal}.map{|rr| rr.role }
1241
- role_values_in_reading_order = all_role_value.sort_by{|rv| reading_roles.index(rv.role) }
1242
- instance_verbalisations = role_values_in_reading_order.map do |rv|
1243
- if rv.instance.value
1244
- v = rv.instance.verbalise
1245
- else
1246
- if (c = rv.instance.object_type).is_a?(EntityType)
1247
- if !c.preferred_identifier.role_sequence.all_role_ref.detect{|rr| rr.role.fact_type == fact_type}
1248
- v = rv.instance.verbalise
1249
- end
1250
- end
1251
- end
1252
- next nil unless v
1253
- v.to_s.sub(/(#{rv.instance.object_type.name}|\S*)\s/,'')
1254
- end
1255
- reading.expand([], false, instance_verbalisations)
1256
- end
1257
- end
1258
-
1259
- class Instance
1260
- def verbalise(context = nil)
1261
- return "#{object_type.name} #{value}" if object_type.is_a?(ValueType)
1262
-
1263
- return "#{object_type.name} (in which #{fact.verbalise(context)})" if object_type.fact_type
1264
-
1265
- # It's an entity that's not an objectified fact type
1266
-
1267
- # If it has a simple identifier, there's no need to fully verbalise the identifying facts.
1268
- # This recursive block returns either the identifying value or nil
1269
- simple_identifier = proc do |instance|
1270
- if instance.object_type.is_a?(ActiveFacts::Metamodel::ValueType)
1271
- instance
1272
- else
1273
- pi = instance.object_type.preferred_identifier
1274
- identifying_role_refs = pi.role_sequence.all_role_ref_in_order
1275
- if identifying_role_refs.size != 1
1276
- nil
1277
- else
1278
- role = identifying_role_refs[0].role
1279
- my_role = (role.fact_type.all_role.to_a-[role])[0]
1280
- identifying_fact = my_role.all_role_value.detect{|rv| rv.instance == self}.fact
1281
- irv = identifying_fact.all_role_value.detect{|rv| rv.role == role}
1282
- identifying_instance = irv.instance
1283
- simple_identifier.call(identifying_instance)
1284
- end
1285
- end
1286
- end
1287
-
1288
- if (id = simple_identifier.call(self))
1289
- "#{object_type.name} #{id.value}"
1290
- else
1291
- pi = object_type.preferred_identifier
1292
- identifying_role_refs = pi.role_sequence.all_role_ref_in_order
1293
- "#{object_type.name}" +
1294
- " is identified by " + # REVISIT: Where the single fact type is TypeInheritance, we can shrink this
1295
- identifying_role_refs.map do |rr|
1296
- rr = rr.preferred_reference
1297
- [ (l = rr.leading_adjective) ? l+"-" : nil,
1298
- rr.role.role_name || rr.role.object_type.name,
1299
- (t = rr.trailing_adjective) ? l+"-" : nil
1300
- ].compact*""
1301
- end * " and " +
1302
- " where " +
1303
- identifying_role_refs.map do |rr| # Go through the identifying roles and emit the facts that define them
1304
- instance_role = object_type.all_role.detect{|r| r.fact_type == rr.role.fact_type}
1305
- identifying_fact = all_role_value.detect{|rv| rv.fact.fact_type == rr.role.fact_type}.fact
1306
- #counterpart_role = (rr.role.fact_type.all_role.to_a-[instance_role])[0]
1307
- #identifying_instance = counterpart_role.all_role_value.detect{|rv| rv.fact == identifying_fact}.instance
1308
- identifying_fact.verbalise(context)
1309
- end*", "
1310
- end
1311
-
1312
- end
1313
- end
1314
-
1315
- class ContextNote
1316
- def verbalise(context=nil)
1317
- as_cql
1318
- end
1319
-
1320
- def as_cql
1321
- ' (' +
1322
- ( if all_context_according_to
1323
- 'according to '
1324
- all_context_according_to.map do |act|
1325
- act.agent.agent_name+', '
1326
- end.join('')
1327
- end
1328
- ) +
1329
- context_note_kind.gsub(/_/, ' ') +
1330
- ' ' +
1331
- discussion +
1332
- ( if agreement
1333
- ', as agreed ' +
1334
- (agreement.date ? ' on '+agreement.date.iso8601.inspect+' ' : '') +
1335
- 'by '
1336
- agreement.all_context_agreed_by.map do |acab|
1337
- acab.agent.agent_name+', '
1338
- end.join('')
1339
- else
1340
- ''
1341
- end
1342
- ) +
1343
- ')'
1344
- end
1345
- end
1346
-
1347
- end
1348
- end