RSpec.describe AST::FunctionCall do it 'evaluates built-in functions' do expect { AST::FunctionCall.new( AST::Identifier.new('print'), [AST::String.new('hello world')] ) .execute(Environment.new) }.to output("hello world\n").to_stdout end it 'evaluates user-defined functions' do env = Environment.new AST::FunctionDefinition.new( AST::Identifier.new('add_one'), [AST::Identifier.new('n')], AST::Block.new( [ AST::Binary.new( AST::Operators::ADD, AST::Identifier.new('n'), AST::Number.new(1.0) ) ] ) ) .execute(env) expect( AST::FunctionCall.new( AST::Identifier.new('add_one'), [AST::Number.new(5.0)] ) .execute(env) ).to eq(6.0) end # This corresponds to the program # function factorial(n) { # if n == 0 { # 1; # } else { # n * factorial(n - 1); # } # } # factorial(5); it 'evaluates recursive functions' do env = Environment.new AST::FunctionDefinition.new( AST::Identifier.new('factorial'), [AST::Identifier.new('n')], AST::Block.new( [ AST::Conditional.new( [ AST::Branch.new( AST::Binary.new( AST::Operators::DOUBLE_EQUALS, AST::Identifier.new('n'), AST::Number.new(0.0) ), AST::Block.new([AST::Number.new(1.0)]) ), AST::Branch.new( AST::Boolean.new(true), AST::Block.new( [ AST::Binary.new( AST::Operators::MULTIPLY, AST::Identifier.new('n'), AST::FunctionCall.new( AST::Identifier.new('factorial'), [ AST::Binary.new( AST::Operators::SUBTRACT, AST::Identifier.new('n'), AST::Number.new(1.0) ) ] ) ) ] ) ) ] ) ] ) ) .execute(env) expect( AST::FunctionCall.new( AST::Identifier.new('factorial'), [AST::Number.new(5.0)] ) .execute(env) ).to eq(120.0) end # This is equivalent to the program # # function add_one(n) { # n + 1; # } # add_one(5); # print(n); # # This test is ensuring that `n` is not still defined outside the scope of # add_one after the function finishes executing it 'destroys variables when they go out of scope' do env = Environment.new AST::FunctionDefinition.new( AST::Identifier.new('add_one'), [AST::Identifier.new('n')], AST::Block.new( [ AST::Binary.new( AST::Operators::ADD, AST::Identifier.new('n'), AST::Number.new(1.0) ) ] ) ) .execute(env) AST::FunctionCall.new(AST::Identifier.new('add_one'), [AST::Number.new(5)]) .execute(env) expect { AST::FunctionCall.new( AST::Identifier.new('print'), [AST::Identifier.new('n')] ) .execute(env) }.to raise_error('Undefined variable n') end # let add_one = function(n) { n + 1; }; # let do = function(f, n) { f(n); }; # do(5); it 'executes higher order functions' do env = Environment.new AST::VariableDeclaration.new( AST::Identifier.new('add_one'), AST::FunctionDefinition.new( nil, [AST::Identifier.new('n')], AST::Block.new( [ AST::Binary.new( AST::Operators::ADD, AST::Identifier.new('n'), AST::Number.new(1.0) ) ] ) ) ) .execute(env) AST::VariableDeclaration.new( AST::Identifier.new('do'), AST::FunctionDefinition.new( nil, [AST::Identifier.new('f'), AST::Identifier.new('x')], AST::Block.new( [ AST::FunctionCall.new( AST::Identifier.new('f'), [AST::Identifier.new('x')] ) ] ) ) ) .execute(env) result = AST::FunctionCall.new( AST::Identifier.new('do'), [AST::Identifier.new('add_one'), AST::Number.new(5.0)] ) .execute(env) expect(result).to eq(6) end it 'allows calling an anonymous function directly' do result = AST::FunctionCall.new( AST::FunctionDefinition.new( nil, [], AST::Block.new( [ AST::Number.new(5.0) ] ) ), [] ).execute(Environment.new) expect(result).to eq(5.0) end # function outer() { # let x = "hello world"; # function () { # x; # }; # } # let y = outer(); # y(); it 'keeps a reference to a closure\'s environment' do env = Environment.new AST::FunctionDefinition.new( AST::Identifier.new('outer'), [], AST::Block.new( [ AST::VariableDeclaration.new( AST::Identifier.new('x'), AST::String.new('hello world') ), AST::FunctionDefinition.new( nil, [], AST::Block.new( [ AST::Identifier.new('x'), ] ) ) ] ) ).execute(env) AST::VariableDeclaration.new( AST::Identifier.new('y'), AST::FunctionCall.new( AST::Identifier.new('outer'), [] ) ).execute(env) result = AST::FunctionCall.new( AST::Identifier.new('y'), [] ).execute(env) expect(result).to eq("hello world") end end