#!/usr/bin/perl
# Unit testing for PPI::Token::Operator
use lib 't/lib';
use PPI::Test::pragmas;
use Test::More tests => 1179 + ($ENV{AUTHOR_TESTING} ? 1 : 0);
use PPI;
use PPI::Singletons qw' %OPERATOR %KEYWORDS ';
FIND_ONE_OP: {
my $source = '$a = .987;';
my $doc = PPI::Document->new( \$source );
isa_ok( $doc, 'PPI::Document', "parsed '$source'" );
my $ops = $doc->find( 'Token::Number::Float' );
is( ref $ops, 'ARRAY', "found number" );
is( @$ops, 1, "number found exactly once" );
is( $ops->[0]->content(), '.987', "text matches" );
$ops = $doc->find( 'Token::Operator' );
is( ref $ops, 'ARRAY', "operator = found operators in number test" );
is( @$ops, 1, "operator = found exactly once in number test" );
}
PARSE_ALL_OPERATORS: {
foreach my $op ( sort keys %OPERATOR ) {
my $source = $op eq '<>' || $op eq '<<>>' ? $op . ';' : "\$foo $op 2;";
my $doc = PPI::Document->new( \$source );
isa_ok( $doc, 'PPI::Document', "operator $op parsed '$source'" );
my $ops = $doc->find( $op eq '<<>>' || $op eq '<>'
? 'Token::QuoteLike::Readline' : 'Token::Operator' );
is( ref $ops, 'ARRAY', "operator $op found operators" );
is( @$ops, 1, "operator $op found exactly once" );
is( $ops->[0]->content(), $op, "operator $op operator text matches" );
}
}
OPERATOR_X: {
my @tests = (
{
desc => 'generic bareword with integer', # github #133
code => 'bareword x 3',
expected => [
'PPI::Token::Word' => 'bareword',
'PPI::Token::Whitespace' => ' ',
'PPI::Token::Operator' => 'x',
'PPI::Token::Whitespace' => ' ',
'PPI::Token::Number' => '3',
],
},
{
desc => 'generic bareword with integer run together', # github #133
code => 'bareword x3',
expected => [
'PPI::Token::Word' => 'bareword',
'PPI::Token::Whitespace' => ' ',
'PPI::Token::Operator' => 'x',
'PPI::Token::Number' => '3',
],
},
{
desc => 'preceding word looks like a force but is not', # github #133
code => '$a->package x3',
expected => [
'PPI::Token::Symbol' => '$a',
'PPI::Token::Operator' => '->',
'PPI::Token::Word' => 'package',
'PPI::Token::Whitespace' => ' ',
'PPI::Token::Operator' => 'x',
'PPI::Token::Number' => '3',
],
},
{
desc => 'method with method',
code => 'sort { $a->package cmp $b->package } ();',
expected => [
'PPI::Token::Word' => 'sort',
'PPI::Token::Whitespace' => ' ',
'PPI::Structure::Block'=> '{ $a->package cmp $b->package }',
'PPI::Token::Structure'=> '{',
'PPI::Token::Whitespace'=> ' ',
'PPI::Statement'=> '$a->package cmp $b->package',
'PPI::Token::Symbol'=> '$a',
'PPI::Token::Operator'=> '->',
'PPI::Token::Word'=> 'package',
'PPI::Token::Whitespace'=> ' ',
'PPI::Token::Operator'=> 'cmp',
'PPI::Token::Whitespace'=> ' ',
'PPI::Token::Symbol'=> '$b',
'PPI::Token::Operator'=> '->',
'PPI::Token::Word'=> 'package',
'PPI::Token::Whitespace'=> ' ',
'PPI::Token::Structure'=> '}',
'PPI::Token::Whitespace'=> ' ',
'PPI::Structure::List'=> '()',
'PPI::Token::Structure'=> '(',
'PPI::Token::Structure'=> ')',
'PPI::Token::Structure'=> ';'
],
},
{
desc => 'method with integer',
code => 'c->d x 3',
expected => [
'PPI::Token::Word' => 'c',
'PPI::Token::Operator' => '->',
'PPI::Token::Word' => 'd',
'PPI::Token::Whitespace' => ' ',
'PPI::Token::Operator' => 'x',
'PPI::Token::Whitespace' => ' ',
'PPI::Token::Number' => '3',
],
},
{
desc => 'integer with integer',
code => '1 x 3',
expected => [
'PPI::Token::Number' => '1',
'PPI::Token::Whitespace' => ' ',
'PPI::Token::Operator' => 'x',
'PPI::Token::Whitespace' => ' ',
'PPI::Token::Number' => '3',
],
},
{
desc => 'string with integer',
code => '"y" x 3',
expected => [
'PPI::Token::Quote::Double' => '"y"',
'PPI::Token::Whitespace' => ' ',
'PPI::Token::Operator' => 'x',
'PPI::Token::Whitespace' => ' ',
'PPI::Token::Number' => '3',
],
},
{
desc => 'string with integer',
code => 'qq{y} x 3',
expected => [
'PPI::Token::Quote::Interpolate' => 'qq{y}',
'PPI::Token::Whitespace' => ' ',
'PPI::Token::Operator' => 'x',
'PPI::Token::Whitespace' => ' ',
'PPI::Token::Number' => '3',
],
},
{
desc => 'string no whitespace with integer',
code => '"y"x 3',
expected => [
'PPI::Token::Quote::Double' => '"y"',
'PPI::Token::Operator' => 'x',
'PPI::Token::Whitespace' => ' ',
'PPI::Token::Number' => '3',
],
},
{
desc => 'variable with integer',
code => '$a x 3',
expected => [
'PPI::Token::Symbol' => '$a',
'PPI::Token::Whitespace' => ' ',
'PPI::Token::Operator' => 'x',
'PPI::Token::Whitespace' => ' ',
'PPI::Token::Number' => '3',
],
},
{
desc => 'variable with no whitespace integer',
code => '$a x3',
expected => [
'PPI::Token::Symbol' => '$a',
'PPI::Token::Whitespace' => ' ',
'PPI::Token::Operator' => 'x',
'PPI::Token::Number' => '3',
],
},
{
desc => 'variable, post ++, x, no whitespace anywhere',
code => '$a++x3',
expected => [
'PPI::Token::Symbol' => '$a',
'PPI::Token::Operator' => '++',
'PPI::Token::Operator' => 'x',
'PPI::Token::Number' => '3',
],
},
{
desc => 'double quote, no whitespace',
code => '"y"x 3',
expected => [
'PPI::Token::Quote::Double' => '"y"',
'PPI::Token::Operator' => 'x',
'PPI::Token::Whitespace' => ' ',
'PPI::Token::Number' => '3',
],
},
{
desc => 'single quote, no whitespace',
code => "'y'x 3",
expected => [
'PPI::Token::Quote::Single' => "'y'",
'PPI::Token::Operator' => 'x',
'PPI::Token::Whitespace' => ' ',
'PPI::Token::Number' => '3',
],
},
{
desc => 'parens, no whitespace, number',
code => "(5)x 3",
expected => [
'PPI::Structure::List' => '(5)',
'PPI::Token::Structure' => '(',
'PPI::Statement::Expression' => '5',
'PPI::Token::Number' => '5',
'PPI::Token::Structure' => ')',
'PPI::Token::Operator' => 'x',
'PPI::Token::Whitespace' => ' ',
'PPI::Token::Number' => '3',
],
},
{
desc => 'number following x is hex',
code => "1x0x1",
expected => [
'PPI::Token::Number' => '1',
'PPI::Token::Operator' => 'x',
'PPI::Token::Number::Hex' => '0x1',
],
},
{
desc => 'x followed by symbol',
code => '1 x$y',
expected => [
'PPI::Token::Number' => '1',
'PPI::Token::Whitespace' => ' ',
'PPI::Token::Operator' => 'x',
'PPI::Token::Symbol' => '$y',
],
},
{
desc => 'x= with no trailing whitespace, symbol',
code => '$z x=3',
expected => [
'PPI::Token::Symbol' => '$z',
'PPI::Token::Whitespace' => ' ',
'PPI::Token::Operator' => 'x=',
'PPI::Token::Number' => '3',
],
},
{
desc => 'x= with no trailing whitespace, symbol',
code => '$z x=$y',
expected => [
'PPI::Token::Symbol' => '$z',
'PPI::Token::Whitespace' => ' ',
'PPI::Token::Operator' => 'x=',
'PPI::Token::Symbol' => '$y',
],
},
{
desc => 'x plus whitespace on the left of => that is not the first token in the doc',
code => '1;x =>1;',
expected => [
'PPI::Statement' => '1;',
'PPI::Token::Number' => '1',
'PPI::Token::Structure' => ';',
'PPI::Statement' => 'x =>1;',
'PPI::Token::Word' => 'x',
'PPI::Token::Whitespace' => ' ',
'PPI::Token::Operator' => '=>',
'PPI::Token::Number' => '1',
'PPI::Token::Structure' => ';',
],
},
{
desc => 'x on the left of => that is not the first token in the doc',
code => '1;x=>1;',
expected => [
'PPI::Statement' => '1;',
'PPI::Token::Number' => '1',
'PPI::Token::Structure' => ';',
'PPI::Statement' => 'x=>1;',
'PPI::Token::Word' => 'x',
'PPI::Token::Operator' => '=>',
'PPI::Token::Number' => '1',
'PPI::Token::Structure' => ';',
],
},
{
desc => 'x on the left of => that is not the first token in the doc',
code => '$hash{x}=1;',
expected => [
'PPI::Token::Symbol' => '$hash',
'PPI::Structure::Subscript' => '{x}',
'PPI::Token::Structure' => '{',
'PPI::Statement::Expression' => 'x',
'PPI::Token::Word' => 'x',
'PPI::Token::Structure' => '}',
'PPI::Token::Operator' => '=',
'PPI::Token::Number' => '1',
'PPI::Token::Structure' => ';',
],
},
{
desc => 'x plus whitespace on the left of => is not an operator',
code => 'x =>1',
expected => [
'PPI::Token::Word' => 'x',
'PPI::Token::Whitespace' => ' ',
'PPI::Token::Operator' => '=>',
'PPI::Token::Number' => '1',
],
},
{
desc => 'x immediately followed by => should not be mistaken for x=',
code => 'x=>1',
expected => [
'PPI::Token::Word' => 'x',
'PPI::Token::Operator' => '=>',
'PPI::Token::Number' => '1',
],
},
{
desc => 'xx on left of => not mistaken for an x operator',
code => 'xx=>1',
expected => [
'PPI::Token::Word' => 'xx',
'PPI::Token::Operator' => '=>',
'PPI::Token::Number' => '1',
],
},
{
desc => 'x right of => is not an operator',
code => '1=>x',
expected => [
'PPI::Token::Number' => '1',
'PPI::Token::Operator' => '=>',
'PPI::Token::Word' => 'x',
],
},
{
desc => 'xor right of => is an operator',
code => '1=>xor',
expected => [
'PPI::Token::Number' => '1',
'PPI::Token::Operator' => '=>',
'PPI::Token::Operator' => 'xor',
],
},
{
desc => 'RT 37892: list as arg to x operator 1',
code => '(1) x 6',
expected => [
'PPI::Structure::List' => '(1)',
'PPI::Token::Structure' => '(',
'PPI::Statement::Expression' => '1',
'PPI::Token::Number' => '1',
'PPI::Token::Structure' => ')',
'PPI::Token::Whitespace' => ' ',
'PPI::Token::Operator' => 'x',
'PPI::Token::Whitespace' => ' ',
'PPI::Token::Number' => '6',
],
},
{
desc => 'RT 37892: list as arg to x operator 2',
code => '(1) x6',
expected => [
'PPI::Structure::List' => '(1)',
'PPI::Token::Structure' => '(',
'PPI::Statement::Expression' => '1',
'PPI::Token::Number' => '1',
'PPI::Token::Structure' => ')',
'PPI::Token::Whitespace' => ' ',
'PPI::Token::Operator' => 'x',
'PPI::Token::Number' => '6',
],
},
{
desc => 'RT 37892: list as arg to x operator 3',
code => '(1)x6',
expected => [
'PPI::Structure::List' => '(1)',
'PPI::Token::Structure' => '(',
'PPI::Statement::Expression' => '1',
'PPI::Token::Number' => '1',
'PPI::Token::Structure' => ')',
'PPI::Token::Operator' => 'x',
'PPI::Token::Number' => '6',
],
},
{
desc => 'RT 37892: x following function is operator',
code => 'foo()x6',
expected => [
'PPI::Token::Word' => 'foo',
'PPI::Structure::List' => '()',
'PPI::Token::Structure' => '(',
'PPI::Token::Structure' => ')',
'PPI::Token::Operator' => 'x',
'PPI::Token::Number' => '6',
],
},
{
desc => 'RT 37892: list as arg to x operator 4',
code => 'qw(1)x6',
expected => [
'PPI::Token::QuoteLike::Words' => 'qw(1)',
'PPI::Token::Operator' => 'x',
'PPI::Token::Number' => '6',
],
},
{
desc => 'RT 37892: list as arg to x operator 5',
code => 'qw<1>x6',
expected => [
'PPI::Token::QuoteLike::Words' => 'qw<1>',
'PPI::Token::Operator' => 'x',
'PPI::Token::Number' => '6',
],
},
{
desc => 'RT 37892: listref as arg to x operator 6',
code => '[1]x6',
expected => [
'PPI::Structure::Constructor' => '[1]',
'PPI::Token::Structure' => '[',
'PPI::Statement' => '1',
'PPI::Token::Number' => '1',
'PPI::Token::Structure' => ']',
'PPI::Token::Operator' => 'x',
'PPI::Token::Number' => '6',
],
},
{
desc => 'x followed by sigil $ that is not also an operator',
code => '1x$bar',
expected => [
'PPI::Token::Number' => '1',
'PPI::Token::Operator' => 'x',
'PPI::Token::Symbol' => '$bar',
],
},
{
desc => 'x followed by sigil @ that is not also an operator',
code => '1x@bar',
expected => [
'PPI::Token::Number' => '1',
'PPI::Token::Operator' => 'x',
'PPI::Token::Symbol' => '@bar',
],
},
{
desc => 'sub name /^x/',
code => 'sub xyzzy : _5x5 {1;}',
expected => [
'PPI::Statement::Sub' => 'sub xyzzy : _5x5 {1;}',
'PPI::Token::Word' => 'sub',
'PPI::Token::Whitespace' => ' ',
'PPI::Token::Word' => 'xyzzy',
'PPI::Token::Whitespace' => ' ',
'PPI::Token::Operator' => ':',
'PPI::Token::Whitespace' => ' ',
'PPI::Token::Attribute' => '_5x5',
'PPI::Token::Whitespace' => ' ',
'PPI::Structure::Block' => '{1;}',
'PPI::Token::Structure' => '{',
'PPI::Statement' => '1;',
'PPI::Token::Number' => '1',
'PPI::Token::Structure' => ';',
'PPI::Token::Structure' => '}',
]
},
{
desc => 'label plus x',
code => 'LABEL: x64',
expected => [
'PPI::Statement::Compound' => 'LABEL:',
'PPI::Token::Label' => 'LABEL:',
'PPI::Token::Whitespace' => ' ',
'PPI::Statement' => 'x64',
'PPI::Token::Word' => 'x64',
]
},
);
# Exhaustively test when a preceding operator implies following
# 'x' is word not an operator. This detects the regression in
# which '$obj->x86_convert()' was being parsed as an x
# operator.
my %operators = (
%OPERATOR,
map { $_ => 1 } qw( -r -w -x -o -R -W -X -O -e -z -s -f -d -l -p -S -b -c -t -u -g -k -T -B -M -A -C )
);
# Don't try to test operators for which PPI currently (1.215)
# doesn't recognize when they're followed immediately by a word.
# E.g.:
# sub x3 {15;} my $z=6; print $z&x3;
# sub x3 {3;} my $z=2; print $z%x3;
delete $operators{'&'};
delete $operators{'%'};
delete $operators{'*'};
foreach my $operator ( keys %operators ) {
my $code = '';
my @expected;
if ( $operator =~ /^\w/ ) {
$code .= '$a ';
push @expected, ( 'PPI::Token::Symbol' => '$a' );
push @expected, ( 'PPI::Token::Whitespace' => ' ' );
}
elsif ( $operator !~ /^-\w/ ) { # filetest operators
$code .= '$a';
push @expected, ( 'PPI::Token::Symbol' => '$a' );
}
$code .= $operator;
push @expected, ( ($operator eq '<<>>' || $operator eq '<>' ?
'PPI::Token::QuoteLike::Readline' : 'PPI::Token::Operator') => $operator );
if ( $operator =~ /\w$/ || $operator eq '<<' ) { # want << operator, not heredoc
$code .= ' ';
push @expected, ( 'PPI::Token::Whitespace' => ' ' );
}
$code .= 'x3';
my $desc;
if ( $operator eq '--' || $operator eq '++' || $operator eq '<>' || $operator eq '<<>>' ) {
push @expected, ( 'PPI::Token::Operator' => 'x' );
push @expected, ( 'PPI::Token::Number' => '3' );
$desc = "operator $operator does not imply following 'x' is a word";
}
else {
push @expected, ( 'PPI::Token::Word' => 'x3' );
$desc = "operator $operator implies following 'x' is a word";
}
push @tests, { desc => $desc, code => $code, expected => \@expected };
}
# Test that Perl builtins known to have a null prototype do not
# force a following 'x' to be a word.
my %noprotos = map { $_ => 1 } qw(
endgrent
endhostent
endnetent
endprotoent
endpwent
endservent
fork
getgrent
gethostent
getlogin
getnetent
getppid
getprotoent
getpwent
getservent
setgrent
setpwent
time
times
wait
wantarray
__SUB__
);
foreach my $noproto ( keys %noprotos ) {
my $code = "$noproto x3";
my @expected = (
'PPI::Token::Word' => $noproto,
'PPI::Token::Whitespace' => ' ',
'PPI::Token::Operator' => 'x',
'PPI::Token::Number' => '3',
);
my $desc = "builtin $noproto does not force following x to be a word";
push @tests, { desc => "builtin $noproto does not force following x to be a word", code => $code, expected => \@expected };
}
foreach my $test ( @tests ) {
my $d = PPI::Document->new( \$test->{code} );
my $tokens = $d->find( sub { 1; } );
$tokens = [ map { ref($_), $_->content() } @$tokens ];
my $expected = $test->{expected};
if ( $expected->[0] !~ /^PPI::Statement/ ) {
unshift @$expected, 'PPI::Statement', $test->{code};
}
my $ok = is_deeply( $tokens, $expected, $test->{desc} );
if ( !$ok ) {
diag "$test->{code} ($test->{desc})\n";
diag explain $tokens;
diag explain $test->{expected};
}
}
}
OPERATOR_FAT_COMMA: {
my @tests = (
{
desc => 'integer with integer',
code => '1 => 2',
expected => [
'PPI::Token::Number' => '1',
'PPI::Token::Whitespace' => ' ',
'PPI::Token::Operator' => '=>',
'PPI::Token::Whitespace' => ' ',
'PPI::Token::Number' => '2',
],
},
{
desc => 'word with integer',
code => 'foo => 2',
expected => [
'PPI::Token::Word' => 'foo',
'PPI::Token::Whitespace' => ' ',
'PPI::Token::Operator' => '=>',
'PPI::Token::Whitespace' => ' ',
'PPI::Token::Number' => '2',
],
},
{
desc => 'dashed word with integer',
code => '-foo => 2',
expected => [
'PPI::Token::Word' => '-foo',
'PPI::Token::Whitespace' => ' ',
'PPI::Token::Operator' => '=>',
'PPI::Token::Whitespace' => ' ',
'PPI::Token::Number' => '2',
],
},
( map { {
desc=>$_,
code=>"$_=>2",
expected=>[
'PPI::Token::Word' => $_,
'PPI::Token::Operator' => '=>',
'PPI::Token::Number' => '2',
]
} } keys %KEYWORDS ),
( map { {
desc=>$_,
code=>"($_=>2)",
expected=>[
'PPI::Structure::List' => "($_=>2)",
'PPI::Token::Structure' => '(',
'PPI::Statement::Expression' => "$_=>2",
'PPI::Token::Word' => $_,
'PPI::Token::Operator' => '=>',
'PPI::Token::Number' => '2',
'PPI::Token::Structure' => ')',
]
} } keys %KEYWORDS ),
( map { {
desc=>$_,
code=>"{$_=>2}",
expected=>[
'PPI::Structure::Constructor' => "{$_=>2}",
'PPI::Token::Structure' => '{',
'PPI::Statement::Expression' => "$_=>2",
'PPI::Token::Word' => $_,
'PPI::Token::Operator' => '=>',
'PPI::Token::Number' => '2',
'PPI::Token::Structure' => '}',
]
} } keys %KEYWORDS ),
);
for my $test ( @tests ) {
my $code = $test->{code};
my $d = PPI::Document->new( \$test->{code} );
my $tokens = $d->find( sub { 1; } );
$tokens = [ map { ref($_), $_->content() } @$tokens ];
my $expected = $test->{expected};
if ( $expected->[0] !~ /^PPI::Statement/ ) {
unshift @$expected, 'PPI::Statement', $test->{code};
}
my $ok = is_deeply( $tokens, $expected, $test->{desc} );
if ( !$ok ) {
diag "$test->{code} ($test->{desc})\n";
diag explain $tokens;
diag explain $test->{expected};
}
}
}
OPERATORS_PLUS_MINUS: {
my @operands = (
'1', '2',
'1', '(2)',
'(1)', '(2)'
);
for my $op (qw/- +/) {
for ( my $i = 0; $i < @operands; $i += 2 ) {
my ( $a, $b ) = @operands[ $i, $i + 1 ];
my $code = "${a}${op}${b}";
my $doc = PPI::Document->new( \$code );
isa_ok( $doc, 'PPI::Document', "parsed '$code'" );
my $ops = $doc->find('Token::Operator');
is( ref $ops, 'ARRAY', "found operator $op" );
is( @$ops, 1, "operator $op found exactly once" );
is( $ops->[0]->content(), $op, "operator $op text matches" );
}
}
# Add "'(1)', '2'" into operands once TODO is resolved.
{
my ( $a, $b ) = ( '(1)', '2' );
my $op = '+';
my $code = "${a}${op}${b}";
my $doc = PPI::Document->new( \$code );
isa_ok( $doc, 'PPI::Document', "parsed '$code'" );
my $ops = $doc->find('Token::Operator');
is( ref $ops, 'ARRAY', "found operator $op" );
}
TODO: {
my ( $a, $b ) = ( '(1)', '2' );
my $op = '-';
my $code = "${a}${op}${b}";
my $doc = PPI::Document->new( \$code );
isa_ok( $doc, 'PPI::Document', "parsed '$code'" );
my $ops = $doc->find('Token::Operator');
local $TODO = "(1)-2 not parsed correctly";
is( ref $ops, 'ARRAY', "found operator $op" );
}
}