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
|
def create_child
|
||||||
@quantity = @project.quantities.new(quantity_params)
|
@quantity = @project.quantities.new(quantity_params)
|
||||||
if @quantity.save
|
unless @quantity.save
|
||||||
flash[:notice] = 'Created new quantity'
|
@parent_quantity = @quantity.parent
|
||||||
prepare_quantities
|
|
||||||
else
|
|
||||||
render :new_child
|
render :new_child
|
||||||
end
|
end
|
||||||
|
flash[:notice] = 'Created new quantity'
|
||||||
|
prepare_quantities
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
@ -39,12 +39,16 @@ class Formula < ActiveRecord::Base
|
|||||||
private
|
private
|
||||||
|
|
||||||
def parse
|
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
|
identifiers, parts = parser.parse
|
||||||
errors = parser.errors
|
errors = parser.errors
|
||||||
|
|
||||||
quantities = Quantity.where(project: self.quantity.project, name: identifiers)
|
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}]
|
errors << [:unknown_quantity, {quantity: q}]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -9,25 +9,25 @@ module BodyTracking
|
|||||||
# List of events with parameter count:
|
# List of events with parameter count:
|
||||||
# https://github.com/racker/ruby-1.9.3-lucid/blob/master/ext/ripper/eventids1.c
|
# https://github.com/racker/ruby-1.9.3-lucid/blob/master/ext/ripper/eventids1.c
|
||||||
class FormulaBuilder < Ripper::SexpBuilder
|
class FormulaBuilder < Ripper::SexpBuilder
|
||||||
def initialize(*args)
|
def initialize(*args, d_methods: [], q_methods: Hash.new([]))
|
||||||
super(*args)
|
super(*args)
|
||||||
@disallowed = Hash.new { |h,k| h[k] = Set.new }
|
@disallowed = Hash.new { |h,k| h[k] = Set.new }
|
||||||
@identifiers = Set.new
|
@identifiers = Set.new
|
||||||
@parts = []
|
@parts = []
|
||||||
|
@decimal_methods = d_methods
|
||||||
|
@quantity_methods = q_methods
|
||||||
end
|
end
|
||||||
|
|
||||||
def errors
|
def errors
|
||||||
@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
|
@errors
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
DECIMAL_METHODS = ['abs', 'nil?']
|
|
||||||
QUANTITY_METHODS = ['all', 'lastBefore']
|
|
||||||
METHODS = DECIMAL_METHODS + QUANTITY_METHODS
|
|
||||||
|
|
||||||
events = private_instance_methods(false).grep(/\Aon_/) {$'.to_sym}
|
events = private_instance_methods(false).grep(/\Aon_/) {$'.to_sym}
|
||||||
(PARSER_EVENTS - events).each do |event|
|
(PARSER_EVENTS - events).each do |event|
|
||||||
module_eval(<<-End, __FILE__, __LINE__ + 1)
|
module_eval(<<-End, __FILE__, __LINE__ + 1)
|
||||||
@ -44,7 +44,7 @@ module BodyTracking
|
|||||||
|
|
||||||
def on_program(stmts)
|
def on_program(stmts)
|
||||||
@parts << {type: :indexed, content: join_stmts(stmts)}
|
@parts << {type: :indexed, content: join_stmts(stmts)}
|
||||||
[@identifiers.to_a, @parts]
|
[@identifiers, @parts]
|
||||||
end
|
end
|
||||||
|
|
||||||
def on_string_content
|
def on_string_content
|
||||||
@ -106,45 +106,35 @@ module BodyTracking
|
|||||||
end
|
end
|
||||||
|
|
||||||
def on_call(left, dot, right)
|
def on_call(left, dot, right)
|
||||||
method, mtype =
|
raise(NotImplementedError, right.inspect) unless right[0] == :bt_ident
|
||||||
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
|
|
||||||
|
|
||||||
case left[0]
|
case left[0]
|
||||||
when :bt_quantity
|
when :bt_quantity
|
||||||
if mtype == :quantity_method
|
if @quantity_methods[left[1]].include?(right[1])
|
||||||
part_index = @parts.length
|
part_index = @parts.length
|
||||||
@parts << {type: :unindexed,
|
@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]
|
[:bt_quantity_method_call, "parts[#{part_index}]", part_index]
|
||||||
else
|
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
|
end
|
||||||
when :bt_quantity_method_call
|
when :bt_quantity_method_call
|
||||||
if mtype == :quantity_method
|
if @quantity_methods.default.include?(right[1])
|
||||||
@parts[left[2]][:content] << "#{dot.to_s}#{method}"
|
@parts[left[2]][:content] << "#{dot.to_s}#{right[1]}"
|
||||||
left
|
left
|
||||||
else
|
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
|
end
|
||||||
when :bt_numeric_method_call, :bt_expression
|
when :bt_numeric_method_call, :bt_expression
|
||||||
if mtype == :quantity_method
|
if @quantity_methods.default.include?(right[1])
|
||||||
# TODO: add error reporting
|
# TODO: add error reporting
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
else
|
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
|
end
|
||||||
else
|
else
|
||||||
raise NotImplementedError, left.inspect
|
raise NotImplementedError, left.inspect
|
||||||
@ -182,7 +172,7 @@ module BodyTracking
|
|||||||
[
|
[
|
||||||
:bt_expression,
|
:bt_expression,
|
||||||
[left, right].map do |side|
|
[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.join(op.to_s)
|
||||||
]
|
]
|
||||||
end
|
end
|
||||||
@ -234,7 +224,7 @@ module BodyTracking
|
|||||||
when :bt_expression, :bt_numeric_method_call
|
when :bt_expression, :bt_numeric_method_call
|
||||||
token
|
token
|
||||||
when :bt_quantity
|
when :bt_quantity
|
||||||
"quantities['#{token}'][index]"
|
"quantities['#{token}'][_index]"
|
||||||
else
|
else
|
||||||
raise NotImplementedError, stmt.inspect
|
raise NotImplementedError, stmt.inspect
|
||||||
end
|
end
|
||||||
|
@ -62,10 +62,16 @@ class FormulaTest < ActiveSupport::TestCase
|
|||||||
{type: :indexed, content: "quantities['Fats'][_index].nil?||" \
|
{type: :indexed, content: "quantities['Fats'][_index].nil?||" \
|
||||||
"quantities['Fats'][_index]/quantities['Proteins'][_index]>2"}
|
"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|
|
vector.each_slice(3) do |formula, identifiers, parts|
|
||||||
parser = FormulaBuilder.new(formula)
|
parser = FormulaBuilder.new(formula, d_methods: d_methods)
|
||||||
i, p = parser.parse
|
i, p = parser.parse
|
||||||
assert_empty parser.errors
|
assert_empty parser.errors
|
||||||
assert_equal identifiers, i
|
assert_equal identifiers, i
|
||||||
|
Reference in New Issue
Block a user