From 613c26728ca9e6822bd60705539afe8c0aea58a1 Mon Sep 17 00:00:00 2001 From: cryptogopher Date: Mon, 11 Nov 2019 00:01:31 +0100 Subject: [PATCH] Removed old (Ripper.lex based) formula validation code Muted multiple 'disallowed' messages for keywords Removed unnecessary .to_d call in formula computation --- config/locales/en.yml | 3 +- lib/body_tracking/formula.rb | 113 +++++++---------------------------- 2 files changed, 23 insertions(+), 93 deletions(-) diff --git a/config/locales/en.yml b/config/locales/en.yml index cc40eb7..6739155 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -29,7 +29,8 @@ en: invalid_formula: 'interpretation failed: %{msg}' unparsable_formula: 'cannot be parsed' disallowed_token: 'includes disallowed token "%{token}" (of type: %{ttype})' - disallowed_function_call: 'includes disallowed function call: %{function}' + disallowed_keyword: 'includes disallowed keyword: "%{keyword}"' + disallowed_function: 'includes disallowed function call: %{function}' unknown_quantity: 'contains undefined quantity: %{quantity}' body_trackers: index: diff --git a/lib/body_tracking/formula.rb b/lib/body_tracking/formula.rb index cc7bd66..e09f931 100644 --- a/lib/body_tracking/formula.rb +++ b/lib/body_tracking/formula.rb @@ -10,66 +10,16 @@ module BodyTracking @formula = formula end - #def validate - # errors = [] - - # # 1st: check if formula is valid Ruby code - # #tokenized_length = Ripper.tokenize(@formula).join.length - # #unless tokenized_length == @formula.length - # # errors << [:invalid_formula, {part: @formula[0...tokenized_length]}] - # #end - # begin - # eval("-> { #{@formula} }") - # rescue ScriptError => e - # errors << [:invalid_formula, {msg: e.message}] - # end - - # # 2nd: check if formula contains only allowed token types - # # 3rd: check for disallowed function calls (they are not detected by Ripper.lex) - # # FIXME: this is unreliable (?) detection of function calls, should be replaced - # # with parsing Ripper.sexp if necessary - # identifiers = [] - # disallowed_functions = Set.new - # prev_ttype, prev_token = nil, nil - # Ripper.lex(@formula).each do |location, ttype, token| - # puts ttype, token - # case - # when QUANTITY_TTYPES.include?(prev_ttype) && ttype == :on_lparen - # disallowed_functions << prev_token unless FUNCTIONS.include?(prev_token) - # identifiers -= [prev_token] - # when prev_ttype == :on_period && QUANTITY_TTYPES.include?(ttype) - # disallowed_functions << token unless FUNCTIONS.include?(token) - # when is_token_quantity?(ttype, token) - # identifiers << token - # when [:on_sp, :on_int, :on_rational, :on_float, :on_tstring_beg, :on_tstring_end, - # :on_lparen, :on_rparen, :on_period].include?(ttype) - # when :on_op == ttype && - # ['+', '-', '*', '/', '%', '**', '==', '!=', '>', '<', '>=', '<=', '<=>', '===', - # '..', '...', '?:', 'and', 'or', 'not', '&&', '||', '!'].include?(token) - # when :on_kw == ttype && ['and', 'or', 'not'].include?(token) - # else - # errors << [:disallowed_token, {token: token, ttype: ttype, location: location}] - # end - # prev_ttype, prev_token = ttype, token unless ttype == :on_sp - # end - # disallowed_functions.each { |f| errors << [:disallowed_function_call, {function: f}] } - - # # 4th: check if identifiers used in formula correspond to existing quantities - # identifiers.uniq! - # quantities = @project.quantities.where(name: identifiers).pluck(:name) - # (identifiers - quantities).each { |q| errors << [:unknown_quantity, {quantity: q}] } - - # errors - #end - def validate + # TODO: add tests + # failing test vectors: + # - fcall disallowed: "abs(Fats)+Energy < 10" + # working test vectors: + # ((Energy-Calculated)/Energy).abs > 0.2 + # Fats.nil? || Fats/Proteins > 2 errors = [] - # 1st: check if formula is valid Ruby code - #tokenized_length = Ripper.tokenize(@formula).join.length - #unless tokenized_length == @formula.length - # errors << [:invalid_formula, {part: @formula[0...tokenized_length]}] - #end + # 1st: check if formula is syntactically valid Ruby code begin eval("-> { #{@formula} }") rescue ScriptError => e @@ -77,16 +27,9 @@ module BodyTracking end # 2nd: check if formula contains only allowed token types - # 3rd: check for disallowed function calls (they are not detected by Ripper.lex) - # FIXME: this is unreliable (?) detection of function calls, should be replaced - # with parsing Ripper.sexp if necessary - # failing test vectors: - # - fcall disallowed: "abs(Fats)+Energy < 10" - # working test vectors: - # Fats.abs+Energy < 10 - # (Energy-Calculated).abs > 10 + # 3rd: check for disallowed function calls identifiers = [] - disallowed_functions = Set.new + disallowed = Hash.new { |h,k| h[k] = Set.new } stree = [Ripper.sexp(@formula)] errors << [:unparsable_formula, {}] unless stree.first @@ -105,10 +48,10 @@ module BodyTracking stree.unshift(token) dot, method = rest ftype, fname, floc = method - disallowed_functions << fname unless FUNCTIONS.include?(fname) + disallowed[:function] << fname unless FUNCTIONS.include?(fname) when :fcall ftype, fname, floc = token - disallowed_functions << fname + disallowed[:function] << fname when :vcall ftype, fname, floc = token identifiers << fname @@ -116,35 +59,20 @@ module BodyTracking stree.unshift(token) when :var_ref vtype, vname, vloc = token - identifiers << vname + case vtype + when vtype == :@conts + identifiers << vname + when vtype == :@kw + disallowed[:keyword] << token if vname != 'nil' + end when :@int, :@float else errors << [:disallowed_token, {token: token, ttype: ttype}] end end - #Ripper.lex(@formula).each do |location, ttype, token| - # puts ttype, token - # case - # when QUANTITY_TTYPES.include?(prev_ttype) && ttype == :on_lparen - # disallowed_functions << prev_token unless FUNCTIONS.include?(prev_token) - # identifiers -= [prev_token] - # when prev_ttype == :on_period && QUANTITY_TTYPES.include?(ttype) - # disallowed_functions << token unless FUNCTIONS.include?(token) - # when is_token_quantity?(ttype, token) - # identifiers << token - # when [:on_sp, :on_int, :on_rational, :on_float, :on_tstring_beg, :on_tstring_end, - # :on_lparen, :on_rparen, :on_period].include?(ttype) - # when :on_op == ttype && - # ['+', '-', '*', '/', '%', '**', '==', '!=', '>', '<', '>=', '<=', '<=>', '===', - # '..', '...', '?:', 'and', 'or', 'not', '&&', '||', '!'].include?(token) - # when :on_kw == ttype && ['and', 'or', 'not'].include?(token) - # else - # errors << [:disallowed_token, {token: token, ttype: ttype, location: location}] - # end - # prev_ttype, prev_token = ttype, token unless ttype == :on_sp - #end - disallowed_functions.each { |f| errors << [:disallowed_function_call, {function: f}] } + disallowed[:function].each { |f| errors << [:disallowed_function, {function: f}] } + disallowed[:keyword].each { |k| errors << [:disallowed_keyword, {keyword: k}] } # 4th: check if identifiers used in formula correspond to existing quantities identifiers.uniq! @@ -167,10 +95,11 @@ module BodyTracking def calculate(inputs) paramed_formula = Ripper.lex(@formula).map do |*, ttype, token| - is_token_quantity?(ttype, token) ? "params['#{token}'].to_d" : token + is_token_quantity?(ttype, token) ? "params['#{token}']" : token end.join inputs.map do |i, values| + puts values.inspect begin [i, [get_binding(values).eval(paramed_formula), nil]] rescue Exception => e