The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
#!/usr/bin/perl

# Formal unit tests for specific PPI::Token classes

use strict;
BEGIN {
	no warnings 'once';
	$| = 1;
	$PPI::XS_DISABLE = 1;
	$PPI::Lexer::X_TOKENIZER ||= $ENV{X_TOKENIZER};
}

# Execute the tests
use Test::More tests => 447;
use Test::NoWarnings;
use File::Spec::Functions ':ALL';
use t::lib::PPI;
use PPI;





#####################################################################
# Code/Dump Testing
# ntests = 2 + 12 * nfiles

t::lib::PPI->run_testdir( catdir( 't', 'data', '07_token' ) );





#####################################################################
# PPI::Token::Symbol Unit Tests
# Note: braces and the symbol() method are tested in regression.t

SCOPE: {
	# Test both creation methods
	my $Token = PPI::Token::Symbol->new( '$foo' );
	isa_ok( $Token, 'PPI::Token::Symbol' );
	
	# Check the creation of a number of different values
	my @symbols = (
		'$foo'       => '$foo',
		'@foo'       => '@foo',
		'$ foo'      => '$foo',
		'$::foo'     => '$main::foo',
		'@::foo'     => '@main::foo',
		'$foo::bar'  => '$foo::bar',
		'$ foo\'bar' => '$foo::bar',
		);
	while ( @symbols ) {
		my ($value, $canon) = ( shift(@symbols), shift(@symbols) );
		my $Symbol = PPI::Token::Symbol->new( $value );
		isa_ok( $Symbol, 'PPI::Token::Symbol' );
		is( $Symbol->content,   $value, "Symbol '$value' returns ->content   '$value'" );
		is( $Symbol->canonical, $canon, "Symbol '$value' returns ->canonical '$canon'" );
	}
}


#####################################################################
# PPI::Token::Number Unit Tests

SCOPE: {
	my @examples = (
		# code => base | '10f' | '10e'
		'0'           => 10,
		'1'           => 10,
		'10'          => 10,
		'1_0'         => 10,
		'.0'          => '10f',
		'.0_0'        => '10f',
		'-.0'         => '10f',
		'0.'          => '10f',
		'0.0'         => '10f',
		'0.0_0'       => '10f',
		'1_0.'        => '10f',
		'.0e0'        => '10e',
		'-.0e0'       => '10e',
		'0.e1'        => '10e',
		'0.0e-1'      => '10e',
		'0.0e+1'      => '10e',
		'0.0e-10'     => '10e',
		'0.0e+10'     => '10e',
		'0.0e100'     => '10e',
		'1_0e1_0'     => '10e', # Known to fail on 5.6.2
		'0b'          => 2,
		'0b0'         => 2,
		'0b10'        => 2,
		'0b1_0'       => 2,
		'00'          => 8,
		'01'          => 8,
		'010'         => 8,
		'01_0'        => 8,
		'0x'          => 16,
		'0x0'         => 16,
		'0x10'        => 16,
		'0x1_0'       => 16,
		'0.0.0'       => 256,
		'.0.0'        => 256,
		'127.0.0.1'   => 256,
		'1.1.1.1.1.1' => 256,
	);

	while ( @examples ) {
		my $code  = shift @examples;
		my $base  = shift @examples;
		if ( $] >= 5.006 and $] < 5.008 and $code eq '1_0e1_0' ) {
			SKIP: {
				skip( 'Ignoring known-bad case on Perl 5.6.2', 5 );
			}
			next;
		}
		my $exp   = $base =~ s/e//;
		my $float = $exp || $base =~ s/f//;
		my $T     = PPI::Tokenizer->new( \$code );
		my $token = $T->get_token;
		is("$token", $code, "'$code' is a single token");
		is($token->base, $base, "base of '$code' is $base");
		if ($float) {
			ok($token->isa('PPI::Token::Number::Float'), "'$code' is ::Float");
		} else {
			ok(!$token->isa('PPI::Token::Number::Float'), "'$code' not ::Float");
		}
		if ($exp) {
			ok($token->isa('PPI::Token::Number::Exp'), "'$code' is ::Exp");
		} else {
			ok(!$token->isa('PPI::Token::Number::Exp'), "'$code' not ::Exp");
		}

		if ($base != 256) {
			$^W = 0;
			my $literal = eval $code;
			if ($@) {
				is($token->literal, undef, "literal('$code'), $@");
			} else {
				cmp_ok($token->literal, '==', $literal, "literal('$code')");
			}
		}
	}
}

foreach my $code ( '1.0._0', '1.0.0.0_0' ) {
	my $T = PPI::Tokenizer->new( \$code );
	my $token = $T->get_token;
	isnt("$token", $code, 'tokenize bad version');
}


foreach my $code ( '08', '09', '0778', '0779' ) {
	my $T = PPI::Tokenizer->new( \$code );
	my $token = $T->get_token;
	isa_ok($token, 'PPI::Token::Number::Octal');
	is("$token", $code, "tokenize bad octal '$code'");
	ok($token->{_error} && $token->{_error} =~ m/octal/i,
	   'invalid octal number should trigger parse error');
	is($token->literal, undef, "literal('$code') is undef");
}

BINARY: {
	my @tests = (
		# Good binary numbers
		{ code => '0b0',        error => 0, value => 0 },
		{ code => '0b1',        error => 0, value => 1 },
		{ code => '0B1',        error => 0, value => 1 },
		{ code => '0b101',      error => 0, value => 5 },
		{ code => '0b1_1',      error => 0, value => 3 },
		{ code => '0b1__1',     error => 0, value => 3 },  # perl warns, but parses it
		{ code => '0b1__1_',    error => 0, value => 3 },  # perl warns, but parses it
		# Bad binary numbers
		{ code => '0b2',        error => 1, value => 0 },
		{ code => '0B2',        error => 1, value => 0 },
		{ code => '0b012',      error => 1, value => 0 },
		{ code => '0B012',      error => 1, value => 0 },
		{ code => '0B0121',     error => 1, value => 0 },
        );
	foreach my $test ( @tests ) {
		my $code = $test->{code};
		my $T = PPI::Tokenizer->new( \$code );
		my $token = $T->get_token;
		isa_ok($token, 'PPI::Token::Number::Binary');
                if ( $test->{error} ) {
                    ok($token->{_error} && $token->{_error} =~ m/binary/i,
                       'invalid binary number should trigger parse error');
                    is($token->literal, undef, "literal('$code') is undef");
                }
                else {
                    ok(!$token->{_error}, "no error for '$code'");
                    is($token->literal, $test->{value}, "literal('$code') is $test->{value}");
                }
                is($token->content, $code, "parsed everything");
	}
}

HEX: {
	my @tests = (
		# Good hex numbers--entire thing goes in the token
		{ code => '0x0',        parsed => '0x0', value => 0 },
		{ code => '0X1',        parsed => '0X1', value => 1 },
		{ code => '0x1',        parsed => '0x1', value => 1 },
		{ code => '0x_1',       parsed => '0x_1', value => 1 },
		{ code => '0x__1',      parsed => '0x__1', value => 1 },  # perl warns, but parses it
		{ code => '0x__1_',     parsed => '0x__1_', value => 1 },  # perl warns, but parses it
		{ code => '0X1',        parsed => '0X1', value => 1 },
		{ code => '0xc',        parsed => '0xc', value => 12 },
		{ code => '0Xc',        parsed => '0Xc', value => 12 },
		{ code => '0XC',        parsed => '0XC', value => 12 },
		{ code => '0xbeef',     parsed => '0xbeef', value => 48879 },
		{ code => '0XbeEf',     parsed => '0XbeEf', value => 48879 },
		{ code => '0x0e',       parsed => '0x0e', value => 14 },
		{ code => '0x00000e',   parsed => '0x00000e', value => 14 },
		{ code => '0x000_00e',  parsed => '0x000_00e', value => 14 },
		{ code => '0x000__00e', parsed => '0x000__00e', value => 14 },  # perl warns, but parses it
		# Bad hex numbers--tokenizing stops when bad digit seen
		{ code => '0x',    parsed => '0x', value => 0 },
		{ code => '0X',    parsed => '0X', value => 0 },
		{ code => '0xg',   parsed => '0x', value => 0 },
		{ code => '0Xg',   parsed => '0X', value => 0 },
		{ code => '0XG',   parsed => '0X', value => 0 },
		{ code => '0x0g',  parsed => '0x0', value => 0 },
		{ code => '0X0g',  parsed => '0X0', value => 0 },
		{ code => '0X0G',  parsed => '0X0', value => 0 },
		{ code => '0x1g',  parsed => '0x1', value => 1 },
		{ code => '0x1g2', parsed => '0x1', value => 1 },
		{ code => '0x1_g', parsed => '0x1_', value => 1 },
	);
	foreach my $test ( @tests ) {
		my $code = $test->{code};
		my $T = PPI::Tokenizer->new( \$code );
		my $token = $T->get_token;
		isa_ok($token, 'PPI::Token::Number::Hex');
		ok(!$token->{_error}, "no error for '$code' even on invalid digits");
		is($token->content, $test->{parsed}, "correctly parsed everything expected");
                is($token->literal, $test->{value}, "literal('$code') is $test->{value}");
	}
}