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