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

# $Id: expression.t,v 1.5 2010/10/01 11:02:26 Paulo Exp $

use 5.010;
use strict;
use warnings;

use Test::More;
use Data::Dump 'dump';

use_ok 'Parse::FSM';
require_ok 't/utils.pl';

#------------------------------------------------------------------------------
# test parsing using the FSM tables
# compile a parser for:
#		prog	: stmt<+;> ''		{ $item[0]}
#		stmt	: expr				{ $item[0] }
#		expr 	: term addop*		{ my $res = $item[0];
#									  $res += $_ for (@{$item[1]});
#									  $res }
#		addop	: '+' term			{   $item[1] }
#				| '-' term			{ - $item[1] }
#				;
#		term	: factor mulop*		{ my $res = $item[0];
#									  $res *= $_ for (@{$item[1]});
#									  $res }
#		mulop	: '*' factor		{     $item[1] }
#			 	| '/' factor		{ 1 / $item[1] }
#				;
#		factor	: 'NUM'				{   $item[0][1] }
#				| '-' 'NUM'			{ - $item[0][1] }
#				| '+' 'NUM'			{   $item[0][1] }
#				| '(' expr ')'		{   $item[1]    }
#				;

#------------------------------------------------------------------------------
# add_rule
my $fsm = new_ok('Parse::FSM');

$fsm->add_rule('prog', '[stmt]<+;>', '', '{ $item[0] }');
$fsm->add_rule('stmt', '[expr]', '{ $item[0] }');

$fsm->add_rule('expr', '[term]', '[addop]*', 
				'{ my $res = $item[0]; $res += $_ for (@{$item[1]}); $res }');

$fsm->add_rule('addop', '+', '[term]', 
				'{ $item[1] }');

$fsm->add_rule('addop', '-', '[term]', 
				'{ - $item[1] }');

$fsm->add_rule('term', '[factor]', '[mulop]*', 
				'{ my $res = $item[0]; $res *= $_ for (@{$item[1]}); $res }');

$fsm->add_rule('mulop', '*', '[factor]', 
				'{ $item[1] }');

$fsm->add_rule('mulop', '/', '[factor]', 
				'{ 1 / $item[1] }');

$fsm->add_rule('factor', 'NUM', 				'{ $item[0][1] }');
$fsm->add_rule('factor', '-', 'NUM', 			'{ - $item[1][1] }');
$fsm->add_rule('factor', '+', 'NUM', 			'{   $item[1][1] }');
$fsm->add_rule('factor', '(', '[expr]', ')',	'{ $item[1] }');

#------------------------------------------------------------------------------
# compute the FSM
$fsm->_compute_fsm;
diag explain($fsm) if $ENV{DEBUG};

#------------------------------------------------------------------------------
# load the module, call the parser
unlink 'Parser.pm';
$fsm->write_module('Parser', 'Parser.pm');
ok -f 'Parser.pm';

use_ok 'Parser';

my $parser = new_ok('Parser');

$parser->input(make_lexer("2"));
is $parser->parse('expr'), 2;
is $parser->peek_token, undef;

$parser->input(make_lexer("2"));
is $parser->parse_expr, 2;
is $parser->peek_token, undef;

$parser->input(make_lexer("+2"));
is $parser->parse_expr, 2;
is $parser->peek_token, undef;

$parser->input(make_lexer("-2"));
is $parser->parse_expr, -2;
is $parser->peek_token, undef;

$parser->input(make_lexer("4+-2"));
is $parser->parse_expr, 2;
is $parser->peek_token, undef;

$parser->input(make_lexer("1+2+3"));
is $parser->parse_expr, 6;
is $parser->peek_token, undef;

$parser->input(make_lexer("1+2+3"));
is $parser->parse_expr, 6;
is $parser->peek_token, undef;

$parser->input(make_lexer("6-2-2"));
is $parser->parse_expr, 2;
is $parser->peek_token, undef;

$parser->input(make_lexer("2+3*4"));
is $parser->parse_expr, 14;
is $parser->peek_token, undef;

$parser->input(make_lexer("(2+3)*4"));
is $parser->parse_expr, 20;
is $parser->peek_token, undef;

$parser->input(make_lexer("(2+3)*+4"));
is $parser->parse_expr, 20;
is $parser->peek_token, undef;

$parser->input(make_lexer("(2+3)*-4"));
is $parser->parse_expr, -20;
is $parser->peek_token, undef;

$parser->input(make_lexer("(2+-3)*-4"));
is $parser->parse_expr, 4;
is $parser->peek_token, undef;

$parser->input(make_lexer("2+"));
eval { $parser->parse_expr };
is $@, 'Expected one of ("(" "+" "-" NUM) at EOF'."\n";

$parser->input(make_lexer("1;1+2;1+2+3;1+2+3+4"));
is_deeply $parser->parse, [1, 3, 6, 10];
is $parser->peek_token, undef;

$parser->input(make_lexer("1;1+2;1+2+3;1+2+3+4"));
is_deeply $parser->parse_prog, [1, 3, 6, 10];
is $parser->peek_token, undef;

#------------------------------------------------------------------------------
# clean-up
unlink 'Parser.pm';

done_testing;