Class: Idl::IfAst
- Inherits:
-
AstNode
show all
- Includes:
- Executable, Returns
- Defined in:
- lib/idlc/ast.rb,
lib/idlc/passes/prune.rb,
lib/idlc/passes/gen_adoc.rb,
lib/idlc/passes/gen_option_adoc.rb,
lib/idlc/passes/find_return_values.rb,
lib/idlc/passes/reachable_functions.rb,
lib/idlc/passes/reachable_exceptions.rb
Constant Summary
Constants inherited
from AstNode
AstNode::Bits1Type, AstNode::Bits32Type, AstNode::Bits64Type, AstNode::BoolType, AstNode::ConstBoolType, AstNode::PossiblyUnknownBits1Type, AstNode::PossiblyUnknownBits32Type, AstNode::PossiblyUnknownBits64Type, AstNode::ReachableFunctionCacheType, AstNode::StringType, AstNode::VoidType
Instance Attribute Summary
Attributes inherited from AstNode
#children, #input, #interval, #parent
Class Method Summary
collapse
Instance Method Summary
collapse
-
#const_eval?(symtab) ⇒ Boolean
-
#elseifs ⇒ Object
-
#execute(symtab) ⇒ void
“execute” the statement by updating the variables in the symbol table.
-
#final_else_body ⇒ Object
-
#gen_adoc(indent = 0, indent_spaces: 2) ⇒ Object
-
#gen_option_adoc ⇒ Object
-
#if_body ⇒ Object
-
#if_cond ⇒ Object
-
#initialize(input, interval, if_cond, if_body, elseifs, final_else_body) ⇒ IfAst
constructor
-
#pass_find_return_values(values, current_conditions, symtab) ⇒ Object
-
#prune(symtab, forced_type: nil) ⇒ Object
-
#reachable_exceptions(symtab, cache = {}) ⇒ Object
-
#reachable_functions(symtab, cache = T.let({}, ReachableFunctionCacheType)) ⇒ Object
-
#return_type(symtab) ⇒ Object
-
#return_value(symtab) ⇒ Integer, ...
Evaluate the compile-time return value of this node, or, if the node does not return (e.g., because it is an IfAst but there is no return on the taken path), execute the node and update the symtab.
-
#return_values(symtab) ⇒ Array<Integer,Bool>
Returns a list of all possible return values, if known.
-
#taken_body(symtab) ⇒ Boolean
True if the taken path is knowable at compile-time.
-
#to_h ⇒ Object
-
#to_idl ⇒ Object
-
#type_check(symtab, strict:) ⇒ void
type check this node and all children.
Methods included from Returns
#expected_return_type
Methods included from Executable
#executable?
Methods inherited from AstNode
#always_terminates?, #declaration?, #executable?, extract_base_var_name, #find_ancestor, #find_dst_registers, #find_referenced_csrs, #find_src_registers, #freeze_tree, #input_file, input_from_source_yaml, #inspect, #internal_error, interval_from_source_yaml, #lineno, #lines_around, #nullify_assignments, #path, #print_ast, #set_input_file, #set_input_file_unless_already_set, #source_line_file_offsets, #source_starting_offset, #source_yaml, #starting_line, #text_value, #truncation_warn, #type_error, #unindent, value_else, #value_else, value_error, #value_error, value_try, #value_try, write_back_nested
Constructor Details
#initialize(input, interval, if_cond, if_body, elseifs, final_else_body) ⇒ IfAst
Returns a new instance of IfAst.
9088
9089
9090
9091
9092
9093
9094
9095
9096
|
# File 'lib/idlc/ast.rb', line 9088
def initialize(input, interval, if_cond, if_body, elseifs, final_else_body)
children_nodes = [if_cond, if_body]
children_nodes += elseifs
children_nodes << final_else_body
@func_type_cache = {}
super(input, interval, children_nodes)
end
|
Class Method Details
.from_h(yaml, source_mapper) ⇒ Object
9321
9322
9323
9324
9325
9326
9327
9328
9329
9330
9331
9332
9333
|
# File 'lib/idlc/ast.rb', line 9321
def self.from_h(yaml, source_mapper)
raise "Bad YAML" unless yaml.key?("kind") && yaml.fetch("kind") == "if_stmt"
input = input_from_source_yaml(yaml.fetch("source"), source_mapper)
interval = interval_from_source_yaml(yaml.fetch("source"))
IfAst.new(
input, interval,
AstNode.from_h(yaml.fetch("condition"), source_mapper),
AstNode.from_h(yaml.fetch("taken_body"), source_mapper),
yaml.fetch("else_ifs").map { |eif| AstNode.from_h(eif, source_mapper) },
yaml.fetch("else").nil? ? IfBodyAst.new(nil, nil, []) : AstNode.from_h(yaml.fetch("else"), source_mapper)
)
end
|
Instance Method Details
#const_eval?(symtab) ⇒ Boolean
9069
9070
9071
9072
9073
9074
|
# File 'lib/idlc/ast.rb', line 9069
def const_eval?(symtab)
if_cond.const_eval?(symtab) && \
if_body.const_eval?(symtab) && \
elseifs.all? { |eif| eif.const_eval?(symtab) } && \
final_else_body.const_eval?(symtab)
end
|
#elseifs ⇒ Object
9083
|
# File 'lib/idlc/ast.rb', line 9083
def elseifs = T.cast(T.must(@children[2..-2]), T::Array[ElseIfAst])
|
#execute(symtab) ⇒ void
This method returns an undefined value.
“execute” the statement by updating the variables in the symbol table
9257
9258
9259
9260
9261
9262
9263
9264
9265
9266
9267
9268
9269
9270
9271
9272
9273
9274
9275
9276
9277
9278
9279
9280
9281
9282
9283
9284
9285
9286
9287
9288
9289
9290
9291
|
# File 'lib/idlc/ast.rb', line 9257
def execute(symtab)
err = T.let(nil, T.nilable(Symbol))
value_result = value_try do
if_cond_value = if_cond.value(symtab)
if if_cond_value
value_result2 = value_try do
if_body.execute(symtab)
end
value_else(value_result2) do
err = :value_error if err.nil?
end
else
execute_after_if(symtab)
end
end
value_else(value_result) do
value_result2 = value_try do
if_body.execute(symtab)
end
value_else(value_result2) do
err = :value_error if err.nil?
end
value_result2 = value_try do
execute_after_if(symtab)
end
value_else(value_result2) do
err = :value_error if err.nil?
end
end
value_error "" unless err.nil?
end
|
#final_else_body ⇒ Object
9086
|
# File 'lib/idlc/ast.rb', line 9086
def final_else_body = T.cast(T.must(@children.last), IfBodyAst)
|
#gen_adoc(indent = 0, indent_spaces: 2) ⇒ Object
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
|
# File 'lib/idlc/passes/gen_adoc.rb', line 333
def gen_adoc(indent = 0, indent_spaces: 2)
lines = ["#{' ' * indent}if pass:[(]#{if_cond.gen_adoc(0, indent_spaces:)}) {"]
if_body.stmts.each do |s|
lines << s.gen_adoc(indent + indent_spaces, indent_spaces:)
end
elseifs.each do |eif|
lines << "#{' ' * indent}} else if pass:[(]#{eif.cond.gen_adoc(0, indent_spaces:)}) {"
eif.body.stmts.each do |s|
lines << s.gen_adoc(indent + indent_spaces, indent_spaces:)
end
end
unless final_else_body.stmts.empty?
lines << "#{' ' * indent}} else {"
final_else_body.stmts.each do |s|
lines << s.gen_adoc(indent + indent_spaces, indent_spaces:)
end
end
lines << "#{' ' * indent}}"
lines.join("\n")
end
|
#gen_option_adoc ⇒ Object
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
|
# File 'lib/idlc/passes/gen_option_adoc.rb', line 34
def gen_option_adoc
adoc =
<<~ADOC
[when,"#{if_cond.to_idl}"]
#{if_body.gen_option_adoc}
ADOC
elseifs.each do |eif|
adoc << <<~ADOC
[when,"#{eif.cond.to_idl}"]
#{eif.body.gen_option_adoc}
ADOC
end
unless final_else_body.nil?
if elseifs.empty?
if if_cond.is_a?(BinaryExpressionAst) || if_cond.is_a?(UnaryOperatorExpressionAst)
adoc << <<~ADOC
[when,"#{if_cond.invert(nil).to_idl}"]
#{final_else_body.gen_option_adoc}
ADOC
else
adoc << <<~ADOC
[when,"!(#{if_cond.to_idl})"]
#{final_else_body.gen_option_adoc}
ADOC
end
else
adoc << <<~ADOC
[when,"else"]
#{final_else_body.gen_option_adoc}
ADOC
end
end
adoc
end
|
#if_body ⇒ Object
9080
|
# File 'lib/idlc/ast.rb', line 9080
def if_body = T.cast(@children.fetch(1), IfBodyAst)
|
#if_cond ⇒ Object
9077
|
# File 'lib/idlc/ast.rb', line 9077
def if_cond = T.cast(@children.fetch(0), RvalueAst)
|
#pass_find_return_values(values, current_conditions, symtab) ⇒ Object
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
|
# File 'lib/idlc/passes/find_return_values.rb', line 35
def pass_find_return_values(values, current_conditions, symtab)
current_conditions.push if_cond
if_body.elements.each do |e|
e.e.pass_find_return_values(values, current_conditions, symtab)
end
current_conditions.pop
unless elseifs.empty?
elseifs.elements.each do |eif|
current_conditions.push eif.expression
eif.body.elements.each do |e|
e.e.pass_find_return_values(values, current_conditions, symtab)
end
current_conditions.pop
end
end
unless final_else.empty?
current_conditions.push if_cond.invert(symtab)
final_else.body.elements.each do |e|
e.e.pass_find_return_values(values, current_conditions, symtab)
end
current_conditions.pop
end
end
|
#prune(symtab, forced_type: nil) ⇒ Object
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
|
# File 'lib/idlc/passes/prune.rb', line 526
def prune(symtab, forced_type: nil)
value_result = value_try do
if if_cond.value(symtab)
return if_body.prune(symtab, restore: false)
elsif !elseifs.empty?
return IfAst.new(
input, interval,
elseifs[0].cond.dup,
elseifs[0].body.dup,
elseifs[1..].map(&:dup),
final_else_body.dup).prune(symtab)
elsif !final_else_body.stmts.empty?
return final_else_body.prune(symtab, restore: false)
else
return NoopAst.new
end
end
value_else(value_result) do
unknown_elsifs = []
elseifs.each do |eif|
value_result = value_try do
if eif.cond.value(symtab)
return IfAst.new(
input, interval,
if_cond.dup,
if_body.dup,
unknown_elsifs.map(&:dup),
eif.body.dup
).prune(symtab)
else
next :ok
end
end
value_else(value_result) do
unknown_elsifs << eif
end
end
pruned_elsifs = unknown_elsifs.filter_map do |eif|
pruned = eif.prune(symtab)
next nil if pruned.cond.is_a?(FalseExpressionAst)
pruned
end
result = IfAst.new(
input, interval,
if_cond.prune(symtab),
if_body.prune(symtab),
pruned_elsifs,
final_else_body.prune(symtab)
)
if_body.nullify_assignments(symtab)
pruned_elsifs.each { |eif| eif.body.nullify_assignments(symtab) }
final_else_body.nullify_assignments(symtab)
result
end
end
|
#reachable_exceptions(symtab, cache = {}) ⇒ Object
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
|
# File 'lib/idlc/passes/reachable_exceptions.rb', line 94
def reachable_exceptions(symtab, cache = {})
mask = 0
value_try do
mask = if_cond.reachable_exceptions(symtab, cache) if if_cond.is_a?(FunctionCallExpressionAst)
value_result = value_try do
if (if_cond.value(symtab))
mask |= if_body.reachable_exceptions(symtab, cache)
return mask else
elseifs.each do |eif|
mask |= eif.cond.reachable_exceptions(symtab, cache) if eif.cond.is_a?(FunctionCallExpressionAst)
value_result = value_try do
if (eif.cond.value(symtab))
mask |= eif.body.reachable_exceptions(symtab, cache)
return mask end
end
value_else(value_result) do
mask |= eif.body.reachable_exceptions(symtab, cache)
end
end
mask |= final_else_body.reachable_exceptions(symtab, cache)
end
end
value_else(value_result) do
mask |= if_body.reachable_exceptions(symtab, cache)
elseifs.each do |eif|
mask |= eif.cond.reachable_exceptions(symtab, cache) if eif.cond.is_a?(FunctionCallExpressionAst)
value_result = value_try do
if (eif.cond.value(symtab))
mask |= eif.body.reachable_exceptions(symtab, cache)
return mask end
end
value_else(value_result) do
mask |= eif.body.reachable_exceptions(symtab, cache)
end
end
mask |= final_else_body.reachable_exceptions(symtab, cache)
end
end
return mask
end
|
#reachable_functions(symtab, cache = T.let({}, ReachableFunctionCacheType)) ⇒ Object
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
|
# File 'lib/idlc/passes/reachable_functions.rb', line 103
def reachable_functions(symtab, cache = T.let({}, ReachableFunctionCacheType))
fns = []
value_try do
fns.concat if_cond.reachable_functions(symtab, cache)
value_result = value_try do
if (if_cond.value(symtab))
fns.concat if_body.reachable_functions(symtab, cache)
return fns else
if (if_cond.text_value == "pending_and_enabled_interrupts != 0")
warn symtab.get("pending_and_enabled_interrupts")
raise "???"
end
elseifs.each do |eif|
fns.concat eif.cond.reachable_functions(symtab, cache)
value_result = value_try do
if (eif.cond.value(symtab))
fns.concat eif.body.reachable_functions(symtab, cache)
return fns end
end
value_else(value_result) do
fns.concat eif.body.reachable_functions(symtab, cache)
end
end
fns.concat final_else_body.reachable_functions(symtab, cache)
end
end
value_else(value_result) do
fns.concat if_body.reachable_functions(symtab, cache)
elseifs.each do |eif|
fns.concat eif.cond.reachable_functions(symtab, cache)
value_result = value_try do
if (eif.cond.value(symtab))
fns.concat eif.body.reachable_functions(symtab, cache)
return fns end
end
value_else(value_result) do
fns.concat eif.body.reachable_functions(symtab, cache)
end
end
fns.concat final_else_body.reachable_functions(symtab, cache)
end
end
return fns
end
|
#return_type(symtab) ⇒ Object
9150
9151
9152
9153
9154
9155
9156
|
# File 'lib/idlc/ast.rb', line 9150
def return_type(symtab)
begin
rt = if_body.return_type(symtab)
rt = elseifs.map { |eif| eif.return_type(symtab) }.compact[0] if rt.nil?
rt ||= final_else_body.return_type(symtab)
end
end
|
#return_value(symtab) ⇒ Integer, ...
Evaluate the compile-time return value of this node, or, if the node does not return (e.g., because it is an IfAst but there is no return on the taken path), execute the node and update the symtab
9159
9160
9161
9162
9163
9164
9165
|
# File 'lib/idlc/ast.rb', line 9159
def return_value(symtab)
body = taken_body(symtab)
return nil if body.nil?
body.return_value(symtab)
end
|
#return_values(symtab) ⇒ Array<Integer,Bool>
Returns a list of all possible return values, if known. Otherwise, raises a ValueError
9200
9201
9202
9203
9204
9205
9206
9207
9208
9209
9210
9211
9212
9213
9214
9215
|
# File 'lib/idlc/ast.rb', line 9200
def return_values(symtab)
value_result = value_try do
if_cond_value = if_cond.value(symtab)
if if_cond_value
return if_body.return_values(symtab)
else
return return_values_after_if(symtab)
end
end
value_else(value_result) do
(if_body.return_values(symtab) + return_values_after_if(symtab)).uniq
end
end
|
#taken_body(symtab) ⇒ Boolean
Returns true if the taken path is knowable at compile-time.
9137
9138
9139
9140
9141
9142
9143
9144
9145
9146
9147
|
# File 'lib/idlc/ast.rb', line 9137
def taken_body(symtab)
return if_body if if_cond.value(symtab)
unless elseifs.empty?
elseifs.each do |eif|
return eif.body if eif.cond.value(symtab)
end
end
final_else_body.stmts.empty? ? nil : final_else_body
end
|
#to_h ⇒ Object
9311
9312
9313
9314
9315
9316
9317
9318
|
# File 'lib/idlc/ast.rb', line 9311
def to_h = {
"kind" => "if_stmt",
"condition" => if_cond.to_h,
"taken_body" => if_body.to_h,
"else_ifs" => elseifs.map(&:to_h),
"else" => final_else_body.stmts.empty? ? nil : final_else_body.to_h,
"source" => source_yaml
}
|
#to_idl ⇒ Object
9295
9296
9297
9298
9299
9300
9301
9302
9303
9304
9305
9306
9307
9308
|
# File 'lib/idlc/ast.rb', line 9295
def to_idl
result = "if (#{if_cond.to_idl}) { "
result << if_body.to_idl
result << "} "
elseifs.each do |eif|
result << eif.to_idl
end
unless final_else_body.stmts.empty?
result << " else { "
result << final_else_body.to_idl
result << "} "
end
result
end
|
#type_check(symtab, strict:) ⇒ void
This method returns an undefined value.
type check this node and all children
Calls to #type and/or #value may depend on type_check being called first with the same symtab. If not, those functions may raise an AstNode::InternalError
9099
9100
9101
9102
9103
9104
9105
9106
9107
9108
9109
9110
9111
9112
9113
9114
9115
9116
9117
9118
9119
9120
9121
9122
9123
9124
9125
9126
9127
9128
9129
9130
9131
9132
9133
|
# File 'lib/idlc/ast.rb', line 9099
def type_check(symtab, strict:)
level = symtab.levels
if_cond.type_check(symtab, strict:)
unless if_cond.type(symtab).convertable_to?(:boolean)
if if_cond.type(symtab).kind == :bits
type_error "'#{if_cond.text_value}' is not boolean. Maybe you meant 'if ((#{if_cond.text_value}) != 0)'?"
else
type_error "'#{if_cond.text_value}' is not boolean"
end
end
if_cond_value = T.let(nil, T.nilable(ValueRbType))
value_try do
if_cond_value = if_cond.value(symtab)
end
if_body.type_check(symtab, strict:) unless if_cond_value == false
internal_error "not at same level #{level} #{symtab.levels}" unless level == symtab.levels
unless (if_cond_value == true) || elseifs.empty?
elseifs.each do |eif|
eif.type_check(symtab, strict:)
end
end
internal_error "not at same level #{level} #{symtab.levels}" unless level == symtab.levels
final_else_body.type_check(symtab, strict:) unless if_cond_value == true
internal_error "not at same level #{level} #{symtab.levels}" unless level == symtab.levels
end
|