在Rails中评估用户输入表达式

时间:2012-06-06 23:38:28

标签: ruby-on-rails parsing algebra

我需要接受来自用户的数学表达式(包括一个或多个未知数),并将值替换为未知数以获得结果。

我可以使用eval()来做到这一点,但除非有办法识别" safe"否则风险太大了。表达式。

如果我能提供帮助,我宁愿不写自己的解析器。

我搜索了一个现成的解析器,但我找到的唯一一个(https://www.ruby-toolbox.com/gems/expression_parser,似乎与http://lukaszwrobel.pl/blog/math-parser-part-4-tests中讨论的解析器相同)似乎仅限于&#34 ;四条规则" + - * /。我至少需要包括指数函数,日志函数和三角函数。

有什么建议吗?

更新:示例

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)

更新 - 预解析验证步骤

我想我会使用这个正则表达式检查字符串中是否有正确的标记:

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

但是我仍然会在实际评估过程中实现解析方法。

感谢您的帮助!

3 个答案:

答案 0 :(得分:1)

你可以查看Dentaku宝石 - https://github.com/rubysolo/dentaku

您可以使用它来执行给定公式的用户。

以下是此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} )

答案 1 :(得分:0)

首先假设eval不存在,除非您对评估的内容有非常严格的控制。即使您不进行解析,也可以将所有输入拆分为令牌,并检查每个输入是否为可接受的令牌。

这是一种非常粗略的方法来检查输入除了有效令牌之外什么都没有。可能有很多重构/改进。

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.

答案 2 :(得分:0)

如果eval可以工作,那么你可以使用ruby解析器(例如gem install ruby_parser)解析表达式,然后递归地计算S表达式,忽略或引发非算术函数的错误。这可能需要一些工作,但听起来很有趣:

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

您应该能够非常轻松地对此进行增强,以包含cossin等内容。它适用于我尝试的一些简单示例,并且包括对格式良好的免费检查(如果没有,则解析会引发Racc::ParseError异常)。