views:

235

answers:

2

I have a string (authenticated, trusted, etc.) containing source code intended to run within a Ruby loop, quickly. In Python, I would compile the string into an abstract syntax tree and eval() or exec() it later:

# Python 3 example
given_code = 'n % 2 == 1'
pred = compile(given_code, '<given>', 'eval')
print("Passed:", [n for n in range(10) if eval(pred)])    
# Outputs: Passing members: [1, 3, 5, 7, 9]

Ruby does not have a compile function, so what is the best way to achieve this?

+1  A: 

I wrap the whole string in a lambda (still as a string), eval that, and then call the resultant Proc object.

# XXX: Only runs on Ruby 1.8.7 and up.
given_code = 'n % 2 == 1'
pred = eval "lambda { |n| #{given_code} }"
puts 1.upto(10).select { |x| pred.call(x) } .inspect # Or (1..10).select for Ruby <= 1.8.6
# Output: [1, 3, 5, 7, 9]
jhs
Just curious if others have a better idea or if this is the preferred idiom.
jhs
does not compile, `Integer#upto` expects a block (it does not return an enumerable)
Adrian
@Adrian I have confirmed this code on Ruby 1.8 and 1.9 on Linux. Please clarify and/or reconsider the downvote. Thanks.
jhs
@Adrian I do however prefer your more succinct solution.
jhs
`1.upto(10).select { |x| }` fails in ruby 1.8.6 with "no block given" are you sure you don't have any rails magic in your ruby? (in any case, I can only remove the downvote when you edit your post).
Adrian
It works on 1.8.7. Glancing at the 1.8.7 changelog http://svn.ruby-lang.org/repos/ruby/tags/v1_8_7/ChangeLog I see what looks like the culprit from `Mon Apr 14 17:55:30 2008`. If that's true, a downvote is fine. 1.8.6 is a pretty widespread release, isn't it?
jhs
d'vote removed, 1.8.6 ships with OSX leopard, that's how I got it.
Adrian
+4  A: 

Based on the solution of jhs, but directly using the lambda as the loop body (the & calls to_proc on the lambda and passes it as block to the select function).

given_code = 'n % 2 == 1'
pred = eval "lambda { |n| #{given_code} }"
p all = (1..10).select(&pred)
Adrian
This is cleaner than the way I was doing it. Thanks! One thing I would add that is out of scope of this question: the creator of `given_code` might want to assume responsibility for the formal parameter list. For example, he should supply `|n| n % 2 == 1` or even `{ |n| n % 2 == 1 }`.
jhs
Ruby fails if the number of passed arguments does not match the number of expected arguments. Thus, you might want to check the string when you receive if from the creator.
Adrian
Good point. In my specific case an admin (probably me mostly) sets the predicate for whether a log entry is interesting; so I can document the parameter convention. The main thing is that it be reasonably fast as it is processing logs. I'm not ready to port to C or anything but whatever low-hanging fruit I can get in Ruby, I'll take.
jhs