require 'spec_helper'

describe Liquor::Lexer do
  it "parses plaintext" do
    lex("abc\ndef").should have_token_structure(
      [:plaintext, "abc\ndef"]
    )
  end

  it "parses plaintext with braces" do
    lex('abc { def').should have_token_structure( [:plaintext] )
    lex('abc \{ def').should have_token_structure( [:plaintext] )
    lex('abc \{% def').should have_token_structure( [:plaintext] )
    lex('abc \{{\{% def').should have_token_structure( [:plaintext] )
  end

  it "parses comments" do
    lex('abc {! def !} test').should have_token_structure(
      [:plaintext, 'abc '],
      [:plaintext, ' test']
    )
    lex('abc {! {% test %} !} test').should have_token_structure(
      [:plaintext, 'abc '],
      [:plaintext, ' test']
    )
    lex('abc {! {{ def }} !} test').should have_token_structure(
      [:plaintext, 'abc '],
      [:plaintext, ' test']
    )
    lex('abc {! test {! a !} b !} def').should have_token_structure(
      [:plaintext, 'abc '],
      [:plaintext, ' def'],
    )
  end

  it "parses blocks and interpolations" do
    lex('abc {{ def }}').should have_token_structure(
      [:plaintext],
      [:linterp],
      [:ident],
      [:rinterp],
    )
    lex('abc {% def %}').should have_token_structure(
      [:plaintext],
      [:lblock],
      [:ident],
      [:rblock],
    )
  end

  it "fails on nested blocks and interpolations" do
    expect { lex('abc {{ def {{') }.to raise_error(Liquor::SyntaxError, %r|unexpected `{'|)
    expect { lex('abc {{ def {%') }.to raise_error(Liquor::SyntaxError, %r|unexpected `{'|)
    expect { lex('abc {% def {{') }.to raise_error(Liquor::SyntaxError, %r|unexpected `{'|)
    expect { lex('abc {% def {%') }.to raise_error(Liquor::SyntaxError, %r|unexpected `{'|)
  end

  it "fails on unrecognized symbols" do
    expect { lex('abc {{ # }}') }.to raise_error(Liquor::SyntaxError, %r|unexpected `#'|)
  end

  it "fails invalid integer literals the correct way" do
    expect { lex('{{ 1a }}') }.to raise_error(Liquor::SyntaxError, %r|unexpected `a'|)
  end

  it "parses complex expressions" do
    lex('{{ 1 * 2 + substr("abc" from: 1 to: 10 via: an.external) }}').should have_token_structure(
      [:linterp],
      [:integer, 1], [:op_mul], [:integer, 2], [:op_plus],
      [:ident], [:lparen],
      [:string, "abc"],
      [:keyword, "from"], [:integer, 1],
      [:keyword, "to"],   [:integer, 10],
      [:keyword, "via"],  [:ident, "an"], [:dot], [:ident, "external"],
      [:rparen],
      [:rinterp],
    )
  end

  it "parses blocks with embedded blocks" do
    lex('{% for x in: [ 1, 2, q ] do: %} value: {{ x }} {% end for %}').should have_token_structure(
      [:lblock],
      [:ident, "for"], [:ident, "x"],
      [:keyword, "in"],
      [:lbracket], [:integer, 1], [:comma], [:integer, 2], [:comma], [:ident, "q"], [:rbracket],
      [:keyword, "do"],
      [:rblock],
      [:plaintext],
      [:linterp], [:ident], [:rinterp],
      [:plaintext],
      [:lblock2], [:endtag], [:rblock],
    )
  end

  it "fails on multiline blocks and interpolations" do
    expect { lex("{{ \n }}") }.to raise_error(Liquor::SyntaxError, %r|unexpected end of line|)
    expect { lex("{% \n %}") }.to raise_error(Liquor::SyntaxError, %r|unexpected end of line|)
  end

  it "parses complex string literals" do
    lex('{{ "abc" + "def" }}').should have_token_structure(
      [:linterp],
      [:string, "abc"],
      [:op_plus],
      [:string, "def"],
      [:rinterp]
    )

    expect { lex(%|{{ "test }}|) }.to raise_error(Liquor::SyntaxError, %r|literal not terminated|)
    expect { lex(%|{{ "test\\" }}|) }.to raise_error(Liquor::SyntaxError, %r|literal not terminated|)
    expect { lex(%|{{ "test\\\\" }}|) }.not_to raise_error

    expect { lex(%|{{ 'test }}|) }.to raise_error(Liquor::SyntaxError, %r|literal not terminated|)
    expect { lex(%|{{ 'test\\' }}|) }.to raise_error(Liquor::SyntaxError, %r|literal not terminated|)
    expect { lex(%|{{ 'test\\\\' }}|) }.not_to raise_error

    lex(%|{{ "test ' \\'" }}|).should have_token_structure(
      [:linterp], [:string, %|test ' \\'|], [:rinterp]
    )
    lex(%|{{ 'test " \\'' }}|).should have_token_structure(
      [:linterp], [:string, %|test " '|], [:rinterp]
    )

    expect { lex(%|{{ "test\n" }}|) }.to raise_error(Liquor::SyntaxError, %r|unexpected end of line|)
  end

  it "parses nested tags correctly" do
    lex('{% a do: %} 1 {% b %} 2 {% c do: %} 3 {% end c %} 4 {% end a %} 5 {% ender %}').
            should have_token_structure(
      [:lblock], [:ident, "a"], [:keyword, "do"], [:rblock],
      [:plaintext, " 1 "],
      [:lblock], [:ident, "b"], [:rblock],
      [:plaintext, " 2 "],
      [:lblock], [:ident, "c"], [:keyword, "do"], [:rblock],
      [:plaintext, " 3 "],
      [:lblock2], [:endtag], [:rblock],
      [:plaintext, " 4 "],
      [:lblock2], [:endtag], [:rblock],
      [:plaintext, " 5 "],
      [:lblock], [:ident, "ender"], [:rblock],
    )
    lex('{% capture do: %}{% if a then: %} 1 {% elsif: b then: %} 2 {% end if %}{% end capture %}').
            should have_token_structure(
      [:lblock], [:ident, "capture"], [:keyword, "do"], [:rblock],
      [:lblock], [:ident, "if"], [:ident, "a"], [:keyword, "then"], [:rblock],
      [:plaintext, " 1 "],
      [:lblock2], [:keyword, "elsif"], [:ident, "b"], [:keyword, "then"], [:rblock],
      [:plaintext, " 2 "],
      [:lblock2], [:endtag], [:rblock],
      [:lblock2], [:endtag], [:rblock],
    )
  end

  it "understands syntactic sugar for =" do
    lex('{% assign x = 1 %}').should have_token_structure(
      [:lblock], [:ident, "assign"], [:ident, "x"], [:keyword, "="], [:integer, 1], [:rblock]
    )
  end

  it "fails unmatched end tag" do
    expect { lex('{% end tag %}') }.to raise_error(Liquor::SyntaxError)
    expect { lex('{% unless x then: %} {% end for %}') }.to raise_error(Liquor::SyntaxError)
  end

  it "correctly fails on complex runaway strings" do
    expect {
      lex(%Q${% for artist in: list do: %}
             {% end 'for %}
             #{"      \n" * 800}
             '220x$)
    }.to raise_error(Liquor::SyntaxError, %r|unexpected end of line .+?: line 2, column 28|)
  end
end