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.
Files changed (42) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +11 -0
  3. data/.rspec +1 -0
  4. data/.travis.yml +4 -0
  5. data/Gemfile +10 -0
  6. data/LICENSE.txt +21 -0
  7. data/README.md +19 -0
  8. data/Rakefile +6 -0
  9. data/activefacts-cql.gemspec +29 -0
  10. data/bin/setup +7 -0
  11. data/lib/activefacts/cql.rb +7 -0
  12. data/lib/activefacts/cql/.gitignore +0 -0
  13. data/lib/activefacts/cql/Rakefile +14 -0
  14. data/lib/activefacts/cql/compiler.rb +156 -0
  15. data/lib/activefacts/cql/compiler/clause.rb +1137 -0
  16. data/lib/activefacts/cql/compiler/constraint.rb +581 -0
  17. data/lib/activefacts/cql/compiler/entity_type.rb +457 -0
  18. data/lib/activefacts/cql/compiler/expression.rb +443 -0
  19. data/lib/activefacts/cql/compiler/fact.rb +390 -0
  20. data/lib/activefacts/cql/compiler/fact_type.rb +421 -0
  21. data/lib/activefacts/cql/compiler/query.rb +106 -0
  22. data/lib/activefacts/cql/compiler/shared.rb +161 -0
  23. data/lib/activefacts/cql/compiler/value_type.rb +174 -0
  24. data/lib/activefacts/cql/parser.rb +234 -0
  25. data/lib/activefacts/cql/parser/CQLParser.treetop +167 -0
  26. data/lib/activefacts/cql/parser/Context.treetop +48 -0
  27. data/lib/activefacts/cql/parser/Expressions.treetop +67 -0
  28. data/lib/activefacts/cql/parser/FactTypes.treetop +358 -0
  29. data/lib/activefacts/cql/parser/Language/English.treetop +315 -0
  30. data/lib/activefacts/cql/parser/Language/French.treetop +315 -0
  31. data/lib/activefacts/cql/parser/Language/Mandarin.treetop +304 -0
  32. data/lib/activefacts/cql/parser/LexicalRules.treetop +253 -0
  33. data/lib/activefacts/cql/parser/ObjectTypes.treetop +210 -0
  34. data/lib/activefacts/cql/parser/Terms.treetop +183 -0
  35. data/lib/activefacts/cql/parser/ValueTypes.treetop +202 -0
  36. data/lib/activefacts/cql/parser/nodes.rb +49 -0
  37. data/lib/activefacts/cql/require.rb +36 -0
  38. data/lib/activefacts/cql/verbaliser.rb +804 -0
  39. data/lib/activefacts/cql/version.rb +5 -0
  40. data/lib/activefacts/input/cql.rb +43 -0
  41. data/lib/rubygems_plugin.rb +12 -0
  42. 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
+