3

I need to accept an mathematical expression (including one or more unknowns) from the user and substitute values in for the unknowns to get a result.

I could use eval() to do this, but it's far too risky unless there is a way to recognise "safe" expressions.

I'd rather not write my own parser if I can help it.

I searched for a ready-made parser but the only one I found ( https://www.ruby-toolbox.com/gems/expression_parser , which seems to be the same as the one discussed at http://lukaszwrobel.pl/blog/math-parser-part-4-tests) seems to be limited to the "four rules" +-*/. I need to include exponential, log and trig functions at the very least.

Any suggestions?

UPDATE: EXAMPLE

include Math

def exp(x)
 Math.exp(x)
end

def cos(x)
 Math.cos(x)
end

pi=Math::PI
t=2
string= '(3*exp(t/2)*cos(3*t-pi/2))'
puts eval(string)

UPDATE - a pre-parsing validation step

I think I will use this regex to check the string has the right kinds of tokens in it:

/((((cos\(|sin\()|(tan\(|acos\())|((asin\(|atan\()|(exp\(|log\())|ln\()(([+-\/*^\(\)A-Z]|\d)+))*([+-\/*^\(\)A-Z]|\d)+/

But I will still implement the parsing method during the actual evaluation.

Thanks for the help!

1
  • Hi. I am working on a gem to provide the capability you need. Would you be interested in helping me test/ refine the gem. Thanks. Commented Jun 11, 2012 at 3:03

3 Answers 3

2

You can checkout the Dentaku gem - https://github.com/rubysolo/dentaku

You can use it to execute the user given formula.

Here is an example usage of this gem.

class FormulaExecutor
  def execute_my_formula(formula, params)
    calc = Dentaku::Calculator.new

    # Param 1 => formula to execute
    # Param 2 => Hash of local variables
    calc.evaluate(formula, params)
  end
end

FormulaExecutor.new.execute_my_formula( "length * breadth", {'length' => 11, 'breadth' => 120} )
Sign up to request clarification or add additional context in comments.

1 Comment

I've just come across this and is perfect for my needs as well! Thanks!
0

If eval would work, then you could parse the expression using a ruby parser (eg gem install ruby_parser), and then evaluate the S expression recursively, either ignoring or raising an error on non-arithmetic functions. This probably needs some work, but sounded like fun:

require 'ruby_parser'

def evaluate_arithmetic_expression(expr)
  parse_tree = RubyParser.new.parse(expr)  # Sexp < Array
  return evaluate_parse_tree(parse_tree)
end

def evaluate_parse_tree(parse_tree)
  case parse_tree[0]
  when :lit
    return parse_tree[1]
  when :call
    meth = parse_tree[2]
    if [:+, :*, :-, :/, :&, :|, :**].include? meth
      val = evaluate_parse_tree parse_tree[1]
      arglist = evaluate_parse_tree parse_tree[3]
      return val.send(meth, *arglist)
    else
      throw 'Unsafe'
    end
  when :arglist
    args = parse_tree[1..-1].map {|sexp| evaluate_parse_tree(sexp) }
    return args
  end
end

You should be able to enhance this to include things like cos, sin, etc. pretty easily. It works for some simple examples I tried, and and includes a free check for well-formedness (parsing raises a Racc::ParseError exception if not).

1 Comment

Many thanks! This looks very promising. I had not played with ruby_parser, but now I will!
0

Start with the assumption that eval doesn't exist unless you have a very tight grip on the evaluated content. Even if you don't parse, you could split all input into tokens and check that each is an acceptable token.

Here is a very crude way to check that input has nothing other than valid tokens. Lots of refactoring/ improvments possible.

include Math

def exp(x)
 Math.exp(x)
end

def cos(x)
 Math.cos(x)
end

pi=Math::PI
t=2

a = %Q(3*exp(t/2)*cos(3*t-pi/2))  # input string

b = a.tr("/*)([0-9]-",'')  # remove all special single chars
b.gsub!(/(exp|cos|pi|t)/,'')  # remove all good tokens

eval(a) if b == ''  # eval if nothing other than good tokens.

5 Comments

say 3*exp(t/2)*cos(3*t-pi/2).
Thanks for the "split into tokens" idea - it may not be too hard to make a recursive test for algebraically_well_formed?
@user1440995 Please post a working example of eval for this code including require and t so that I can test my recommendation.
def exp(x) Math.exp(x) end def cos(x) Math.cos(x) end pi=Math::PI t=2 string= '(3*exp(t/2)*cos(3*t-pi/2))' puts eval(string)
I just realized that you wouldn't know about t beforehand. If you tokenized, then you could verify that it's class was some number. Good luck!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.