require 'spec_helper'

describe 'Infix expression parsing' do
  class InfixExpressionParser < Parslet::Parser
    rule(:space) { match['\s'] }

    def cts atom
      atom >> space.repeat
    end
    def infix *args
      Infix.new(*args)
    end

    rule(:mul_op) { match['*/'] >> str(' ').maybe }
    rule(:add_op) { match['+-'] >> str(' ').maybe }
    rule(:digit) { match['0-9'] }
    rule(:integer) { cts digit.repeat(1) }

    rule(:expression) { infix_expression(integer, 
      [mul_op, 2, :left], 
      [add_op, 1, :right]) }
  end

  let(:p) { InfixExpressionParser.new }
  describe '#integer' do
    let(:i) { p.integer }
    it "parses integers" do
      i.should parse('1')
      i.should parse('123')
    end 
    it "consumes trailing white space" do
      i.should parse('1   ')
      i.should parse('134   ')
    end 
    it "doesn't parse floats" do
      i.should_not parse('1.3')
    end 
  end
  describe '#multiplication' do
    let(:m) { p.expression }
    it "parses simple multiplication" do
      m.should parse('1*2').as(l: '1', o: '*', r: '2')
    end
    it "parses simple multiplication with spaces" do
      m.should parse('1 * 2').as(l: '1 ', o: '* ', r: '2')
    end
    it "parses division" do
      m.should parse('1/2')
    end 
  end
  describe '#addition' do
    let(:a) { p.expression }
    
    it "parses simple addition" do
      a.should parse('1+2')
    end 
    it "parses complex addition" do
      a.should parse('1+2+3-4')
    end
    it "parses a single element" do
      a.should parse('1')
    end
  end

  describe 'mixed operations' do
    let(:mo) { p.expression }

    describe 'inspection' do
      it 'produces useful expressions' do
        p.expression.parslet.inspect.should == 
          "infix_expression(INTEGER, [MUL_OP, ADD_OP])"
      end
    end
    describe 'right associativity' do
      it 'produces trees that lean right' do
        mo.should parse('1+2+3').as(
          l: '1', o: '+', r: {l: '2', o: '+', r: '3'})
      end      
    end
    describe 'left associativity' do
      it 'produces trees that lean left' do
        mo.should parse('1*2*3').as(
          l: {l:'1', o:'*', r:'2'}, o:'*', r:'3')
      end      
    end
    describe 'error handling' do
      describe 'incomplete expression' do
        it 'produces the right error' do
          cause = catch_failed_parse {
            mo.parse('1+') }

          cause.ascii_tree.to_s.should == <<-ERROR
INTEGER was expected at line 1 char 3.
`- Failed to match sequence (DIGIT{1, } SPACE{0, }) at line 1 char 3.
   `- Expected at least 1 of DIGIT at line 1 char 3.
      `- Premature end of input at line 1 char 3.
          ERROR
        end
      end
      describe 'invalid operator' do
        it 'produces the right error' do
          cause = catch_failed_parse {
            mo.parse('1%') }

          cause.ascii_tree.to_s.should == <<-ERROR
Don't know what to do with "%" at line 1 char 2.
          ERROR
        end
      end
    end
  end
  describe "providing a reducer block" do
    class InfixExpressionReducerParser < Parslet::Parser
      rule(:top) { infix_expression(str('a'), [str('-'), 1, :right]) { |l,o,r| {:and=>[l,r]} } }
    end
    
    it "applies the reducer" do
      InfixExpressionReducerParser.new.top.parse("a-a-a").should == {:and=>["a", {:and=>["a", "a"]}]}
    end
  end
end