I recently started playing around with parsers and parser generators and their uses in DSL design. To get things started, and to kill two birds with one stone, I wrote a pure Ruby PEG parser DSL by stealing some ideas from peg.js. The difference is that peg.js will compile the grammar to JavaScript whereas my library uses the interpreter pattern combined with some syntax sugar that Ruby provides to do everything in pure Ruby. This adds some non-trivial overhead that I would like to avoid.
To reduce some of the overhead I started thinking about compiling some of the parsing expressions that are generated into lower level representation. One idea I had was to use eval
to evaluate the string representation of the code within the singleton class of some object. Here's some pseudo-code to demonstrate the process:
# will be used to pass CompiledExpression instance to `eval`
def get_binding(instance)
instance.instance_eval { binding }
end
# an instance of this class will be used with `eval`
# to define an `execute` method
class CompiledExpression
attr_reader :code_repr
# need to instantiate with a string representation of
# the code we are going to use to define the `execute` method
def initialize(code)
@code_repr = code
end
end
# create the instance and define `execute` for that instance
# by evaluating the code representation
compiled_expr = CompiledExpression.new
# first way
eval "class << self; def execute; " +
"#{compiled_expr.code_repr}; end; end", get_binding(compiled_expr)
# second way
compiled_expr.instance_eval "class << self; " +
"def execute; #{compiled_expr.code_repr}: end; end"
# third way
compiled_expr.singleton_class.class_eval "def execute; " +
"#{compiled_expr.code_repr}; end"
# fourth way
compiled_expr.instance_eval "def execute; " +
"#{compiled_expr.code_repr}; end"
I want to know whether there are other/better approaches to accomplishing such code generation? I'm new to this stuff so I'm probably missing something obvious.