activefacts-cql 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.
- checksums.yaml +7 -0
- data/.gitignore +11 -0
- data/.rspec +1 -0
- data/.travis.yml +4 -0
- data/Gemfile +10 -0
- data/LICENSE.txt +21 -0
- data/README.md +19 -0
- data/Rakefile +6 -0
- data/activefacts-cql.gemspec +29 -0
- data/bin/setup +7 -0
- data/lib/activefacts/cql.rb +7 -0
- data/lib/activefacts/cql/.gitignore +0 -0
- data/lib/activefacts/cql/Rakefile +14 -0
- data/lib/activefacts/cql/compiler.rb +156 -0
- data/lib/activefacts/cql/compiler/clause.rb +1137 -0
- data/lib/activefacts/cql/compiler/constraint.rb +581 -0
- data/lib/activefacts/cql/compiler/entity_type.rb +457 -0
- data/lib/activefacts/cql/compiler/expression.rb +443 -0
- data/lib/activefacts/cql/compiler/fact.rb +390 -0
- data/lib/activefacts/cql/compiler/fact_type.rb +421 -0
- data/lib/activefacts/cql/compiler/query.rb +106 -0
- data/lib/activefacts/cql/compiler/shared.rb +161 -0
- data/lib/activefacts/cql/compiler/value_type.rb +174 -0
- data/lib/activefacts/cql/parser.rb +234 -0
- data/lib/activefacts/cql/parser/CQLParser.treetop +167 -0
- data/lib/activefacts/cql/parser/Context.treetop +48 -0
- data/lib/activefacts/cql/parser/Expressions.treetop +67 -0
- data/lib/activefacts/cql/parser/FactTypes.treetop +358 -0
- data/lib/activefacts/cql/parser/Language/English.treetop +315 -0
- data/lib/activefacts/cql/parser/Language/French.treetop +315 -0
- data/lib/activefacts/cql/parser/Language/Mandarin.treetop +304 -0
- data/lib/activefacts/cql/parser/LexicalRules.treetop +253 -0
- data/lib/activefacts/cql/parser/ObjectTypes.treetop +210 -0
- data/lib/activefacts/cql/parser/Terms.treetop +183 -0
- data/lib/activefacts/cql/parser/ValueTypes.treetop +202 -0
- data/lib/activefacts/cql/parser/nodes.rb +49 -0
- data/lib/activefacts/cql/require.rb +36 -0
- data/lib/activefacts/cql/verbaliser.rb +804 -0
- data/lib/activefacts/cql/version.rb +5 -0
- data/lib/activefacts/input/cql.rb +43 -0
- data/lib/rubygems_plugin.rb +12 -0
- metadata +167 -0
@@ -0,0 +1,581 @@
|
|
1
|
+
module ActiveFacts
|
2
|
+
module CQL
|
3
|
+
class Compiler < ActiveFacts::CQL::Parser
|
4
|
+
class Enforcement
|
5
|
+
attr_reader :action, :agent
|
6
|
+
def initialize action, agent
|
7
|
+
@action = action
|
8
|
+
@agent = agent
|
9
|
+
end
|
10
|
+
|
11
|
+
def compile constellation, constraint
|
12
|
+
constellation.Enforcement(constraint, :enforcement_code => @action, :agent => @agent)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
class ContextNote
|
17
|
+
attr_reader :context_kind, :discussion, :who, :agreed_date, :agreed_agents
|
18
|
+
|
19
|
+
def initialize context_kind, discussion, who, agreed
|
20
|
+
@context_kind, @discussion, @who, @agreed = context_kind, discussion, who, agreed
|
21
|
+
@agreed_date, @agreed_agents = *agreed
|
22
|
+
end
|
23
|
+
|
24
|
+
def compile constellation, target
|
25
|
+
context_note =
|
26
|
+
constellation.ContextNote(
|
27
|
+
:new,
|
28
|
+
:context_note_kind => @context_kind,
|
29
|
+
:discussion => @discussion
|
30
|
+
)
|
31
|
+
context_note.relevant_concept = target.concept
|
32
|
+
if @agreed_date || @agreed_agents
|
33
|
+
agreement = constellation.Agreement(context_note)
|
34
|
+
agreement.date = @agreed_date if @agreed_date
|
35
|
+
@agreed_agents.each do |agent|
|
36
|
+
constellation.ContextAgreedBy(agreement, agent)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
if @who && @who.size > 0
|
40
|
+
@who.each do |agent|
|
41
|
+
constellation.ContextAccordingTo(context_note, agent)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
context_note
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
class Constraint < Definition
|
49
|
+
def initialize context_note, enforcement, clauses_lists = []
|
50
|
+
if context_note.is_a?(Treetop::Runtime::SyntaxNode) && !context_note.empty?
|
51
|
+
context_note = context_note.empty? ? nil : context_note.ast
|
52
|
+
else
|
53
|
+
context_note = nil # Perhaps a context note got attached to one of the clauses. Steal it.
|
54
|
+
clauses_lists.detect do |clauses_list|
|
55
|
+
if c = clauses_list.last.context_note
|
56
|
+
context_note = c
|
57
|
+
clauses_list.last.context_note = nil
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
@context_note = context_note
|
62
|
+
@enforcement = enforcement
|
63
|
+
@clauses_lists = clauses_lists
|
64
|
+
end
|
65
|
+
|
66
|
+
def compile
|
67
|
+
@context_note.compile @constellation, @constraint if @context_note
|
68
|
+
@constraint
|
69
|
+
end
|
70
|
+
|
71
|
+
def loose_binding
|
72
|
+
# Override for constraint types that need loose binding (same role player matching with different adjectives)
|
73
|
+
end
|
74
|
+
|
75
|
+
def bind_clauses extra = []
|
76
|
+
@context = CompilationContext.new(@vocabulary)
|
77
|
+
@context.left_contraction_allowed = true
|
78
|
+
|
79
|
+
@context.bind @clauses_lists, extra
|
80
|
+
@clauses_lists.map do |clauses_list|
|
81
|
+
@context.left_contractable_clause = nil # Don't contract outside this set of clauses
|
82
|
+
clauses_list.each do |clause|
|
83
|
+
fact_type = clause.match_existing_fact_type @context
|
84
|
+
raise "Unrecognised fact type #{clause.inspect} in #{self.class}" unless fact_type
|
85
|
+
raise "Negated fact type #{clause.inspect} in #{self.class} is not yet supported" if clause.certainty == false
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
# Any constrained roles will be first identified here.
|
90
|
+
# This means that they can't introduce role names.
|
91
|
+
loose_binding
|
92
|
+
|
93
|
+
# Ok, we have bound all players by subscript/role_name, by adjectives, and by loose binding,
|
94
|
+
# and matched all the fact types that matter. Now assemble a query (with all steps) for
|
95
|
+
# each query list, and build an array of the bindings that are involved in the steps.
|
96
|
+
@bindings_by_list =
|
97
|
+
@clauses_lists.map do |clauses_list|
|
98
|
+
all_bindings_in_clauses(clauses_list)
|
99
|
+
end
|
100
|
+
|
101
|
+
warn_ignored_queries
|
102
|
+
end
|
103
|
+
|
104
|
+
def warn_ignored_queries
|
105
|
+
# Warn about ignored queries
|
106
|
+
@clauses_lists.each do |clauses_list|
|
107
|
+
fact_types = clauses_list.map{|clauses| (rr = clauses.refs[0].role_ref) && rr.role.fact_type}.compact.uniq
|
108
|
+
if fact_types.size > 1
|
109
|
+
raise "------->>>> join ignored in #{self.class}: #{fact_types.map{|ft| ft.preferred_reading.expand}*' and '}"
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
def loose_bind_wherever_possible
|
115
|
+
# Apply loose binding over applicable roles:
|
116
|
+
trace :binding, "Loose binding on #{self.class.name}" do
|
117
|
+
@clauses_lists.each do |clauses_list|
|
118
|
+
clauses_list.each do |clause|
|
119
|
+
clause.refs.each_with_index do |ref, i|
|
120
|
+
next if ref.binding.refs.size > 1
|
121
|
+
# if clause.side_effects && !clause.side_effects.role_side_effects[i].residual_adjectives
|
122
|
+
# trace :binding, "Discounting #{ref.inspect} as needing loose binding because it has no residual_adjectives"
|
123
|
+
# next
|
124
|
+
# end
|
125
|
+
# This ref didn't match any other ref. Have a scout around for a suitable partner
|
126
|
+
candidates = @context.bindings.
|
127
|
+
select do |key, binding|
|
128
|
+
binding.player == ref.binding.player and
|
129
|
+
binding != ref.binding and
|
130
|
+
binding.role_name == ref.binding.role_name and # Both will be nil if they match
|
131
|
+
# REVISIT: Don't bind to a binding with a role occurrence in the same clause
|
132
|
+
!binding.refs.detect{|vr|
|
133
|
+
x = vr.clause == clause
|
134
|
+
# puts "Discounting binding #{binding.inspect} as a match for #{ref.inspect} because it's already bound to a player in #{ref.clause.inspect}" if x
|
135
|
+
x
|
136
|
+
}
|
137
|
+
end.map{|k,b| b}
|
138
|
+
next if candidates.size != 1 # Fail
|
139
|
+
trace :binding, "Loose binding #{ref.inspect} to #{candidates[0].inspect}"
|
140
|
+
ref.rebind_to(@context, candidates[0].refs[0])
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
def loose_bind
|
148
|
+
# Apply loose binding over applicable @roles:
|
149
|
+
trace :binding, "Check for loose bindings on #{@roles.size} roles in #{self.class.name}" do
|
150
|
+
@roles.each do |ref|
|
151
|
+
if ref.binding.refs.size < @clauses_lists.size+1
|
152
|
+
trace :binding, "Insufficient bindings for #{ref.inspect} (#{ref.binding.refs.size}, expected #{@clauses_lists.size+1}), attempting loose binding" do
|
153
|
+
@clauses_lists.each do |clauses_list|
|
154
|
+
candidates = []
|
155
|
+
next if clauses_list.
|
156
|
+
detect do |clause|
|
157
|
+
trace :binding, "Checking #{clause.inspect}"
|
158
|
+
clause.refs.
|
159
|
+
detect do |vr|
|
160
|
+
already_bound = vr.binding == ref.binding
|
161
|
+
if !already_bound && vr.player == ref.player
|
162
|
+
candidates << vr
|
163
|
+
end
|
164
|
+
already_bound
|
165
|
+
end
|
166
|
+
end
|
167
|
+
trace :binding, "Attempting loose binding for #{ref.inspect} in #{clauses_list.inspect}, from the following candidates: #{candidates.inspect}"
|
168
|
+
|
169
|
+
if candidates.size == 1
|
170
|
+
trace :binding, "Rebinding #{candidates[0].inspect} to #{ref.inspect}"
|
171
|
+
candidates[0].rebind_to(@context, ref)
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
def common_bindings
|
181
|
+
@common_bindings ||= @bindings_by_list[1..-1].inject(@bindings_by_list[0]) { |r, b| r & b }
|
182
|
+
raise "#{self.class} must cover some of the same roles, see #{@bindings_by_list.inspect}" unless @common_bindings.size > 0
|
183
|
+
@common_bindings
|
184
|
+
end
|
185
|
+
|
186
|
+
def to_s
|
187
|
+
"#{self.class.name.sub(/.*::/,'')}" + (@clauses_lists.size > 0 ? " over #{@clauses_lists.inspect}" : '')
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
class PresenceConstraint < Constraint
|
192
|
+
def initialize context_note, enforcement, clauses_lists, refs, quantifier
|
193
|
+
super context_note, enforcement, clauses_lists
|
194
|
+
@refs = refs || []
|
195
|
+
@quantifier = quantifier
|
196
|
+
end
|
197
|
+
|
198
|
+
def compile
|
199
|
+
@clauses = @clauses_lists.map do |clauses_list|
|
200
|
+
raise "REVISIT: join presence constraints not supported yet" if clauses_list.size > 1 or
|
201
|
+
clauses_list.detect{|clause| clause.refs.detect{|vr| vr.nested_clauses } }
|
202
|
+
clauses_list[0]
|
203
|
+
end
|
204
|
+
|
205
|
+
bind_clauses @refs
|
206
|
+
|
207
|
+
if @refs.size > 0
|
208
|
+
bind_constrained_roles
|
209
|
+
else
|
210
|
+
cb = common_bindings
|
211
|
+
raise "Either/or must have only one duplicated role, not #{cb.inspect}" unless cb.size == 1
|
212
|
+
@refs = cb[0].refs.reverse # REVISIT: Should have order these by clause, not like this
|
213
|
+
end
|
214
|
+
|
215
|
+
role_sequence = @constellation.RoleSequence(:new)
|
216
|
+
@refs.each do |ref|
|
217
|
+
raise "The constrained role #{ref.inspect} was not found in the invoked fact types" if ref.binding.refs.size == 1
|
218
|
+
(ref.binding.refs-[ref]).each do |ref|
|
219
|
+
role = (ref.role_ref && ref.role_ref.role) || ref.role
|
220
|
+
raise "FactType role not found for #{ref.inspect}" unless role
|
221
|
+
@constellation.RoleRef(role_sequence, role_sequence.all_role_ref.size, :role => role)
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
225
|
+
@constraint =
|
226
|
+
@constellation.PresenceConstraint(
|
227
|
+
:new,
|
228
|
+
:name => '',
|
229
|
+
:vocabulary => @vocabulary,
|
230
|
+
:role_sequence => role_sequence,
|
231
|
+
:min_frequency => @quantifier.min,
|
232
|
+
:max_frequency => @quantifier.max,
|
233
|
+
:is_preferred_identifier => false,
|
234
|
+
:is_mandatory => @quantifier.min && @quantifier.min > 0
|
235
|
+
)
|
236
|
+
if @quantifier.pragmas
|
237
|
+
@quantifier.pragmas.each do |p|
|
238
|
+
@constellation.ConceptAnnotation(:concept => @constraint.concept, :mapping_annotation => p)
|
239
|
+
end
|
240
|
+
end
|
241
|
+
@enforcement.compile(@constellation, @constraint) if @enforcement
|
242
|
+
trace :constraint, "Made new PC GUID=#{@constraint.concept.guid} min=#{@quantifier.min.inspect} max=#{@quantifier.max.inspect} over #{role_sequence.describe}"
|
243
|
+
super
|
244
|
+
end
|
245
|
+
|
246
|
+
# In a PresenceConstraint, each role in "each XYZ" must occur in exactly one clauses_list
|
247
|
+
def loose_binding
|
248
|
+
# loose_bind_wherever_possible
|
249
|
+
end
|
250
|
+
|
251
|
+
def bind_constrained_roles
|
252
|
+
@refs.each do |ref|
|
253
|
+
if ref.binding.refs.size == 1
|
254
|
+
# Apply loose binding over the constrained roles
|
255
|
+
candidates =
|
256
|
+
@clauses.map do |clause|
|
257
|
+
clause.refs.select{ |vr| vr.player == ref.player }
|
258
|
+
end.flatten
|
259
|
+
if candidates.size == 1
|
260
|
+
trace :binding, "Rebinding #{ref.inspect} to #{candidates[0].inspect} in presence constraint"
|
261
|
+
ref.rebind_to(@context, candidates[0])
|
262
|
+
end
|
263
|
+
end
|
264
|
+
end
|
265
|
+
end
|
266
|
+
|
267
|
+
def to_s
|
268
|
+
"#{super} #{@quantifier.min}-#{@quantifier.max} over (#{@refs.map{|vr| vr.inspect}*', '})"
|
269
|
+
end
|
270
|
+
end
|
271
|
+
|
272
|
+
class SetConstraint < Constraint
|
273
|
+
def initialize context_note, enforcement, clauses_lists
|
274
|
+
super context_note, enforcement, clauses_lists
|
275
|
+
end
|
276
|
+
|
277
|
+
def warn_ignored_queries
|
278
|
+
# No warnings needed here any more
|
279
|
+
end
|
280
|
+
|
281
|
+
def role_sequences_for_common_bindings ignore_trailing_steps = false
|
282
|
+
@clauses_lists.
|
283
|
+
zip(@bindings_by_list).
|
284
|
+
map do |clauses_list, bindings|
|
285
|
+
# Does this clauses_list involve a query?
|
286
|
+
if clauses_list.size > 1 or
|
287
|
+
clauses_list.detect do |clause|
|
288
|
+
clause.refs.detect{|ref| ref.nested_clauses } or
|
289
|
+
clause.includes_literals
|
290
|
+
end
|
291
|
+
|
292
|
+
trace :query, "Building query for #{clauses_list.inspect}" do
|
293
|
+
trace :query, "Constrained bindings are #{@common_bindings.inspect}"
|
294
|
+
# Every Binding in these clauses becomes a Variable,
|
295
|
+
# and every clause becomes a Step (and a RoleSequence).
|
296
|
+
# The returned RoleSequences contains the RoleRefs for the common_bindings.
|
297
|
+
|
298
|
+
# Create a query with a variable for every binding and all steps:
|
299
|
+
query = build_variables(clauses_list)
|
300
|
+
roles_by_binding = build_all_steps(clauses_list)
|
301
|
+
query.validate
|
302
|
+
|
303
|
+
# Create the projected RoleSequence for the constraint:
|
304
|
+
role_sequence = @constellation.RoleSequence(:new)
|
305
|
+
@common_bindings.each do |binding|
|
306
|
+
role, play = *roles_by_binding[binding]
|
307
|
+
@constellation.RoleRef(role_sequence, role_sequence.all_role_ref.size, :role => role, :play => play)
|
308
|
+
end
|
309
|
+
|
310
|
+
role_sequence
|
311
|
+
end
|
312
|
+
else
|
313
|
+
# There's no query in this clauses_list, just create a role_sequence
|
314
|
+
role_sequence = @constellation.RoleSequence(:new)
|
315
|
+
query_bindings = bindings-@common_bindings
|
316
|
+
unless query_bindings.empty? or ignore_trailing_steps && query_bindings.size <= 1
|
317
|
+
trace :constraint, "REVISIT: #{self.class}: Ignoring query from #{@common_bindings.inspect} to #{query_bindings.inspect} in #{clauses_list.inspect}"
|
318
|
+
end
|
319
|
+
@common_bindings.each do |binding|
|
320
|
+
roles = clauses_list.
|
321
|
+
map do |clause|
|
322
|
+
clause.refs.detect{|vr| vr.binding == binding }
|
323
|
+
end.
|
324
|
+
compact. # A query clause will probably not have the common binding
|
325
|
+
map do |ref|
|
326
|
+
ref.role_ref && ref.role_ref.role or ref.role
|
327
|
+
end.
|
328
|
+
compact
|
329
|
+
# REVISIT: Should use clause side effects to preserve residual adjectives here.
|
330
|
+
@constellation.RoleRef(role_sequence, role_sequence.all_role_ref.size, :role => roles[0])
|
331
|
+
end
|
332
|
+
role_sequence
|
333
|
+
end
|
334
|
+
end
|
335
|
+
end
|
336
|
+
end
|
337
|
+
|
338
|
+
class SubsetConstraint < SetConstraint
|
339
|
+
def initialize context_note, enforcement, clauses_lists
|
340
|
+
super context_note, enforcement, clauses_lists
|
341
|
+
@subset_clauses = @clauses_lists[0]
|
342
|
+
@superset_clauses = @clauses_lists[1]
|
343
|
+
end
|
344
|
+
|
345
|
+
def compile
|
346
|
+
bind_clauses
|
347
|
+
common_bindings
|
348
|
+
|
349
|
+
role_sequences =
|
350
|
+
role_sequences_for_common_bindings
|
351
|
+
|
352
|
+
@constraint =
|
353
|
+
@constellation.SubsetConstraint(
|
354
|
+
:new,
|
355
|
+
:vocabulary => @vocabulary,
|
356
|
+
:subset_role_sequence => role_sequences[0],
|
357
|
+
:superset_role_sequence => role_sequences[1]
|
358
|
+
)
|
359
|
+
@enforcement.compile(@constellation, @constraint) if @enforcement
|
360
|
+
super
|
361
|
+
end
|
362
|
+
|
363
|
+
def loose_binding
|
364
|
+
loose_bind_wherever_possible
|
365
|
+
end
|
366
|
+
end
|
367
|
+
|
368
|
+
class SetComparisonConstraint < SetConstraint
|
369
|
+
def initialize context_note, enforcement, clauses_lists
|
370
|
+
super context_note, enforcement, clauses_lists
|
371
|
+
end
|
372
|
+
end
|
373
|
+
|
374
|
+
class SetExclusionConstraint < SetComparisonConstraint
|
375
|
+
def initialize context_note, enforcement, clauses_lists, roles, quantifier
|
376
|
+
super context_note, enforcement, clauses_lists
|
377
|
+
@roles = roles || []
|
378
|
+
@quantifier = quantifier
|
379
|
+
end
|
380
|
+
|
381
|
+
def compile
|
382
|
+
bind_clauses @roles
|
383
|
+
common_bindings
|
384
|
+
|
385
|
+
role_sequences =
|
386
|
+
role_sequences_for_common_bindings
|
387
|
+
|
388
|
+
@constraint = @constellation.SetExclusionConstraint(
|
389
|
+
:new,
|
390
|
+
:vocabulary => @vocabulary,
|
391
|
+
:is_mandatory => @quantifier.min == 1
|
392
|
+
)
|
393
|
+
if @quantifier.pragmas
|
394
|
+
@quantifier.pragmas.each do |p|
|
395
|
+
@constellation.ConceptAnnotation(:concept => @constraint.concept, :mapping_annotation => p)
|
396
|
+
end
|
397
|
+
end
|
398
|
+
@enforcement.compile(@constellation, @constraint) if @enforcement
|
399
|
+
role_sequences.each_with_index do |role_sequence, i|
|
400
|
+
@constellation.SetComparisonRoles(@constraint, i, :role_sequence => role_sequence)
|
401
|
+
end
|
402
|
+
super
|
403
|
+
end
|
404
|
+
|
405
|
+
# In a SetExclusionConstraint, each role in "for each XYZ" must occur in each clauses_list
|
406
|
+
def loose_binding
|
407
|
+
if @roles.size == 0
|
408
|
+
loose_bind_wherever_possible
|
409
|
+
else
|
410
|
+
loose_bind
|
411
|
+
end
|
412
|
+
end
|
413
|
+
|
414
|
+
end
|
415
|
+
|
416
|
+
class SetEqualityConstraint < SetComparisonConstraint
|
417
|
+
def initialize context_note, enforcement, clauses_lists
|
418
|
+
super context_note, enforcement, clauses_lists
|
419
|
+
end
|
420
|
+
|
421
|
+
def compile
|
422
|
+
bind_clauses
|
423
|
+
common_bindings
|
424
|
+
|
425
|
+
role_sequences =
|
426
|
+
role_sequences_for_common_bindings
|
427
|
+
|
428
|
+
@constraint = @constellation.SetEqualityConstraint(
|
429
|
+
:new,
|
430
|
+
:vocabulary => @vocabulary
|
431
|
+
)
|
432
|
+
@enforcement.compile(@constellation, @constraint) if @enforcement
|
433
|
+
role_sequences.each_with_index do |role_sequence, i|
|
434
|
+
@constellation.SetComparisonRoles(@constraint, i, :role_sequence => role_sequence)
|
435
|
+
end
|
436
|
+
super
|
437
|
+
end
|
438
|
+
|
439
|
+
def loose_binding
|
440
|
+
loose_bind_wherever_possible
|
441
|
+
end
|
442
|
+
end
|
443
|
+
|
444
|
+
class RingConstraint < Constraint
|
445
|
+
Types = %w{acyclic intransitive stronglyintransitive symmetric asymmetric transitive antisymmetric irreflexive reflexive}
|
446
|
+
Pairs = {
|
447
|
+
:stronglyintransitive => [:acyclic, :asymmetric, :symmetric],
|
448
|
+
:intransitive => [:acyclic, :asymmetric, :symmetric],
|
449
|
+
:transitive => [:acyclic],
|
450
|
+
:acyclic => [:transitive],
|
451
|
+
:irreflexive => [:symmetric]
|
452
|
+
}
|
453
|
+
|
454
|
+
def initialize role_sequence, qualifiers
|
455
|
+
super nil, nil
|
456
|
+
@role_sequence = role_sequence
|
457
|
+
@rings, rest = qualifiers.partition{|q| Types.include?(q) }
|
458
|
+
qualifiers.replace rest
|
459
|
+
end
|
460
|
+
|
461
|
+
def compile
|
462
|
+
# Process the ring constraints:
|
463
|
+
return if @rings.empty?
|
464
|
+
|
465
|
+
role_refs = @role_sequence.all_role_ref_in_order.to_a
|
466
|
+
supertypes_by_position = role_refs.
|
467
|
+
map do |role_ref|
|
468
|
+
role_ref.role.object_type.supertypes_transitive
|
469
|
+
end
|
470
|
+
role_pairs = []
|
471
|
+
supertypes_by_position.each_with_index do |sts, i|
|
472
|
+
(i+1...supertypes_by_position.size).each do |j|
|
473
|
+
common_supertype = (sts & supertypes_by_position[j])[0]
|
474
|
+
role_pairs << [role_refs[i], role_refs[j], common_supertype] if common_supertype
|
475
|
+
end
|
476
|
+
end
|
477
|
+
if role_pairs.size > 1
|
478
|
+
# REVISIT: Verbalise the role_refs better:
|
479
|
+
raise "ambiguous #{@rings*' '} ring constraint, consider #{role_pairs.map{|rp| "#{rp[0].inspect}<->#{rp[1].inspect}"}*', '}"
|
480
|
+
end
|
481
|
+
if role_pairs.size == 0
|
482
|
+
raise "No matching role pair found for #{@rings*' '} ring constraint over #{role_refs.map(&:role).map(&:object_type).map(&:name).inspect}"
|
483
|
+
end
|
484
|
+
|
485
|
+
rp = role_pairs[0]
|
486
|
+
|
487
|
+
# Ensure that the keys in Pairs follow others:
|
488
|
+
@rings = @rings.partition{|rc| !Pairs.keys.include?(rc.downcase.to_sym) }.flatten
|
489
|
+
|
490
|
+
if @rings.size > 1 and !(p = Pairs[@rings[-1].to_sym]) and !p.include?(@rings[0].to_sym)
|
491
|
+
raise "incompatible ring constraint types (#{@rings*", "})"
|
492
|
+
end
|
493
|
+
ring_type = @rings.map{|c| c.capitalize}*""
|
494
|
+
|
495
|
+
@constraint = @constellation.RingConstraint(
|
496
|
+
:new,
|
497
|
+
:vocabulary => @vocabulary,
|
498
|
+
# :name => name, # Create a name for Ring Constraints?
|
499
|
+
:role => rp[0].role,
|
500
|
+
:other_role => rp[1].role,
|
501
|
+
:ring_type => ring_type
|
502
|
+
)
|
503
|
+
|
504
|
+
trace :constraint, "Added #{@constraint.verbalise}"
|
505
|
+
super
|
506
|
+
end
|
507
|
+
|
508
|
+
def to_s
|
509
|
+
"#{super} #{@rings*','} over #{@clauses_lists.inspect}"
|
510
|
+
end
|
511
|
+
end
|
512
|
+
|
513
|
+
class ValueConstraint < Constraint
|
514
|
+
def initialize ast, enforcement
|
515
|
+
super nil, enforcement
|
516
|
+
@value_ranges = ast[:ranges]
|
517
|
+
@units = ast[:units]
|
518
|
+
@regular_expression = ast[:regular_expression]
|
519
|
+
end
|
520
|
+
|
521
|
+
def assert_value(val)
|
522
|
+
if val.is_a?(String)
|
523
|
+
@constellation.Value(eval(val), true, nil)
|
524
|
+
elsif val
|
525
|
+
@constellation.Value(val.to_s, false , nil)
|
526
|
+
else
|
527
|
+
nil
|
528
|
+
end
|
529
|
+
end
|
530
|
+
|
531
|
+
def compile
|
532
|
+
@constraint = @constellation.ValueConstraint(:new)
|
533
|
+
raise "Units on value constraints are not yet processed (at line #{'REVISIT'})" if @units
|
534
|
+
# @string.line_of(node.interval.first)
|
535
|
+
|
536
|
+
if @value_ranges
|
537
|
+
@value_ranges.each do |range|
|
538
|
+
min, max = Array === range ? range : [range, range]
|
539
|
+
v_range = @constellation.ValueRange(
|
540
|
+
min && @constellation.Bound(:value => assert_value(min), :is_inclusive => true),
|
541
|
+
max && @constellation.Bound(:value => assert_value(max), :is_inclusive => true))
|
542
|
+
ar = @constellation.AllowedRange(@constraint, v_range)
|
543
|
+
end
|
544
|
+
else
|
545
|
+
@constraint.regular_expression = @regular_expression
|
546
|
+
end
|
547
|
+
@enforcement.compile(@constellation, @constraint) if @enforcement
|
548
|
+
super
|
549
|
+
end
|
550
|
+
|
551
|
+
def vrto_s vr
|
552
|
+
if Array === vr
|
553
|
+
min = vr[0]
|
554
|
+
max = vr[1]
|
555
|
+
if Numeric === min or Numeric === max
|
556
|
+
infinite = 1.0/0
|
557
|
+
min ||= -infinite
|
558
|
+
max ||= infinite
|
559
|
+
else
|
560
|
+
min ||= 'MIN'
|
561
|
+
max ||= 'MAX'
|
562
|
+
end
|
563
|
+
Range.new(min, max)
|
564
|
+
else
|
565
|
+
vr
|
566
|
+
end
|
567
|
+
end
|
568
|
+
|
569
|
+
def to_s
|
570
|
+
"#{super} to " +
|
571
|
+
(@value_ranges ?
|
572
|
+
"(#{@value_ranges.map{|vr| vrto_s(vr) }.inspect })#{ @units ? " in #{@units.inspect}" : ''}" :
|
573
|
+
@regular_expression
|
574
|
+
)
|
575
|
+
end
|
576
|
+
end
|
577
|
+
|
578
|
+
end
|
579
|
+
end
|
580
|
+
end
|
581
|
+
|