The Perl Advent Calendar needs more articles for 2022. Submit your idea today!
#!perl

# $Id$

use strict;
use warnings;

use Test::More tests => 57;
use_ok 'CPU::Z80::Assembler';
use_ok 'CPU::Z80::Assembler::Expr';
use_ok 'Asm::Preproc::Line';
use_ok 'Iterator::Simple::Lookahead';
require_ok 't/test_utils.pl';

our $stream;

my $warn; 
$SIG{__WARN__} = sub {$warn = shift};
END { is $warn, undef, "no warnings"; }

# construct
isa_ok		my $line = Asm::Preproc::Line->new("hello\n", "f.asm", 10),
			'Asm::Preproc::Line';

isa_ok		my $expr = CPU::Z80::Assembler::Expr->new(line => $line),
			'CPU::Z80::Assembler::Expr';
is_deeply	$expr->child,	[], 	"no children";
is			$expr->line->text, 		"hello\n", 	"line text";
is			$expr->line->line_nr, 	10, 		"line line_nr";
is			$expr->line->file, 		"f.asm", 	"line file";
is			$expr->evaluate, 0,			"empty expression is 0";


isa_ok		my $dollar = CPU::Z80::Assembler::Expr->new,
			'CPU::Z80::Assembler::Expr';
$stream = z80lexer('$');
ok 			$stream = $dollar->parse($stream), "parse expr";
is			$dollar->evaluate(0), 0, "eval";
is			$dollar->evaluate(1), 1, "eval";
is			$dollar->evaluate(11), 11, "eval";

$stream = z80lexer('10+$');
ok 			$stream = $expr->parse($stream), "parse expr";
is			$expr->evaluate(0), 10, "eval";
is			$expr->evaluate(1), 11, "eval";
is			$expr->evaluate(11), 21, "eval";

my %symbols = ( va => 51, vb => $dollar, vc => $expr );

$stream = z80lexer('10+va');
ok 			$stream = $expr->parse($stream), "parse expr";
is			$expr->evaluate(0, \%symbols), 61, "eval";
is			$expr->evaluate(1, \%symbols), 61, "eval";

$stream = z80lexer('10+vb');
ok 			$stream = $expr->parse($stream), "parse expr";
is			$expr->evaluate(0, \%symbols), 10, "eval";
is			$expr->evaluate(1, \%symbols), 11, "eval";

$stream = z80lexer('""');
ok 			$stream = $expr->parse($stream), "parse expr";
is			$expr->evaluate, 0, "eval";

$stream = z80lexer("''");
ok 			$stream = $expr->parse($stream), "parse expr";
is			$expr->evaluate, 0, "eval";

$stream = z80lexer('"A"');
ok 			$stream = $expr->parse($stream), "parse expr";
is			$expr->evaluate, ord('A'), "eval";

$stream = z80lexer('"AZ"');
ok 			$stream = $expr->parse($stream), "parse expr";
is			$expr->evaluate, ord('A') + (ord('Z') << 8), "eval";

$stream = z80lexer('"AZY"');
ok 			$stream = $expr->parse($stream), "parse expr";
is			$warn, undef, "no warnings";
is			$expr->evaluate, ord('A') + (ord('Z') << 8), "eval";
is			$warn, "-(1) : warning: Expression AZY: extra bytes ignored\n", "warning";
$warn = undef;

$stream = z80lexer('10+vc');
ok 			$stream = $expr->parse($stream), "parse expr";
eval {$expr->evaluate(0, \%symbols)};
is			$@, "-(1) : error: Circular reference computing 'vc'\n",
			"circular reference";

# simulate bug:
#	err equ 10
#	y0  equ err
#	ld a,(iy+err-y0) ; gives circular reference error, and should not
isa_ok		my $err_expr = CPU::Z80::Assembler::Expr->new(line => $line),
			'CPU::Z80::Assembler::Expr';
$stream = z80lexer('10');
ok 			$stream = $err_expr->parse($stream), "parse expr";
$symbols{err} = $err_expr;

isa_ok		my $y0_expr = CPU::Z80::Assembler::Expr->new(line => $line),
			'CPU::Z80::Assembler::Expr';
$stream = z80lexer('err');
ok 			$stream = $y0_expr->parse($stream), "parse expr";
$symbols{y0} = $y0_expr;

$stream = z80lexer('err-y0');
ok 			$stream = $expr->parse($stream), "parse expr";
is			$expr->evaluate(0, \%symbols), 0, "eval";

$stream = z80lexer('10+vd');
ok 			$stream = $expr->parse($stream), "parse expr";
eval {$expr->evaluate(0, \%symbols)};
is			$@, "-(1) : error: Symbol 'vd' not defined\n",
			"undefined";

$stream = z80lexer('10/(51-va)');
ok 			$stream = $expr->parse($stream), "parse expr";
eval {$expr->evaluate(0, \%symbols)};
is			$@, "-(1) : error: Expression '10 / ( 51 - 51 )': Illegal division by zero\n",
			"division by zero";

$stream = z80lexer('10+');
eval { $expr->parse($stream) };
is			$@, "-(1) : error: expected one of (\"(\" NAME NUMBER STRING) at \"\\n\"\n",
			"syntax error";

$stream = z80lexer('10+hl');
eval { $expr->parse($stream) };
is			$@, "-(1) : error: expected one of (\"(\" NAME NUMBER STRING) at hl\n",
			"syntax error";