#coding:utf-8

require 'spec_helper'

describe Liquor::Parser do
  it "parses plaintext" do
    parse("abc\ndef").should have_node_structure(
      [:plaintext, "abc\ndef"]
    )
  end

  it "recognizes interpolations" do
    parse("a {{ 1 + a }} b").should have_node_structure(
      [:plaintext, "a "],
      [:interp,
        [:plus,
          [:integer, 1],
          [:ident, "a"]]],
      [:plaintext, " b"],
    )
  end

  it "fails on incorrect syntax" do
    expect { parse("{{ }}") }.to raise_error(Liquor::SyntaxError, %r|unexpected token `}}'|)
    expect { parse("{{ 1 %}") }.to raise_error(Liquor::SyntaxError, %r|unexpected token `%}'|)
    expect { parse("{{ -2] }}") }.to raise_error(Liquor::SyntaxError, %r|unexpected token `\]'|)
    expect { parse("{{ a[2 }}") }.to raise_error(Liquor::SyntaxError, %r|unexpected token `}}'|)
    expect { parse("{{ a && (b || ) }}") }.to raise_error(Liquor::SyntaxError, %r|unexpected token `\)'|)
  end

  it "obeys operator precedence" do
    parse('{{ 1 + 2 * 3 }}').should have_node_structure(
      [:interp,
        [:plus,
          [:integer, 1],
          [:mul,
            [:integer, 2],
            [:integer, 3]]]]
    )
    parse('{{ a && b || c && d }}').should have_node_structure(
      [:interp,
        [:or,
          [:and, [:ident, "a"], [:ident, "b"]],
          [:and, [:ident, "c"], [:ident, "d"]]]]
    )
    parse('{{ a || b && c || d }}').should have_node_structure(
      [:interp,
        [:or,
          [:or,
            [:ident, "a"],
            [:and, [:ident, "b"], [:ident, "c"]]],
          [:ident, "d"]]]
    )
    parse('{{ !x && x }}').should have_node_structure(
      [:interp,
        [:and,
          [:not,
            [:ident, "x"]],
          [:ident, "x"]]]
    )
    expect {
      exec(%|{% for x in: [1,2,3] do: %}{% if !x_loop.is_first then: %}y{% end if %}{% end for %}|)
    }.not_to raise_error
  end

  it "changes evaluation order due to parentheses" do
    parse('{{ a && (b || c) }}').should have_node_structure(
      [:interp,
        [:and,
          [:ident, "a"],
          [:or, [:ident, "b"], [:ident, "c"]]]]
    )
  end

  it "recognizes every operator" do
    expect { parse('{{ fun(a: 1 + 2 * 3 / 4 % 5 - 6 ' +
        ' b: !(a && b || c) c: (1 >= 2) || 1 < 2 || ' +
        'a != b || b == c || 5 <= 6 || 7 > 8 ' +
        'd: e.f e: ([1,2])[0] ) }}') }.not_to raise_error

    parse('{{ 2 - -1 }}').should have_node_structure(
      [:interp,
        [:minus,
          [:integer, 2],
          [:uminus, [:integer, 1]]]]
    )
    parse('{{ -1 }}').should have_node_structure(
      [:interp,
        [:uminus, [:integer, 1]]]
    )
  end

  it "accepts correct tuples" do
    parse('{{ [] }}').should have_node_structure(
      [:interp,
        [:tuple, []]]
    )
    parse('{{ [1] }}').should have_node_structure(
      [:interp,
        [:tuple, [ [:integer, 1] ]]]
    )
    parse('{{ [1,] }}').should have_node_structure(
      [:interp,
        [:tuple, [ [:integer, 1] ]]]
    )
    parse('{{ [1,2] }}').should have_node_structure(
      [:interp,
        [:tuple, [ [:integer, 1], [:integer, 2] ]]]
    )
    parse('{{ [1,2,] }}').should have_node_structure(
      [:interp,
        [:tuple, [ [:integer, 1], [:integer, 2] ]]]
    )
  end

  it "accepts correct method calls" do
    parse('{{ a() }}').should have_node_structure(
      [:interp,
        [:call,
          [:ident, "a"],
          [:args, nil, {}]]]
    )
    parse('{{ a("str") }}').should have_node_structure(
      [:interp,
        [:call,
          [:ident, "a"],
          [:args, [:string, "str"], {}]]]
    )
    parse('{{ a("str" from: 1) }}').should have_node_structure(
      [:interp,
        [:call,
          [:ident, "a"],
          [:args, [:string, "str"],
                  {:from => [:integer, 1]}]]]
    )
    parse('{{ a(from: 1 to: 2) }}').should have_node_structure(
      [:interp,
        [:call,
          [:ident, "a"],
          [:args, nil,
                  {:from => [:integer, 1],
                   :to   => [:integer, 2]}]]]
    )
  end

  it "rejects method calls with duplicate keyword arguments" do
    expect { parse('{{ a(to: 1 to: 1) }}') }.to \
                raise_error(Liquor::SyntaxError, %r|duplicate keyword argument `to'|)
  end

  it "accepts correct filter expressions" do
    parse('{{ a | b }}').should have_node_structure(
      [:interp,
        [:call,
          [:ident, "b"],
          [:args,
            [:ident, "a"],
            {}]]]
    )
    parse('{{ a(x: 1) | b y: 2 z: 3 | c p: 4 }}').should have_node_structure(
      [:interp,
        [:call,
          [:ident, "c"],
          [:args,
            [:call,
              [:ident, "b"],
              [:args,
                [:call,
                  [:ident, "a"],
                  [:args,
                    nil,
                    { :x => [:integer, 1] }]],
                { :y => [:integer, 2],
                  :z => [:integer, 3] }]],
            { :p => [:integer, 4] }]]]
    )
  end

  it "rejects malformed filter expressions" do
    expect { parse('{{ a b: 1 }}') }.to raise_error(Liquor::SyntaxError)
    expect { parse('{{ a("str", b: 1) | b ') }.to raise_error(Liquor::SyntaxError, %r{unexpected token `|'})
    expect { parse('{{ a | b(b: 1)') }.to raise_error(Liquor::SyntaxError, %r{unexpected token `\('})
    expect { parse('{{ a | b("str", b: 1)') }.to raise_error(Liquor::SyntaxError, %r{unexpected token `\('})
  end

  it "parses blocks correctly" do
    parse('{% yield %}').should have_node_structure(
      [:tag,
        [:ident, "yield"],
        nil]
    )
    parse('{% yield 1 %}').should have_node_structure(
      [:tag,
        [:ident, "yield"],
        [:integer, 1]]
    )
    parse('{% yield from: 1 %}').should have_node_structure(
      [:tag,
        [:ident, "yield"],
        nil,
        [:kwarg, [:keyword, "from"], [:integer, 1]]]
    )
    parse('{% yield "str" from: 1 %}').should have_node_structure(
      [:tag,
        [:ident, "yield"],
        [:string, "str"],
        [:kwarg, [:keyword, "from"], [:integer, 1]]]
    )
    parse('{% yield from: 1 to: 2 from: 3 %}').should have_node_structure(
      [:tag,
        [:ident, "yield"],
        nil,
        [:kwarg, [:keyword, "from" ], [:integer, 1]],
        [:kwarg, [:keyword, "to" ],   [:integer, 2]],
        [:kwarg, [:keyword, "from" ], [:integer, 3]]]
    )
    parse('{% capture do: %} 1 {% end capture %}').
              should have_node_structure(
      [:tag,
        [:ident, "capture"],
        nil,
        [:blockarg, [:keyword, "do"],
          [
            [:plaintext, " 1 "]
          ]]]
    )
    parse('{% capture "test" do: %} 1 {% end capture %}').
              should have_node_structure(
      [:tag,
        [:ident, "capture"],
        [:string, "test"],
        [:blockarg, [:keyword, "do"],
          [
            [:plaintext, " 1 "]
          ]]]
    )
    parse('{% capture as: "test" do: %} 1 {% end capture %}').
              should have_node_structure(
      [:tag,
        [:ident, "capture"],
        nil,
        [:kwarg, [:keyword, "as"], [:string, "test"]],
        [:blockarg, [:keyword, "do"],
          [
            [:plaintext, " 1 "]
          ]]]
    )
    parse('{% if a1 then: %} 1 {% elsif: a2 then: %} 2 {% else: %} 3 {% end if %}').
              should have_node_structure(
      [:tag,
        [:ident, "if"],
        [:ident, "a1"],
        [:blockarg, [:keyword, "then"],
          [[:plaintext, " 1 "]]],
        [:kwarg, [:keyword, "elsif"], [:ident, "a2"]],
        [:blockarg, [:keyword, "then"],
          [[:plaintext, " 2 "]]],
        [:blockarg, [:keyword, "else"],
          [[:plaintext, " 3 "]]]]
    )
  end

  it "reports EOF errors" do
    expect { parse('{% if a1 then: %} 1 {% wat %}') }.to raise_error(Liquor::SyntaxError, %r|unexpected end of program|)
  end

  it "handles lexer errors" do
    parser = Liquor::Parser.new
    parser.parse '{{ $'
    parser.success?.should == false
    parser.ast.should == nil
  end

  it "correctly handles multiple errors" do
    parser = Liquor::Parser.new
    parser.parse '{{ fun(a: 1 a: 1) + fun(b: 1 b: 1) }}'
    parser.errors.count.should == 2
    parser.ast.should_not == nil
  end

  it "resets error state on next parse" do
    parser = Liquor::Parser.new
    parser.parse '{{ fun(a: 1 a: 1) + fun(b: 1 b: 1) }}'
    parser.errors.count.should == 2
    parser.parse '{{ 1 }}'
    parser.errors.count.should == 0
    parser.success?.should == true
  end

  it "correctly handles empty blocks" do
    parse('{% tag do: %}{% end tag %}').should have_node_structure(
      [:tag,
        [:ident, 'tag'],
        nil,
        [:blockarg,
          [:keyword, 'do'],
          []]]
    )
  end

  it "understands various constructs for externals" do
    parse('{{ ext.test }}').should have_node_structure(
      [:interp,
        [:external,
          [:ident, "ext"],
          [:ident, "test"],
          nil]]
    )
    parse('{{ ext.test() }}').should have_node_structure(
      [:interp,
        [:external,
          [:ident, "ext"],
          [:ident, "test"],
          [:args,
            nil,
            {}]]]
    )
    parse('{{ a.b.c }}').should have_node_structure(
      [:interp,
        [:external,
          [:external,
            [:ident, "a"],
            [:ident, "b"],
            nil
          ],
          [:ident, "c"],
          nil]]
    )
    parse('{{ a.b().c }}').should have_node_structure(
      [:interp,
        [:external,
          [:external,
            [:ident, "a"],
            [:ident, "b"],
            [:args,
              nil,
              {}]
          ],
          [:ident, "c"],
          nil]]
    )
    parse('{{ a.b[0] }}').should have_node_structure(
      [:interp,
        [:index,
          [:external,
            [:ident, "a"],
            [:ident, "b"],
            nil,
          ],
          [:integer, 0]]]
    )
  end

  it "does not allow duplicate kwargs for external calls" do
    expect { parse('{{ ext.call(kw: 1 kw: 2) }}') }.to raise_error(Liquor::SyntaxError, %r|duplicate keyword argument|)
  end

  it "does not want {% end elsif %}" do
    expect {
      compiler = Liquor::Compiler.new
      parse %|
        {% if !is_empty(clip_videos) then: %}
          {% assign current_video = clip_videos[0] %}
        {% elsif !is_empty(live_videos) then: %}
        {% elsif: !is_empty(live_videos) then: %}
        {% elsif !is_empty(live_videos) then: %}
        {% end if %}
      |, compiler
    }.not_to raise_error
  end

  it "reports correct line numbers" do
    expect {
      parse("{\n%\n{% end if %}")
    }.to raise_error(Liquor::SyntaxError, /line 3/)
  end

  it "does not return nil for empty string input" do
    parse("").should_not == nil
  end
end
