activefacts 1.6.0 → 1.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.
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