Ruby:解析,替换和评估字符串公式

Swa*_*rtz 1 ruby parameters parsing eval ruby-on-rails

我正在为朋友的心理调查项目创建一个简单的Ruby on Rails调查应用程序.所以我们有调查,每个调查都有一堆问题,每个问题都有参与者可以选择的选项之一.没什么好激动的.

其中一个有趣的方面是每个答案选项都有一个与之相关的分数值.因此,对于每个调查,需要根据这些值计算总分.

现在我的想法是代替硬编码计算,允许用户添加一个公式,通过该公式计算总调查分数.示例公式:

"Q1 + Q2 + Q3"
"(Q1 + Q2 + Q3) / 3"
"(10 - Q1) + Q2 + (Q3 * 2)"
Run Code Online (Sandbox Code Playgroud)

所以只是基本的数学(为了清晰起见,附加一些括号).我们的想法是保持公式非常简单,这样任何拥有基本数学的人都可以输入它们,而无需解析某些奇特的语法.

我的想法是采用任何给定的公式,并用基于参与者选择的分数值替换Q1,Q2等占位符.然后eval()新形成的字符串.像这样的东西:

f = "(Q1 + Q2 + Q3) / 2"  # some crazy formula for this survey
values = {:Q1 => 1, :Q2 => 2, :Q3 => 2}  # values for substitution 
result = f.gsub(/(Q\d+)/) {|m| values[$1.to_sym] }   # string to be eval()-ed
eval(result)
Run Code Online (Sandbox Code Playgroud)

所以我的问题是:

  1. 有一个更好的方法吗?我愿意接受任何建议.

  2. 如何处理并非所有占位符都被成功替换的公式(例如,一个问题没有得到解答)?例如:{:Q2 => 2}不是值哈希?我的想法是拯救eval(),但在这种情况下它不会失败因为coz (1 + + 2) / 2仍然可以是eval() - ed ...任何想法?

  3. 如何获得正确的结果?应该是2.5,但由于整数运算,它将截断为2.我不能指望提供正确公式(例如/ 2.0)的人理解这种细微差别.

  4. 我不期望这样,但如何最好地保护eval()免受滥用(例如糟糕的公式,操纵值进入)?例:f = 'system("ruby -v"); (Q1 + (Q2 / 3) + Q3 + (Q4 * 2)) / 2 '

谢谢!

ros*_*ssl 6

使用登乐

Dentaku 是数学和逻辑公式语言的解析器和评估器,允许运行时将值绑定到公式中引用的变量。它的目的是在不打开安全漏洞的情况下安全地评估不受信任的表达式。


Phr*_*ogz 5

这可能不值得付出努力,但如果我要这样做,我会使用Treetop来定义解析语法。甚至还有使用像这样的 PEG 风格语法进行简单算术的示例,因此您已经掌握了 90% 的语法,并且大部分都掌握了评估权重的方法。


小智 5

好的,现在它完全安全了.我发誓!

我通常会克隆formula变量,但在这种情况下,因为你担心恶意用户我清理了变量:

class Evaluator

  def self.formula(formula, values)
    # remove anything but Q's, numbers, ()'s, decimal points, and basic math operators 
    formula.gsub!(/((?![qQ0-9\s\.\-\+\*\/\(\)]).)*/,'').upcase!
    begin
      formula.gsub!(/Q\d+/) { |match|
        ( 
          values[match.to_sym] && 
          values[match.to_sym].class.ancestors.include?(Numeric) ?
          values[match.to_sym].to_s :
          '0'
        )+'.0'
      }
      instance_eval(formula)
    rescue Exception => e
      e.inspect
    end
  end

end

f = '(q1 + (q2 / 3) + q3 + (q4 * 2))'  # some crazy formula for this survey
values = {:Q2 => 1, :Q4 => 2}  # values for substitution 
puts "formula: #{f} = #{Evaluator.formula(f,values)}"  
=> formula: (0.0 + (1.0 / 3) + 0.0 + (2.0 * 2)) = 4.333333333333333

f = '(Q1 + (Q2 / 3) + Q3 + (Q4 * 2)) / 2'  # some crazy formula for this survey
values = {:Q1 => 1, :Q3 => 2}  # values for substitution 
puts "formula: #{f} = #{Evaluator.formula(f,values)}"  
=> formula: (1.0 + (0.0 / 3) + 2.0 + (0.0 * 2)) / 2 = 1.5

f = '(Q1 + (Q2 / 3) + Q3 + (Q4 * 2)) / 2'  # some crazy formula for this survey
values = {:Q1 => 'delete your hard drive', :Q3 => 2}  # values for substitution 
puts "formula: #{f} = #{Evaluator.formula(f,values)}"  
=> formula: (0.0 + (0.0 / 3) + 2.0 + (0.0 * 2)) / 2 = 1.0

f = 'system("ruby -v")'  # some crazy formula for this survey
values = {:Q1 => 'delete your hard drive', :Q3 => 2}  # values for substitution 
puts "formula: #{f} = #{Evaluator.formula(f,values)}"  
=> formula: ( -) = #<SyntaxError: (eval):1: syntax error, unexpected ')'>
Run Code Online (Sandbox Code Playgroud)