activefacts 0.8.10 → 0.8.12

Sign up to get free protection for your applications and to get access to all the features.
Files changed (52) hide show
  1. data/Rakefile +3 -2
  2. data/bin/afgen +25 -23
  3. data/bin/cql +9 -8
  4. data/css/orm2.css +23 -3
  5. data/examples/CQL/CompanyDirectorEmployee.cql +1 -1
  6. data/examples/CQL/Diplomacy.cql +3 -3
  7. data/examples/CQL/Insurance.cql +27 -21
  8. data/examples/CQL/Metamodel.cql +12 -8
  9. data/examples/CQL/MetamodelNext.cql +172 -149
  10. data/examples/CQL/ServiceDirector.cql +17 -17
  11. data/examples/CQL/Supervision.cql +3 -5
  12. data/examples/CQL/WaiterTips.cql +1 -1
  13. data/examples/CQL/unit.cql +1 -1
  14. data/index.html +0 -0
  15. data/lib/activefacts/cql/FactTypes.treetop +41 -8
  16. data/lib/activefacts/cql/Language/English.treetop +10 -0
  17. data/lib/activefacts/cql/ObjectTypes.treetop +3 -1
  18. data/lib/activefacts/cql/Terms.treetop +34 -53
  19. data/lib/activefacts/cql/compiler.rb +1 -1
  20. data/lib/activefacts/cql/compiler/clause.rb +21 -8
  21. data/lib/activefacts/cql/compiler/constraint.rb +3 -1
  22. data/lib/activefacts/cql/compiler/entity_type.rb +1 -1
  23. data/lib/activefacts/cql/compiler/fact_type.rb +9 -3
  24. data/lib/activefacts/cql/compiler/join.rb +3 -0
  25. data/lib/activefacts/cql/compiler/value_type.rb +9 -4
  26. data/lib/activefacts/cql/parser.rb +11 -3
  27. data/lib/activefacts/generate/oo.rb +3 -3
  28. data/lib/activefacts/generate/ordered.rb +0 -4
  29. data/lib/activefacts/input/orm.rb +305 -250
  30. data/lib/activefacts/persistence/tables.rb +6 -0
  31. data/lib/activefacts/support.rb +18 -0
  32. data/lib/activefacts/version.rb +1 -1
  33. data/lib/activefacts/vocabulary/extensions.rb +59 -20
  34. data/lib/activefacts/vocabulary/metamodel.rb +23 -13
  35. data/lib/activefacts/vocabulary/verbaliser.rb +5 -3
  36. data/spec/absorption_spec.rb +3 -2
  37. data/spec/cql/comparison_spec.rb +1 -3
  38. data/spec/cql/context_spec.rb +1 -1
  39. data/spec/cql/entity_type_spec.rb +2 -2
  40. data/spec/cql/expressions_spec.rb +2 -4
  41. data/spec/cql/fact_type_matching_spec.rb +55 -3
  42. data/spec/cql/parser/fact_types_spec.rb +3 -0
  43. data/spec/cql/role_matching_spec.rb +8 -7
  44. data/spec/cql/samples_spec.rb +10 -2
  45. data/spec/cql_dm_spec.rb +2 -1
  46. data/spec/helpers/array_matcher.rb +18 -35
  47. data/spec/helpers/diff_matcher.rb +34 -13
  48. data/spec/helpers/file_matcher.rb +27 -43
  49. data/spec/helpers/string_matcher.rb +23 -33
  50. data/spec/norma_cql_spec.rb +1 -0
  51. data/spec/norma_tables_spec.rb +1 -2
  52. metadata +95 -102
@@ -58,14 +58,14 @@ Data Store is one Major-Version;
58
58
  Data Store is one Minor-Version;
59
59
  Data Store is one Revision-Version;
60
60
 
61
- Date is identified by DDMMYYYY where
62
- Date has one DDMMYYYY,
63
- DDMMYYYY is of at most one Date;
64
- Credential has at most one Expiration-Date;
61
+ Date Time YMDHMS is identified by MDYHMS where
62
+ Date Time YMDHMS has one MDYHMS,
63
+ MDYHMS is of at most one Date Time YMDHMS;
65
64
 
66
- Date Time is identified by MDYHMS where
67
- Date Time has one MDYHMS,
68
- MDYHMS is of at most one Date Time;
65
+ DateYYMMDD is identified by DDMMYYYY where
66
+ DateYYMMDD has one DDMMYYYY,
67
+ DDMMYYYY is of at most one DateYYMMDD;
68
+ Credential has at most one Expiration-DateYYMMDD;
69
69
 
70
70
  Duration is identified by Seconds where
71
71
  Duration has one Seconds,
@@ -126,16 +126,16 @@ Recurring Schedule wednesday;
126
126
 
127
127
  Satellite Message is identified by its Id;
128
128
  Satellite Message is designated for one Data Store;
129
- Satellite Message one Insertion-Date Time;
130
129
  Satellite Message has at most one Message Data;
131
130
  Satellite Message has at most one Message Header;
132
131
  Satellite Message is of at most one Provider Type;
133
132
  Satellite Message has one Serial Number;
133
+ Satellite Message has one insertion-Date Time YMDHMS;
134
134
 
135
135
  Subscription is identified by its Nr;
136
136
  Company has one Driver- Tech Subscription;
137
- Subscription has one Beginning-Date;
138
- Subscription has at most one Ending-Date;
137
+ Subscription has one Beginning-DateYYMMDD;
138
+ Subscription has at most one Ending-DateYYMMDD;
139
139
  Subscription is enabled;
140
140
 
141
141
  Switch is identified by its Id;
@@ -155,10 +155,10 @@ Switch is backup updates;
155
155
  Switch is send disabled;
156
156
  Switch is test vectors enabled;
157
157
 
158
- Time is identified by HHMMSS where
159
- Time has one HHMMSS,
160
- HHMMSS is of at most one Time;
161
- Recurring Schedule has one Start-Time;
158
+ TimeHMS is identified by HHMMSS where
159
+ TimeHMS has one HHMMSS,
160
+ HHMMSS is of at most one TimeHMS;
161
+ Recurring Schedule has one Start-TimeHMS;
162
162
 
163
163
  Transaction is identified by its Nr;
164
164
  Satellite Message has at most one Group-Transaction;
@@ -210,11 +210,11 @@ Data Store Service has one Subscription;
210
210
  */
211
211
  either Company is client or Company is vendor but not both;
212
212
  for each Credential exactly one of these holds:
213
- Data Store(2) requires Credential,
213
+ Data Store (2) requires Credential,
214
214
  Data Store Service requires Credential,
215
215
  Vendor requires Credential,
216
216
  Data Store File Host System has Internal-Credential,
217
- Data Store(1) has Internal-Credential;
217
+ Data Store (1) has Internal-Credential;
218
218
  for each Network exactly one of these holds:
219
219
  Network is used by Host System,
220
220
  Company has Origin-Network,
@@ -227,7 +227,7 @@ either Host System runs Switch or Data Store has Legacy-Switch but not both;
227
227
  for each Network at most one of these holds:
228
228
  Network is ip_single,
229
229
  Network has Ending-IP;
230
- Data Store Service (where Service is from Data Store) belongs to Client
230
+ Data Store Service (in which Service is from Data Store) belongs to Client
231
231
  if and only if
232
232
  Client has default Data Store;
233
233
  Network has Ending IP
@@ -28,12 +28,10 @@ CEO runs Company,
28
28
  /*
29
29
  * Constraints:
30
30
  */
31
- either Employee reports to Manager(2) or Employee is a Manager(1) that is a CEO that runs Company but not both;
31
+ either Employee reports to Manager(1) or Employee is a Manager(2) that is a CEO that runs Company but not both;
32
32
  Employee is a Manager that is a CEO that runs Company
33
33
  if and only if
34
34
  Employee works for Company;
35
-
36
- // This constraint cannot be expressed in NORMA until it adds explicit join paths:
37
- Employee(2) reports to Manager that is a kind of Employee(1) that works for Company
35
+ Employee(1) reports to Manager that is a kind of Employee(2) that works for Company
38
36
  if and only if
39
- Employee(2) works for Company;
37
+ Employee(1) works for Company;
@@ -28,6 +28,6 @@ Service earned a tip of at most one Amount;
28
28
  /*
29
29
  * Constraints:
30
30
  */
31
- Service (where Waiter served Meal) earned a tip of Amount
31
+ Service (in which Waiter served Meal) earned a tip of Amount
32
32
  if and only if
33
33
  Waiter for serving Meal reported a tip of Amount;
@@ -3,7 +3,7 @@ vocabulary Units;
3
3
  /*
4
4
  * Units
5
5
  */
6
- 1 converts to K;
6
+ 1000.0 converts to K;
7
7
  0.000000000000000001 converts to atto;
8
8
  13.0 converts to bakersdozen;
9
9
  1 converts to bit;
data/index.html CHANGED
File without changes
@@ -64,6 +64,8 @@ module ActiveFacts
64
64
 
65
65
  rule joined_clauses
66
66
  qualified_clauses
67
+ # REVISIT: This creates no precedence between and/or, which could cause confusion.
68
+ # Should disallow mixed conjuntions - using a sempred?
67
69
  ftail:( conjunction:(',' / and / or ) s qualified_clauses s )*
68
70
  {
69
71
  def ast
@@ -192,13 +194,13 @@ module ActiveFacts
192
194
  end
193
195
 
194
196
  rule condition_contraction
195
- role p:post_qualifiers? q:qualifier? s comparator s e2:expression
197
+ role pq:post_qualifiers? q:qualifier? s comparator s e2:expression
196
198
  !phrase # The contracted_clauses must not continue here!
197
199
  {
198
200
  def ast
199
201
  c = Compiler::Comparison.new(comparator.text_value, role.ast, e2.ast, q.empty? ? [] : [q.text_value])
200
202
  c.conjunction = comparator.text_value
201
- [ role.ast, p.empty? ? [] : p.list, c ]
203
+ [ role.ast, pq.empty? ? [] : pq.list, c ]
202
204
  end
203
205
  }
204
206
  end
@@ -248,9 +250,36 @@ module ActiveFacts
248
250
  }
249
251
  end
250
252
 
251
- # This is the rule that causes most back-tracking. I think you can see why.
252
253
  rule role
253
- q:(quantifier enforcement)?
254
+ aggregate
255
+ /
256
+ simple_role
257
+ end
258
+
259
+ rule aggregate
260
+ aggregate:id s of s term s # REVISIT: this term may need to pre-scanned in the qualified_clauses
261
+ in s '('
262
+ qualified_clauses # REVISIT: Need to test to verify this is the right level (not joined_clauses, etc)
263
+ s ')'
264
+ {
265
+ def ast
266
+ raise "Not implemented: AST for '#{aggregate.text_value} of #{term.text_value}'"
267
+ # This returns just the role with the nested clauses, which doesn;t even work:
268
+ term.ast(
269
+ nil, # No quantifier
270
+ nil, # No function call
271
+ nil, # No role_name
272
+ nil, # No value_constraint
273
+ nil, # No literal
274
+ qualified_clauses.ast
275
+ )
276
+ end
277
+ }
278
+ end
279
+
280
+ # This is the rule that causes most back-tracking. I think you can see why.
281
+ rule simple_role
282
+ q:(quantifier enforcement cn:context_note?)?
254
283
  player:derived_variable
255
284
  lr:(
256
285
  literal u:unit?
@@ -261,12 +290,16 @@ module ActiveFacts
261
290
  {
262
291
  def ast
263
292
  if !q.empty? && q.quantifier.value
264
- quantifier = Compiler::Quantifier.new(q.quantifier.value[0], q.quantifier.value[1], q.enforcement.ast)
293
+ quantifier = Compiler::Quantifier.new(
294
+ q.quantifier.value[0],
295
+ q.quantifier.value[1],
296
+ q.enforcement.ast,
297
+ q.cn.empty? ? nil : q.cn.ast
298
+ )
265
299
  end
266
300
  if !lr.empty?
267
301
  if lr.respond_to?(:literal)
268
- literal = lr.literal.value
269
- raise "Literals with units are not yet processed" unless lr.u.empty?
302
+ literal = Compiler::Literal.new(lr.literal.value, lr.u.empty? ? nil : lr.u.text_value)
270
303
  end
271
304
  value_constraint = Compiler::ValueConstraint.new(lr.value_constraint.ranges, lr.value_constraint.units, lr.enforcement.ast) if lr.respond_to?(:value_constraint)
272
305
  raise "It is not permitted to provide both a literal value and a value constraint" if value_constraint and literal
@@ -286,7 +319,7 @@ module ActiveFacts
286
319
  end
287
320
 
288
321
  rule objectification_join
289
- '(' s where s facts:joined_clauses s ')' s
322
+ '(' s in_which s facts:joined_clauses s ')' s
290
323
  {
291
324
  def ast
292
325
  facts.ast
@@ -38,6 +38,12 @@ module ActiveFacts
38
38
  { def independent; !i.empty?; end }
39
39
  end
40
40
 
41
+ rule in_which # Introduce an objectification join
42
+ where / # Old syntax
43
+ in s which # preferred syntax
44
+ { def independent; !i.empty?; end }
45
+ end
46
+
41
47
  # Units conversion keyword
42
48
  rule conversion
43
49
  converts s a:(approximately s)? to s
@@ -240,6 +246,7 @@ module ActiveFacts
240
246
  rule feminine 'feminine' !alphanumeric end
241
247
  rule identified ('known'/'identified') !alphanumeric end
242
248
  rule if 'if' !alphanumeric end
249
+ rule in 'in' !alphanumeric end
243
250
  rule import 'import' !alphanumeric end
244
251
  rule independent 'independent' !alphanumeric end
245
252
  rule intransitive 'intransitive' !alphanumeric end
@@ -250,6 +257,7 @@ module ActiveFacts
250
257
  rule maybe 'maybe' !alphanumeric end
251
258
  rule only 'only' !alphanumeric end
252
259
  rule or 'or' !alphanumeric end
260
+ rule of 'of' !alphanumeric end
253
261
  rule ordering_prefix by s (ascending/descending)? s end
254
262
  rule otherwise 'otherwise' !alphanumeric end
255
263
  rule partitioned 'partitioned' !alphanumeric end
@@ -271,6 +279,8 @@ module ActiveFacts
271
279
  rule true 'true' !alphanumeric end
272
280
  rule vocabulary 'vocabulary' !alphanumeric end
273
281
  rule where 'where' !alphanumeric end
282
+ rule which 'which' !alphanumeric end
283
+ rule was 'was' !alphanumeric end
274
284
  rule who 'who' !alphanumeric end
275
285
 
276
286
  end
@@ -164,7 +164,9 @@ module ActiveFacts
164
164
  end
165
165
 
166
166
  rule mapping_pragma
167
- (independent / separate / partitioned / personal / feminine / masculine)
167
+ (independent / separate / partitioned / personal / feminine / masculine /
168
+ was s names:(id s)+ { def text_value; [ was.text_value, names.elements.map{|n|n.text_value} ]; end }
169
+ )
168
170
  { def value; text_value; end }
169
171
  end
170
172
 
@@ -8,19 +8,8 @@ module ActiveFacts
8
8
  module CQL
9
9
  grammar Terms
10
10
  rule term_definition_name
11
- id s
12
- t:(!non_term_def id s)*
13
- {
14
- def value
15
- t.elements.inject([
16
- id.value
17
- ]){|a, e| a << e.id.value}*' '
18
- end
19
-
20
- def node_type
21
- :term
22
- end
23
- }
11
+ id s t:(!non_term_def id s)*
12
+ <Parser::TermDefinitionNameNode>
24
13
  end
25
14
 
26
15
  rule non_term_def
@@ -69,12 +58,15 @@ module ActiveFacts
69
58
  &{|s| input.context.reset_role_names }
70
59
  (
71
60
  context_note # Context notes have different lexical conventions
72
- / '(' as S term_definition_name s ')' s # Prescan for a Role Name
61
+ / '(' as S term_definition_name s ')' s # Prepare for a Role Name
73
62
  &{|s| input.context.role_name(s[3].value) }
74
- / new_derived_value
75
- / new_adjective_term # Adjective definitions
63
+ / new_derived_value # Prepare for a derived term
64
+ / new_adjective_term # Prepae for an existing term with new Adjectives
65
+ # The remaining rules exist to correctly eat up anything that doesn't match the above:
76
66
  / global_term # If we see A B - C D, don't recognise B as a new adjective for C D.
67
+ / prescan_aggregate
77
68
  / id
69
+ # / literal # REVISIT: Literals might contain "(as Foo)" and mess things up
78
70
  / range # Covers all numbers and strings
79
71
  / comparator # handle two-character operators
80
72
  / S # White space and comments, must precede / and *
@@ -82,13 +74,9 @@ module ActiveFacts
82
74
  )* [?;] s
83
75
  end
84
76
 
85
- rule derived_value_continuation
86
- s '-' tail:(s !global_term !(that/who) id)*
87
- {
88
- def value
89
- tail.elements.map{|e| e.id.text_value}
90
- end
91
- }
77
+ # Not sure this is even needed, but it doesn't seem to hurt:
78
+ rule prescan_aggregate
79
+ aggregate_type:id s of s global_term in s &'('
92
80
  end
93
81
 
94
82
  rule new_derived_value
@@ -105,12 +93,24 @@ module ActiveFacts
105
93
  }
106
94
  end
107
95
 
96
+ # Derived values are new terms introduced by an = sign before an expression
97
+ # This rule handles trailing words of a multi-word derived value
98
+ rule derived_value_continuation
99
+ s '-' tail:(s !global_term !(that/who) id)*
100
+ {
101
+ def value
102
+ tail.elements.map{|e| e.id.text_value}
103
+ end
104
+ }
105
+ end
106
+
107
+ # Used during the pre-scan, match a term with new adjective(s)
108
108
  rule new_adjective_term
109
- !global_term adj:id '-' lead_intervening s global_term # Definitely a new leading adjective for this term
110
- &{|s| input.context.new_leading_adjective_term([s[1].text_value, s[3].value].compact*" ", s[5].text_value) }
109
+ !global_term adj:id '-' '-'? lead_intervening s global_term # Definitely a new leading adjective for this term
110
+ &{|s| adj = [s[1].text_value, s[4].value].compact*" "; input.context.new_leading_adjective_term(adj, s[6].text_value) }
111
111
  /
112
- global_term s trail_intervening '-' !global_term adj:id # Definitely a new trailing adjective for this term
113
- &{|s| input.context.new_trailing_adjective_term([s[2].value, s[5].text_value].compact*" ", s[0].text_value) }
112
+ global_term s trail_intervening '-' '-'? !global_term adj:id # Definitely a new trailing adjective for this term
113
+ &{|s| adj = [s[2].value, s[6].text_value].compact*" "; input.context.new_trailing_adjective_term(adj, s[0].text_value) }
114
114
  end
115
115
 
116
116
  rule lead_intervening # Words intervening between a new adjective and the term
@@ -133,33 +133,14 @@ module ActiveFacts
133
133
 
134
134
  # This is the rule to use after the prescan; it only succeeds on a complete term or role reference
135
135
  rule term
136
- s head:id x &{|s| w = s[1].text_value; input.context.term_starts?(w, s[2]) }
137
- tail:(s '-'? s w:id &{|s| w = s[3].text_value; input.context.term_continues?(w) })*
138
- &{|s| input.context.term_complete? }
139
- {
140
- def ast quantifier = nil, function_call = nil, role_name = nil, value_constraint = nil, literal = nil, nested_clauses = nil
141
- t = x.context[:term]
142
- gt = x.context[:global_term]
143
- leading_adjective = t[0...-gt.size-1] if t.size > gt.size and t[-gt.size..-1] == gt
144
- trailing_adjective = t[gt.size+1..-1] if t.size > gt.size and t[0...gt.size] == gt
145
- Compiler::VarRef.new(gt, leading_adjective, trailing_adjective, quantifier, function_call, role_name, value_constraint, literal, nested_clauses)
146
- end
147
-
148
- def value # Sometimes we just want the full term name
149
- x.context[:term]
150
- end
151
- def node_type; :term; end
152
- }
136
+ s head:id x &{|s| w = s[1].text_value; input.context.term_starts?(w, s[2]) }
137
+ tail:(
138
+ s '-'? dbl:'-'? s w:id &{|s| w = s[4].text_value; input.context.term_continues?(w) }
139
+ )* &{|s| input.context.term_complete? }
140
+ <Parser::TermNode>
153
141
  /
154
- s head:id '-' s term &{|s| s[4].ast.leading_adjective == nil }
155
- {
156
- def ast quantifier = nil, function_call = nil, role_name = nil, value_constraint = nil, literal = nil, nested_clauses = nil
157
- ast = term.ast(quantifier, function_call, role_name, value_constraint, literal, nested_clauses)
158
- ast.leading_adjective = head.text_value
159
- ast
160
- end
161
- def node_type; :term; end
162
- }
142
+ s head:id '-' '-'? s term &{|s| s[5].ast.leading_adjective == nil }
143
+ <Parser::TermLANode>
163
144
  end
164
145
 
165
146
  rule x
@@ -72,7 +72,7 @@ module ActiveFacts
72
72
 
73
73
  def unit? s
74
74
  name = @constellation.Name[s]
75
- units = !name ? [] : name.all_unit.to_a + name.all_unit_as_plural_name.to_a
75
+ units = (!name ? [] : Array(name.unit) + Array(name.unit_as_plural_name)).uniq
76
76
  debug :units, "Looking for unit #{s}, got #{units.map{|u|u.name}.inspect}"
77
77
  units.size > 0
78
78
  end
@@ -351,13 +351,15 @@ module ActiveFacts
351
351
  if la = role_ref.leading_adjective and !la.empty?
352
352
  # The leading adjectives must match, one way or another
353
353
  la = la.split(/\s+/)
354
- return nil unless la[0,intervening_words.size] == intervening_words
354
+ # We may have hyphenated adjectives. Break them up to check:
355
+ iw = intervening_words.map{|w| w.split(/-/)}.flatten
356
+ return nil unless la[0,iw.size] == iw
355
357
  # Any intervening_words matched, see what remains
356
- la.slice!(0, intervening_words.size)
358
+ la.slice!(0, iw.size)
357
359
 
358
360
  # If there were intervening_words, the remaining reading adjectives must match the phrase's leading_adjective exactly.
359
361
  phrase_la = (next_player_phrase.leading_adjective||'').split(/\s+/)
360
- return nil if !intervening_words.empty? && la != phrase_la
362
+ return nil if !iw.empty? && la != phrase_la
361
363
  # If not, the phrase's leading_adjectives must *end* with the reading's
362
364
  return nil if phrase_la[-la.size..-1] != la
363
365
  role_has_residual_adjectives = true if phrase_la.size > la.size
@@ -468,7 +470,8 @@ module ActiveFacts
468
470
  # the role_ref, those must be local, and we'll need to extract them.
469
471
 
470
472
  if rra = side_effect.role_ref.trailing_adjective
471
- debug :matching, "Deleting matched trailing adjective '#{rra}'#{side_effect.absorbed_followers>0 ? " in #{side_effect.absorbed_followers} followers" : ""}"
473
+ debug :matching, "Deleting matched trailing adjective '#{rra}'#{side_effect.absorbed_followers>0 ? " in #{side_effect.absorbed_followers} followers" : ""}, cost is #{side_effect.cost}"
474
+ side_effect.cancel_cost side_effect.absorbed_followers
472
475
 
473
476
  # These adjective(s) matched either an adjective here, or a follower word, or both.
474
477
  if a = phrase.trailing_adjective
@@ -487,7 +490,8 @@ module ActiveFacts
487
490
  end
488
491
 
489
492
  if rra = side_effect.role_ref.leading_adjective
490
- debug :matching, "Deleting matched leading adjective '#{rra}'#{side_effect.absorbed_precursors>0 ? " in #{side_effect.absorbed_precursors} precursors" : ""}}"
493
+ debug :matching, "Deleting matched leading adjective '#{rra}'#{side_effect.absorbed_precursors>0 ? " in #{side_effect.absorbed_precursors} precursors" : ""}, cost is #{side_effect.cost}"
494
+ side_effect.cancel_cost side_effect.absorbed_precursors
491
495
 
492
496
  # These adjective(s) matched either an adjective here, or a precursor word, or both.
493
497
  if a = phrase.leading_adjective
@@ -694,11 +698,16 @@ module ActiveFacts
694
698
  @absorbed_followers = absorbed_followers
695
699
  @common_supertype = common_supertype
696
700
  @residual_adjectives = residual_adjectives
701
+ @cancelled_cost = 0
697
702
  debug :matching_fails, "Saving side effects for #{@phrase.term}, absorbs #{@absorbed_precursors}/#{@absorbed_followers}#{@common_supertype ? ', join over supertype '+ @common_supertype.name : ''}" if @absorbed_precursors+@absorbed_followers+(@common_supertype ? 1 : 0) > 0
698
703
  end
699
704
 
700
705
  def cost
701
- absorbed_precursors + absorbed_followers + (common_supertype ? 1 : 0)
706
+ absorbed_precursors + absorbed_followers + (common_supertype ? 1 : 0) - @cancelled_cost
707
+ end
708
+
709
+ def cancel_cost c
710
+ @cancelled_cost += c
702
711
  end
703
712
 
704
713
  def to_s
@@ -963,16 +972,20 @@ module ActiveFacts
963
972
 
964
973
  class Quantifier
965
974
  attr_accessor :enforcement
975
+ attr_accessor :context_note
966
976
  attr_reader :min, :max
967
977
 
968
- def initialize min, max, enforcement = nil
978
+ def initialize min, max, enforcement = nil, context_note = nil
969
979
  @min = min
970
980
  @max = max
971
981
  @enforcement = enforcement
982
+ @context_note = context_note
972
983
  end
973
984
 
974
985
  def inspect
975
- "[#{@min}..#{@max}]"
986
+ "[#{@min}..#{@max}]#{
987
+ @context_note && ' ' + @context_note.inspect
988
+ }"
976
989
  end
977
990
  end
978
991