require 'spec_helper' require 'dentaku/ast/arithmetic' require 'dentaku' describe Dentaku::AST::Arithmetic do let(:one) { Dentaku::AST::Numeric.new(Dentaku::Token.new(:numeric, 1)) } let(:two) { Dentaku::AST::Numeric.new(Dentaku::Token.new(:numeric, 2)) } let(:x) { Dentaku::AST::Identifier.new(Dentaku::Token.new(:identifier, 'x')) } let(:y) { Dentaku::AST::Identifier.new(Dentaku::Token.new(:identifier, 'y')) } let(:ctx) { {'x' => 1, 'y' => 2} } let(:date) { Dentaku::AST::DateTime.new(Dentaku::Token.new(:datetime, DateTime.new(2020, 4, 16))) } it 'performs an arithmetic operation with numeric operands' do expect(add(one, two)).to eq(3) expect(sub(one, two)).to eq(-1) expect(mul(one, two)).to eq(2) expect(div(one, two)).to eq(0.5) expect(neg(one)).to eq(-1) end it 'performs an arithmetic operation with one numeric operand and one string operand' do expect(add(one, x)).to eq(2) expect(sub(one, x)).to eq(0) expect(mul(one, x)).to eq(1) expect(div(one, x)).to eq(1) expect(add(y, two)).to eq(4) expect(sub(y, two)).to eq(0) expect(mul(y, two)).to eq(4) expect(div(y, two)).to eq(1) end it 'performs an arithmetic operation with string operands' do expect(add(x, y)).to eq(3) expect(sub(x, y)).to eq(-1) expect(mul(x, y)).to eq(2) expect(div(x, y)).to eq(0.5) expect(neg(x)).to eq(-1) end it 'correctly parses string operands to numeric values' do expect(add(x, one, 'x' => '1')).to eq(2) expect(add(x, one, 'x' => '1.1')).to eq(2.1) expect(add(x, one, 'x' => '.1')).to eq(1.1) expect { add(x, one, 'x' => 'invalid') }.to raise_error(Dentaku::ArgumentError) expect { add(x, one, 'x' => '') }.to raise_error(Dentaku::ArgumentError) int_one = Dentaku::AST::Numeric.new(Dentaku::Token.new(:numeric, "1")) int_neg_one = Dentaku::AST::Numeric.new(Dentaku::Token.new(:numeric, "-1")) decimal_one = Dentaku::AST::Numeric.new(Dentaku::Token.new(:numeric, "1.0")) decimal_neg_one = Dentaku::AST::Numeric.new(Dentaku::Token.new(:numeric, "-1.0")) [int_one, int_neg_one].permutation(2).each do |(left, right)| expect(add(left, right).class).to eq(Integer) end [decimal_one, decimal_neg_one].each do |left| [int_one, int_neg_one, decimal_one, decimal_neg_one].each do |right| expect(add(left, right).class).to eq(BigDecimal) end end end it 'performs arithmetic on arrays' do expect(add(x, y, 'x' => [1], 'y' => [2])).to eq([1, 2]) expect(sub(x, y, 'x' => [1], 'y' => [2])).to eq([1]) end it 'performs date arithmetic' do expect(add(date, one)).to eq(DateTime.new(2020, 4, 17)) expect(sub(date, one)).to eq(DateTime.new(2020, 4, 15)) end it 'performs arithmetic on object which implements arithmetic' do CanHazMath = Struct.new(:value) do extend Forwardable def_delegators :value, :zero? def coerce(other) case other when Numeric [other, value] else super end end [:+, :-, :/, :*].each do |operand| define_method(operand) do |other| case other when CanHazMath value.public_send(operand, other.value) when Numeric value.public_send(operand, other) end end end end op_one = CanHazMath.new(1) op_two = CanHazMath.new(2) [op_two, two].each do |left| [op_one, one].each do |right| expect(add(x, y, 'x' => left, 'y' => right)).to eq(3) expect(sub(x, y, 'x' => left, 'y' => right)).to eq(1) expect(mul(x, y, 'x' => left, 'y' => right)).to eq(2) expect(div(x, y, 'x' => left, 'y' => right)).to eq(2) end end end it 'raises ArgumentError if given individually valid but incompatible arguments' do expect { add(one, date) }.to raise_error(Dentaku::ArgumentError) expect { add(x, one, 'x' => [1]) }.to raise_error(Dentaku::ArgumentError) end private def add(left, right, context = ctx) Dentaku::AST::Addition.new(left, right).value(context) end def sub(left, right, context = ctx) Dentaku::AST::Subtraction.new(left, right).value(context) end def mul(left, right, context = ctx) Dentaku::AST::Multiplication.new(left, right).value(context) end def div(left, right, context = ctx) Dentaku::AST::Division.new(left, right).value(context) end def neg(node, context = ctx) Dentaku::AST::Negation.new(node).value(context) end end