From 841fcb28076ae28efff7132cd35f37feef3582c8 Mon Sep 17 00:00:00 2001 From: cryptogopher Date: Sun, 10 Nov 2019 00:48:54 +0100 Subject: [PATCH] Working on formula validation to accomodate functions, WIP --- config/locales/en.yml | 6 ++-- lib/body_tracking/formula.rb | 53 +++++++++++++++++++++++------------- 2 files changed, 37 insertions(+), 22 deletions(-) diff --git a/config/locales/en.yml b/config/locales/en.yml index fabac86..73bc16a 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -26,11 +26,11 @@ en: parent: parent_domain_mismatch: 'parent quantity has to be in the same domain' formula: - invalid_formula: 'interpretation failed after: %{part}' + invalid_formula: 'interpretation failed: %{msg}' disallowed_token: 'includes disallowed token "%{token}" (of type %{ttype}) at %{location}' - disallowed_function_call: 'includes disallowed function call %{function}' - unknown_quantity: 'contains undefined quantities: %{quantities}' + disallowed_function_call: 'includes disallowed function call: %{function}' + unknown_quantity: 'contains undefined quantity: %{quantity}' body_trackers: index: heading: 'Summary' diff --git a/lib/body_tracking/formula.rb b/lib/body_tracking/formula.rb index fdace66..275c317 100644 --- a/lib/body_tracking/formula.rb +++ b/lib/body_tracking/formula.rb @@ -2,6 +2,7 @@ module BodyTracking module Formula require 'ripper' QUANTITY_TTYPES = [:on_ident, :on_tstring_content, :on_const] + FUNCTIONS = ['abs', 'nil?'] class Formula def initialize(project, formula) @@ -13,19 +14,35 @@ module BodyTracking 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]}] + #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?(ttype) + 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].include?(ttype) + :on_lparen, :on_rparen, :on_period].include?(ttype) when :on_op == ttype && ['+', '-', '*', '/', '%', '**', '==', '!=', '>', '<', '>=', '<=', '<=>', '===', '..', '...', '?:', 'and', 'or', 'not', '&&', '||', '!'].include?(token) @@ -33,20 +50,14 @@ module BodyTracking else errors << [:disallowed_token, {token: token, ttype: ttype, location: location}] end + prev_ttype, prev_token = ttype, token unless ttype == :on_sp end - - # 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 - function = Ripper.slice(@formula, 'ident [sp]* lparen') - errors << [:disallowed_function_call, {function: function}] if function + 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) - if quantities.length != identifiers.length - errors << [:unknown_quantity, {quantities: identifiers - quantities}] - end + (identifiers - quantities).each { |q| errors << [:unknown_quantity, {quantity: q}] } errors end @@ -57,27 +68,31 @@ module BodyTracking def get_quantities q_names = Ripper.lex(@formula).map do |*, ttype, token| - token if QUANTITY_TTYPES.include?(ttype) + token if is_token_quantity?(ttype, token) end.compact @project.quantities.where(name: q_names).to_a end def calculate(inputs) paramed_formula = Ripper.lex(@formula).map do |*, ttype, token| - QUANTITY_TTYPES.include?(ttype) ? "params['#{token}'].to_d" : token + is_token_quantity?(ttype, token) ? "params['#{token}'].to_d" : token end.join inputs.map do |i, values| begin - [i, get_binding(values).eval(paramed_formula)] - rescue - [i, nil] + [i, [get_binding(values).eval(paramed_formula), nil]] + rescue Exception + [i, [nil, nil]] end end end private + def is_token_quantity?(ttype, token) + QUANTITY_TTYPES.include?(ttype) && !FUNCTIONS.include?(token) + end + def get_binding(params) binding end