class Yoda::Typing::Evaluator

Evaluator interpret codes abstractly and assumes types of terms.

Attributes

context[R]

@return [Context]

Public Class Methods

new(context) click to toggle source

@param context [Context]

# File lib/yoda/typing/evaluator.rb, line 9
def initialize(context)
  @context = context
end

Public Instance Methods

bind_trace(node, trace) click to toggle source

@param node [::AST::Node] @param trace [Trace::Base]

# File lib/yoda/typing/evaluator.rb, line 21
def bind_trace(node, trace)
  context.bind_trace(node, trace)
end
find_trace(node) click to toggle source

@param node [::AST::Node] @return [Trace::Base, nil]

# File lib/yoda/typing/evaluator.rb, line 27
def find_trace(node)
  context.find_trace(node)
end
process(node) click to toggle source

@param node [::AST::Node] @return [Model::Types::Base]

# File lib/yoda/typing/evaluator.rb, line 15
def process(node)
  evaluate(node).tap { |type| bind_trace(node, Traces::Normal.new(context, type)) unless find_trace(node) }
end

Private Instance Methods

boolean_type() click to toggle source
# File lib/yoda/typing/evaluator.rb, line 232
def boolean_type
  Model::Types::UnionType.new(Model::Types::ValueType.new('true'), Model::Types::ValueType.new('false'))
end
env() click to toggle source

@return [Environment]

# File lib/yoda/typing/evaluator.rb, line 245
def env
  context.env
end
evaluate(node) click to toggle source

@param node [::AST::Node] @return [Model::Types::Base]

# File lib/yoda/typing/evaluator.rb, line 35
def evaluate(node)
  case node.type
  when :lvasgn, :ivasgn, :cvasgn, :gvasgn
    evaluate_bind(node.children[0], process(node.children[1]))
  when :casgn
    # TODO
    process(node.children.last)
  when :masgn
    # TODO
    process(node.children.last)
  when :op_asgn, :or_asgn, :and_asgn
    # TODO
    process(node.children.last)
  when :and, :or, :not
    # TODO
    node.children.reduce(unknown_type) { |_type, node| process(node) }
  when :if
    evaluate_branch_nodes(node.children.slice(1..2).compact)
  when :while, :until, :while_post, :until_post
    # TODO
    process(node.children[1])
  when :for
    # TODO
    process(node.children[2])
  when :case
    evaluate_case_node(node)
  when :super, :zsuper, :yield
    # TODO
    type_for_sexp_type(node.type)
  when :return, :break, :next
    # TODO
    node.children[0] ? process(node.children[0]) : Model::Types::ValueType.new('nil')
  when :resbody
    # TODO
    process(node.children[2])
  when :csend, :send
    evaluate_send_node(node)
  when :block
    evaluate_block_node(node)
  when :const
    const_node = Parsing::NodeObjects::ConstNode.new(node)
    if const = context.lexical_scope.find_constant(context.registry, const_node.to_s)
      Model::Types::ModuleType.new(const.path)
    else
      unknown_type
    end
  when :lvar, :cvar, :ivar, :gvar
    env.resolve(node.children.first) || unknown_type
  when :begin, :kwbegin, :block
    node.children.reduce(unknown_type) { |_type, node| process(node) }
  when :dstr, :dsym, :xstr
    node.children.map { |node| process(node) }
    type_for_sexp_type(node.type)
  when :def
    evaluate_method_definition(node)
  when :defs
    evaluate_smethod_definition(node)
  when :hash
    evaluate_hash_node(node)
  else
    type_for_sexp_type(node.type)
  end
end
evaluate_bind(symbol, type) click to toggle source

@param node [::AST::Node] @param type [Model::Types::Base] @param env [Environment] @return [Model::Types::Base]

# File lib/yoda/typing/evaluator.rb, line 166
def evaluate_bind(symbol, type)
  env.bind(symbol, type)
  type
end
evaluate_block_node(node) click to toggle source

@param node [::AST::Node] @param env [Environment] @return [[Model::Types::Baseironment]]

# File lib/yoda/typing/evaluator.rb, line 145
def evaluate_block_node(node)
  send_node, arguments_node, body_node = node.children
  # TODO
  _type = process(body_node)
  process(send_node)
end
evaluate_branch_nodes(nodes) click to toggle source

@param node [Array<::AST::Node>] @return [Model::Types::Base]

# File lib/yoda/typing/evaluator.rb, line 101
def evaluate_branch_nodes(nodes)
  Model::Types::UnionType.new(nodes.map { |node| process(node) })
end
evaluate_case_node(node) click to toggle source

@param node [::AST::Node] @param env [Environment] @return [[Model::Types::Baseironment]]

# File lib/yoda/typing/evaluator.rb, line 155
def evaluate_case_node(node)
  # TODO
  subject_node, *when_nodes, else_node = node.children
  _when_types = when_nodes.map { |node| node.children.last && process(node.children.last) }.compact
  process(else_node)
end
evaluate_hash_node(node) click to toggle source

@param node [Array<::AST::Node>] @return [Model::Types::Base]

# File lib/yoda/typing/evaluator.rb, line 130
def evaluate_hash_node(node)
  node.children.each do |node|
    case node.type
    when :pair
      node.children.each(&method(:process))
    when :kwsplat
      node.children.each(&method(:process))
    end
  end
  Model::Types::InstanceType.new('::Hash')
end
evaluate_method_definition(node) click to toggle source

@param node [::AST::Node] @return [Model::Types::Base]

# File lib/yoda/typing/evaluator.rb, line 199
def evaluate_method_definition(node)
  new_caller_object = context.lexical_scope.namespace
  method_object = Store::Query::FindSignature.new(context.registry).select(new_caller_object, node.children[-3].to_s).first
  new_context = context.derive(caller_object: new_caller_object)
  new_context.env.bind_method_parameters(method_object) if method_object
  method_body_node = node.children[-1]
  method_body_node ? self.class.new(new_context).process(method_body_node) : nil_type
end
evaluate_send_node(node) click to toggle source

@param node [::AST::Node] @param env [Environment] @return [Model::Types::Base]

# File lib/yoda/typing/evaluator.rb, line 108
def evaluate_send_node(node)
  receiver_node, method_name_sym, *argument_nodes = node.children
  if receiver_node
    receiver_type = process(receiver_node)
    receiver_candidates = receiver_type.resolve(context.registry)
  else
    receiver_type = type_of_class(context.caller_object)
    receiver_candidates = [context.caller_object]
  end

  _type = argument_nodes.reduce([unknown_type]) { |(_type), node| process(node) }
  method_candidates = receiver_candidates.map { |receiver| Store::Query::FindSignature.new(context.registry).select(receiver, method_name_sym.to_s) }.flatten
  return_type = Model::Types::UnionType.new(method_candidates.map(&:type).map(&:return_type)).map do |type|
    type.is_a?(Model::Types::ValueType) && type.value == 'self' ? receiver_type : type
  end
  trace = Traces::Send.new(context, method_candidates, return_type)
  bind_trace(node, trace)
  trace.type
end
evaluate_smethod_definition(node) click to toggle source

@param node [::AST::Node] @return [Model::Types::Base]

# File lib/yoda/typing/evaluator.rb, line 210
def evaluate_smethod_definition(node)
  type = process(node.children[-4])
  new_caller_object = type.resolve(context.registry).first
  method_object = Store::Query::FindSignature.new(context.registry).select(new_caller_object, node.children[-3].to_s).first
  new_context = context.derive(caller_object: new_caller_object)
  new_context.env.bind_method_parameters(method_object) if method_object
  self.class.new(new_context).process(node.children[-1])
end
nil_type() click to toggle source
# File lib/yoda/typing/evaluator.rb, line 240
def nil_type
  Model::Types::ValueType.new('nil')
end
process_to_instanciate(node) click to toggle source

@param node [Array<::AST::Node>] @param env [Environment] @return [Array<Model::Values::Base>]

# File lib/yoda/typing/evaluator.rb, line 252
def process_to_instanciate(node)
  type = evaluate(node)
  bind_trace(node, Traces::Normal.new(context, type)) unless find_trace(node)
  trace.values
end
type_for_sexp_type(sexp_type) click to toggle source

@param sexp_type [::Symbol, nil]

# File lib/yoda/typing/evaluator.rb, line 172
def type_for_sexp_type(sexp_type)
  case sexp_type
  when :dstr, :str, :xstr, :string
    Model::Types::InstanceType.new('::String')
  when :dsym, :sym
    Model::Types::InstanceType.new('::Symbol')
  when :array, :splat
    Model::Types::InstanceType.new('::Array')
  when :irange, :erange
    Model::Types::InstanceType.new('::Range')
  when :regexp
    Model::Types::InstanceType.new('::RegExp')
  when :defined
    boolean_type
  when :self
    type_of_class(context.caller_object)
  when :true, :false, :nil
    Model::Types::ValueType.new(sexp_type.to_s)
  when :int, :float, :complex, :rational
    Model::Types::InstanceType.new('::Numeric')
  else
    Model::Types::UnknownType.new(sexp_type)
  end
end
type_of_class(object) click to toggle source

@param object [Store::Objects::Base] @return [Model::Types::Base]

# File lib/yoda/typing/evaluator.rb, line 221
def type_of_class(object)
  case object
  when Store::Objects::ClassObject, Store::Objects::ModuleObject
    Model::Types::InstanceType.new(object.path)
  when Store::Objects::MetaClassObject
    Model::Types::ModuleType.new(object.path)
  else
    Model::Types::UnknownType.new
  end
end
unknown_type() click to toggle source
# File lib/yoda/typing/evaluator.rb, line 236
def unknown_type
  Model::Types::UnknownType.new
end