module BinData
# The enviroment in which a lazily evaluated lamba is called. These lambdas
# are those that are passed to data objects as parameters. Each lambda
# has access to the following:
#
# parent:: the environment of the parent data object
# params:: any extra parameters that have been passed to the data object.
# The value of a parameter is either a lambda, a symbol or a
# literal value (such as a Fixnum).
# value:: the value of the data object if it is single
# index:: the index of the data object if it is in an array
# offset:: the current offset of the IO object when reading
#
# Unknown methods are resolved in the context of the parent data object,
# first as keys in the extra parameters, and secondly as methods in the
# parent data object. This makes the lambda easier to read as we just write
# field instead of obj.field.
class LazyEvalEnv
# Creates a new environment. +parent+ is the environment of the
# parent data object.
def initialize(parent = nil)
@parent = parent
end
attr_reader :parent
attr_accessor :data_object, :params, :index, :offset
# only accessible by another LazyEvalEnv
protected :data_object
# TODO: offset_of needs to be better thought out
def offset_of(sym)
if @parent and @parent.data_object and
@parent.data_object.respond_to?(:offset_of)
@parent.data_object.offset_of(sym)
else
nil
end
end
# Returns the data_object for the parent environment.
def parent_data_object
@parent.nil? ? nil : @parent.data_object
end
# Returns the value of the data object wrapped by this environment.
def value
@data_object.respond_to?(:value) ? @data_object.value : nil
end
# Evaluates +obj+ in the context of this environment. Evaluation
# recurses until it yields a value that is not a symbol or lambda.
def lazy_eval(obj)
if obj.is_a? Symbol
# treat :foo as lambda { foo }
lazy_eval(__send__(obj))
elsif obj.respond_to? :arity
instance_eval(&obj)
else
obj
end
end
def method_missing(symbol, *args)
if @parent and @parent.params and @parent.params.has_key?(symbol)
# is there a param with this name?
@parent.lazy_eval(@parent.params[symbol])
elsif @parent and @parent.data_object and
@parent.data_object.respond_to?(symbol)
# how about a field or method in the parent?
@parent.data_object.__send__(symbol, *args)
else
super
end
end
end
end