Formula can contain model method calls
This commit is contained in:
parent
36a2e7bcbe
commit
8749710330
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
Reference in New Issue
Block a user