require 'spec_helper'
require 'dentaku/token'
require 'dentaku/parser'

describe Dentaku::Parser do
  it 'is constructed from a token' do
    token = Dentaku::Token.new(:numeric, 5)
    node  = described_class.new([token]).parse
    expect(node.value).to eq 5
  end

  it 'performs simple addition' do
    five = Dentaku::Token.new(:numeric, 5)
    plus = Dentaku::Token.new(:operator, :add)
    four = Dentaku::Token.new(:numeric, 4)

    node = described_class.new([five, plus, four]).parse
    expect(node.value).to eq 9
  end

  it 'compares two numbers' do
    five = Dentaku::Token.new(:numeric, 5)
    lt   = Dentaku::Token.new(:comparator, :lt)
    four = Dentaku::Token.new(:numeric, 4)

    node = described_class.new([five, lt, four]).parse
    expect(node.value).to eq false
  end

  it 'calculates unary percentage' do
    five = Dentaku::Token.new(:numeric, 5)
    mod  = Dentaku::Token.new(:operator, :mod)

    node = described_class.new([five, mod]).parse
    expect(node.value).to eq 0.05
  end

  it 'calculates bitwise OR' do
    two   = Dentaku::Token.new(:numeric, 2)
    bitor = Dentaku::Token.new(:operator, :bitor)
    three = Dentaku::Token.new(:numeric, 3)

    node  = described_class.new([two, bitor, three]).parse
    expect(node.value).to eq 3
  end

  it 'performs multiple operations in one stream' do
    five  = Dentaku::Token.new(:numeric, 5)
    plus  = Dentaku::Token.new(:operator, :add)
    four  = Dentaku::Token.new(:numeric, 4)
    times = Dentaku::Token.new(:operator, :multiply)
    three = Dentaku::Token.new(:numeric, 3)

    node  = described_class.new([five, plus, four, times, three]).parse
    expect(node.value).to eq 17
  end

  it 'respects order of operations' do
    five  = Dentaku::Token.new(:numeric, 5)
    times = Dentaku::Token.new(:operator, :multiply)
    four  = Dentaku::Token.new(:numeric, 4)
    plus  = Dentaku::Token.new(:operator, :add)
    three = Dentaku::Token.new(:numeric, 3)

    node  = described_class.new([five, times, four, plus, three]).parse
    expect(node.value).to eq 23
  end

  it 'respects grouping by parenthesis' do
    lpar  = Dentaku::Token.new(:grouping, :open)
    five  = Dentaku::Token.new(:numeric, 5)
    plus  = Dentaku::Token.new(:operator, :add)
    four  = Dentaku::Token.new(:numeric, 4)
    rpar  = Dentaku::Token.new(:grouping, :close)
    times = Dentaku::Token.new(:operator, :multiply)
    three = Dentaku::Token.new(:numeric, 3)

    node  = described_class.new([lpar, five, plus, four, rpar, times, three]).parse
    expect(node.value).to eq 27
  end

  it 'evaluates functions' do
    fn    = Dentaku::Token.new(:function, :if)
    fopen = Dentaku::Token.new(:grouping, :open)
    five  = Dentaku::Token.new(:numeric, 5)
    lt    = Dentaku::Token.new(:comparator, :lt)
    four  = Dentaku::Token.new(:numeric, 4)
    comma = Dentaku::Token.new(:grouping, :comma)
    three = Dentaku::Token.new(:numeric, 3)
    two   = Dentaku::Token.new(:numeric, 2)
    rpar  = Dentaku::Token.new(:grouping, :close)

    node  = described_class.new([fn, fopen, five, lt, four, comma, three, comma, two, rpar]).parse
    expect(node.value).to eq 2
  end

  it 'represents formulas with variables' do
    five  = Dentaku::Token.new(:numeric, 5)
    times = Dentaku::Token.new(:operator, :multiply)
    x     = Dentaku::Token.new(:identifier, :x)

    node  = described_class.new([five, times, x]).parse
    expect { node.value }.to raise_error(Dentaku::UnboundVariableError)
    expect(node.value(x: 3)).to eq 15
  end

  it 'evaluates access into data structures' do
    a        = token(:a)
    lbracket = token(:lbracket)
    one      = token(1)
    rbracket = token(:rbracket)

    node = described_class.new([a, lbracket, one, rbracket]).parse
    expect { node.value }.to raise_error(Dentaku::UnboundVariableError)
    expect(node.value(a: [1, 2, 3])).to eq 2
  end

  it 'evaluates boolean expressions' do
    d_true  = Dentaku::Token.new(:logical, true)
    d_and   = Dentaku::Token.new(:combinator, :and)
    d_false = Dentaku::Token.new(:logical, false)

    node = described_class.new([d_true, d_and, d_false]).parse
    expect(node.value).to eq false
  end

  it 'evaluates a case statement' do
    case_start = Dentaku::Token.new(:case, :open)
    x = Dentaku::Token.new(:identifier, :x)
    case_when1 = Dentaku::Token.new(:case, :when)
    one = Dentaku::Token.new(:numeric, 1)
    case_then1 = Dentaku::Token.new(:case, :then)
    two = Dentaku::Token.new(:numeric, 2)
    case_when2 = Dentaku::Token.new(:case, :when)
    three = Dentaku::Token.new(:numeric, 3)
    case_then2 = Dentaku::Token.new(:case, :then)
    four = Dentaku::Token.new(:numeric, 4)
    case_close = Dentaku::Token.new(:case, :close)

    node = described_class.new(
      [case_start,
       x,
       case_when1,
       one,
       case_then1,
       two,
       case_when2,
       three,
       case_then2,
       four,
       case_close]).parse
    expect(node.value(x: 3)).to eq(4)
  end

  context 'invalid expression' do
    it 'raises a parse error for bad math' do
      five  = Dentaku::Token.new(:numeric, 5)
      times = Dentaku::Token.new(:operator, :multiply)
      minus = Dentaku::Token.new(:operator, :subtract)

      expect {
        described_class.new([five, times, minus]).parse
      }.to raise_error(Dentaku::ParseError)
    end

    it 'raises a parse error for bad logic' do
      this = Dentaku::Token.new(:logical, true)
      also = Dentaku::Token.new(:combinator, :and)

      expect {
        described_class.new([this, also]).parse
      }.to raise_error(Dentaku::ParseError)
    end

    it 'raises an exception when trying to access an undefined function' do
      fn = Dentaku::Token.new(:function, 'non_exists_func')

      expect {
        described_class.new([fn]).parse
      }.to raise_error(Dentaku::ParseError)
    end
  end

  it "evaluates explicit 'NULL' as a Nil" do
    null = Dentaku::Token.new(:null, nil)
    node = described_class.new([null]).parse
    expect(node.value).to eq(nil)
  end
end