require File.expand_path('../helper', __FILE__)

class CitrusFileTest < Test::Unit::TestCase

  ## File tests

  def run_file_test(file, root)
    match = File.parse(::File.read(file), :root => root)
    assert(match)
  end

  %w<file grammar rule>.each do |type|
    Dir[::File.dirname(__FILE__) + "/_files/#{type}*.citrus"].each do |path|
      module_eval(<<-CODE.gsub(/^        /, ''), __FILE__, __LINE__ + 1)
        def test_#{::File.basename(path, '.citrus')}
          run_file_test("#{path}", :#{type})
        end
      CODE
    end
  end

  ## Hierarchical syntax

  def test_expression_empty
    assert_raise SyntaxError do
      File.parse('', :root => :expression)
    end
  end

  def test_expression_alias
    match = File.parse('rule_name', :root => :expression)
    assert(match)
    assert_instance_of(Alias, match.value)
  end

  def test_expression_dot
    match = File.parse('.', :root => :expression)
    assert(match)
    assert_instance_of(Terminal, match.value)
  end

  def test_expression_character_range
    match = File.parse('[a-z]', :root => :expression)
    assert(match)
    assert_instance_of(Terminal, match.value)
  end

  def test_expression_terminal
    match = File.parse('/./', :root => :expression)
    assert(match)
    assert_instance_of(Terminal, match.value)
  end

  def test_expression_string_terminal_empty
    match = File.parse('""', :root => :expression)
    assert(match)
    assert_instance_of(StringTerminal, match.value)
  end

  def test_expression_string_terminal
    match = File.parse('"a"', :root => :expression)
    assert(match)
    assert_instance_of(StringTerminal, match.value)
  end

  def test_expression_string_terminal_empty_block
    match = File.parse('"" {}', :root => :expression)
    assert(match)
    assert_instance_of(StringTerminal, match.value)
  end

  def test_expression_repeat_string_terminal
    match = File.parse('"a"*', :root => :expression)
    assert(match)
    assert_instance_of(Repeat, match.value)
  end

  def test_expression_repeat_empty_string_terminal_block
    match = File.parse('""* {}', :root => :expression)
    assert(match)
    assert_instance_of(Repeat, match.value)
  end

  def test_expression_repeat_sequence
    match = File.parse('("a" "b")*', :root => :expression)
    assert(match)
    assert_instance_of(Repeat, match.value)
  end

  def test_expression_repeat_choice
    match = File.parse('("a" | "b")*', :root => :expression)
    assert(match)
    assert_instance_of(Repeat, match.value)
  end

  def test_expression_repeat_sequence_block
    match = File.parse('("a" "b")* {}', :root => :expression)
    assert(match)
    assert_instance_of(Repeat, match.value)
  end

  def test_expression_repeat_choice_block
    match = File.parse('("a" | "b")* {}', :root => :expression)
    assert(match)
    assert_instance_of(Repeat, match.value)
  end

  def test_expression_repeat_sequence_extension
    match = File.parse('("a" "b")* <Module>', :root => :expression)
    assert(match)
    assert_instance_of(Repeat, match.value)
  end

  def test_expression_repeat_sequence_extension_spaced
    match = File.parse('( "a" "b" )* <Module>', :root => :expression)
    assert(match)
    assert_instance_of(Repeat, match.value)
  end

  def test_expression_repeat_choice_extension
    match = File.parse('("a" | "b")* <Module>', :root => :expression)
    assert(match)
    assert_instance_of(Repeat, match.value)
  end

  def test_expression_choice_terminal
    match = File.parse('/./ | /./', :root => :expression)
    assert(match)
    assert_instance_of(Choice, match.value)
  end

  def test_expression_choice_string_terminal
    match = File.parse('"a" | "b"', :root => :expression)
    assert(match)
    assert_instance_of(Choice, match.value)
  end

  def test_expression_choice_embedded_sequence
    match = File.parse('"a" | ("b" "c")', :root => :expression)
    assert(match)
    assert_instance_of(Choice, match.value)
  end

  def test_expression_choice_mixed
    match = File.parse('("a" | /./)', :root => :expression)
    assert(match)
    assert_instance_of(Choice, match.value)
  end

  def test_expression_choice_extended
    match = File.parse('("a" | "b") <Module>', :root => :expression)
    assert(match)
    assert_instance_of(Choice, match.value)
  end

  def test_expression_sequence_terminal
    match = File.parse('/./ /./', :root => :expression)
    assert(match)
    assert_instance_of(Sequence, match.value)
  end

  def test_expression_sequence_string_terminal
    match = File.parse('"a" "b"', :root => :expression)
    assert(match)
    assert_instance_of(Sequence, match.value)
  end

  def test_expression_sequence_extension
    match = File.parse('( "a" "b" ) <Module>', :root => :expression)
    assert(match)
    assert_instance_of(Sequence, match.value)
  end

  def test_expression_sequence_mixed
    match = File.parse('"a" ("b" | /./)* <Module>', :root => :expression)
    assert(match)
    assert_instance_of(Sequence, match.value)
  end

  def test_expression_sequence_block
    match = File.parse('"a" ("b" | /./)* {}', :root => :expression)
    assert(match)
    assert_instance_of(Sequence, match.value)
  end

  def test_precedence_sequence_before_choice
    # Sequence should bind more tightly than Choice.
    match = File.parse('"a" "b" | "c"', :root => :expression)
    assert(match)
    assert_instance_of(Choice, match.value)
  end

  def test_precedence_parentheses
    # Parentheses should change binding precedence.
    match = File.parse('"a" ("b" | "c")', :root => :expression)
    assert(match)
    assert_instance_of(Sequence, match.value)
  end

  def test_precedence_repeat_before_predicate
    # Repeat should bind more tightly than AndPredicate.
    match = File.parse("&'a'+", :root => :expression)
    assert(match)
    assert_instance_of(AndPredicate, match.value)
  end

  def test_sequence
    match = File.parse('"" ""', :root => :sequence)
    assert(match)
    assert_instance_of(Sequence, match.value)
  end

  def test_sequence_embedded_choice
    match = File.parse('"a" ("b" | "c")', :root => :sequence)
    assert(match)
    assert_instance_of(Sequence, match.value)
  end

  def test_labelled
    match = File.parse('label:""', :root => :labelled)
    assert(match)
    assert_instance_of(StringTerminal, match.value)
  end

  def test_extended_tag
    match = File.parse('"" <Module>', :root => :extended)
    assert(match)
    assert_kind_of(Rule, match.value)
    assert_kind_of(Module, match.value.extension)
  end

  def test_extended_block
    match = File.parse('"" {}', :root => :extended)
    assert(match)
    assert_kind_of(Rule, match.value)
    assert_kind_of(Module, match.value.extension)
  end

  def test_prefix_and
    match = File.parse('&""', :root => :prefix)
    assert(match)
    assert_instance_of(AndPredicate, match.value)
  end

  def test_prefix_not
    match = File.parse('!""', :root => :prefix)
    assert(match)
    assert_instance_of(NotPredicate, match.value)
  end

  def test_prefix_but
    match = File.parse('~""', :root => :prefix)
    assert(match)
    assert_instance_of(ButPredicate, match.value)
  end

  def test_suffix_plus
    match = File.parse('""+', :root => :suffix)
    assert(match)
    assert_instance_of(Repeat, match.value)
  end

  def test_suffix_question
    match = File.parse('""?', :root => :suffix)
    assert(match)
    assert_instance_of(Repeat, match.value)
  end

  def test_suffix_star
    match = File.parse('""*', :root => :suffix)
    assert(match)
    assert_instance_of(Repeat, match.value)
  end

  def test_suffix_n_star
    match = File.parse('""1*', :root => :suffix)
    assert(match)
    assert_instance_of(Repeat, match.value)
  end

  def test_suffix_star_n
    match = File.parse('""*2', :root => :suffix)
    assert(match)
    assert_instance_of(Repeat, match.value)
  end

  def test_suffix_n_star_n
    match = File.parse('""1*2', :root => :suffix)
    assert(match)
    assert_instance_of(Repeat, match.value)
  end

  def test_primary_alias
    match = File.parse('rule_name', :root => :primary)
    assert(match)
    assert_instance_of(Alias, match.value)
  end

  def test_primary_string_terminal
    match = File.parse('"a"', :root => :primary)
    assert(match)
    assert_instance_of(StringTerminal, match.value)
  end


  ## Lexical syntax


  def test_require
    match = File.parse('require "some/file"', :root => :require)
    assert(match)
    assert_equal('some/file', match.value)
  end

  def test_require_no_space
    match = File.parse('require"some/file"', :root => :require)
    assert(match)
    assert_equal('some/file', match.value)
  end

  def test_require_single_quoted
    match = File.parse("require 'some/file'", :root => :require)
    assert(match)
    assert_equal('some/file', match.value)
  end

  def test_include
    match = File.parse('include Module', :root => :include)
    assert(match)
    assert_equal(Module, match.value)
  end

  def test_include_colon_colon
    match = File.parse('include ::Module', :root => :include)
    assert(match)
    assert_equal(Module, match.value)
  end

  def test_root
    match = File.parse('root some_rule', :root => :root)
    assert(match)
    assert_equal('some_rule', match.value)
  end

  def test_root_invalid
    assert_raise SyntaxError do
      File.parse('root :a_root', :root => :root)
    end
  end

  def test_rule_name
    match = File.parse('some_rule', :root => :rule_name)
    assert(match)
    assert('some_rule', match.value)
  end

  def test_rule_name_space
    match = File.parse('some_rule ', :root => :rule_name)
    assert(match)
    assert('some_rule', match.value)
  end

  def test_terminal_single_quoted_string
    match = File.parse("'a'", :root => :terminal)
    assert(match)
    assert_instance_of(StringTerminal, match.value)
  end

  def test_terminal_double_quoted_string
    match = File.parse('"a"', :root => :terminal)
    assert(match)
    assert_instance_of(StringTerminal, match.value)
  end

  def test_terminal_case_insensitive_string
    match = File.parse('`a`', :root => :terminal)
    assert(match)
    assert_instance_of(StringTerminal, match.value)
  end

  def test_terminal_regular_expression
    match = File.parse('/./', :root => :terminal)
    assert(match)
    assert_instance_of(Terminal, match.value)
  end

  def test_terminal_character_class
    match = File.parse('[a-z]', :root => :terminal)
    assert(match)
    assert_instance_of(Terminal, match.value)
  end

  def test_terminal_dot
    match = File.parse('.', :root => :terminal)
    assert(match)
    assert_instance_of(Terminal, match.value)
  end

  def test_single_quoted_string
    match = File.parse("'test'", :root => :quoted_string)
    assert(match)
    assert_equal('test', match.value)
  end

  def test_single_quoted_string_embedded_single_quote
    match = File.parse("'te\\'st'", :root => :quoted_string)
    assert(match)
    assert_equal("te'st", match.value)
  end

  def test_single_quoted_string_embedded_double_quote
    match = File.parse("'te\"st'", :root => :quoted_string)
    assert(match)
    assert_equal('te"st', match.value)
  end

  def test_double_quoted_string
    match = File.parse('"test"', :root => :quoted_string)
    assert(match)
    assert_equal('test', match.value)
  end

  def test_double_quoted_string_embedded_double_quote
    match = File.parse('"te\\"st"', :root => :quoted_string)
    assert(match)
    assert_equal('te"st', match.value)
  end

  def test_double_quoted_string_embedded_single_quote
    match = File.parse('"te\'st"', :root => :quoted_string)
    assert(match)
    assert_equal("te'st", match.value)
  end

  def test_double_quoted_string_hex
    match = File.parse('"\\x26"', :root => :quoted_string)
    assert(match)
    assert_equal('&', match.value)
  end

  def test_case_insensitive_string
    match = File.parse('`test`', :root => :case_insensitive_string)
    assert(match)
    assert_equal('test', match.value)
  end

  def test_case_insensitive_string_embedded_double_quote
    match = File.parse('`te\\"st`', :root => :case_insensitive_string)
    assert(match)
    assert_equal('te"st', match.value)
  end

  def test_case_insensitive_string_embedded_backtick
    match = File.parse('`te\`st`', :root => :case_insensitive_string)
    assert(match)
    assert_equal("te`st", match.value)
  end

  def test_case_insensitive_string_hex
    match = File.parse('`\\x26`', :root => :case_insensitive_string)
    assert(match)
    assert_equal('&', match.value)
  end

  def test_regular_expression
    match = File.parse('/./', :root => :regular_expression)
    assert(match)
    assert_equal(/./, match.value)
  end

  def test_regular_expression_escaped_forward_slash
    match = File.parse('/\\//', :root => :regular_expression)
    assert(match)
    assert_equal(/\//, match.value)
  end

  def test_regular_expression_escaped_backslash
    match = File.parse('/\\\\/', :root => :regular_expression)
    assert(match)
    assert_equal(/\\/, match.value)
  end

  def test_regular_expression_hex
    match = File.parse('/\\x26/', :root => :regular_expression)
    assert(match)
    assert_equal(/\x26/, match.value)
  end

  def test_regular_expression_with_flag
    match = File.parse('/a/i', :root => :regular_expression)
    assert(match)
    assert_equal(/a/i, match.value)
  end

  def test_character_class
    match = File.parse('[_]', :root => :character_class)
    assert(match)
    assert_equal(/[_]/n, match.value)
  end

  def test_character_class_a_z
    match = File.parse('[a-z]', :root => :character_class)
    assert(match)
    assert_equal(/[a-z]/n, match.value)
  end

  def test_character_class_nested_square_brackets
    match = File.parse('[\[-\]]', :root => :character_class)
    assert(match)
    assert_equal(/[\[-\]]/n, match.value)
  end

  def test_character_class_hex_range
    match = File.parse('[\\x26-\\x29]', :root => :character_class)
    assert(match)
    assert_equal(/[\x26-\x29]/, match.value)
  end

  def test_dot
    match = File.parse('.', :root => :dot)
    assert(match)
    assert_equal(DOT, match.value)
  end

  def test_label
    match = File.parse('label:', :root => :label)
    assert(match)
    assert_equal(:label, match.value)
  end

  def test_label_spaced
    match = File.parse('a_label : ', :root => :label)
    assert(match)
    assert_equal(:a_label, match.value)
  end

  def test_tag
    match = File.parse('<Module>', :root => :tag)
    assert(match)
    assert_equal(Module, match.value)
  end

  def test_tag_inner_space
    match = File.parse('< Module >', :root => :tag)
    assert(match)
    assert_equal(Module, match.value)
  end

  def test_tag_space
    match = File.parse('<Module> ', :root => :tag)
    assert(match)
    assert_equal(Module, match.value)
  end

  def test_block
    match = File.parse('{}', :root => :block)
    assert(match)
    assert(match.value)
  end

  def test_block_space
    match = File.parse("{} \n", :root => :block)
    assert(match)
    assert(match.value)
  end

  def test_block_n
    match = File.parse('{ 2 }', :root => :block)
    assert(match)
    assert(match.value)
    assert_equal(2, match.value.call)
  end

  def test_block_with_hash
    match = File.parse("{ {:a => :b}\n}", :root => :block)
    assert(match)
    assert(match.value)
    assert_equal({:a => :b}, match.value.call)
  end

  def test_block_proc
    match = File.parse("{|b|\n  Proc.new(&b)\n}", :root => :block)
    assert(match)
    assert(match.value)

    b = match.value.call(Proc.new { :hi })

    assert(b)
    assert_equal(:hi, b.call)
  end

  def test_block_def
    match = File.parse("{def value; 'a' end}", :root => :block)
    assert(match)
    assert(match.value)
    assert_instance_of(Module, match.value)
    method_names = match.value.instance_methods.map {|m| m.to_sym }
    assert_equal([:value], method_names)
  end

  def test_block_def_multiline
    match = File.parse("{\n  def value\n    'a'\n  end\n} ", :root => :block)
    assert(match)
    assert(match.value)
    assert_instance_of(Module, match.value)
    method_names = match.value.instance_methods.map {|m| m.to_sym }
    assert_equal([:value], method_names)
  end

  def test_block_with_interpolation
    match = File.parse('{ "#{number}" }', :root => :block)
    assert(match)
    assert(match.value)
  end

  def test_predicate_and
    match = File.parse('&', :root => :predicate)
    assert(match)
    assert_instance_of(AndPredicate, match.value(''))
  end

  def test_predicate_not
    match = File.parse('!', :root => :predicate)
    assert(match)
    assert_instance_of(NotPredicate, match.value(''))
  end

  def test_predicate_but
    match = File.parse('~', :root => :predicate)
    assert(match)
    assert_instance_of(ButPredicate, match.value(''))
  end

  def test_and
    match = File.parse('&', :root => :and)
    assert(match)
    assert_instance_of(AndPredicate, match.value(''))
  end

  def test_and_space
    match = File.parse('& ', :root => :and)
    assert(match)
    assert_instance_of(AndPredicate, match.value(''))
  end

  def test_not
    match = File.parse('!', :root => :not)
    assert(match)
    assert_instance_of(NotPredicate, match.value(''))
  end

  def test_not_space
    match = File.parse('! ', :root => :not)
    assert(match)
    assert_instance_of(NotPredicate, match.value(''))
  end

  def test_but
    match = File.parse('~', :root => :but)
    assert(match)
    assert_instance_of(ButPredicate, match.value(''))
  end

  def test_but_space
    match = File.parse('~ ', :root => :but)
    assert(match)
    assert_instance_of(ButPredicate, match.value(''))
  end

  def test_repeat_question
    match = File.parse('?', :root => :repeat)
    assert(match)
    assert_instance_of(Repeat, match.value(''))
  end

  def test_repeat_plus
    match = File.parse('+', :root => :repeat)
    assert(match)
    assert_instance_of(Repeat, match.value(''))
  end

  def test_repeat_star
    match = File.parse('*', :root => :repeat)
    assert(match)
    assert_instance_of(Repeat, match.value(''))
  end

  def test_repeat_n_star
    match = File.parse('1*', :root => :repeat)
    assert(match)
    assert_instance_of(Repeat, match.value(''))
  end

  def test_repeat_star_n
    match = File.parse('*2', :root => :repeat)
    assert(match)
    assert_instance_of(Repeat, match.value(''))
  end

  def test_repeat_n_star_n
    match = File.parse('1*2', :root => :repeat)
    assert(match)
    assert_instance_of(Repeat, match.value(''))
  end

  def test_question
    match = File.parse('?', :root => :question)
    assert(match)
    rule = match.value('')
    assert_instance_of(Repeat, rule)
    assert_equal(0, rule.min)
    assert_equal(1, rule.max)
  end

  def test_question_space
    match = File.parse('? ', :root => :question)
    assert(match)
    rule = match.value('')
    assert_instance_of(Repeat, rule)
    assert_equal(0, rule.min)
    assert_equal(1, rule.max)
  end

  def test_plus
    match = File.parse('+', :root => :plus)
    assert(match)
    rule = match.value('')
    assert_instance_of(Repeat, rule)
    assert_equal(1, rule.min)
    assert_equal(Infinity, rule.max)
  end

  def test_plus_space
    match = File.parse('+ ', :root => :plus)
    assert(match)
    rule = match.value('')
    assert_instance_of(Repeat, rule)
    assert_equal(1, rule.min)
    assert_equal(Infinity, rule.max)
  end

  def test_star
    match = File.parse('*', :root => :star)
    assert(match)
    rule = match.value('')
    assert_instance_of(Repeat, rule)
    assert_equal(0, rule.min)
    assert_equal(Infinity, rule.max)
  end

  def test_n_star
    match = File.parse('1*', :root => :star)
    assert(match)
    rule = match.value('')
    assert_instance_of(Repeat, rule)
    assert_equal(1, rule.min)
    assert_equal(Infinity, rule.max)
  end

  def test_star_n
    match = File.parse('*2', :root => :star)
    assert(match)
    rule = match.value('')
    assert_instance_of(Repeat, rule)
    assert_equal(0, rule.min)
    assert_equal(2, rule.max)
  end

  def test_n_star_n
    match = File.parse('1*2', :root => :star)
    assert(match)
    rule = match.value('')
    assert_instance_of(Repeat, rule)
    assert_equal(1, rule.min)
    assert_equal(2, rule.max)
  end

  def test_n_star_n_space
    match = File.parse('1*2 ', :root => :star)
    assert(match)
    rule = match.value('')
    assert_instance_of(Repeat, rule)
    assert_equal(1, rule.min)
    assert_equal(2, rule.max)
  end

  def test_module_name
    match = File.parse('Module', :root => :module_name)
    assert(match)
    assert_equal('Module', match)
  end

  def test_module_name_space
    match = File.parse('Module ', :root => :module_name)
    assert(match)
    assert_equal('Module', match.first)
  end

  def test_module_name_colon_colon
    match = File.parse('::Proc', :root => :module_name)
    assert(match)
    assert_equal('::Proc', match)
  end

  def test_constant
    match = File.parse('Math', :root => :constant)
    assert(match)
    assert_equal('Math', match)
  end

  def test_constant_invalid
    assert_raise SyntaxError do
      File.parse('math', :root => :constant)
    end
  end

  def test_comment
    match = File.parse('# A comment.', :root => :comment)
    assert(match)
    assert_equal('# A comment.', match)
  end
end
