#!/opt/perl/bin/perl

use strict;
use warnings;
no warnings 'syntax';

use Regex::Common;
use Test::More;

my $r = eval "require Test::Regexp; 1";

unless ($r) {
    print "1..0 # SKIP Test::Regexp not found\n";
    exit;
}

sub make_test {
    my ( $name, $pat ) = @_;
    my $keep = $$pat{-keep};
    Test::Regexp::->new->init(
        pattern      => $pat,
        keep_pattern => $keep,
        name         => $name,
    );
}

#
# Test double quoted delimiter
#
{
    my $test =
      make_test "Double quoted string" => $RE{delimited}{ -delim => '"' };

    $test->match(
        q {"a few words"},
        [ q {"a few words"}, q {"}, q {a few words}, q {"} ],
        test => "Simple string"
      ),
      $test->match(
        q {"a few \ words"},
        [ q {"a few \ words"}, q {"}, q {a few \ words}, q {"} ],
        test => "Simple string with escape"
      ),
      $test->match(
        q {"a few \" words"},
        [ q {"a few \" words"}, q {"}, q {a few \" words}, q {"} ],
        test => "Simple string with escaped delimiter"
      ),
      $test->match(
        q {"a 'few' words"},
        [ q {"a 'few' words"}, q {"}, q {a 'few' words}, q {"} ],
        test => "Simple string with single quotes"
      ),

      $test->no_match( q {'a few words'}, reason => "Wrong delimiters" );
    $test->no_match( q {a few words"},       reason => "No opening delimiter" );
    $test->no_match( q {"a few words},       reason => "No closing delimiter" );
    $test->no_match( q {"a few" words"},     reason => "Unescaped delimiter" );
    $test->no_match( q {"a few\\\\" words"}, reason => "Escaped escape" );
    $test->no_match( q { "a few words"},
        reason => "Characters before opening delimiter" );
    $test->no_match( q {"a few words" },
        reason => "Characters after opening delimiter" );
}

#
# Test single quoted delimiter
#
{
    my $test =
      make_test "Single quoted string" => $RE{delimited}{ -delim => "'" };

    $test->match(
        q {'a few words'},
        [ q {'a few words'}, q {'}, q {a few words}, q {'} ],
        test => "Simple string"
      ),
      $test->match(
        q {'a few \ words'},
        [ q {'a few \ words'}, q {'}, q {a few \ words}, q {'} ],
        test => "Simple string with escape"
      ),
      $test->match(
        q {'a few \' words'},
        [ q {'a few \' words'}, q {'}, q {a few \' words}, q {'} ],
        test => "Simple string with escaped delimiter"
      ),
      $test->match(
        q {'a "few" words'},
        [ q {'a "few" words'}, q {'}, q {a "few" words}, q {'} ],
        test => "Simple string with double quotes"
      ),

      $test->no_match( q {"a few words"}, reason => "Wrong delimiters" );
    $test->no_match( q {a few words'},       reason => "No opening delimiter" );
    $test->no_match( q {'a few words},       reason => "No closing delimiter" );
    $test->no_match( q {'a few' words'},     reason => "Unescaped delimiter" );
    $test->no_match( q {'a few\\\\' words'}, reason => "Escaped escape" );
    $test->no_match( q { 'a few words'},
        reason => "Characters before opening delimiter" );
    $test->no_match( q {'a few words' },
        reason => "Characters after opening delimiter" );
}

#
# Test an odd (space) delimiter
#
{
    my $test =
      make_test "Space quoted string" => $RE{delimited}{ -delim => " " };

    $test->match(
        q { a-few-words },
        [ q { a-few-words }, q { }, q {a-few-words}, q { } ],
        test => "Simple string"
      ),
      $test->match(
        q { a-few-\-words },
        [ q { a-few-\-words }, q { }, q {a-few-\-words}, q { } ],
        test => "Simple string with escape"
      ),
      $test->match(
        q { a-few-\ -words },
        [ q { a-few-\ -words }, q { }, q {a-few-\ -words}, q { } ],
        test => "Simple string with escaped delimiter"
      ),

      $test->no_match( q {"a few words"}, reason => "Wrong delimiters" );
}

#
# Test $RE {quoted}. This automatically tests multiple delimiters as well.
#
{
    my $test = make_test "Quoted string (using 'quoted')" => $RE{quoted};

    $test->match(
        q {"a few words"},
        [ q {"a few words"}, q {"}, q {a few words}, q {"} ],
        test => "Simple string, using double quotes"
      ),
      $test->match(
        q {'a few words'},
        [ q {'a few words'}, q {'}, q {a few words}, q {'} ],
        test => "Simple string, using single quotes"
      ),
      $test->match(
        q {`a few words`},
        [ q {`a few words`}, q {`}, q {a few words}, q {`} ],
        test => "Simple string, using backticks"
      ),
      $test->match(
        q {"a few \ words"},
        [ q {"a few \ words"}, q {"}, q {a few \ words}, q {"} ],
        test => "Double quoted string with escape"
      ),
      $test->match(
        q {'a few \ words'},
        [ q {'a few \ words'}, q {'}, q {a few \ words}, q {'} ],
        test => "Single quoted string with escape"
      ),
      $test->match(
        q {`a few \ words`},
        [ q {`a few \ words`}, q {`}, q {a few \ words}, q {`} ],
        test => "Backtick quoted string with escape"
      ),
      $test->match(
        q {"a few \" words"},
        [ q {"a few \" words"}, q {"}, q {a few \" words}, q {"} ],
        test => "Double quoted string with escaped delimiter"
      ),
      $test->match(
        q {'a few \' words'},
        [ q {'a few \' words'}, q {'}, q {a few \' words}, q {'} ],
        test => "Single quoted string with escaped delimiter"
      ),
      $test->match(
        q {`a few \` words`},
        [ q {`a few \` words`}, q {`}, q {a few \` words}, q {`} ],
        test => "Backtick quoted string with escaped delimiter"
      ),
      $test->match(
        q {"a 'few' words"},
        [ q {"a 'few' words"}, q {"}, q {a 'few' words}, q {"} ],
        test => "Double quoted string with single quotes"
      ),
      $test->match(
        q {'a "few" words'},
        [ q {'a "few" words'}, q {'}, q {a "few" words}, q {'} ],
        test => "Single quoted string with double quotes"
      ),
      $test->match(
        q {`a "few" words`},
        [ q {`a "few" words`}, q {`}, q {a "few" words}, q {`} ],
        test => "Backtick quoted string with double quotes"
      ),

      $test->no_match( q {'a few words"}, reason => "Mixed delimiters" );
    $test->no_match( q {'a few words`}, reason => "Mixed delimiters" );
    $test->no_match( q {"a few words`}, reason => "Mixed delimiters" );
    $test->no_match( q {"a few words'}, reason => "Mixed delimiters" );
    $test->no_match( q {`a few words"}, reason => "Mixed delimiters" );
    $test->no_match( q {`a few words'}, reason => "Mixed delimiters" );
}

#
# Test an another delimiter
#
{
    my $test = make_test "Bang as delimiter" =>
      $RE{delimited}{ -delim => '"' }{ -esc => '!' };

    $test->match(
        q {"a few words"},
        [ q {"a few words"}, q {"}, q {a few words}, q {"} ],
        test => "Simple string"
      ),
      $test->match(
        q {"a few ! words"},
        [ q {"a few ! words"}, q {"}, q {a few ! words}, q {"} ],
        test => "Simple string with escape"
      ),
      $test->match(
        q {"a few !" words"},
        [ q {"a few !" words"}, q {"}, q {a few !" words}, q {"} ],
        test => "Simple string with escaped delimiter"
      ),

      $test->no_match( q {"a few\" words"}, reason => "Incorrect escape" );
    $test->no_match( q {"a few!!" words"}, reason => "Escaped escape" );
}

#
# Test delimiter and escape the same characters
#
{
    my $test = make_test "Delimiter is same as escape" =>
      $RE{delimited}{ -delim => '!' }{ -esc => '!' };

    $test->match(
        q {!a few words!},
        [ q {!a few words!}, q {!}, q {a few words}, q {!} ],
        test => "Simple string"
      ),
      $test->match(
        q {!a few !! words!},
        [ q {!a few !! words!}, q {!}, q {a few !! words}, q {!} ],
        test => "Simple string with escaped delimiter"
      ),

      $test->no_match( q {!a few\! words!}, reason => "Incorrect escape" );
    $test->no_match( q {!a few! words!},
        reason => "Escape must be followed by delimiter" );
}

#
# Test multiple escapes; they should match up with the delimiters
#
{
    my $test =
      make_test "Multiple escape characters" => $RE{quoted}{ -esc => '!_' };

    $test->match(
        q {"a few words"},
        [ q {"a few words"}, q {"}, q {a few words}, q {"} ],
        test => "Simple string"
    );
    $test->match(
        q {"a !"few words"},
        [ q {"a !"few words"}, q {"}, q {a !"few words}, q {"} ],
        test => "Simple string, with escape"
    );
    $test->match(
        q {'a _'few words'},
        [ q {'a _'few words'}, q {'}, q {a _'few words}, q {'} ],
        test => "Simple string, with another delimiter/escape"
    );
    $test->match(
        q {`a _`few words`},
        [ q {`a _`few words`}, q {`}, q {a _`few words}, q {`} ],
        test => "Escape copied when there are more delimiters"
    );

    $test->no_match( q {"a _"few words"}, reason => "Incorrect delimiter" );
    $test->no_match( q {'a !'few words'}, reason => "Incorrect delimiter" );
    $test->no_match( q {`a !`few words`}, reason => "Incorrect delimiter" );
    $test->no_match( q {"a \"few words"}, reason => "Incorrect delimiter" );
}

#
# Test no escape character
#
{
    my $test = make_test "Double quoted string" =>
      $RE{delimited}{ -delim => '"' }{ -esc => };

    $test->match(
        q {"a few words"},
        [ q {"a few words"}, q {"}, q {a few words}, q {"} ],
        test => "Simple string"
      ),

      $test->no_match( q {"a few" words"}, reason => "Delimiter in string" );
    $test->no_match( q {"a few\" words"}, reason => "There is no escape" );
}

#
# Test different closing delimiters
#
{
    my $test = make_test "Bracketed strings" =>
      $RE{delimited}{ -delim => '([{<' }{ -cdelim => ')]}>' };

    $test->match(
        "(a few words)",
        [ "(a few words)", "(", "a few words", ")" ],
        test => "Using parenthesis"
    );
    $test->match(
        "[a few words]",
        [ "[a few words]", "[", "a few words", "]" ],
        test => "Using brackets"
    );
    $test->match(
        "{a few words}",
        [ "{a few words}", "{", "a few words", "}" ],
        test => "Using braces"
    );
    $test->match(
        "<a few words>",
        [ "<a few words>", "<", "a few words", ">" ],
        test => "Using angle brackets"
    );
    $test->match(
        "[a [few words]",
        [ "[a [few words]", "[", "a [few words", "]" ],
        test => "Opening delimiter needs no escape"
    );
    $test->match(
        '[a [few\] words]',
        [ '[a [few\] words]', "[", 'a [few\] words', "]" ],
        test => "Closing delimiter needs escape"
    );

    $test->no_match( "[a few words}", reason => "Mismatched delimiters" );
    $test->no_match( "(a few words>", reason => "Mismatched delimiters" );
    $test->no_match( "{a few words{",
        reason => "Using opening delimiter as the closing delimiter" );
    $test->no_match( ">a few words>",
        reason => "Using closing delimiter as the opening delimiter" );
    $test->no_match( "[a [few] words]",
        reason => "Unescaped closing delimiter" );
}

#
# Use less closing delimiters than opening delimiters
#
{
    my $test = make_test "Less closing delimiters than opening delimiters" =>
      $RE{delimited}{ -delim => "\x{AB}<" }{ -cdelim => "\x{BB}" };
    $test->match(
        "\x{AB}a few words\x{BB}",
        [ "\x{AB}a few words\x{BB}", "\x{AB}", "a few words", "\x{BB}" ],
        test => "Using double angled quotation marks"
    );
    $test->match(
        "<a few words\x{BB}",
        [ "<a few words\x{BB}", "<", "a few words", "\x{BB}" ],
        test => "Closing delimiter repeats"
    );
}

done_testing;

__END__
