1
0

Formula can contain model method calls

This commit is contained in:
cryptogopher 2020-05-05 01:52:34 +02:00
parent 36a2e7bcbe
commit 8749710330
4 changed files with 39 additions and 39 deletions

View File

@ -91,12 +91,12 @@ class QuantitiesController < ApplicationController
def create_child
@quantity = @project.quantities.new(quantity_params)
if @quantity.save
flash[:notice] = 'Created new quantity'
prepare_quantities
else
unless @quantity.save
@parent_quantity = @quantity.parent
render :new_child
end
flash[:notice] = 'Created new quantity'
prepare_quantities
end
private

View File

@ -39,12 +39,16 @@ class Formula < ActiveRecord::Base
private
def parse
parser = FormulaBuilder.new(self.code)
d_methods = ['abs', 'nil?']
q_methods = Hash.new(['all', 'lastBefore'])
q_methods['Meal'] = Meal.attribute_names
parser = FormulaBuilder.new(self.code, d_methods: d_methods, q_methods: q_methods)
identifiers, parts = parser.parse
errors = parser.errors
quantities = Quantity.where(project: self.quantity.project, name: identifiers)
(identifiers - quantities.map(&:name)).each do |q|
(identifiers - quantities.map(&:name) - quantity_m.keys).each do |q|
errors << [:unknown_quantity, {quantity: q}]
end

View File

@ -9,25 +9,25 @@ module BodyTracking
# List of events with parameter count:
# https://github.com/racker/ruby-1.9.3-lucid/blob/master/ext/ripper/eventids1.c
class FormulaBuilder < Ripper::SexpBuilder
def initialize(*args)
def initialize(*args, d_methods: [], q_methods: Hash.new([]))
super(*args)
@disallowed = Hash.new { |h,k| h[k] = Set.new }
@identifiers = Set.new
@parts = []
@decimal_methods = d_methods
@quantity_methods = q_methods
end
def errors
@errors = []
@disallowed.each { |k, v| v.each { |e| @errors << ["disallowed_#{k}", {k => e} ] } }
@disallowed.each do |k, v|
v.each { |e| @errors << ["disallowed_#{k}".to_sym, {k => e} ] }
end
@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)
@ -44,7 +44,7 @@ module BodyTracking
def on_program(stmts)
@parts << {type: :indexed, content: join_stmts(stmts)}
[@identifiers.to_a, @parts]
[@identifiers, @parts]
end
def on_string_content
@ -106,45 +106,35 @@ module BodyTracking
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, right.inspect
end
raise(NotImplementedError, right.inspect) unless right[0] == :bt_ident
case left[0]
when :bt_quantity
if mtype == :quantity_method
if @quantity_methods[left[1]].include?(right[1])
part_index = @parts.length
@parts << {type: :unindexed,
content: "quantities['#{left[1]}']#{dot.to_s}#{method}"}
content: "quantities['#{left[1]}']#{dot.to_s}#{right[1]}"}
[:bt_quantity_method_call, "parts[#{part_index}]", part_index]
else
[:bt_numeric_method_call, "quantities['#{left[1]}'][index]#{dot.to_s}#{method}"]
@disallowed[:method] << right[1] unless @decimal_methods.include?(right[1])
[:bt_numeric_method_call,
"quantities['#{left[1]}'][_index]#{dot.to_s}#{right[1]}"]
end
when :bt_quantity_method_call
if mtype == :quantity_method
@parts[left[2]][:content] << "#{dot.to_s}#{method}"
if @quantity_methods.default.include?(right[1])
@parts[left[2]][:content] << "#{dot.to_s}#{right[1]}"
left
else
[:bt_numeric_method_call, "#{left[1]}#{dot.to_s}#{method}"]
@disallowed[:method] << right[1] unless @decimal_methods.include?(right[1])
[:bt_numeric_method_call, "#{left[1]}#{dot.to_s}#{right[1]}"]
end
when :bt_numeric_method_call, :bt_expression
if mtype == :quantity_method
if @quantity_methods.default.include?(right[1])
# TODO: add error reporting
raise NotImplementedError
else
[:bt_numeric_method_call, "#{left[1]}#{dot.to_s}#{method}"]
@disallowed[:method] << right[1] unless @decimal_methods.include?(right[1])
[:bt_numeric_method_call, "#{left[1]}#{dot.to_s}#{right[1]}"]
end
else
raise NotImplementedError, left.inspect
@ -182,7 +172,7 @@ module BodyTracking
[
:bt_expression,
[left, right].map do |side|
side[0] == :bt_quantity ? "quantities['#{side[1]}'][index]" : "#{side[1]}"
side[0] == :bt_quantity ? "quantities['#{side[1]}'][_index]" : "#{side[1]}"
end.join(op.to_s)
]
end
@ -234,7 +224,7 @@ module BodyTracking
when :bt_expression, :bt_numeric_method_call
token
when :bt_quantity
"quantities['#{token}'][index]"
"quantities['#{token}'][_index]"
else
raise NotImplementedError, stmt.inspect
end

View File

@ -62,10 +62,16 @@ class FormulaTest < ActiveSupport::TestCase
{type: :indexed, content: "quantities['Fats'][_index].nil?||" \
"quantities['Fats'][_index]/quantities['Proteins'][_index]>2"}
],
# Model method calls
'100*Energy/RM.lastBefore(Meal.eaten_at||Meal.created_at)', Set['Energy', 'RM', 'Meal'],
# TODO: fill parts
[]
]
d_methods = ['nil?', 'abs']
vector.each_slice(3) do |formula, identifiers, parts|
parser = FormulaBuilder.new(formula)
parser = FormulaBuilder.new(formula, d_methods: d_methods)
i, p = parser.parse
assert_empty parser.errors
assert_equal identifiers, i