eturk/marvin

View on GitHub
spec/lib/marvin/parser_spec.rb

Summary

Maintainability
D
2 days
Test Coverage
require 'spec_helper'

describe Marvin::Parser do
  it 'inherits from RLTK::Parser' do
    expect(subject).to be_a RLTK::Parser
  end

  describe '.parse' do
    describe 'arithmetic' do
      context 'addition' do
        let(:ast) { get_ast('4 + 4') }
        let(:addition) { ast.first }

        it 'creates an Addition node' do
          expect(addition).to be_a Marvin::AST::Addition
        end

        it 'has an Integer node on the left side' do
          expect(addition.left).to be_a Marvin::AST::Integer
        end

        it 'sets the value on the left side' do
          expect(addition.left.value).to eql 4
        end

        it 'has an Integer node on the right side' do
          expect(addition.right).to be_a Marvin::AST::Integer
        end

        it 'sets the value on the right side' do
          expect(addition.right.value).to eql 4
        end
      end

      context 'subtraction' do
        let(:ast) { get_ast('4 - 4') }
        let(:subtraction) { ast.first }

        it 'creates an Subtraction node' do
          expect(subtraction).to be_a Marvin::AST::Subtraction
        end

        it 'has an Integer node on the left side' do
          expect(subtraction.left).to be_a Marvin::AST::Integer
        end

        it 'sets the value on the left side' do
          expect(subtraction.left.value).to eql 4
        end

        it 'has an Integer node on the right side' do
          expect(subtraction.right).to be_a Marvin::AST::Integer
        end

        it 'sets the value on the right side' do
          expect(subtraction.right.value).to eql 4
        end
      end

      context 'multiplication' do
        let(:ast) { get_ast('4 * 4') }
        let(:multiplication) { ast.first }

        it 'creates an Multiplication node' do
          expect(multiplication).to be_a Marvin::AST::Multiplication
        end

        it 'has an Integer node on the left side' do
          expect(multiplication.left).to be_a Marvin::AST::Integer
        end

        it 'sets the value on the left side' do
          expect(multiplication.left.value).to eql 4
        end

        it 'has an Integer node on the right side' do
          expect(multiplication.right).to be_a Marvin::AST::Integer
        end

        it 'sets the value on the right side' do
          expect(multiplication.right.value).to eql 4
        end
      end

      context 'division' do
        let(:ast) { get_ast('4 / 4') }
        let(:division) { ast.first }

        it 'creates an Division node' do
          expect(division).to be_a Marvin::AST::Division
        end

        it 'has an Integer node on the left side' do
          expect(division.left).to be_a Marvin::AST::Integer
        end

        it 'sets the value on the left side' do
          expect(division.left.value).to eql 4
        end

        it 'has an Integer node on the right side' do
          expect(division.right).to be_a Marvin::AST::Integer
        end

        it 'sets the value on the right side' do
          expect(division.right.value).to eql 4
        end
      end

      context 'chaining multiple operations' do
        let(:ast) { get_ast('4 - 4 * 4 + 4 / 4') }
        let(:rightmost) { ast.first }

        it 'creates an Division node' do
          expect(rightmost).to be_a Marvin::AST::Division
        end

        it 'has an Addition node on the left side' do
          expect(rightmost.left).to be_a Marvin::AST::Addition
        end

        it 'has an Integer node on the right side' do
          expect(rightmost.right).to be_a Marvin::AST::Integer
        end

        it 'sets the value on the right side' do
          expect(rightmost.right.value).to eql 4
        end
      end
    end

    describe 'assignment' do
      let(:ast) { get_ast('a = 4') }
      let(:assignment) { ast.first }

      it 'creates an Assignment node' do
        expect(assignment).to be_a Marvin::AST::Assignment
      end

      it 'has a Symbol as a name' do
        expect(assignment.name).to be_a Symbol
      end

      it 'sets the value of the name' do
        expect(assignment.name).to eql :a
      end

      it 'has an Expression as the value' do
        expect(assignment.value).to be_a_kind_of Marvin::AST::Expression
      end
    end

    describe 'block' do
      let(:ast) { get_ast('if (true) { }') }
      let(:block) { ast.last.body }

      it 'creates a Block node' do
        expect(block).to be_a Marvin::AST::Block
      end

      it 'has an empty body' do
        expect(block.body).to eql []
      end
    end

    describe 'boolean' do
      let(:ast) { get_ast("a = true\nb = false") }
      let(:boolean_true) { ast.first }
      let(:boolean_false) { ast.last }

      it 'creates a Boolean node' do
        expect(ast.first.value).to be_a Marvin::AST::Boolean
      end

      it 'casts to a number for storage' do
        expect(boolean_true.value._value).to be_a Fixnum
        expect(boolean_true.value._value).to eql 1
        expect(boolean_false.value._value).to eql 0
      end

      describe '#to_bool' do
        it 'returns 1 as true' do
          expect(boolean_true.value.value).to eql true
        end

        it 'returns 0 as false' do
          expect(boolean_false.value.value).to eql false
        end
      end
    end

    describe 'call' do
      let(:ast) { get_ast("foo\nbar(1)") }
      let(:call_variable) { ast.first }
      let(:call_function) { ast.last }

      it 'creates a Call node' do
        expect(call_variable).to be_a Marvin::AST::Call
        expect(call_function).to be_a Marvin::AST::Call
      end

      it 'has a Symbol as a name' do
        expect(call_variable.name).to be_a Symbol
      end

      it 'sets the name' do
        expect(call_variable.name).to eql :foo
        expect(call_function.name).to eql :bar
      end

      it 'has an Array for the arguments' do
        expect(call_variable.arguments).to be_a Array
      end

      context 'variable calls' do
        it 'has no arguments' do
          expect(call_variable.arguments).to be_empty
        end
      end

      context 'function calls' do
        it 'sets arguments' do
          expect(call_function.arguments.first).to be_a Marvin::AST::Integer
          expect(call_function.arguments.first.value).to eql 1
        end
      end
    end

    describe 'float' do
      let(:ast) { get_ast('4.0') }
      let(:float) { ast.first }

      it 'creates a Float node' do
        expect(float).to be_a Marvin::AST::Float
      end

      it 'has a Float as a value' do
        expect(float.value).to be_a Float
      end

      it 'sets the value' do
        expect(float.value).to eql 4.0
      end
    end

    describe 'function' do
      let(:ast) { get_ast('fun foo(biz, baz) { 4 }') }
      let(:function) { ast.first }

      it 'creates a Function node' do
        expect(function).to be_a Marvin::AST::Function
      end

      it 'has a Symbol as a name' do
        expect(function.name).to be_a Symbol
      end

      it 'sets the name' do
        expect(function.name).to eql :foo
      end

      it 'has an Array for parameters' do
        expect(function.parameters).to be_a Array
      end

      it 'sets the parameters' do
        expect(function.parameters).to eql [:biz, :baz]
      end

      it 'has a Block for the body' do
        expect(function.body).to be_a Marvin::AST::Block
      end

      it 'sets the body' do
        expect(function.body.body.first).to be_a Marvin::AST::Integer
        expect(function.body.body.first.value).to eql 4
      end
    end

    describe 'if' do
      context 'with test' do
        let(:ast) { get_ast('if (4 == 4) { 4 }') }
        let(:iff) { ast.first }

        it 'creates an If node' do
          expect(iff).to be_a Marvin::AST::If
        end

        it 'has a Test as a test' do
          expect(iff.test).to be_a Marvin::AST::Test
        end

        it 'sets the test' do
          expect(iff.test.left.value).to eql 4
          expect(iff.test.right.value).to eql 4
        end

        it 'has a Block as a body' do
          expect(iff.body).to be_a Marvin::AST::Block
        end

        it 'sets the body' do
          expect(iff.body.body.first).to be_a Marvin::AST::Integer
          expect(iff.body.body.first.value).to eql 4
        end
      end

      context 'with boolean' do
        let(:ast) { get_ast('if (true) { 4 }') }
        let(:iff) { ast.first }

        it 'creates an If node' do
          expect(iff).to be_a Marvin::AST::If
        end

        it 'has a Test as a test' do
          expect(iff.test).to be_a Marvin::AST::Test
        end

        it 'sets the test' do
          expect(iff.test.left.value).to eql true
          expect(iff.test.right.value).to eql true
        end

        it 'has a Block as a body' do
          expect(iff.body).to be_a Marvin::AST::Block
        end

        it 'sets the body' do
          expect(iff.body.body.first).to be_a Marvin::AST::Integer
          expect(iff.body.body.first.value).to eql 4
        end
      end
    end

    describe 'integer' do
      let(:ast) { get_ast('4') }
      let(:integer) { ast.first }

      it 'creates an Integer node' do
        expect(integer).to be_a Marvin::AST::Integer
      end

      it 'has a Fixnum as the value' do
        expect(integer.value).to be_a Fixnum
      end

      it 'sets the value' do
        expect(integer.value).to eql 4
      end
    end

    describe 'print' do
      let(:ast) { get_ast('print(4)') }
      let(:print) { ast.first }

      it 'creates a Print node' do
        expect(print).to be_a Marvin::AST::Print
      end

      it 'has an Expression as a body' do
        expect(print.body).to be_a_kind_of Marvin::AST::Expression
      end

      it 'sets the body' do
        expect(print.body.value).to eql 4
      end
    end

    describe 'program' do
      let(:program) { Marvin::Parser.parse(Marvin::Lexer.lex('4')) }

      it 'creates a Program node' do
        expect(program).to be_a Marvin::AST::Program
      end

      it 'has an Array for contents' do
        expect(program.contents).to be_a Array
      end

      it 'sets the contents' do
        expect(program.contents.first.value).to eql 4
      end
    end

    describe 'return' do
      let(:ast) { get_ast('return 4') }
      let(:returnn) { ast.first }

      it 'creates a Call node' do
        expect(returnn).to be_a Marvin::AST::Call
      end

      it 'has a Symbol as a name' do
        expect(returnn.name).to be_a Symbol
      end

      it 'sets the name to `:return`' do
        expect(returnn.name).to eql :return
      end

      it 'has an Array for the arguments' do
        expect(returnn.arguments).to be_a Array
      end

      it 'sets the arguments to the given expression' do
        expect(returnn.arguments.first.value).to eql 4
      end
    end

    describe 'string' do
      let(:ast) { get_ast('"foobar"') }
      let(:string) { ast.first }

      it 'creates a String node' do
        expect(string).to be_a Marvin::AST::String
      end

      it 'has a String as the value' do
        expect(string.value).to be_a String
      end

      it 'sets the value' do
        expect(string.value).to eql '"foobar"'
      end
    end

    describe 'test' do
      context 'equal to' do
        let(:ast) { get_ast('4 == 4') }
        let(:equal_to) { ast.first }

        it 'creates an EqualTo node' do
          expect(equal_to).to be_a Marvin::AST::EqualTo
        end

        it 'has an Integer node on the left side' do
          expect(equal_to.left).to be_a Marvin::AST::Integer
        end

        it 'sets the value on the left side' do
          expect(equal_to.left.value).to eql 4
        end

        it 'has an Integer node on the right side' do
          expect(equal_to.right).to be_a Marvin::AST::Integer
        end

        it 'sets the value on the right side' do
          expect(equal_to.right.value).to eql 4
        end
      end

      context 'not equal to' do
        let(:ast) { get_ast('4 != 4') }
        let(:not_equal_to) { ast.first }

        it 'creates an NotEqualTo node' do
          expect(not_equal_to).to be_a Marvin::AST::NotEqualTo
        end

        it 'has an Integer node on the left side' do
          expect(not_equal_to.left).to be_a Marvin::AST::Integer
        end

        it 'sets the value on the left side' do
          expect(not_equal_to.left.value).to eql 4
        end

        it 'has an Integer node on the right side' do
          expect(not_equal_to.right).to be_a Marvin::AST::Integer
        end

        it 'sets the value on the right side' do
          expect(not_equal_to.right.value).to eql 4
        end
      end

      context 'less than' do
        let(:ast) { get_ast('4 < 4') }
        let(:less_than) { ast.first }

        it 'creates an LessThan node' do
          expect(less_than).to be_a Marvin::AST::LessThan
        end

        it 'has an Integer node on the left side' do
          expect(less_than.left).to be_a Marvin::AST::Integer
        end

        it 'sets the value on the left side' do
          expect(less_than.left.value).to eql 4
        end

        it 'has an Integer node on the right side' do
          expect(less_than.right).to be_a Marvin::AST::Integer
        end

        it 'sets the value on the right side' do
          expect(less_than.right.value).to eql 4
        end
      end

      context 'greater than' do
        let(:ast) { get_ast('4 > 4') }
        let(:greater_than) { ast.first }

        it 'creates an LessThan node' do
          expect(greater_than).to be_a Marvin::AST::GreaterThan
        end

        it 'has an Integer node on the left side' do
          expect(greater_than.left).to be_a Marvin::AST::Integer
        end

        it 'sets the value on the left side' do
          expect(greater_than.left.value).to eql 4
        end

        it 'has an Integer node on the right side' do
          expect(greater_than.right).to be_a Marvin::AST::Integer
        end

        it 'sets the value on the right side' do
          expect(greater_than.right.value).to eql 4
        end
      end
    end
  end
end

def get_ast(source)
  Marvin::Parser.parse(Marvin::Lexer.lex(source)).contents
end