Check if a block includes a method without running it in Ruby
I am writing an internal DSL in Ruby. Currently, the basic structure in this mini language is a block that must include the method during
, and optionally some other methods. E.g.:
do
something...
during do ... end
something else ...
end
I would like to simplify the language by treating blocks differently if they do not include the during
keyword. In this case I can just treat them like any other Ruby block.
Is it possible to detect if a block includes a specific method without running it? It is ok to assume that the method is not nested within an inner scope, like an if
or while
statement. I assume that if possible, this will involve reflection, which is ok.
Refinement and explanations
I am not trying to solve the halting problem or overcome any trick that a Ruby hacker might think of. The solution may apply only to the most naive case.
In several languages it is possible to get some reflection on the source code at runtime. In Python method objects have fields like bindings and bytecode. In Java,开发者_运维问答 it is possible, although not trivial, to read the bytecode of a method and look for invokevirtual
commands.
I am not very familiar with Ruby. I don't know if Ruby objects that represent control elements, like procs and blocks, have similar fields. If my memory serves, Ruby has some framework for accessing its AST on runtime. However, I don't know if and how this framework can access specific blocks. A solution based on runtime AST parsing that works only for simple cases is perfectly acceptable.
Spontaneously the only way that comes to mind is using the to_source
method from the sourcify
gem and then matching against the resulting string:
https://github.com/ngty/sourcify
Abit late to this though. Anyway, sourcify's Proc#to_source supports passing in a body matcher to help u get what u want:
x = proc do
something...
during do ... end
something else ...
end
x.to_source {|body| body =~ /\bduring\b\s*(\{|do)/m }
With this, u can avoid traversing the sexp. However, whatever is returned by Proc#to_sexp & Proc#to_source, has been normalized to be ParseTree compatible, which may or may not be what u want. If u really want to grab the source (as it is originally written, eg. retaining the comment), u can try:
x.to_raw_source {|body| body =~ /\bduring\b\s*(\{|do)/m }
If u have 1.9 specific code, sourcify may break, as under the hood, it is using RubyParser to do some syntax checking, & RubyParser isn't 100% compatible w 1.9 syntax yet. However, there is on-going work to fix this issue.
Hope the above helps :]
IMO the best way for you is to do so from within the method during itself and not try anything extremely complicated unnecessarily. Although that does depend on whether you define the method or not, but you could still work out a way as to override the same if needed.
精彩评论