tags:

views:

119

answers:

3

I want to write a simple Ruby DSL to translate some statements and expressions into another language. A basic example would be:

some_function {
    t + 2
}

Here, t is not a ruby variable and thus the block can't (and must not!) be evaluated by Ruby. So my best bet would be to use the parsing output (or AST) to do the translation myself. To do so, I can use ParseTree and ruby2ruby. However, I have other constructs that I would like to use, for example:

1.upto(10) {|x|
    some_function {
        t + x
    }
}

Here, I have a local variable and I need to get its value in order to do my translation. However, in the function that does the block evaluation, I don't have access to the local variables of the calling block. If it were a global variable ($x), I could check if its name exists in the global_variables array and in the worst case use eval, but how could I do so for a local variable, if possible at all?

Update:

Just to clear up things. Like I said originally, I'm using ruby2ruby (and hence ParseTree) to get the AST (using to_sexp) corresponding to the block. But when using a local variable inside my block, I encounter the following:

[:dvar, :x]

And thus, I would need to get the value of a variable from its name as a string/symbol. And I can't use method_missing or instance_eval, because I want to translate the whole expression to another language or syntax (like a RPN).

Another solution not based on ParseTree would be welcome nonetheless, since it apparently is not fully supported with Ruby 1.9.

A: 

Here, t is not a ruby variable and thus the block can't (and must not!) be evaluated by Ruby.

Any reason for the "not evaluated" restriction? It seems like method_missing would elegantly handle evaluating the "missing" t variable, but Ruby would automatically dereference the x variable.

flicken
For such a simple example, method_missing may fit, but I need to translate the whole expression for some more complex cases like: some_function { if t < 12 0 else t end }And there, I need to also translate the if statement.
Sebastien Tanguy
Makes sense. Instead, use Kernel#bindings to dereference any variable. I'll post another answer with details.
flicken
Nevermind, Justin Love beat me to it.
flicken
A: 

You can use instance_eval against an object with t.

class Context
  attr_accessor :t

  def initialize(_t)
    @t = _t
  end
end

def some_function(&block)
  puts Context.new(1).instance_eval(&block)
end

1.upto(10) {|x|
  some_function {
    t + x
  }
}
Justin Love
Like method_missing, this would be a solution if I wanted to let Ruby evaluate the expression, but I really want to translate the expression to have another tool evaluate it.
Sebastien Tanguy
+1  A: 

To get the variable values, use the proc's binding:

def some_function(&block)
  b = block.binding
  p [b.eval("t"), b.eval("x")]
end

t = 1
1.upto(10) {|x|
  some_function {
    t + x
  }
}
Justin Love
The OP also wants unbound variables, like 't' in the original example, so you'd have to catch NameError exceptions.
flicken
Since I cross the AST to handle the ops and variables, I can filter out of the eval() the variables that are unbound on the ruby side but that would have been declared previously by the DSL.
Sebastien Tanguy