Runtime for Your Life

Eh? is coming along as a functioning interpreted language, but we are still missing a runtime. We have a lexer that chops and tags our code string into tokens, and a grammar that contains the rules for order of operations, and a parser that is generated by RACC from the grammar. The parser assembles the tokens into an evaluation tree (AST), but we need a runtime to do the actual evaluation.

Recall from my previous post that the AST consists of nodes, like this one for a method definition:

class DefNode
  def initialize(name, params, body)
    @name = name
    @params = params
    @body = body
  end

  def eval(context)
    context.current_class.can_methods[@name] = CanMethod.new(@params, @body)
  end
end

When a method definition node is evaluated it adds a new entry to the can_methods hash. The name of the method is its key, and the value is an object (CanMethod.new) that contains the details of method evaluation (parameters and the method body). The context is the scope of the evaluation – in the case of a method call, the context is the enclosing class.

Ok, so when does the CanMethod get evaluated? It happens at runtime. A class node is built and all the method definitions are added, but the node tree will remain untouched until until a method is called.

class CanMethod
  def initialize(params, body)
    @params = params
    @body = body
  end

  def call(receiver, arguments)
    @body.eval(Context.new(receiver))
  end
end

When your program calls a method, a portion of the AST is evaluated. The body of the method object is called with the provided parameters in the context of the method receiver. For ruby newbies, the receiver is the object on the left side of the method call — ie. in foo.bar(3), foo is the receiver and bar is called with the parameter of 3 in the context of foo. The runtime will create a new context for the method (recall that ruby is block scoped, meaning that all variables and blocks within the method are confined to that method… mostly).

class Context
  attr_reader :locals, :current_self, :current_class

  @@constants = {}

  def initialize(current_self, current_class=current_self.can_class)
    @locals = {}
    @current_self = current_self
    @current_class = current_class
  end
end

The context contains all the local variables for the scope as well as a reference to what object is self, and which is super. We are getting very close to putting all these pieces together!

Awesome