Class: Idl::FunctionBodyAst

Inherits:
AstNode
  • Object
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

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

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, #reachable_exceptions, #reachable_functions, #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, stmts) ⇒ FunctionBodyAst

Returns a new instance of FunctionBodyAst.



8095
8096
8097
# File 'lib/idlc/ast.rb', line 8095

def initialize(input, interval, stmts)
  super(input, interval, stmts)
end

Class Method Details

.from_h(yaml, source_mapper) ⇒ Object



8203
8204
8205
8206
8207
8208
8209
8210
8211
8212
# File 'lib/idlc/ast.rb', line 8203

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

  input = input_from_source_yaml(yaml.fetch("source"), source_mapper)
  interval = interval_from_source_yaml(yaml.fetch("source"))
  FunctionBodyAst.new(
    input, interval,
    yaml.fetch("stmts").map { |s| AstNode.from_h(s, source_mapper) }
  )
end

Instance Method Details

#const_eval?(symtab) ⇒ Boolean

Returns:

  • (Boolean)


8088
8089
8090
8091
8092
# File 'lib/idlc/ast.rb', line 8088

def const_eval?(symtab)
  stmts.all? do |stmt|
    stmt.const_eval?(symtab)
  end
end

#execute(symtab) ⇒ Object



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

def execute(symtab) = return_value(symtab)

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



313
314
315
# File 'lib/idlc/passes/gen_adoc.rb', line 313

def gen_adoc(indent = 0, indent_spaces: 2)
  statements.map { |s| "#{' ' * indent}#{s.gen_adoc(0, indent_spaces:)}" }.join("\n")
end

#gen_option_adocObject



22
23
24
# File 'lib/idlc/passes/gen_option_adoc.rb', line 22

def gen_option_adoc
  statements.map(&:gen_option_adoc).join("\n")
end

#pass_find_return_values(symtab) ⇒ Array<Ast, Array<Ast>>

Returns List of possible return values, along with the condition it occurs under.

Returns:

  • (Array<Ast, Array<Ast>>)

    List of possible return values, along with the condition it occurs under



67
68
69
70
71
72
73
74
# File 'lib/idlc/passes/find_return_values.rb', line 67

def pass_find_return_values(symtab)
  values = []
  current_conditions = []
  statements.each do |s|
    s.pass_find_return_values(values, current_conditions, symtab)
  end
  values
end

#prune(symtab, forced_type: nil, args_already_applied: false) ⇒ Object



314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
# File 'lib/idlc/passes/prune.rb', line 314

def prune(symtab, forced_type: nil, args_already_applied: false)
  symtab.push(self)

  begin
    func_def = find_ancestor(FunctionDefAst)
    unless args_already_applied || func_def.nil?

      # push args
      func_def.arguments(symtab).each do |arg_type, arg_name|
        symtab.add(arg_name, Var.new(arg_name, arg_type))
      end
    end

    pruned_body = nil
    prune_stmts = -> {
      [].tap do |out|
        statements.each do |s|
          out << s.prune(symtab)
          break if out.last.always_terminates?
        end
      end
    }

    value_result = value_try do
      # go through the statements, and stop if we find one that returns or raises an exception
      statements.each_with_index do |s, idx|
        if s.is_a?(ReturnStatementAst)
          pruned_body = FunctionBodyAst.new(input, interval, statements[0..idx].map { |s| s.prune(symtab) })
          return pruned_body
        elsif s.is_a?(ConditionalReturnStatementAst)
          value_try do
            v = s.return_value(symtab)

            # conditional return, condition not taken if v.nil?
            unless v.nil?
              pruned_body = FunctionBodyAst.new(input, interval, statements[0..idx].map { |s| s.prune(symtab) })
              return pruned_body
            end
          end
          # || conditional return, condition not known; keep going
        elsif s.is_a?(StatementAst) && s.action.is_a?(FunctionCallExpressionAst) && s.action.name == "raise"
          pruned_body = FunctionBodyAst.new(input, interval, statements[0..idx].map { |s| s.prune(symtab) })
          return pruned_body
        else
          s.execute(symtab)
        end
      end

      pruned_body = FunctionBodyAst.new(input, interval, prune_stmts.())
    end
    value_else(value_result) do
      pruned_body = FunctionBodyAst.new(input, interval, prune_stmts.())
    end
  ensure
    symtab.pop
  end

  pruned_body
end

#return_type(symtab) ⇒ Object



8127
8128
8129
8130
8131
8132
8133
8134
8135
8136
8137
# File 'lib/idlc/ast.rb', line 8127

def return_type(symtab)
  # go through the statements, and return the first one that has a return type
  stmts.each do |s|
    if s.is_a?(Returns)
      return s.return_type(symtab)
    elsif s.action.declaration?
      s.action.add_symbol(symtab)
    end
  end
  VoidType
end

#return_value(symtab) ⇒ Integer, ...

Note:

arguments must be put on the symtab before calling

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



8142
8143
8144
8145
8146
8147
8148
8149
8150
8151
8152
8153
8154
8155
8156
# File 'lib/idlc/ast.rb', line 8142

def return_value(symtab)
  internal_error "Function bodies should be at global + 1 scope" unless symtab.levels == 2

  # go through the statements, and return the first one that has a return value
  stmts.each do |s|
    if s.is_a?(Returns)
      v = s.return_value(symtab)
      return v unless v.nil?
    else
      s.execute(symtab)
    end
  end

  value_error "No function body statement returned a value"
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



8161
8162
8163
8164
8165
8166
8167
8168
8169
8170
8171
8172
8173
8174
8175
8176
8177
8178
8179
8180
8181
8182
8183
8184
8185
8186
8187
8188
# File 'lib/idlc/ast.rb', line 8161

def return_values(symtab)
  internal_error "Function bodies should be at global + 1 scope" unless symtab.levels == 2

  values = T.let([], T::Array[ValueRbType])
  value_result = value_try do
    # if there is a definite return value, then just return that
    return [return_value(symtab)]
  end
  value_else(value_result) do
    # go through the statements, and collect return values
    # we can stop if we encounter a statement with a known return value
    stmts.each do |s|
      if s.is_a?(Returns)
        value_result = value_try do
          v = s.return_value(symtab)
          return values.push(v).uniq unless v.nil?
        end
        value_else(value_result) do
          values += s.return_values(symtab)
        end
      else
        s.execute(symtab)
      end
    end
  end

  values.uniq
end

#statementsObject



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

def statements = @children

#stmtsObject



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

def stmts = @children

#to_hObject



8196
8197
8198
8199
8200
# File 'lib/idlc/ast.rb', line 8196

def to_h = {
  "kind" => "function_body",
  "stmts" => stmts.map(&:to_h),
  "source" => source_yaml
}

#to_idlObject



8191
8192
8193
# File 'lib/idlc/ast.rb', line 8191

def to_idl
  stmts.map(&:to_idl).join("\n")
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:



8104
8105
8106
8107
8108
8109
8110
8111
8112
8113
8114
8115
8116
8117
8118
8119
8120
8121
8122
8123
8124
8125
# File 'lib/idlc/ast.rb', line 8104

def type_check(symtab, strict:)
  internal_error "Function bodies should be at global + 1 scope (at #{symtab.levels})" unless symtab.levels == 2

  return_value_might_be_known = true

  stmts.each do |s|
    s.type_check(symtab, strict:)
    # next unless return_value_might_be_known

    # begin
    #   if s.is_a?(Returns)
    #     s.return_value(symtab)
    #     # if we reach here, the return value is known, so we don't have to go further
    #     break
    #   else
    #     s.execute(symtab)
    #   end
    # rescue ValueError
    #   return_value_might_be_known = false
    # end
  end
end