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.



8624
8625
8626
# File 'lib/idlc/ast.rb', line 8624

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



8766
8767
8768
8769
8770
8771
8772
8773
8774
8775
8776
8777
8778
# File 'lib/idlc/ast.rb', line 8766

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



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

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

#const_eval?(symtab) ⇒ Boolean

Returns:

  • (Boolean)


8604
8605
8606
8607
8608
8609
# File 'lib/idlc/ast.rb', line 8604

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



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

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



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

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



8691
8692
8693
8694
# File 'lib/idlc/ast.rb', line 8691

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



8658
8659
8660
8661
8662
8663
8664
8665
8666
8667
8668
8669
8670
8671
8672
8673
8674
8675
8676
8677
8678
8679
8680
8681
8682
8683
8684
8685
8686
8687
8688
# File 'lib/idlc/ast.rb', line 8658

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



8697
8698
8699
8700
8701
8702
8703
8704
8705
8706
8707
8708
8709
8710
8711
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
# File 'lib/idlc/ast.rb', line 8697

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)


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

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



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

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

#to_hObject



8756
8757
8758
8759
8760
8761
8762
8763
# File 'lib/idlc/ast.rb', line 8756

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



8746
8747
8748
8749
8750
8751
8752
8753
# File 'lib/idlc/ast.rb', line 8746

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:



8629
8630
8631
8632
8633
8634
8635
8636
8637
8638
# File 'lib/idlc/ast.rb', line 8629

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



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

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