Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Ruby: evaluate string with dynamic binding of variables

Tags:

ruby

I have a database of "formulas" stored as strings. Let's assume for simplicity, that each formula contains 2 variables denoted by a and b, and that the formulas are all wellformed and it is ensured that it consists only of characters from the set ()ab+-*.

At runtime, formulas are fetched from this database, and from another source, numeric values for a and b are fetched, and the formulas are evaluated. The evaluation can be programmed like this:

# This is how it works right now
formula = fetch_formula(....)
a = fetch_left_arg(....)
b = fetch_right_arg(....)
result = eval(formula)

This design works, but I'm not entirely happy with it. It requires that my program names the free variables exactly the same as they are named in the formula, which is ugly.

If my "formula" would not be a string, but a Proc object or Lambda which accepts two parameters, I could do something like

# No explicitly named variables
result = fetch_proc(...).call(fetch_left_arg(....),fetch_right_arg(....))

but unfortunately, the formulas have to be strings.

I tried to experiment in the following way: What if the method, which fetches the formula from the database, would wrap the string into something, which behaves like a block, and where I could pass parameters to it?

# This does not work of course, but maybe you get the idea:
block_string = "|a,b| #{fetch_formula(....)}"

Of course I can't eval such a block_string, but is there something similar which I could use? I know that instance_eval can pass parameters, but what object should I apply it to? So this is perhaps not an option either....

like image 804
user1934428 Avatar asked Mar 13 '23 08:03

user1934428


2 Answers

This is very nasty approach, but for simple formulas you’ve mentioned it should work:

▶ formula = 'a + b'
▶ vars = formula.scan(/[a-z]+/).uniq.join(',')  # getting vars names
#⇒ "a,b"
▶ pr = eval("proc { |#{vars}| #{formula} }") # preparing proc
▶ pr.call 3, 5
#⇒ 8

Here we rely on the fact, that parameters are passed to the proc in the same order, as they appear in the formula.

like image 66
Aleksei Matiushkin Avatar answered Apr 27 '23 15:04

Aleksei Matiushkin


If I get your question correctly, it is something that I have done recently, and is fairly easy. Given a string:

s = "{|x, y| x + y}"

You can create a proc by doing:

eval("Proc.new#{s}")
like image 30
sawa Avatar answered Apr 27 '23 15:04

sawa