RSpec.describe Parser do def parse(source) Parser.new(Lexer.new(source)).parse end it 'parses null' do expect(parse('null;')).to eq([AST::Null.new]) end it 'parses booleans' do expect(parse('true;')).to eq([AST::Boolean.new(true)]) expect(parse('false;')).to eq([AST::Boolean.new(false)]) end it 'parses numbers' do expect(parse('5;')).to eq([AST::Number.new(5.0)]) end it 'parses strings' do expect(parse('"hello world";')).to eq([AST::String.new('hello world')]) end it 'parses addition' do expect(parse('1 + 2;')).to eq( [ AST::Binary.new( AST::Operators::ADD, AST::Number.new(1.0), AST::Number.new(2.0) ) ] ) end it 'parses subtraction' do expect(parse('1 - 2;')).to eq( [ AST::Binary.new( AST::Operators::SUBTRACT, AST::Number.new(1.0), AST::Number.new(2.0) ) ] ) end it 'parses multiplication' do expect(parse('1 * 2;')).to eq( [ AST::Binary.new( AST::Operators::MULTIPLY, AST::Number.new(1.0), AST::Number.new(2.0) ) ] ) end it 'parses division' do expect(parse('1 / 2;')).to eq( [ AST::Binary.new( AST::Operators::DIVIDE, AST::Number.new(1.0), AST::Number.new(2.0) ) ] ) end it 'gives multiplication higher precedence than addition' do expect(parse('1 + 2 * 3;')).to eq( [ AST::Binary.new( AST::Operators::ADD, AST::Number.new(1.0), AST::Binary.new( AST::Operators::MULTIPLY, AST::Number.new(2.0), AST::Number.new(3.0) ) ) ] ) end it 'gives multiplication higher precedence than subtraction' do expect(parse('1 - 2 / 3;')).to eq( [ AST::Binary.new( AST::Operators::SUBTRACT, AST::Number.new(1.0), AST::Binary.new( AST::Operators::DIVIDE, AST::Number.new(2.0), AST::Number.new(3.0) ) ) ] ) end it 'parses if statement' do expect(parse('if true { "true"; }')).to eq( [ AST::Conditional.new( [ AST::Branch.new( AST::Boolean.new(true), AST::Block.new([AST::String.new('true')]) ) ] ) ] ) end it 'parses if else statement' do expect(parse('if true { "true"; } else { "false"; }')).to eq( [ AST::Conditional.new( [ AST::Branch.new( AST::Boolean.new(true), AST::Block.new([AST::String.new('true')]) ), AST::Branch.new( AST::Boolean.new(true), AST::Block.new([AST::String.new('false')]) ) ] ) ] ) end it 'parses if elseif else statement' do expect( parse('if true { "true"; } elseif false { "false"; } else { "neither"; }') ).to eq( [ AST::Conditional.new( [ AST::Branch.new( AST::Boolean.new(true), AST::Block.new([AST::String.new('true')]) ), AST::Branch.new( AST::Boolean.new(false), AST::Block.new([AST::String.new('false')]) ), AST::Branch.new( AST::Boolean.new(true), AST::Block.new([AST::String.new('neither')]) ) ] ) ] ) end it 'parses comparisons' do expect(parse('1 < 2;')).to eq( [ AST::Binary.new( AST::Operators::LESS_THAN, AST::Number.new(1.0), AST::Number.new(2.0) ) ] ) expect(parse('1 > 2;')).to eq( [ AST::Binary.new( AST::Operators::GREATER_THAN, AST::Number.new(1.0), AST::Number.new(2.0) ) ] ) expect(parse('1 <= 2;')).to eq( [ AST::Binary.new( AST::Operators::LESS_THAN_OR_EQUAL, AST::Number.new(1.0), AST::Number.new(2.0) ) ] ) expect(parse('1 >= 2;')).to eq( [ AST::Binary.new( AST::Operators::GREATER_THAN_OR_EQUAL, AST::Number.new(1.0), AST::Number.new(2.0) ) ] ) expect(parse('1 or 2;')).to eq( [ AST::Binary.new( AST::Operators::OR, AST::Number.new(1.0), AST::Number.new(2.0) ) ] ) expect(parse('1 and 2;')).to eq( [ AST::Binary.new( AST::Operators::AND, AST::Number.new(1.0), AST::Number.new(2.0) ) ] ) end it 'parses identifier' do expect(parse('x;')).to eq([AST::Identifier.new('x')]) end it 'parses variable declaration' do expect(parse('let x = 1;')).to eq( [ AST::VariableDeclaration.new( AST::Identifier.new('x'), AST::Number.new(1.0) ) ] ) end it 'parses function definition' do result = [ AST::FunctionDefinition.new( AST::Identifier.new('add_one'), [AST::Identifier.new('x')], AST::Block.new( [ AST::Binary.new( AST::Operators::ADD, AST::Identifier.new('x'), AST::Number.new(1.0) ) ] ) ) ] expect(parse('function add_one(x) { x + 1; }')).to eq(result) expect(parse('function add_one(x,) { x + 1; }')).to eq(result) end it 'parses function call' do result = [ AST::FunctionCall.new( AST::Identifier.new('func'), [AST::Number.new(1.0), AST::Number.new(2.0)] ) ] expect(parse('func(1, 2);')).to eq(result) expect(parse('func(1, 2,);')).to eq(result) end it 'parses array literal' do result = [ AST::Array.new( [AST::Number.new(1.0), AST::Number.new(2.0), AST::Number.new(3.0)] ) ] expect(parse('[1, 2, 3];')).to eq(result) expect(parse('[1, 2, 3,];')).to eq(result) end it 'parses a class definition do' do expect( parse(<<~CLASS) class Class { public foo, bar; private baz; method my_method() { "my method"; } classmethod my_classmethod() { "my classmethod"; } } CLASS ).to eq( [ AST::ClassDefinition.new( AST::Identifier.new('Class'), [ AST::PropertyDeclaration.new(AST::Identifier.new('foo'), true), AST::PropertyDeclaration.new(AST::Identifier.new('bar'), true), AST::PropertyDeclaration.new(AST::Identifier.new('baz'), false) ], [ AST::FunctionDefinition.new( AST::Identifier.new('my_method'), [], AST::Block.new([AST::String.new('method')]) ) ], [ AST::FunctionDefinition.new( AST::Identifier.new('my_classmethod'), [], AST::Block.new([AST::String.new('my classmethod')]) ) ] ) ] ) end it 'gives function calls higher precedence than binary operations' do expect(parse('x + func(y);')).to eq( [ AST::Binary.new( AST::Operators::ADD, AST::Identifier.new('x'), AST::FunctionCall.new( AST::Identifier.new('func'), [AST::Identifier.new('y')] ) ) ] ) end it 'parses hashes' do expect(parse('{ :a => 1, :b => 2 };')).to eq( [AST::Hash.new({ a: AST::Number.new(1.0), b: AST::Number.new(2.0) })] ) end it 'parses bracket indexing' do expect(parse('array[0];')).to eq( [AST::Index.new(AST::Identifier.new('array'), AST::Number.new(0))] ) end it 'parses atoms' do expect(parse(':atom;')).to eq([AST::Atom.new(:atom)]) end it 'parses unary expressions' do expect(parse('-1;')).to eq([AST::Unary.new(:-, AST::Number.new(1.0))]) expect(parse('!true;')).to eq([AST::Unary.new(:!, AST::Boolean.new(true))]) end it 'gives parenthetical expressions higher precedence' do expect(parse('(1 + 2) * 3;')).to eq( [ AST::Binary.new( AST::Operators::MULTIPLY, AST::Binary.new( AST::Operators::ADD, AST::Number.new(1.0), AST::Number.new(2.0) ), AST::Number.new(3.0) ) ] ) end it 'parses for loops' do expect(parse('for x in xs { print(x); }')).to eq( [ AST::ForLoop.new( AST::Identifier.new('x'), AST::Identifier.new('xs'), AST::Block.new( [ AST::FunctionCall.new( AST::Identifier.new('print'), [AST::Identifier.new('x')] ) ] ) ) ] ) end it 'parses a function expression' do expect(parse('let sum = function(x, y) { x + y; };')).to eq( [ AST::VariableDeclaration.new( AST::Identifier.new('sum'), AST::FunctionDefinition.new( nil, [AST::Identifier.new('x'), AST::Identifier.new('y')], AST::Block.new( [ AST::Binary.new( AST::Operators::ADD, AST::Identifier.new('x'), AST::Identifier.new('y') ) ] ) ) ) ] ) end it 'allows returning an anonymous function from another function' do expect( parse('function outer() { function() { print("Hello world"); }; }') ).to eq( [ AST::FunctionDefinition.new( AST::Identifier.new('outer'), [], AST::Block.new( [ AST::FunctionDefinition.new( nil, [], AST::Block.new( [ AST::FunctionCall.new( AST::Identifier.new('print'), [AST::String.new('Hello world')] ) ] ) ) ] ) ) ] ) end it 'allows toplevel function expressions' do expect(parse('function() { print("Hello world"); };')).to eq( [ AST::FunctionDefinition.new( nil, [], AST::Block.new( [ AST::FunctionCall.new( AST::Identifier.new('print'), [AST::String.new('Hello world')] ) ] ) ) ] ) end it 'parses assignments' do expect(parse('x = 5;')).to eq( [AST::Assignment.new(AST::Identifier.new('x'), AST::Number.new(5.0))] ) end it 'allows calling the result of a function call as a function' do expect(parse('func()();')).to eq( [ AST::FunctionCall.new( AST::FunctionCall.new(AST::Identifier.new('func'), []), [] ) ] ) end it 'allows arbitrarily chaining function calls and indexes' do expect(parse('func()[0]()[1][2]();')).to eq( [ AST::FunctionCall.new( AST::Index.new( AST::Index.new( AST::FunctionCall.new( AST::Index.new( AST::FunctionCall.new(AST::Identifier.new('func'), []), AST::Number.new(0.0) ), [] ), AST::Number.new(1.0) ), AST::Number.new(2.0) ), [] ) ] ) end it 'parses property access' do expect(parse('hello#world;')).to eq( [ AST::Property.new( AST::Identifier.new('hello'), AST::Identifier.new('world') ) ] ) end it 'parses allows calling properties (methods)' do expect(parse('hello#world();')).to eq( [ AST::FunctionCall.new( AST::Property.new( AST::Identifier.new('hello'), AST::Identifier.new('world') ), [] ) ] ) end end