spec/external_function_spec.rb in dentaku-3.5.1 vs spec/external_function_spec.rb in dentaku-3.5.2

- old
+ new

@@ -3,12 +3,11 @@ require 'dentaku/calculator' describe Dentaku::Calculator do describe 'functions' do describe 'external functions' do - - let(:with_external_funcs) do + let(:custom_calculator) do c = described_class.new c.add_function(:now, :string, -> { Time.now.to_s }) fns = [ @@ -20,34 +19,34 @@ c.add_functions(fns) end it 'includes NOW' do - now = with_external_funcs.evaluate('NOW()') + now = custom_calculator.evaluate('NOW()') expect(now).not_to be_nil expect(now).not_to be_empty end it 'includes POW' do - expect(with_external_funcs.evaluate('POW(2,3)')).to eq(8) - expect(with_external_funcs.evaluate('POW(3,2)')).to eq(9) - expect(with_external_funcs.evaluate('POW(mantissa,exponent)', mantissa: 2, exponent: 4)).to eq(16) + expect(custom_calculator.evaluate('POW(2,3)')).to eq(8) + expect(custom_calculator.evaluate('POW(3,2)')).to eq(9) + expect(custom_calculator.evaluate('POW(mantissa,exponent)', mantissa: 2, exponent: 4)).to eq(16) end it 'includes BIGGEST' do - expect(with_external_funcs.evaluate('BIGGEST(8,6,7,5,3,0,9)')).to eq(9) + expect(custom_calculator.evaluate('BIGGEST(8,6,7,5,3,0,9)')).to eq(9) end it 'includes SMALLEST' do - expect(with_external_funcs.evaluate('SMALLEST(8,6,7,5,3,0,9)')).to eq(0) + expect(custom_calculator.evaluate('SMALLEST(8,6,7,5,3,0,9)')).to eq(0) end it 'includes OPTIONAL' do - expect(with_external_funcs.evaluate('OPTIONAL(1,2)')).to eq(3) - expect(with_external_funcs.evaluate('OPTIONAL(1,2,3)')).to eq(6) - expect { with_external_funcs.dependencies('OPTIONAL()') }.to raise_error(Dentaku::ParseError) - expect { with_external_funcs.dependencies('OPTIONAL(1,2,3,4)') }.to raise_error(Dentaku::ParseError) + expect(custom_calculator.evaluate('OPTIONAL(1,2)')).to eq(3) + expect(custom_calculator.evaluate('OPTIONAL(1,2,3)')).to eq(6) + expect { custom_calculator.dependencies('OPTIONAL()') }.to raise_error(Dentaku::ParseError) + expect { custom_calculator.dependencies('OPTIONAL(1,2,3,4)') }.to raise_error(Dentaku::ParseError) end it 'supports array parameters' do calculator = described_class.new calculator.add_function( @@ -60,10 +59,70 @@ expect(calculator.evaluate("INCLUDES(list, 2)", list: [1, 2, 3])).to eq(true) end end + describe 'with callbacks' do + let(:custom_calculator) do + c = described_class.new + + @counts = Hash.new(0) + + @initial_time = "2023-02-03" + @last_time = @initial_time + + c.add_function( + :reverse, + :stringl, + ->(a) { a.reverse }, + lambda do |args| + args.each do |arg| + @counts[arg.value] += 1 if arg.type == :string + end + end + ) + + fns = [ + [:biggest_callback, :numeric, ->(*args) { args.max }, ->(args) { args.each { |arg| raise Dentaku::ArgumentError unless arg.type == :numeric } }], + [:pythagoras, :numeric, ->(l1, l2) { Math.sqrt(l1**2 + l2**2) }, ->(e) { @last_time = Time.now.to_s }], + [:callback_lambda, :string, ->() { " " }, ->() { "lambda executed" }], + [:no_lambda_function, :numeric, ->(a) { a**a }], + ] + + c.add_functions(fns) + end + + it 'includes BIGGEST_CALLBACK' do + expect(custom_calculator.evaluate('BIGGEST_CALLBACK(1, 2, 5, 4)')).to eq(5) + expect { custom_calculator.dependencies('BIGGEST_CALLBACK(1, 3, 6, "hi", 10)') }.to raise_error(Dentaku::ArgumentError) + end + + it 'includes REVERSE' do + expect(custom_calculator.evaluate('REVERSE(\'Dentaku\')')).to eq('ukatneD') + expect { custom_calculator.evaluate('REVERSE(22)') }.to raise_error(NoMethodError) + expect(@counts["Dentaku"]).to eq(1) + end + + it 'includes PYTHAGORAS' do + expect(custom_calculator.evaluate('PYTHAGORAS(8, 7)')).to eq(10.63014581273465) + expect(custom_calculator.evaluate('PYTHAGORAS(3, 4)')).to eq(5) + expect(@last_time).not_to eq(@initial_time) + end + + it 'exposes the `callback` method of a function' do + expect(Dentaku::AST::Function::Callback_lambda.callback.call()).to eq("lambda executed") + end + + it 'does not add a `callback` method to built-in functions' do + expect { Dentaku::AST::If.callback.call }.to raise_error(NoMethodError) + end + + it 'defaults `callback` method to nil if not specified' do + expect(Dentaku::AST::Function::No_lambda_function.callback).to eq(nil) + end + end + it 'allows registering "bang" functions' do calculator = described_class.new calculator.add_function(:hey!, :string, -> { "hey!" }) expect(calculator.evaluate("hey!()")).to eq("hey!") end @@ -80,27 +139,39 @@ Marshal.dump(calculator.ast('MAX(1, 2)')) }.not_to raise_error end it 'does not store functions across all calculators' do - calculator1 = Dentaku::Calculator.new + calculator1 = described_class.new calculator1.add_function(:my_function, :numeric, ->(x) { 2 * x + 1 }) - calculator2 = Dentaku::Calculator.new + calculator2 = described_class.new calculator2.add_function(:my_function, :numeric, ->(x) { 4 * x + 3 }) expect(calculator1.evaluate!("1 + my_function(2)")). to eq(1 + 2 * 2 + 1) expect(calculator2.evaluate!("1 + my_function(2)")). to eq(1 + 4 * 2 + 3) expect { - Dentaku::Calculator.new.evaluate!("1 + my_function(2)") + described_class.new.evaluate!("1 + my_function(2)") }.to raise_error(Dentaku::ParseError) end describe 'Dentaku::Calculator.add_function' do - it 'adds to default/global function registry' do - Dentaku::Calculator.add_function(:global_function, :numeric, ->(x) { 10 + x**2 }) - expect(Dentaku::Calculator.new.evaluate("global_function(3) + 5")).to eq(10 + 3**2 + 5) + it 'adds a function to default/global function registry' do + described_class.add_function(:global_function, :numeric, ->(x) { 10 + x**2 }) + expect(described_class.new.evaluate("global_function(3) + 5")).to eq(10 + 3**2 + 5) + end + end + + describe 'Dentaku::Calculator.add_functions' do + it 'adds multiple functions to default/global function registry' do + described_class.add_functions([ + [:cube, :numeric, ->(x) { x**3 }], + [:spongebob, :string, ->(x) { x.split("").each_with_index().map { |c,i| i.even? ? c.upcase : c.downcase }.join() }], + ]) + + expect(described_class.new.evaluate("1 + cube(3)")).to eq(28) + expect(described_class.new.evaluate("spongebob('How are you today?')")).to eq("HoW ArE YoU ToDaY?") end end end end