From 8d0f6eec81c0ea4b64129e098f62ba7bb8ec2679 Mon Sep 17 00:00:00 2001 From: cryptogopher Date: Fri, 24 Jan 2020 00:17:00 +0100 Subject: [PATCH] Moved FormulaBuilder to Formula module --- lib/body_tracking/formula.rb | 229 ++++++++++++++++++++++++++ lib/body_tracking/formula_builder.rb | 230 --------------------------- 2 files changed, 229 insertions(+), 230 deletions(-) delete mode 100644 lib/body_tracking/formula_builder.rb diff --git a/lib/body_tracking/formula.rb b/lib/body_tracking/formula.rb index 21747ae..b3a6def 100644 --- a/lib/body_tracking/formula.rb +++ b/lib/body_tracking/formula.rb @@ -1,6 +1,10 @@ module BodyTracking module Formula + require 'ripper' + require 'set' + class InvalidFormula < RuntimeError; end + class Formula def initialize(project, formula) @project_quantities = Quantity.where(project: project) @@ -62,6 +66,7 @@ module BodyTracking end end + class FormulaValidator < ActiveModel::EachValidator def initialize(options) super(options) @@ -73,5 +78,229 @@ module BodyTracking end end end + + class FormulaBuilder < Ripper::SexpBuilder + def initialize(*args) + super(*args) + @disallowed = Hash.new { |h,k| h[k] = Set.new } + @identifiers = Set.new + @parts = [] + end + + def errors + @errors = [] + @disallowed.each { |k, v| v.each { |e| @errors << ["disallowed_#{k}", {k => e} ] } } + @errors + end + + private + + DECIMAL_METHODS = ['abs', 'nil?'] + QUANTITY_METHODS = ['all', 'lastBefore'] + METHODS = DECIMAL_METHODS + QUANTITY_METHODS + + events = private_instance_methods(false).grep(/\Aon_/) {$'.to_sym} + (PARSER_EVENTS - events).each do |event| + module_eval(<<-End, __FILE__, __LINE__ + 1) + def on_#{event}(*args) + @disallowed[:token] << args.to_s + ' [#{event}]' + [:bt_unimplemented, args] + end + End + end + + def on_parse_error(error) + @disallowed[:syntax] << error + end + + def on_program(stmts) + @parts << {type: :indexed, content: join_stmts(stmts)} + [@identifiers, @parts] + end + + def on_string_content + '' + end + + def on_string_add(str, new_str) + str << new_str + end + + def on_string_literal(str) + @identifiers << str + [:bt_quantity, str] + end + + def on_args_new + [] + end + + def on_args_add(args, new_arg) + args << new_arg + end + + def on_args_add_block(args, block) + raise NotImplementedError if block + args + end + + def on_arg_paren(args) + "(" << + args.map do |arg| + ttype, token = arg + case ttype + when :bt_quantity + "quantities['#{token}']" + when :bt_expression + @parts << {type: :indexed, content: token} + "parts[#{@parts.length - 1}]" + else + raise NotImplementedError + end + end.join(',') << + ")" + end + + def on_stmts_new + [] + end + + def on_stmts_add(stmts, new_stmt) + stmts << new_stmt + end + + def on_paren(stmts) + [ + :bt_expression, + "(" << join_stmts(stmts) << ")" + ] + end + + def on_call(left, dot, right) + method, mtype = + case right[0] + when :bt_ident + case + when DECIMAL_METHODS.include?(right[1]) + [right[1], :numeric_method] + when QUANTITY_METHODS.include?(right[1]) + [right[1], :quantity_method] + else + @disallowed[:method] << right[1] + [right[1], :unknown_method] + end + else + raise NotImplementedError + end + + case left[0] + when :bt_quantity + if mtype == :quantity_method + part_index = @parts.length + @parts << {type: :unindexed, + content: "quantities['#{left[1]}']#{dot.to_s}#{method}"} + [:bt_quantity_method_call, "parts[#{part_index}]", part_index] + else + [:bt_numeric_method_call, "quantities['#{left[1]}'][_index]#{dot.to_s}#{method}"] + end + when :bt_quantity_method_call + if mtype == :quantity_method + @parts[left[2]][:content] << "#{dot.to_s}#{method}" + left + else + [:bt_numeric_method_call, "#{left[1]}#{dot.to_s}#{method}"] + end + when :bt_numeric_method_call + if mtype == :quantity_method + # TODO: add error reporting + raise NotImplementedError + else + [:bt_numeric_method_call, "#{left[1]}#{dot.to_s}#{method}"] + end + else + raise NotImplementedError + end + end + + def on_fcall(token) + @disallowed[:method] = token[1] + [:bt_numeric_method_call, token[1]] + end + + def on_vcall(token) + case token[0] + when :bt_ident + @identifiers << token[1] + [:bt_quantity, token[1]] + else + raise NotImplementedError + end + end + + def on_method_add_arg(method, paren) + case method[0] + when :bt_quantity_method_call + @parts[method[2]][:content] << paren + method + when :bt_numeric_method_call + [:bt_numeric_method_call, "#{method[1]}#{paren}"] + else + raise NotImplementedError + end + end + + def on_binary(left, op, right) + [ + :bt_expression, + [left, right].map do |side| + side[0] == :bt_quantity ? "quantities['#{side[1]}'][_index]" : "#{side[1]}" + end.join(op.to_s) + ] + end + + def on_var_ref(var_ref) + var_ref[0] == :bt_quantity ? var_ref : raise(NotImplementedError) + end + + silenced_events = [:lparen, :rparen, :op, :period, :sp, :int, :float, + :tstring_beg, :tstring_end] + (SCANNER_EVENTS - silenced_events).each do |event| + module_eval(<<-End, __FILE__, __LINE__ + 1) + def on_#{event}(token) + @disallowed[:token] << token + ' [#{event}]' + [:bt_unimplemented, token] + end + End + end + + def on_const(token) + @identifiers << token + [:bt_quantity, token] + end + + def on_ident(token) + [:bt_ident, token] + end + + def on_kw(token) + @disallowed[:keyword] << token unless token == 'nil' + end + + def on_tstring_content(token) + token + end + + def join_stmts(stmts) + stmts.map do |stmt| + ttype, token = stmt + case ttype + when :bt_expression + token + else + raise NotImplementedError + end + end.join(';') + end + end end end diff --git a/lib/body_tracking/formula_builder.rb b/lib/body_tracking/formula_builder.rb deleted file mode 100644 index b1745c8..0000000 --- a/lib/body_tracking/formula_builder.rb +++ /dev/null @@ -1,230 +0,0 @@ -module BodyTracking - module FormulaBuilder - require 'ripper' - require 'set' - - class FormulaBuilder < Ripper::SexpBuilder - def initialize(*args) - super(*args) - @disallowed = Hash.new { |h,k| h[k] = Set.new } - @identifiers = Set.new - @parts = [] - end - - def errors - @errors = [] - @disallowed.each { |k, v| v.each { |e| @errors << ["disallowed_#{k}", {k => e} ] } } - @errors - end - - private - - DECIMAL_METHODS = ['abs', 'nil?'] - QUANTITY_METHODS = ['all', 'lastBefore'] - METHODS = DECIMAL_METHODS + QUANTITY_METHODS - - events = private_instance_methods(false).grep(/\Aon_/) {$'.to_sym} - (PARSER_EVENTS - events).each do |event| - module_eval(<<-End, __FILE__, __LINE__ + 1) - def on_#{event}(*args) - @disallowed[:token] << args.to_s + ' [#{event}]' - [:bt_unimplemented, args] - end - End - end - - def on_parse_error(error) - @disallowed[:syntax] << error - end - - def on_program(stmts) - @parts << {type: :indexed, content: join_stmts(stmts)} - [@identifiers, @parts] - end - - def on_string_content - '' - end - - def on_string_add(str, new_str) - str << new_str - end - - def on_string_literal(str) - @identifiers << str - [:bt_quantity, str] - end - - def on_args_new - [] - end - - def on_args_add(args, new_arg) - args << new_arg - end - - def on_args_add_block(args, block) - raise NotImplementedError if block - args - end - - def on_arg_paren(args) - "(" << - args.map do |arg| - ttype, token = arg - case ttype - when :bt_quantity - "quantities['#{token}']" - when :bt_expression - @parts << {type: :indexed, content: token} - "parts[#{@parts.length - 1}]" - else - raise NotImplementedError - end - end.join(',') << - ")" - end - - def on_stmts_new - [] - end - - def on_stmts_add(stmts, new_stmt) - stmts << new_stmt - end - - def on_paren(stmts) - [ - :bt_expression, - "(" << join_stmts(stmts) << ")" - ] - end - - def on_call(left, dot, right) - method, mtype = - case right[0] - when :bt_ident - case - when DECIMAL_METHODS.include?(right[1]) - [right[1], :numeric_method] - when QUANTITY_METHODS.include?(right[1]) - [right[1], :quantity_method] - else - @disallowed[:method] << right[1] - [right[1], :unknown_method] - end - else - raise NotImplementedError - end - - case left[0] - when :bt_quantity - if mtype == :quantity_method - part_index = @parts.length - @parts << {type: :unindexed, - content: "quantities['#{left[1]}']#{dot.to_s}#{method}"} - [:bt_quantity_method_call, "parts[#{part_index}]", part_index] - else - [:bt_numeric_method_call, "quantities['#{left[1]}'][_index]#{dot.to_s}#{method}"] - end - when :bt_quantity_method_call - if mtype == :quantity_method - @parts[left[2]][:content] << "#{dot.to_s}#{method}" - left - else - [:bt_numeric_method_call, "#{left[1]}#{dot.to_s}#{method}"] - end - when :bt_numeric_method_call - if mtype == :quantity_method - # TODO: add error reporting - raise NotImplementedError - else - [:bt_numeric_method_call, "#{left[1]}#{dot.to_s}#{method}"] - end - else - raise NotImplementedError - end - end - - def on_fcall(token) - @disallowed[:method] = token[1] - [:bt_numeric_method_call, token[1]] - end - - def on_vcall(token) - case token[0] - when :bt_ident - @identifiers << token[1] - [:bt_quantity, token[1]] - else - raise NotImplementedError - end - end - - def on_method_add_arg(method, paren) - case method[0] - when :bt_quantity_method_call - @parts[method[2]][:content] << paren - method - when :bt_numeric_method_call - [:bt_numeric_method_call, "#{method[1]}#{paren}"] - else - raise NotImplementedError - end - end - - def on_binary(left, op, right) - [ - :bt_expression, - [left, right].map do |side| - side[0] == :bt_quantity ? "quantities['#{side[1]}'][_index]" : "#{side[1]}" - end.join(op.to_s) - ] - end - - def on_var_ref(var_ref) - var_ref[0] == :bt_quantity ? var_ref : raise(NotImplementedError) - end - - silenced_events = [:lparen, :rparen, :op, :period, :sp, :int, :float, - :tstring_beg, :tstring_end] - (SCANNER_EVENTS - silenced_events).each do |event| - module_eval(<<-End, __FILE__, __LINE__ + 1) - def on_#{event}(token) - @disallowed[:token] << token + ' [#{event}]' - [:bt_unimplemented, token] - end - End - end - - def on_const(token) - @identifiers << token - [:bt_quantity, token] - end - - def on_ident(token) - [:bt_ident, token] - end - - def on_kw(token) - @disallowed[:keyword] << token unless token == 'nil' - end - - def on_tstring_content(token) - token - end - - def join_stmts(stmts) - stmts.map do |stmt| - ttype, token = stmt - case ttype - when :bt_expression - token - else - raise NotImplementedError - end - end.join(';') - end - end - end -end