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.
9073
9074
9075
9076
9077
9078
9079
9080
9081
|
# File 'lib/idlc/ast.rb', line 9073
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
9306
9307
9308
9309
9310
9311
9312
9313
9314
9315
9316
9317
9318
|
# File 'lib/idlc/ast.rb', line 9306
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
9054
9055
9056
9057
9058
9059
|
# File 'lib/idlc/ast.rb', line 9054
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
9068
|
# File 'lib/idlc/ast.rb', line 9068
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
9242
9243
9244
9245
9246
9247
9248
9249
9250
9251
9252
9253
9254
9255
9256
9257
9258
9259
9260
9261
9262
9263
9264
9265
9266
9267
9268
9269
9270
9271
9272
9273
9274
9275
9276
|
# File 'lib/idlc/ast.rb', line 9242
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
9071
|
# File 'lib/idlc/ast.rb', line 9071
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
9065
|
# File 'lib/idlc/ast.rb', line 9065
def if_body = T.cast(@children.fetch(1), IfBodyAst)
|
#if_cond ⇒ Object
9062
|
# File 'lib/idlc/ast.rb', line 9062
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
9135
9136
9137
9138
9139
9140
9141
|
# File 'lib/idlc/ast.rb', line 9135
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
9144
9145
9146
9147
9148
9149
9150
|
# File 'lib/idlc/ast.rb', line 9144
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
9185
9186
9187
9188
9189
9190
9191
9192
9193
9194
9195
9196
9197
9198
9199
9200
|
# File 'lib/idlc/ast.rb', line 9185
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.
9122
9123
9124
9125
9126
9127
9128
9129
9130
9131
9132
|
# File 'lib/idlc/ast.rb', line 9122
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
9296
9297
9298
9299
9300
9301
9302
9303
|
# File 'lib/idlc/ast.rb', line 9296
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
9280
9281
9282
9283
9284
9285
9286
9287
9288
9289
9290
9291
9292
9293
|
# File 'lib/idlc/ast.rb', line 9280
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
9084
9085
9086
9087
9088
9089
9090
9091
9092
9093
9094
9095
9096
9097
9098
9099
9100
9101
9102
9103
9104
9105
9106
9107
9108
9109
9110
9111
9112
9113
9114
9115
9116
9117
9118
|
# File 'lib/idlc/ast.rb', line 9084
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
|