#!perl

# $Id: Table.t,v 1.1 2010/11/20 20:38:51 Paulo Exp $

use warnings;
use strict;

use Test::More;

use_ok 'Asm::Z80::Table';

is_deeply 
	Asm::Z80::Table->asm_table->{'adc'}{'a'}{','}{'('}{'hl'}{')'}{''},
	[0x8E];
is_deeply 
	Asm::Z80::Table->disasm_table->{0x8E}{''},
	['adc', 'a', ',', '(', 'hl', ')'];

is_deeply 
	Asm::Z80::Table->asm_table->{'adc'}{'a'}{','}{'N'}{''},
	[0xCE, 'N'];
is_deeply 
	Asm::Z80::Table->disasm_table->{0xCE}{'N'}{''},
	['adc', 'a', ',', 'N'];
	
is_deeply 
	Asm::Z80::Table->asm_table->{'call'}{'NN'}{''},
	[0xCD, 'NNl', 'NNh'];
is_deeply 
	Asm::Z80::Table->disasm_table->{0xCD}{'NNl'}{'NNh'}{''},
	['call', 'NN'];
	
is_deeply 
	Asm::Z80::Table->asm_table->{'jr'}{'NN'}{''},
	[0x18, 'NNo'];
is_deeply 
	Asm::Z80::Table->disasm_table->{0x18}{'NNo'}{''},
	['jr', 'NN'];
	
is_deeply 
	Asm::Z80::Table->asm_table->{'adc'}{'a'}{','}{'('}{'ix'}{'+'}{'DIS'}{')'}{''},
	[0xDD, 0x8E, 'DIS'];
is_deeply 
	Asm::Z80::Table->disasm_table->{0xDD}{0x8E}{'DIS'}{''},
	['adc', 'a', ',', '(', 'ix', '+', 'DIS', ')'];
	
is_deeply 
	Asm::Z80::Table->asm_table->{'adc'}{'a'}{','}{'('}{'ix'}{'-'}{'NDIS'}{')'}{''},
	[0xDD, 0x8E, 'NDIS'];
is_deeply 
	Asm::Z80::Table->disasm_table->{0xDD}{0x8E}{'NDIS'}{''},
	['adc', 'a', ',', '(', 'ix', '-', 'NDIS', ')'];

is_deeply 
	Asm::Z80::Table->asm_table->{'ld'}{'bc'}{','}{'('}{'ix'}{'+'}{'DIS'}{')'}{''},
	[0xDD, 0x4E, 'DIS', 0xDD, 0x46, 'DIS+1'];
is_deeply 
	Asm::Z80::Table->disasm_table->{0xDD}{0x4E}{'DIS'}{0xDD}{0x46}{'DIS+1'}{''},
	['ld', 'bc', ',', '(', 'ix', '+', 'DIS', ')'];

is_deeply 
	Asm::Z80::Table->asm_table->{'ld'}{'bc'}{','}{'('}{'ix'}{'-'}{'NDIS'}{')'}{''},
	[0xDD, 0x4E, 'NDIS', 0xDD, 0x46, 'NDIS+1'];
is_deeply 
	Asm::Z80::Table->disasm_table->{0xDD}{0x4E}{'NDIS'}{0xDD}{0x46}{'NDIS+1'}{''},
	['ld', 'bc', ',', '(', 'ix', '-', 'NDIS', ')'];

#------------------------------------------------------------------------------
# iterator

# recursively build table of all asm_table
my %asm_table;
my %disasm_table;
my %seen_disasm_key;
build_table(\%asm_table, 	Asm::Z80::Table->asm_table);
build_table(\%disasm_table,	Asm::Z80::Table->disasm_table);

# note that some byte sequences have more than one possible representation
is scalar(keys %asm_table), 	2445, "assembly table";
is scalar(keys %disasm_table), 	2384, "disassembly table";

isa_ok my $iter = Asm::Z80::Table->iterator, 'CODE';

# iterator lookup in instruction sort order
for my $key (sort keys %asm_table) {
	ok my($iter_tokens,  $iter_bytes)  = $iter->(),				"iterator lookup";

	# compare iterator with asm_table
	ok my($table_tokens, $table_bytes) = @{$asm_table{$key}},	"asm table lookup";	
	is_deeply $table_tokens, $iter_tokens,						"same tokens";
	is_deeply $table_bytes,  $iter_bytes,						"same bytes";
	
	# compare with corresponding disassembly
	my $disasm_key = key(@$iter_bytes);
	ok exists $disasm_table{$disasm_key},						"disasm table lookup";
	$seen_disasm_key{$disasm_key}++;
	
	ok my($distable_bytes, $distable_tokens) = @{$disasm_table{$disasm_key}},	
																"asm distable lookup";	

	# check for special cases
	if ("@$table_tokens" eq "sla hl") {
		ok "@$distable_tokens", "add hl , hl";			# sla hl is add hl,hl
	}
	elsif ("@$table_tokens" =~ /^(sli|sll)(.*)/i) {
		my $rest = qr/\Q$2\E/;
		like "@$distable_tokens", qr/^(sli|sll)$rest/;	# sli and sll are the same
	}
	else {
		ok "@$table_tokens", "@$distable_tokens";
	}
	
	is_deeply $distable_bytes,  $iter_bytes,					"same bytes";
}

# check which disasm_table was not seen
for my $key (sort keys %disasm_table) {
	next if $seen_disasm_key{$key};
	
	ok my($distable_bytes, $distable_tokens) = @{$disasm_table{$key}},	
																"asm distable lookup";

	# special cases of instrutions that have a smaller code sequence for assembly
	next if "@$distable_bytes" eq "203 37 203 20" && "@$distable_tokens" eq "sla hl";
	next if "@$distable_bytes" eq "237 107 NNl NNh" && "@$distable_tokens" eq "ld hl , ( NN )";
	next if "@$distable_bytes" eq "237 99 NNl NNh" && "@$distable_tokens" eq "ld ( NN ) , hl";

	ok 0, "unexpected @$distable_bytes => @$distable_tokens";
}


done_testing();


#------------------------------------------------------------------------------
# tools
sub build_table {
	my($table, $node, @tokens) = @_;
	for my $child (sort keys %$node) {
		if ($child eq '') {
			$table->{key(@tokens)} = [\@tokens, $node->{''}];
		}
		else {
			build_table($table, $node->{$child}, @tokens, $child);
		}
	}
}

sub key {
	my(@tokens) = @_;
	join(' ', map {sprintf("%-4s", $_)} @tokens);
}

