123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522 |
- 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;
- function method() {
- "method";
- }
- }
- 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('method'),
- [],
- AST::Block.new([AST::String.new('method')])
- )
- ]
- )
- ]
- )
- 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
|