Class: Idl::ForLoopAst

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/find_src_registers.rb,
lib/idlc/passes/reachable_functions.rb,
lib/idlc/passes/reachable_exceptions.rb

Constant Summary collapse

StmtType =
T.type_alias { T.any(StatementAst, ReturnStatementAst, IfAst, ForLoopAst, ImplicationStatementAst) }

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

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_referenced_csrs, #freeze_tree, #gen_option_adoc, #input_file, input_from_source_yaml, #inspect, #internal_error, interval_from_source_yaml, #lineno, #lines_around, #nullify_assignments, #pass_find_return_values, #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, init, condition, update, stmts) ⇒ ForLoopAst

Returns a new instance of ForLoopAst.



8639
8640
8641
# File 'lib/idlc/ast.rb', line 8639

def initialize(input, interval, init, condition, update, stmts)
  super(input, interval, [init, condition, update] + stmts)
end

Class Method Details

.from_h(yaml, source_mapper) ⇒ Object



8781
8782
8783
8784
8785
8786
8787
8788
8789
8790
8791
8792
8793
# File 'lib/idlc/ast.rb', line 8781

def self.from_h(yaml, source_mapper)
  raise "Bad YAML" unless yaml.key?("kind") && yaml.fetch("kind") == "for_loop_stmt"

  input = input_from_source_yaml(yaml.fetch("source"), source_mapper)
  interval = interval_from_source_yaml(yaml.fetch("source"))
  ForLoopAst.new(
    input, interval,
    AstNode.from_h(yaml.fetch("init_expr"), source_mapper),
    AstNode.from_h(yaml.fetch("condition_expr"), source_mapper),
    AstNode.from_h(yaml.fetch("update_expr"), source_mapper),
    yaml.fetch("body").map { |s| AstNode.from_h(s, source_mapper) }
  )
end

Instance Method Details

#conditionObject



8630
# File 'lib/idlc/ast.rb', line 8630

def condition = T.cast(@children.fetch(1), RvalueAst)

#const_eval?(symtab) ⇒ Boolean

Returns:

  • (Boolean)


8619
8620
8621
8622
8623
8624
# File 'lib/idlc/ast.rb', line 8619

def const_eval?(symtab)
  init.const_eval?(symtab) && \
    condition.const_eval?(symtab) && \
    update.const_eval?(symtab) && \
    stmts.all? { |stmt| stmt.const_eval?(symtab) }
end

#execute(symtab) ⇒ void

This method returns an undefined value.

“execute” the statement by updating the variables in the symbol table

Parameters:

  • symtab (SymbolTable)

    The symbol table for the context

Raises:

  • ValueError if some part of the statement cannot be executed at compile time



8757
# File 'lib/idlc/ast.rb', line 8757

def execute(symtab) = return_value(symtab)

#find_dst_registers(symtab) ⇒ Object

we don’t unroll, but we don’t add the index variable to the symtab, either that will cause any register accesses dependent on the index variable to raise Complex



66
67
68
69
70
71
72
73
74
75
76
77
# File 'lib/idlc/passes/find_src_registers.rb', line 66

def find_dst_registers(symtab)
  dsts = init.find_dst_registers(symtab)
  # don't add init to the symtab, since we don't want to use it...
  dsts += condition.find_dst_registers(symtab)

  stmts.each do |stmt|
    dsts += stmt.find_dst_registers(symtab)
  end
  dsts += update.find_dst_registers(symtab)

  dsts
end

#find_src_registers(symtab) ⇒ Object

we don’t unroll, but we don’t add the index variable to the symtab, either that will cause any register accesses dependent on the index variable to raise Complex



51
52
53
54
55
56
57
58
59
60
61
62
# File 'lib/idlc/passes/find_src_registers.rb', line 51

def find_src_registers(symtab)
  srcs = init.find_src_registers(symtab)
  # don't add init to the symtab, since we don't want to use it...
  srcs += condition.find_src_registers(symtab)

  stmts.each do |stmt|
    srcs += stmt.find_src_registers(symtab)
  end
  srcs += update.find_src_registers(symtab)

  srcs
end

#gen_adoc(indent = 0, indent_spaces: 2) ⇒ Object



207
208
209
210
211
212
213
214
# File 'lib/idlc/passes/gen_adoc.rb', line 207

def gen_adoc(indent = 0, indent_spaces: 2)
  lines = ["#{' ' * indent}for pass:[(]#{init.gen_adoc(0, indent_spaces:)}; #{condition.gen_adoc(0, indent_spaces:)}; #{update.gen_adoc(0, indent_spaces:)}) {"]
  stmts.each do |s|
    lines << s.gen_adoc(indent + indent_spaces, indent_spaces:)
  end
  lines << "#{' ' * indent}}"
  lines.join("\n")
end

#initObject



8627
# File 'lib/idlc/ast.rb', line 8627

def init = T.cast(@children.fetch(0), VariableDeclarationWithInitializationAst)

#prune(symtab, forced_type: nil) ⇒ Object



252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
# File 'lib/idlc/passes/prune.rb', line 252

def prune(symtab, forced_type: nil)
  symtab.push(self)
  symtab.add(init.lhs.name, Var.new(init.lhs.name, init.lhs_type(symtab)))

  # Nullify any outer-scope variable assigned in the loop body, since we
  # don't know how many iterations ran (or if any ran at all)
  stmts.each { |stmt| stmt.nullify_assignments(symtab) }

  # Snapshot after nullification so restore brings back nil values, not pre-loop values
  snapshot = symtab.snapshot_values

  begin
    new_loop =
      ForLoopAst.new(
        input, interval,
        init.prune(symtab),
        condition.prune(symtab),
        update.prune(symtab),
        stmts.map { |s| s.prune(symtab) }
      )
  ensure
    symtab.restore_values(snapshot)
    symtab.pop
  end
  # Nullify any outer-scope variable assigned in the loop body, since we
  # don't know how many iterations ran (or if any ran at all)
  stmts.each { |stmt| stmt.nullify_assignments(symtab) }
  new_loop
end

#reachable_exceptions(symtab, cache = {}) ⇒ Object



190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
# File 'lib/idlc/passes/reachable_exceptions.rb', line 190

def reachable_exceptions(symtab, cache = {})
  symtab.push(self)
  begin
    symtab.add(init.lhs.name, Var.new(init.lhs.name, init.lhs_type(symtab)))
    mask = init.is_a?(FunctionCallExpressionAst) ? init.reachable_exceptions(symtab, cache) : 0
    mask |= condition.reachable_exceptions(symtab, cache) if condition.is_a?(FunctionCallExpressionAst)
    mask |= update.reachable_exceptions(symtab, cache) if update.is_a?(FunctionCallExpressionAst)
    stmts.each do |stmt|
      mask |= stmt.reachable_exceptions(symtab, cache)
    end
  ensure
    symtab.pop
  end
  mask
end

#reachable_functions(symtab, cache = T.let({}, ReachableFunctionCacheType)) ⇒ Object



205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
# File 'lib/idlc/passes/reachable_functions.rb', line 205

def reachable_functions(symtab, cache = T.let({}, ReachableFunctionCacheType))
  symtab.push(self)
  begin
    symtab.add(init.lhs.name, Var.new(init.lhs.name, init.lhs_type(symtab)))
    fns = init.is_a?(FunctionCallExpressionAst) ? init.reachable_functions(symtab, cache) : []
    fns.concat(condition.reachable_functions(symtab, cache))
    fns.concat(update.reachable_functions(symtab, cache))
    stmts.each do |stmt|
      fns.concat(stmt.reachable_functions(symtab, cache))
    end
  ensure
    symtab.pop
  end
  fns
end

#return_type(symtab) ⇒ Object



8706
8707
8708
8709
# File 'lib/idlc/ast.rb', line 8706

def return_type(symtab)
  # the return type is determined by the function
  expected_return_type(symtab)
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

Parameters:

  • symtab (SymbolTable)

    The symbol table for the context

Returns:

  • (Integer)

    The return value, if it is integral

  • (Boolean)

    The return value, if it is boolean

  • (nil)

    if the return value is not compile-time-known

Raises:

  • ValueError if, during evaluation, a node without a compile-time value is found



8673
8674
8675
8676
8677
8678
8679
8680
8681
8682
8683
8684
8685
8686
8687
8688
8689
8690
8691
8692
8693
8694
8695
8696
8697
8698
8699
8700
8701
8702
8703
# File 'lib/idlc/ast.rb', line 8673

def return_value(symtab)
  symtab.push(self)

  begin
    value_result = value_try do
      init.execute(symtab)

      while condition.value(symtab)
        stmts.each do |s|
          if s.is_a?(Returns)
            v = s.return_value(symtab)
            unless v.nil?
              return v
            end
          else
            unless s.is_a?(ImplicationStatementAst)
              s.execute(symtab)
            end
          end
        end
        update.execute(symtab)
      end
    end
    value_else(value_result) do
      value_error ""
    end
  ensure
    symtab.pop
  end
  nil
end

#return_values(symtab) ⇒ Array<Integer>, Array<Boolean>

Evaluate all possible compile-time return values of this node, or, if the node does not return (e.g., because it is an IfAst but there is no return on a possible path), execute the node and update the symtab

Parameters:

  • symtab (SymbolTable)

    The symbol table for the context

Returns:

  • (Array<Integer>)

    The possible return values. Will be an empty array if there are no return values

  • (Array<Boolean>)

    The possible return values. Will be an empty array if there are no return values

Raises:

  • ValueError if, during evaluation, a node without a compile-time value is found



8712
8713
8714
8715
8716
8717
8718
8719
8720
8721
8722
8723
8724
8725
8726
8727
8728
8729
8730
8731
8732
8733
8734
8735
8736
8737
8738
8739
8740
8741
8742
8743
8744
8745
8746
8747
8748
8749
8750
8751
8752
8753
8754
# File 'lib/idlc/ast.rb', line 8712

def return_values(symtab)
  value_result = value_try do
    # if there is a known return value, then we are done
    return [return_value(symtab)]
  end
  value_else(value_result) do
    # see if we can collect a list
    values = T.let([], T::Array[ValueRbType])
    symtab.push(self)

    begin
      value_result = value_try do
        init.execute(symtab)

        while condition.value(symtab)
          stmts.each do |s|
            if s.is_a?(Returns)
              value_result = value_try do
                v = s.return_value(symtab)
                unless v.nil?
                  return values.push(v).uniq
                end
              end
              value_else(value_result) do
                values += s.return_values(symtab)
              end
            else
              unless s.is_a?(ImplicationStatementAst)
                s.execute(symtab)
              end
            end
          end
          update.execute(symtab)
        end
        :ok
      end
    ensure
      symtab.pop
    end

    values.uniq
  end
end

#satisfied?(symtab) ⇒ Boolean

Returns:

  • (Boolean)


8656
8657
8658
8659
8660
8661
8662
8663
8664
8665
8666
8667
8668
8669
8670
# File 'lib/idlc/ast.rb', line 8656

def satisfied?(symtab)
  symtab.push(self)
  begin
    init.execute(symtab)
    while condition.value(symtab)
      stmts.each do |s|
        return false unless T.cast(s, ImplicationStatementAst).satisfied?(symtab)
      end
      update.execute(symtab)
    end
    return true
  ensure
    symtab.pop
  end
end

#stmtsObject



8637
# File 'lib/idlc/ast.rb', line 8637

def stmts = T.cast(@children[3..], T::Array[StmtType])

#to_hObject



8771
8772
8773
8774
8775
8776
8777
8778
# File 'lib/idlc/ast.rb', line 8771

def to_h = {
  "kind" => "for_loop_stmt",
  "init_expr" => init.to_h,
  "condition_expr" => condition.to_h,
  "update_expr" => update.to_h,
  "body" => stmts.map(&:to_h),
  "source" => source_yaml
}

#to_idlObject



8761
8762
8763
8764
8765
8766
8767
8768
# File 'lib/idlc/ast.rb', line 8761

def to_idl
  idl = "for (#{init.to_idl}; #{condition.to_idl}; #{update.to_idl}) {"
  stmts.each do |s|
    idl << s.to_idl
  end
  idl << "}"
  idl
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

Parameters:

Raises:



8644
8645
8646
8647
8648
8649
8650
8651
8652
8653
# File 'lib/idlc/ast.rb', line 8644

def type_check(symtab, strict:)
  symtab.push(self)
  init.type_check(symtab, strict:)
  condition.type_check(symtab, strict:)
  update.type_check(symtab, strict:)

  stmts.each { |stmt| stmt.type_check(symtab, strict:) }

  symtab.pop
end

#updateObject



8633
# File 'lib/idlc/ast.rb', line 8633

def update = T.cast(@children.fetch(2), ExecutableAst)