activefacts-cql 1.8.1 → 1.8.2

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 (31) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/Gemfile +6 -5
  4. data/activefacts-cql.gemspec +4 -3
  5. data/lib/activefacts/cql/compiler.rb +29 -22
  6. data/lib/activefacts/cql/compiler/clause.rb +86 -84
  7. data/lib/activefacts/cql/compiler/constraint.rb +53 -53
  8. data/lib/activefacts/cql/compiler/entity_type.rb +28 -28
  9. data/lib/activefacts/cql/compiler/expression.rb +27 -27
  10. data/lib/activefacts/cql/compiler/fact.rb +37 -37
  11. data/lib/activefacts/cql/compiler/fact_type.rb +91 -85
  12. data/lib/activefacts/cql/compiler/informal.rb +48 -0
  13. data/lib/activefacts/cql/compiler/query.rb +52 -52
  14. data/lib/activefacts/cql/compiler/shared.rb +17 -17
  15. data/lib/activefacts/cql/compiler/value_type.rb +11 -11
  16. data/lib/activefacts/cql/parser/CQLParser.treetop +76 -56
  17. data/lib/activefacts/cql/parser/Context.treetop +15 -17
  18. data/lib/activefacts/cql/parser/Expressions.treetop +21 -21
  19. data/lib/activefacts/cql/parser/FactTypes.treetop +216 -216
  20. data/lib/activefacts/cql/parser/Language/English.treetop +136 -131
  21. data/lib/activefacts/cql/parser/Language/French.treetop +118 -118
  22. data/lib/activefacts/cql/parser/Language/Mandarin.treetop +111 -111
  23. data/lib/activefacts/cql/parser/LexicalRules.treetop +45 -45
  24. data/lib/activefacts/cql/parser/ObjectTypes.treetop +120 -120
  25. data/lib/activefacts/cql/parser/Terms.treetop +63 -63
  26. data/lib/activefacts/cql/parser/ValueTypes.treetop +71 -71
  27. data/lib/activefacts/cql/require.rb +8 -8
  28. data/lib/activefacts/cql/verbaliser.rb +88 -88
  29. data/lib/activefacts/cql/version.rb +1 -1
  30. data/lib/activefacts/input/cql.rb +7 -7
  31. metadata +12 -16
@@ -8,47 +8,47 @@ module ActiveFacts
8
8
  module CQL
9
9
  grammar ValueTypes
10
10
  rule value_type
11
- s each?
11
+ s each?
12
12
  s term_definition_name
13
- m1:mapping_pragmas
14
- # REVISIT: ORM2 would allow (subtype_prefix term)?
13
+ m1:mapping_pragmas
14
+ # REVISIT: ORM2 would allow (subtype_prefix term)?
15
15
  written_as
16
- any? s
16
+ any? s
17
17
  base:(term/implicit_value_type_name) s
18
18
  value_type_parameters
19
19
  u:in_units?
20
- a:auto_assignment?
21
- c:context_note?
20
+ a:auto_assignment?
21
+ c:context_note?
22
22
  r:(value_constraint enforcement)?
23
23
  m2:mapping_pragmas
24
- c2:context_note?
24
+ c2:context_note?
25
25
  s ';' s
26
26
  {
27
- def ast
28
- name = term_definition_name.value
29
- params = value_type_parameters.values
30
- value_constraint = nil
31
- unless r.empty?
32
- value_constraint = Compiler::ValueConstraint.new(r.value_constraint.ast, r.enforcement.ast)
33
- end
34
- units = u.empty? ? [] : u.units.value
35
- auto_assigned_at = a.empty? ? nil : a.auto_assigned_at
36
- pragmas = m1.value+m2.value
37
- context_note = !c.empty? ? c.ast : (!c2.empty? ? c2.ast : nil)
38
- Compiler::ValueType.new name, base.value, params, units, value_constraint, pragmas, context_note, auto_assigned_at
39
- end
27
+ def ast
28
+ name = term_definition_name.value
29
+ params = value_type_parameters.values
30
+ value_constraint = nil
31
+ unless r.empty?
32
+ value_constraint = Compiler::ValueConstraint.new(r.value_constraint.ast, r.enforcement.ast)
33
+ end
34
+ units = u.empty? ? [] : u.units.value
35
+ auto_assigned_at = a.empty? ? nil : a.auto_assigned_at
36
+ pragmas = m1.value+m2.value
37
+ context_note = !c.empty? ? c.ast : (!c2.empty? ? c2.ast : nil)
38
+ Compiler::ValueType.new name, base.value, params, units, value_constraint, pragmas, context_note, auto_assigned_at
39
+ end
40
40
  }
41
41
  end
42
42
 
43
43
  rule in_units
44
- in S units
44
+ in S units
45
45
  end
46
46
 
47
47
  rule implicit_value_type_name
48
- id
49
- {
50
- def node_type; :term; end
51
- }
48
+ id
49
+ {
50
+ def node_type; :term; end
51
+ }
52
52
  end
53
53
 
54
54
  rule value_type_parameters
@@ -70,50 +70,50 @@ module ActiveFacts
70
70
  rule unit_definition
71
71
  u:(
72
72
  s coeff:unit_coefficient? base:units? s o:unit_offset?
73
- conversion
73
+ conversion
74
74
  singular:unit_name s plural:('/' s p:unit_name s)?
75
75
  /
76
76
  s singular:unit_name s plural:('/' s p:unit_name s)?
77
- conversion
77
+ conversion
78
78
  coeff:unit_coefficient? base:units? s o:unit_offset?
79
79
  )
80
80
  q:(approximately '' / ephemera s url )? s
81
81
  ';'
82
82
  {
83
- def ast
83
+ def ast
84
84
  singular = u.singular.text_value
85
85
  plural = u.plural.text_value.empty? ? nil : u.plural.p.text_value
86
- if u.coeff.empty?
87
- raise "Unit definition requires either a coefficient or an ephemera URL" unless q.respond_to?(:ephemera)
88
- numerator,denominator = 1, 1
89
- else
90
- numerator, denominator = *u.coeff.ast
91
- end
86
+ if u.coeff.empty?
87
+ raise "Unit definition requires either a coefficient or an ephemera URL" unless q.respond_to?(:ephemera)
88
+ numerator,denominator = 1, 1
89
+ else
90
+ numerator, denominator = *u.coeff.ast
91
+ end
92
92
  offset = u.o.text_value.empty? ? 0 : u.o.value
93
- bases = u.base.empty? ? [] : u.base.value
94
- approximately = q.respond_to?(:approximately) || u.conversion.approximate?
95
- ephemera = q.respond_to?(:ephemera) ? q.url.text_value : nil
96
- Compiler::Unit.new singular, plural, numerator, denominator, offset, bases, approximately, ephemera
97
- end
93
+ bases = u.base.empty? ? [] : u.base.value
94
+ approximately = q.respond_to?(:approximately) || u.conversion.approximate?
95
+ ephemera = q.respond_to?(:ephemera) ? q.url.text_value : nil
96
+ Compiler::Unit.new singular, plural, numerator, denominator, offset, bases, approximately, ephemera
97
+ end
98
98
  }
99
99
  end
100
100
 
101
101
  rule unit_name
102
- id
103
- {
104
- def node_type; :unit; end
105
- }
102
+ id
103
+ {
104
+ def node_type; :unit; end
105
+ }
106
106
  end
107
107
 
108
108
 
109
109
  rule unit_coefficient
110
110
  numerator:number denominator:(s '/' s number)? s
111
111
  {
112
- def ast
112
+ def ast
113
113
  [ numerator.text_value,
114
114
  (denominator.text_value.empty? ? "1" : denominator.number.text_value)
115
- ]
116
- end
115
+ ]
116
+ end
117
117
  }
118
118
  end
119
119
 
@@ -129,8 +129,8 @@ module ActiveFacts
129
129
  rule units
130
130
  !non_unit maybe_unit s tail:(!non_unit maybe_unit s)* div:('/' s maybe_unit s tail:(!non_unit maybe_unit s)*)?
131
131
  { def value
132
- tail.elements.inject([maybe_unit.value]) { |a, e| a << e.maybe_unit.value } +
133
- (div.text_value.empty? ? [] : div.tail.elements.inject([div.maybe_unit.inverse]) { |a, e| a << e.maybe_unit.inverse })
132
+ tail.elements.inject([maybe_unit.value]) { |a, e| a << e.maybe_unit.value } +
133
+ (div.text_value.empty? ? [] : div.tail.elements.inject([div.maybe_unit.inverse]) { |a, e| a << e.maybe_unit.inverse })
134
134
  end
135
135
  }
136
136
  end
@@ -140,7 +140,7 @@ module ActiveFacts
140
140
  end
141
141
 
142
142
  rule unit
143
- maybe_unit &{|s| input.context.unit?(s[0].unit_name.text_value) }
143
+ maybe_unit &{|s| input.context.unit?(s[0].unit_name.text_value) }
144
144
  end
145
145
 
146
146
  rule maybe_unit
@@ -157,33 +157,33 @@ module ActiveFacts
157
157
  end
158
158
 
159
159
  rule value_constraint
160
- restricted_to restricted_values c:context_note?
160
+ restricted_to restricted_values c:context_note?
161
161
  {
162
- def ast
163
- v = restricted_values.values
164
- c[:context_note] = c.ast unless c.empty?
165
- v
166
- end
162
+ def ast
163
+ v = restricted_values.values
164
+ c[:context_note] = c.ast unless c.empty?
165
+ v
166
+ end
167
167
  }
168
- # REVISIT: "where the possible value/s of that <Term> is/are value (, ...)"
168
+ # REVISIT: "where the possible value/s of that <Term> is/are value (, ...)"
169
169
  end
170
170
 
171
171
  rule restricted_values
172
- range_list s u:units?
173
- {
174
- def values
175
- { :ranges => range_list.ranges,
176
- :units => u.empty? ? nil : u.value
177
- }
178
- end
179
- }
180
- /
181
- regular_expression
182
- {
183
- def values
184
- { :regular_expression => contents }
185
- end
186
- }
172
+ range_list s u:units?
173
+ {
174
+ def values
175
+ { :ranges => range_list.ranges,
176
+ :units => u.empty? ? nil : u.value
177
+ }
178
+ end
179
+ }
180
+ /
181
+ regular_expression
182
+ {
183
+ def values
184
+ { :regular_expression => contents }
185
+ end
186
+ }
187
187
  end
188
188
 
189
189
  rule range_list
@@ -19,15 +19,15 @@ if (require 'activefacts/generators/ruby' rescue nil)
19
19
  # require 'activefacts/cql'
20
20
  class CQLLoader
21
21
  def self.load(file) #:nodoc:
22
- trace "Loading #{file}" do
23
- vocabulary = ActiveFacts::Input::CQL.readfile(file)
22
+ trace "Loading #{file}" do
23
+ vocabulary = ActiveFacts::Input::CQL.readfile(file)
24
24
 
25
- ruby = StringIO.new
26
- @dumper = ActiveFacts::Generators::RUBY.new(vocabulary.constellation)
27
- @dumper.generate(ruby)
28
- ruby.rewind
29
- eval ruby.read, ::TOPLEVEL_BINDING
30
- end
25
+ ruby = StringIO.new
26
+ @dumper = ActiveFacts::Generators::RUBY.new(vocabulary.constellation)
27
+ @dumper.generate(ruby)
28
+ ruby.rewind
29
+ eval ruby.read, ::TOPLEVEL_BINDING
30
+ end
31
31
  end
32
32
  end
33
33
 
@@ -286,16 +286,16 @@ module ActiveFacts
286
286
  p.role_refs.map{|rr| rr.role.fact_type.preferred_reading.text}.sort.to_s
287
287
  end.
288
288
  each do |player|
289
- jrname = player.plays.map{|play| play.role_ref && play.role_ref.role.role_name}.compact[0]
290
- rname = (rr = player.role_refs[0]) && rr.role.role_name
291
- if jrname and !rname
292
- # puts "Oops: rolename #{rname.inspect} != #{jrname.inspect}" if jrname != rname
293
- player.variables_by_query.values.each{|jn| jn.role_name = jrname }
294
- else
295
- player.subscript = s+1
296
- s += 1
297
- end
298
- end
289
+ jrname = player.plays.map{|play| play.role_ref && play.role_ref.role.role_name}.compact[0]
290
+ rname = (rr = player.role_refs[0]) && rr.role.role_name
291
+ if jrname and !rname
292
+ # puts "Oops: rolename #{rname.inspect} != #{jrname.inspect}" if jrname != rname
293
+ player.variables_by_query.values.each{|jn| jn.role_name = jrname }
294
+ else
295
+ player.subscript = s+1
296
+ s += 1
297
+ end
298
+ end
299
299
  end
300
300
  end
301
301
  end
@@ -447,20 +447,20 @@ module ActiveFacts
447
447
  raise "Need to change this call to expand_reading_text to pass a role->variable hash"
448
448
  end
449
449
  rrs = role_sequence.all_role_ref_in_order
450
- variable_by_role = {}
451
- if step
452
- plays = step.all_play
453
- variable_by_role = plays.inject({}) { |h, play| h[play.role] = play.variable; h }
454
- end
450
+ variable_by_role = {}
451
+ if step
452
+ plays = step.all_play
453
+ variable_by_role = plays.inject({}) { |h, play| h[play.role] = play.variable; h }
454
+ end
455
455
  trace :subscript, "expanding '#{text}' with #{role_sequence.describe}" do
456
456
  text.gsub(/\{(\d)\}/) do
457
457
  role_ref = rrs[$1.to_i]
458
458
  # REVISIT: We may need to use the step's role_refs to expand the role players here, not the reading's one (extra adjectives?)
459
459
  player = player_by_role[role_ref.role]
460
- variable = variable_by_role[role_ref.role]
460
+ variable = variable_by_role[role_ref.role]
461
461
 
462
462
  play_name = variable && variable.role_name
463
- raise hell if player && player.is_a?(ActiveFacts::Metamodel::EntityType) && player.fact_type && !variable
463
+ raise hell if player && player.is_a?(ActiveFacts::Metamodel::EntityType) && player.fact_type && !variable
464
464
  subscripted_player(role_ref, player && player.subscript, play_name, variable && variable.value) +
465
465
  objectification_verbalisation(variable)
466
466
  end
@@ -479,7 +479,7 @@ module ActiveFacts
479
479
  role_ref.trailing_adjective
480
480
  ].compact*' '
481
481
  ) +
482
- (value ? ' '+value.inspect : '') +
482
+ (value ? ' '+value.inspect : '') +
483
483
  (subscript ? "(#{subscript})" : '')
484
484
  end
485
485
 
@@ -508,11 +508,11 @@ module ActiveFacts
508
508
  def step_completed(step)
509
509
  @steps.delete(step)
510
510
 
511
- step.all_play.each do |play|
512
- var = play.variable
511
+ step.all_play.each do |play|
512
+ var = play.variable
513
513
  steps = @steps_by_variable[var]
514
514
  steps.delete(step)
515
- @steps_by_variable.delete(var) if steps.empty?
515
+ @steps_by_variable.delete(var) if steps.empty?
516
516
  end
517
517
  end
518
518
 
@@ -589,15 +589,15 @@ module ActiveFacts
589
589
  end
590
590
 
591
591
  def objectification_verbalisation(variable)
592
- return '' unless variable
593
- raise "Not fully re-implemented, should pass the variable instead of #{variable.inspect}" unless variable.is_a?(ActiveFacts::Metamodel::Variable)
592
+ return '' unless variable
593
+ raise "Not fully re-implemented, should pass the variable instead of #{variable.inspect}" unless variable.is_a?(ActiveFacts::Metamodel::Variable)
594
594
  objectified_node = nil
595
- object_type = variable.object_type
595
+ object_type = variable.object_type
596
596
  return '' unless object_type.is_a?(Metamodel::EntityType) # Not a entity type
597
- return '' unless object_type.fact_type # Not objectified
597
+ return '' unless object_type.fact_type # Not objectified
598
598
 
599
- objectification_step = variable.step
600
- return '' unless objectification_step
599
+ objectification_step = variable.step
600
+ return '' unless objectification_step
601
601
 
602
602
  steps = [objectification_step]
603
603
  step_completed(objectification_step)
@@ -634,9 +634,9 @@ module ActiveFacts
634
634
  # Choose a reading that's contractable against the previous step, if possible
635
635
  reading = fact_type.all_reading_by_ordinal.
636
636
  detect do |reading|
637
- # Only contract a negative reading if we want one
638
- (!next_step.is_disallowed || !reading.is_negative == !next_step.is_disallowed) and
639
- reading_starts_with_node(reading, next_var)
637
+ # Only contract a negative reading if we want one
638
+ (!next_step.is_disallowed || !reading.is_negative == !next_step.is_disallowed) and
639
+ reading_starts_with_node(reading, next_var)
640
640
  end
641
641
  end
642
642
  last_is_contractable = false unless reading
@@ -648,26 +648,26 @@ module ActiveFacts
648
648
  exit_node = @variables.detect{|jn| jn.all_play.detect{|play| play.role == last_role_ref.role}}
649
649
  exit_step = nil
650
650
 
651
- trace :query, "Stepping over an objectification to #{exit_node.object_type.name} requires eliding the other implied steps" do
652
- count = 0
653
- while other_step =
654
- @steps.
655
- detect{|js|
656
- trace :query, "Considering step '#{js.fact_type.default_reading}'"
657
- next unless js.is_objectification_step
658
-
659
- # REVISIT: This test is too weak: We need to ensure that the same variables are involved, not just the same object types:
660
- next unless js.input_play.variable.object_type == fact_type.entity_type || js.output_play.variable.object_type == fact_type.entity_type
661
- exit_step = js if js.output_play.variable == exit_node
662
- true
663
- }
664
- trace :query, "Emitting objectified FT allows deleting #{other_step.describe}"
665
- step_completed(other_step)
651
+ trace :query, "Stepping over an objectification to #{exit_node.object_type.name} requires eliding the other implied steps" do
652
+ count = 0
653
+ while other_step =
654
+ @steps.
655
+ detect{|js|
656
+ trace :query, "Considering step '#{js.fact_type.default_reading}'"
657
+ next unless js.is_objectification_step
658
+
659
+ # REVISIT: This test is too weak: We need to ensure that the same variables are involved, not just the same object types:
660
+ next unless js.input_play.variable.object_type == fact_type.entity_type || js.output_play.variable.object_type == fact_type.entity_type
661
+ exit_step = js if js.output_play.variable == exit_node
662
+ true
663
+ }
664
+ trace :query, "Emitting objectified FT allows deleting #{other_step.describe}"
665
+ step_completed(other_step)
666
666
  # raise "The objectification of '#{fact_type.default_reading}' should not cause the deletion of more than #{fact_type.all_role.size} other steps" if (count += 1) > fact_type.all_role.size
667
- end
668
- end
667
+ end
668
+ end
669
669
 
670
- [ reading, exit_step ? exit_step.input_play.variable : exit_node, exit_step, last_is_contractable]
670
+ [ reading, exit_step ? exit_step.input_play.variable : exit_node, exit_step, last_is_contractable]
671
671
  end
672
672
 
673
673
  def verbalise_query query
@@ -677,18 +677,18 @@ module ActiveFacts
677
677
  last_is_contractable = false
678
678
 
679
679
  trace :query, "Verbalising query" do
680
- if trace(:query)
681
- trace :query, "variables:" do
682
- @variables.each do |var|
683
- trace :query, var.describe
684
- end
685
- end
686
- trace :query, "steps:" do
687
- @steps.each do |step|
688
- trace :query, step.describe
689
- end
690
- end
691
- end
680
+ if trace(:query)
681
+ trace :query, "variables:" do
682
+ @variables.each do |var|
683
+ trace :query, var.describe
684
+ end
685
+ end
686
+ trace :query, "steps:" do
687
+ @steps.each do |step|
688
+ trace :query, step.describe
689
+ end
690
+ end
691
+ end
692
692
 
693
693
  until @steps.empty?
694
694
  next_reading = nil
@@ -707,8 +707,8 @@ module ActiveFacts
707
707
 
708
708
  player_by_role =
709
709
  next_step.all_play.inject({}) {|h, play| h[play.role] = @player_by_play[play]; h }
710
- raise "REVISIT: Needed a negated reading here" if !next_reading.is_negative != !next_step.is_disallowed
711
- raise "REVISIT: Need to emit 'maybe' here" if next_step.is_optional
710
+ raise "REVISIT: Needed a negated reading here" if !next_reading.is_negative != !next_step.is_disallowed
711
+ raise "REVISIT: Need to emit 'maybe' here" if next_step.is_optional
712
712
  readings += expand_contracted_text(next_step, next_reading, player_by_role)
713
713
  step_completed(next_step)
714
714
  else
@@ -721,10 +721,10 @@ module ActiveFacts
721
721
  # Objectified unaries get emitted as unaries, not as objectifications:
722
722
  role = next_step.input_play.role
723
723
  role = role.fact_type.implying_role if role.fact_type.is_a?(ActiveFacts::Metamodel::LinkFactType)
724
- next_reading = role.fact_type.preferred_reading(next_step.is_disallowed) || role.fact_type.preferred_reading
724
+ next_reading = role.fact_type.preferred_reading(next_step.is_disallowed) || role.fact_type.preferred_reading
725
725
  readings += " and " unless readings.empty?
726
- readings += "it is not the case that " if !next_step.is_disallowed != !next_reading.is_negative
727
- raise "REVISIT: Need to emit 'maybe' here" if next_step.is_optional
726
+ readings += "it is not the case that " if !next_step.is_disallowed != !next_reading.is_negative
727
+ raise "REVISIT: Need to emit 'maybe' here" if next_step.is_optional
728
728
  readings += expand_reading_text(next_step, next_reading.text, next_reading.role_sequence, player_by_role)
729
729
  step_completed(next_step)
730
730
  elsif next_step.is_objectification_step
@@ -735,7 +735,7 @@ module ActiveFacts
735
735
  objectified_node = next_step.input_play.variable
736
736
  raise "Assumption violated that the objectification is the input play" unless objectified_node.object_type.fact_type
737
737
  objectified_node.all_step.map do |other_step|
738
- other_step.all_play.map do |play|
738
+ other_step.all_play.map do |play|
739
739
  player_by_role[play.role] = @player_by_play[play]
740
740
  end
741
741
  end
@@ -746,16 +746,16 @@ module ActiveFacts
746
746
  readings += objectification_verbalisation(fact_type.entity_type)
747
747
  else
748
748
  # This objectified fact type does not need to be made explicit.
749
- negation = next_step.is_disallowed
749
+ negation = next_step.is_disallowed
750
750
  next_reading, next_var, next_step, last_is_contractable =
751
751
  *elided_objectification(next_step, fact_type, last_is_contractable, next_var)
752
752
  if last_is_contractable
753
- raise "REVISIT: Need to emit 'maybe' here" if next_step and next_step.is_optional
753
+ raise "REVISIT: Need to emit 'maybe' here" if next_step and next_step.is_optional
754
754
  readings += expand_contracted_text(next_step, next_reading, player_by_role)
755
755
  else
756
756
  readings += " and " unless readings.empty?
757
- readings += "it is not the case that " if !!negation != !!next_reading.is_negative
758
- raise "REVISIT: Need to emit 'maybe' here" if next_step and next_step.is_optional
757
+ readings += "it is not the case that " if !!negation != !!next_reading.is_negative
758
+ raise "REVISIT: Need to emit 'maybe' here" if next_step and next_step.is_optional
759
759
  readings += expand_reading_text(next_step, next_reading.text, next_reading.role_sequence, player_by_role)
760
760
  end
761
761
  # No need to continue if we just deleted the last step
@@ -767,32 +767,32 @@ module ActiveFacts
767
767
  # Prefer a reading that starts with the player of next_var
768
768
  next_reading = fact_type.all_reading_by_ordinal.
769
769
  detect do |reading|
770
- (!next_step.is_disallowed || !reading.is_negative == !next_step.is_disallowed) and
771
- reading_starts_with_node(reading, next_var)
770
+ (!next_step.is_disallowed || !reading.is_negative == !next_step.is_disallowed) and
771
+ reading_starts_with_node(reading, next_var)
772
772
  end || fact_type.preferred_reading(next_step.is_disallowed)
773
773
  # REVISIT: If this step and reading has role references with adjectives, we need to expand using those
774
774
  readings += " and " unless readings.empty?
775
- readings += "it is not the case that " if !next_step.is_disallowed != !next_reading.is_negative
776
- raise "REVISIT: Need to emit 'maybe' here" if next_step and next_step.is_optional
775
+ readings += "it is not the case that " if !next_step.is_disallowed != !next_reading.is_negative
776
+ raise "REVISIT: Need to emit 'maybe' here" if next_step and next_step.is_optional
777
777
  readings += expand_reading_text(next_step, next_reading.text, next_reading.role_sequence, player_by_role)
778
778
  step_completed(next_step)
779
779
  end
780
780
  end
781
781
 
782
- if next_step
783
- # Continue from this step with the node having the most steps remaining
784
- input_steps = @steps_by_variable[input_var = next_step.input_play.variable] || []
785
- output_play = next_step.output_plays.last
786
- output_steps = (output_play && (output_var = output_play.variable) && @steps_by_variable[output_var]) || []
787
-
788
- next_var = input_steps.size > output_steps.size ? input_var : output_var
789
- # Prepare for possible contraction following:
790
- last_is_contractable = next_reading && node_contractable_against_reading(next_var, next_reading)
791
- else
792
- # This shouldn't happen, but an elided objectification that had missing steps can cause it. Survive:
793
- next_var = (steps[0].input_play || steps[0].output_plays.last).variable
794
- last_is_contractable = false
795
- end
782
+ if next_step
783
+ # Continue from this step with the node having the most steps remaining
784
+ input_steps = @steps_by_variable[input_var = next_step.input_play.variable] || []
785
+ output_play = next_step.output_plays.last
786
+ output_steps = (output_play && (output_var = output_play.variable) && @steps_by_variable[output_var]) || []
787
+
788
+ next_var = input_steps.size > output_steps.size ? input_var : output_var
789
+ # Prepare for possible contraction following:
790
+ last_is_contractable = next_reading && node_contractable_against_reading(next_var, next_reading)
791
+ else
792
+ # This shouldn't happen, but an elided objectification that had missing steps can cause it. Survive:
793
+ next_var = (steps[0].input_play || steps[0].output_plays.last).variable
794
+ last_is_contractable = false
795
+ end
796
796
 
797
797
  end
798
798
  end