require 'spec_helper'

module CharacterClassSpec
  class Foo < Treetop::Runtime::SyntaxNode
  end

  describe "a character class followed by a node class declaration and a block" do

    testing_expression "[A-Z] <CharacterClassSpec::Foo> { def a_method; end }"

    it "matches single characters within that range, returning instances of the declared node class that respond to the method defined in the inline module" do
      result = parse('A')
      result.should be_an_instance_of(Foo)
      result.should respond_to(:a_method)
      result = parse('N')
      result.should be_an_instance_of(Foo)
      result.should respond_to(:a_method)
      result = parse('Z')
      result.should be_an_instance_of(Foo)
      result.should respond_to(:a_method)
    end

    it "does not match single characters outside of that range" do
      parse('8') do |result|
        result.should be_nil
        parser.terminal_failures.size.should == 1
      end
      parse('a').should be_nil
    end

    it "matches a single character within that range at index 1" do
      parse(' A', :index => 1).should_not be_nil
    end

    it "fails to match a single character out of that range at index 1" do
      parse(' 1', :index => 1).should be_nil
    end
  end

  module ModFoo
  end

  describe "a character class followed by a node module declaration and a block" do

    testing_expression "[A-Z] <CharacterClassSpec::ModFoo> { def a_method; end }"

    it "matches single characters within that range, returning instances of SyntaxNode extended by the specified module" do
      result = parse('A')
      result.should be_an_instance_of(Treetop::Runtime::SyntaxNode)
      result.should be_a_kind_of(ModFoo)
      result.should respond_to(:a_method)
      result = parse('N')
      result.should be_an_instance_of(Treetop::Runtime::SyntaxNode)
      result.should be_a_kind_of(ModFoo)
      result.should respond_to(:a_method)
      result = parse('Z')
      result.should be_an_instance_of(Treetop::Runtime::SyntaxNode)
      result.should be_a_kind_of(ModFoo)
      result.should respond_to(:a_method)
    end

    it "does not match single characters outside of that range" do
      parse('8').should be_nil
      parse('a').should be_nil
    end

    it "matches a single character within that range at index 1" do
      parse(' A', :index => 1).should_not be_nil
    end

    it "fails to match a single character out of that range at index 1" do
      parse(' 1', :index => 1).should be_nil
    end
  end

  describe "a character class with a POSIX bracket expression" do
    testing_expression "[[:digit:]]"
    it "matches a single character within the class" do
      parse('1').should_not be_nil
    end
    it "does not match a single character outside the class" do
      parse('a').should be_nil
      parse('-').should be_nil
    end
    testing_expression "[[:digit:][:space:]]+"
    it "matches a string with a mix of two character classes" do
      parse('1 4 9').should_not be_nil
    end
  end

  describe "a character class with a negated POSIX bracket expression" do
    testing_expression "[^[:space:]]"
    it "matches a character outside the negated class" do
      parse('a').should_not be_nil
    end
    it "doesn't match a character within the negated class" do
      parse(' ').should be_nil
    end
  end

  describe "a character class followed by a node class declaration and a block" do

    testing_expression "[A-Z] <CharacterClassSpec::Foo>"
    
    it "actively generates nodes for the character when it is the primary node" do
      result = parse('A')
      result.should be_a(Treetop::Runtime::SyntaxNode)
      result.elements.should be_nil
    end
    
  end

  describe "A character class containing quotes" do
    testing_expression "[\"']"

    it "matches a quote" do
      parse("'").should_not be_nil
    end

    it "matches a double-quote" do
      parse('"').should_not be_nil
    end
  end

  describe "A character class containing a special character" do
    testing_expression "[\t]"
    it "matches that character only" do
      parse("\t").should_not be_nil
      parse('t').should be_nil
    end
  end

  describe "A character class containing an escaped backslash" do
    slash = "\\"  # Make it explicit that there are *two* backslashes here
    testing_expression "[#{slash}#{slash}]"
    it "matches a backslash only" do
      parse("\\").should_not be_nil
      parse('t').should be_nil
    end
  end

  describe "A character class containing a hex escape" do
    slash = "\\"
    testing_expression "[#{slash}x41]"
    it "matches that character only" do
      parse('A').should_not be_nil
      parse('\\').should be_nil
      parse('x').should be_nil
      parse('4').should be_nil
      parse('1').should be_nil
    end
  end

  describe "A character class containing an octal escape" do
    slash = "\\"
    testing_expression "[#{slash}101]"
    it "matches that character only" do
      parse('A').should_not be_nil
      parse('\\').should be_nil
      parse('1').should be_nil
      parse('0').should be_nil
    end
  end

  describe "A character class containing a \\c control-char escape" do
    slash = "\\"
    testing_expression "[#{slash}cC]"
    it "matches that character only" do
      parse("\003").should_not be_nil
      parse('\\').should be_nil
      parse('c').should be_nil
      parse('C').should be_nil
    end
  end

  describe "A character class containing a \\C- control-char escape" do
    slash = "\\"
    testing_expression "[#{slash}C-C]"
    it "matches that character only" do
      parse("\003").should_not be_nil
      parse('\\').should be_nil
      parse('C').should be_nil
      parse('-').should be_nil
    end
  end

  if RUBY_VERSION =~ /\A1\.8\./
    describe "A character class containing a \\M- meta-char escape" do
      slash = "\\"
      testing_expression "[#{slash}M- ]"
      it "matches that character only" do
        parse("\240").should_not be_nil
        parse('\\').should be_nil
        parse('M').should be_nil
        parse('-').should be_nil
        parse(' ').should be_nil
      end
    end
  end

  describe "A character class containing an escaped non-special character" do
    slash = "\\"
    testing_expression "[#{slash}y]"
    it "matches that character only" do
      parse("y").should_not be_nil
      parse('\\').should be_nil
    end
  end

  describe "A character class containing an \#{...} insertion" do
    testing_expression "[\#{raise 'error'}]"
    it "doesn't evaluate the insertion" do
      x = true
      lambda{
        x = parse("y")
      }.should_not raise_error
      x.should be_nil
      parse('#').should_not be_nil
      parse("'").should_not be_nil
      parse("0").should be_nil
    end
  end
  
  describe "a character class" do
    testing_expression "[A-Z]"
    it "actively generates a node for the character because it is the primary node" do
      result = parse('A')
      result.should be_a(Treetop::Runtime::SyntaxNode)
      result.elements.should be_nil
    end
  end
  
  describe "a character class mixed with other expressions" do
    testing_expression '[A-Z] "a" "bc"'
    it "lazily instantiates a node for the character" do
      result = parse('Aabc')
      result.instance_variable_get("@elements").should include(true)
      result.elements.should_not include(true)
      result.elements.size.should == 3
    end
  end
  
  describe "a character class with a node class declaration mixed with other expressions" do
    testing_expression '([A-Z] <CharacterClassSpec::Foo>) "ab"'
    it "actively generates a node for the character because it has a node class declared" do
      result = parse('Aab')
      result.instance_variable_get("@elements").should_not include(true)
      result.elements.should_not include(true)
      result.elements.size.should == 2
    end
  end
  
  describe "a character class with a node module declaration mixed with other expressions" do
    testing_expression '([A-Z] <CharacterClassSpec::ModFoo>) "ab"'
    it "actively generates a node for the character because it has a node module declared" do
      result = parse('Aab')
      result.instance_variable_get("@elements").should_not include(true)
      result.elements.should_not include(true)
      result.elements.size.should == 2
    end
  end
  
  describe "a character class with an inline block mixed with other expressions" do
    testing_expression '([A-Z] { def a_method; end }) "ab"'
    it "actively generates a node for the character because it has an inline block" do
      result = parse('Aab')
      result.instance_variable_get("@elements").should_not include(true)
      result.elements.should_not include(true)
      result.elements.size.should == 2
    end
  end
  
  describe "a character class with a label mixed with other expressions" do
    testing_expression 'upper:([A-Z]) "b" "cd"'
    it "returns the correct element for the labeled expression" do
      result = parse('Abcd')
      result.upper.text_value.should == "A"
      result.elements.size.should == 3
    end
  end
  
  describe "a character class repetition mixed with other expressions" do
    testing_expression '[A-Z]+ "a" "bc"'
    it "lazily instantiates a node for the character" do
      result = parse('ABCabc')
      result.elements[0].instance_variable_get("@elements").should include(true)
      result.elements[0].elements.should_not include(true)
      result.elements[0].elements.size.should == 3
      result.elements.size.should == 3
      result.elements.inspect.should == %Q{[SyntaxNode offset=0, "ABC":\n  SyntaxNode offset=0, "A"\n  SyntaxNode offset=1, "B"\n  SyntaxNode offset=2, "C", SyntaxNode offset=3, "a", SyntaxNode offset=4, "bc"]}
    end
  end
  
  describe "a character class that gets cached because of a choice" do
    testing_expression "[A-Z] 'a' 'bc' / [A-Z]"
    
    it "generates a node for the lazily-instantiated character when it is the primary node" do
      result = parse('A')
      result.should be_a(Treetop::Runtime::SyntaxNode)
      result.elements.should be_nil
    end
  end

end
