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.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/Gemfile +6 -5
- data/activefacts-cql.gemspec +4 -3
- data/lib/activefacts/cql/compiler.rb +29 -22
- data/lib/activefacts/cql/compiler/clause.rb +86 -84
- data/lib/activefacts/cql/compiler/constraint.rb +53 -53
- data/lib/activefacts/cql/compiler/entity_type.rb +28 -28
- data/lib/activefacts/cql/compiler/expression.rb +27 -27
- data/lib/activefacts/cql/compiler/fact.rb +37 -37
- data/lib/activefacts/cql/compiler/fact_type.rb +91 -85
- data/lib/activefacts/cql/compiler/informal.rb +48 -0
- data/lib/activefacts/cql/compiler/query.rb +52 -52
- data/lib/activefacts/cql/compiler/shared.rb +17 -17
- data/lib/activefacts/cql/compiler/value_type.rb +11 -11
- data/lib/activefacts/cql/parser/CQLParser.treetop +76 -56
- data/lib/activefacts/cql/parser/Context.treetop +15 -17
- data/lib/activefacts/cql/parser/Expressions.treetop +21 -21
- data/lib/activefacts/cql/parser/FactTypes.treetop +216 -216
- data/lib/activefacts/cql/parser/Language/English.treetop +136 -131
- data/lib/activefacts/cql/parser/Language/French.treetop +118 -118
- data/lib/activefacts/cql/parser/Language/Mandarin.treetop +111 -111
- data/lib/activefacts/cql/parser/LexicalRules.treetop +45 -45
- data/lib/activefacts/cql/parser/ObjectTypes.treetop +120 -120
- data/lib/activefacts/cql/parser/Terms.treetop +63 -63
- data/lib/activefacts/cql/parser/ValueTypes.treetop +71 -71
- data/lib/activefacts/cql/require.rb +8 -8
- data/lib/activefacts/cql/verbaliser.rb +88 -88
- data/lib/activefacts/cql/version.rb +1 -1
- data/lib/activefacts/input/cql.rb +7 -7
- metadata +12 -16
@@ -8,47 +8,47 @@ module ActiveFacts
|
|
8
8
|
module CQL
|
9
9
|
grammar ValueTypes
|
10
10
|
rule value_type
|
11
|
-
|
11
|
+
s each?
|
12
12
|
s term_definition_name
|
13
|
-
|
14
|
-
|
13
|
+
m1:mapping_pragmas
|
14
|
+
# REVISIT: ORM2 would allow (subtype_prefix term)?
|
15
15
|
written_as
|
16
|
-
|
16
|
+
any? s
|
17
17
|
base:(term/implicit_value_type_name) s
|
18
18
|
value_type_parameters
|
19
19
|
u:in_units?
|
20
|
-
|
21
|
-
|
20
|
+
a:auto_assignment?
|
21
|
+
c:context_note?
|
22
22
|
r:(value_constraint enforcement)?
|
23
23
|
m2:mapping_pragmas
|
24
|
-
|
24
|
+
c2:context_note?
|
25
25
|
s ';' s
|
26
26
|
{
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
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
|
-
|
44
|
+
in S units
|
45
45
|
end
|
46
46
|
|
47
47
|
rule implicit_value_type_name
|
48
|
-
|
49
|
-
|
50
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
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
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
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
|
-
|
103
|
-
|
104
|
-
|
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
|
-
|
112
|
+
def ast
|
113
113
|
[ numerator.text_value,
|
114
114
|
(denominator.text_value.empty? ? "1" : denominator.number.text_value)
|
115
|
-
|
116
|
-
|
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
|
-
|
133
|
-
|
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
|
-
|
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
|
-
|
160
|
+
restricted_to restricted_values c:context_note?
|
161
161
|
{
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
162
|
+
def ast
|
163
|
+
v = restricted_values.values
|
164
|
+
c[:context_note] = c.ast unless c.empty?
|
165
|
+
v
|
166
|
+
end
|
167
167
|
}
|
168
|
-
|
168
|
+
# REVISIT: "where the possible value/s of that <Term> is/are value (, ...)"
|
169
169
|
end
|
170
170
|
|
171
171
|
rule restricted_values
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
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
|
-
|
23
|
-
|
22
|
+
trace "Loading #{file}" do
|
23
|
+
vocabulary = ActiveFacts::Input::CQL.readfile(file)
|
24
24
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
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
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
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
|
-
|
451
|
-
|
452
|
-
|
453
|
-
|
454
|
-
|
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
|
-
|
460
|
+
variable = variable_by_role[role_ref.role]
|
461
461
|
|
462
462
|
play_name = variable && variable.role_name
|
463
|
-
|
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
|
-
|
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
|
-
|
512
|
-
|
511
|
+
step.all_play.each do |play|
|
512
|
+
var = play.variable
|
513
513
|
steps = @steps_by_variable[var]
|
514
514
|
steps.delete(step)
|
515
|
-
|
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
|
-
|
593
|
-
|
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
|
-
|
595
|
+
object_type = variable.object_type
|
596
596
|
return '' unless object_type.is_a?(Metamodel::EntityType) # Not a entity type
|
597
|
-
|
597
|
+
return '' unless object_type.fact_type # Not objectified
|
598
598
|
|
599
|
-
|
600
|
-
|
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
|
-
|
638
|
-
|
639
|
-
|
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
|
-
|
652
|
-
|
653
|
-
|
654
|
-
|
655
|
-
|
656
|
-
|
657
|
-
|
658
|
-
|
659
|
-
|
660
|
-
|
661
|
-
|
662
|
-
|
663
|
-
|
664
|
-
|
665
|
-
|
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
|
-
|
668
|
-
|
667
|
+
end
|
668
|
+
end
|
669
669
|
|
670
|
-
|
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
|
-
|
681
|
-
|
682
|
-
|
683
|
-
|
684
|
-
|
685
|
-
|
686
|
-
|
687
|
-
|
688
|
-
|
689
|
-
|
690
|
-
|
691
|
-
|
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
|
-
|
711
|
-
|
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
|
-
|
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
|
-
|
727
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
758
|
-
|
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
|
-
|
771
|
-
|
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
|
-
|
776
|
-
|
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
|
-
|
783
|
-
|
784
|
-
|
785
|
-
|
786
|
-
|
787
|
-
|
788
|
-
|
789
|
-
|
790
|
-
|
791
|
-
|
792
|
-
|
793
|
-
|
794
|
-
|
795
|
-
|
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
|