The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
#!/usr/bin/perl
#Copyright 2008-10 Arthur S Goldstein
use Test::More tests => 56;
BEGIN { use_ok('Parse::Stallion') };
#use Data::Dumper;

my %parsing_rules_with_min_first = (
 start_expression => A(
  'parse_expression', L(qr/x*/), L(qr/\z/),
  E(sub {
#use Data::Dumper;print STDERR "in se is ".Dumper(\@_);
    return $_[0]->{parse_expression}})
 ),
 parse_expression => M(
   'pe', MATCH_MIN_FIRST(), USE_STRING_MATCH()
 ),
 pe => L(
   qr/./
 ),
);

my %parsing_rules_without_min_first = (
 start_expression =>
  A('parse_expression', L(qr/x*/), L(qr/\z/),
  E(sub { return $_[0]->{parse_expression}})
 ),
 parse_expression => M(
   'pe', USE_STRING_MATCH
 ),
 pe => L(
   qr/./
 )
);

my $with_min_parser = new Parse::Stallion(
  \%parsing_rules_with_min_first,
  { start_rule => 'start_expression',
});

my $without_min_parser = new Parse::Stallion(
  \%parsing_rules_without_min_first,
  { start_rule => 'start_expression',
});

#my $result;

my ($result, $other) = $with_min_parser->parse_and_evaluate("qxxx");

#use Data::Dumper;print STDERR "parse trace is ".Dumper($other->{parse_trace})."\n";
is ($result,'q', 'min parser');

$result = $without_min_parser->parse_and_evaluate("qxxx");

is ($result,'qxxx', 'without min parser');

$result = $with_min_parser->parse_and_evaluate("xxx");

is ($result,'', 'no q min parser');

$result = $without_min_parser->parse_and_evaluate("xxx");

is ($result,'xxx', 'no q without min parser');

my %parsing_rules_with_match_once = (
 qqstart_expression => A(M({f => qr/x/}, MATCH_ONCE()), {g => qr/x/})
);

my %parsing_rules_without_match_once = (
 ppstart_expression => A(M({f => qr/x/}), {g => qr/x/})
);

my $with_match_parser = new Parse::Stallion(\%parsing_rules_with_match_once);
$result = $with_match_parser->parse_and_evaluate('xxx');
is ($result, undef, 'with match');

my $without_match_parser =
 new Parse::Stallion(\%parsing_rules_without_match_once);

$result = $without_match_parser->parse_and_evaluate('xxx');
is_deeply ($result, {f=> ['x','x'] , g=> 'x'}, 'without match');

my %another = (
 oostart_expression => A(M({f => qr/x/}, MATCH_ONCE(),
   MATCH_MIN_FIRST(), 3,5), {g => qr/y/})
);
my %anotherm = (
 lstart_expression => A(M({f => qr/x/},
   MATCH_MIN_FIRST(), 3,5), {g => qr/y/})
);


my $another_parser = new Parse::Stallion(\%another);
my $anotherm_parser = new Parse::Stallion(\%anotherm);

$result = $another_parser->parse_and_evaluate('xxxy');
is_deeply ($result, {f=> ['x','x','x'] , g=> 'y'}, 'another');
$result = $anotherm_parser->parse_and_evaluate('xxxy');
is_deeply ($result, {f=> ['x','x','x'] , g=> 'y'}, 'anotherm');

$result = $another_parser->parse_and_evaluate('xxxxy');
is ($result, undef, 'another 4 x');
$result = $anotherm_parser->parse_and_evaluate('xxxxy');
is_deeply ($result, {f=> ['x','x','x','x'] , g=> 'y'}, 'anotherm 4 x');

my %and_match = (
 ustart_expression => A(
     A({e=>qr/f/}, M({f => qr/x/}), MATCH_ONCE()),
    {k=>qr/x/})
);
my %and_no_match = (
 fstart_expression => A(A({e => qr/f/}, M({f => qr/x/})),{k =>qr/x/})
);

my $and_parser = new Parse::Stallion(\%and_match);
my $and_no_parser = new Parse::Stallion(\%and_no_match);
$result = $and_parser->parse_and_evaluate('fxxx');
is_deeply($result, undef, 'match once on and');
$result = $and_no_parser->parse_and_evaluate('fxxx');
is_deeply($result, {e=>'f',f=>['x','x'],k=>'x'}, 'no match once on and');

my %or_match = (
 pqstart => A(O('case1', 'case2', MATCH_ONCE()), qr/x/),

 case1 => qr/xx/,

 case2 => qr/x/
);

my %or_no_match = (
 pistart => A(O('case1', 'case2'), qr/x/),

 case1 => qr/xx/,

 case2 => qr/x/
);

my $or_parser = new Parse::Stallion(\%or_match);
my $or_no_parser = new Parse::Stallion(\%or_no_match);

$result = $or_parser->parse_and_evaluate('xx');
is_deeply($result, undef, 'match once on or');
$result = $or_no_parser->parse_and_evaluate('xx');
is_deeply($result, {''=>'x', 'case2'=>'x'}, 'no match once on or');

my $mo_parser_1 = new Parse::Stallion(
   {nrule1 => A(M(qr/t/), M(qr/t/), qr/u/)});

  my $mo_parser_2 = new Parse::Stallion(
   {mrule2 => A(M(qr/t/, MATCH_ONCE()), M(qr/t/, MATCH_ONCE()), qr/u/)});

  my $mo_parser_3 = new Parse::Stallion(
   {orule2 => A(M(qr/t/, MATCH_ONCE()), M(qr/t/, MATCH_ONCE()),
    L(qr/u/, PB(sub {return 0})), MATCH_ONCE())});

  my $mo_parser_4 = new Parse::Stallion(
   {yrule2 => A(M(qr/t/, MATCH_ONCE()), M(qr/t/, MATCH_ONCE()),
    L(qr/u/, PB(sub {return 0})), MATCH_ONCE())}, {fast_move_back => 1});

my $pi = {};

$result = $mo_parser_1->parse_and_evaluate('ttttt',{parse_info => $pi});

is ($pi->{number_of_steps}, 157, 'match once steps 1');
#print "parse info steps 1 ".$pi->{number_of_steps}."\n";

$result = $mo_parser_2->parse_and_evaluate('ttttt',{parse_info => $pi});

#print "parse info steps 2 ".$pi->{number_of_steps}."\n";
is ($pi->{number_of_steps}, 15, 'match once steps 2');

$result = $mo_parser_3->parse_and_evaluate('ttttt',{parse_info => $pi});

#print "parse info steps 3 ".$pi->{number_of_steps}."\n";
is ($pi->{number_of_steps}, 27, 'match once steps 3');

$result = $mo_parser_4->parse_and_evaluate('ttttt',{parse_info => $pi});

#print "parse info steps 4 ".$pi->{number_of_steps}."\n";
is ($pi->{number_of_steps}, 15, 'match once steps 4');

my $g = {no_double_x => O(qr/x/, qr/xx/, qr/yy/, MATCH_ONCE())};
my $h = new Parse::Stallion($g);

$result = $h->parse_and_evaluate('xx');
is_deeply($result, undef, 'no double x on double x');
#use Data::Dumper; print Dumper($result);
$result = $h->parse_and_evaluate('x');
is_deeply($result, 'x', 'no double x on single x');
#use Data::Dumper; print Dumper($result);
$result = $h->parse_and_evaluate('yy');
is_deeply($result, 'yy', 'no double x on double y');

  my $parser = new Parse::Stallion({number => L(qr/(\d+)\;/,E(sub{$_[0]+1}))});
  $input = '342;234;532;444;3;23;';
  $pi = {final_position => 0};
  while ($pi->{final_position} != length($input)) {
    push @results, $parser->parse_and_evaluate($input,
     {parse_info=> $pi, start_position => $pi->{final_position},
      match_length => 0});
  }
  # @results should contain (343, 235, 533, 445, 4, 24)
is_deeply(\@results, [343, 235, 533, 445, 4, 24], 'list of results');

my @xresults;

#$posinput = pos $input;
#print "pre posinput $posinput\n";
pos $input = 0;
  while (my $result = $parser->parse_and_evaluate($input,{global => 1,
   match_length => 0})) {
    push @xresults, $result;
  }
  # @xresults should contain (343, 235, 533, 445, 4, 24)
is_deeply(\@xresults, [343, 235, 533, 445, 4, 24], 'list of results two');

pos $input = 0;
  @xresults = $parser->parse_and_evaluate($input,
   {global => 1, match_length=>0});
is_deeply(\@xresults, [343, 235, 533, 445, 4, 24], 'list of results three');
#$posinput = pos $input;
#print "posinput $posinput\n";


my $measure_grammar = {
  start => A('bb', 'cc', 'dd'),
  bb => qr/bb/,
  cc => qr/cc/,
  dd => qr/dd/,
};
my $measure_parser = new Parse::Stallion($measure_grammar);
$measure_parser->parse_and_evaluate('bbccee', {parse_info=>$pi});
is ($pi->{parse_succeeded}, 0, 'measured success');
is ($pi->{maximum_position}, 4, 'measured maximum position');
is ($pi->{maximum_position_rule}, 'cc', 'measured maximum position rule');
is ($pi->{final_position}, 0, 'measured final position');
is ($pi->{final_position_rule}, 'start', 'measured final position');

my $measure_pb_grammar = {
  start => A('bb', 'cc', qr/\n/, 'dd', 'ee'),
  bb => qr/bb/,
  cc => L(qr/cc/, PB(sub {return 1})),
  dd => qr/dd/,
  ee => qr/ee/,
};
my $ptt = [];
my $measure_pb_parser = new Parse::Stallion($measure_pb_grammar);
$measure_pb_parser->parse_and_evaluate("bbcc\nddff", {parse_info=>$pi,
 parse_trace => $ptt});
my ($max_line, $max_line_position) = LOCATION(\"bbcc\nddff",
 $pi->{maximum_position});
is ($pi->{parse_succeeded}, 0, 'measured pb success');
is ($pi->{maximum_position}, 7, 'measured pb maximum position');
is ($pi->{maximum_position_rule}, 'dd', 'measured pb maximum position rule');
is ($max_line, 2, 'measured pb maximum line rule');
is ($max_line_position, 3, 'measured pb maximum line position rule');
is ($pi->{final_position}, 2, 'measured pb final position');
is ($pi->{final_position_rule}, 'cc', 'measured pb final position');

my $pt = [];
$pi = {};
eval {$measure_pb_parser->parse_and_evaluate("bbcc\nddee", {parse_info=>$pi,
 parse_trace => $pt,
 max_steps =>4})};
is ($pi->{parse_succeeded}, undef, 'measured mspb success');
is ($pi->{maximum_position}, 7, 'measured mspb maximum position');
is ($pi->{maximum_position_rule}, 'dd', 'measured mspb maximum position rule');
is ($pi->{final_position}, 7, 'measured mspb final position');
is ($pi->{final_position_rule}, 'start', 'measured mspb final position');
#use Data::Dumper;print "pt is ".Dumper($pt)."\n";

my $line;
my $tab;
my $loc_grammar = {
  start =>
   A(qr/....../s,
    L(qr//, E(sub {
   ($line, $tab) = LOCATION($_[1]->{parse_this_ref},
    $_[1]->{current_node}->{position_when_entered})})),
    qr/.*/s)
};
my $loc_parser = new Parse::Stallion($loc_grammar);
$loc_parser->parse_and_evaluate('abcdefghi');
is ($line, 1, 'line loc 1');
is ($tab, 7, 'line tab 1');

$loc_parser->parse_and_evaluate("ab\nd\nfghi");
is ($line, 3, 'line loc 2');
is ($tab, 2, 'line tab 2');

  our %keywords = ('key1'=> 1, 'key2' => 1);
  my %grammar = (
   start => A('leaf', qr/\;/),
   leaf => L(
     qr/\w+/,
     E(sub {if ($keywords{$_[0]}) {return (undef, 1)} return $_[0]}),
   )
  );
  my $keyparser = new Parse::Stallion(\%grammar, {do_evaluation_in_parsing=>1});
  is ($keyparser->parse_and_evaluate('key1;'), undef, 'do eval key 1');
  is_deeply ($keyparser->parse_and_evaluate('key3;'), {''=>';',leaf=>'key3'},
   'do eval key 3');

  my $s;
  my $nmo_parser_x = new Parse::Stallion(
   {grule => A(M('mm', 1,0), qr/tu/),
    mm => A(qr/t/, L(PF(sub {$s .= '0';return 1}),
     PB(sub {$s .= '1';return}))),
  });

  my $mo_parser_x = new Parse::Stallion(
   {hrule => A(M('mm', 1,0, MATCH_ONCE), qr/tu/),
    mm => A(qr/t/, L(PF(sub {$s .= '0';return 1}),
     PB(sub {$s .= '1';return}))),
  });

  my $mo_parser_y = new Parse::Stallion(
   {oorule => A(M('mm', 1,0, MATCH_ONCE), qr/tu/),
    mm => A(qr/t/, L(PF(sub {$s .= '0';return 1}),
     PB(sub {$s .= '1';return}))),
  },
 {fast_move_back => 1}
);

  $result = $mo_parser_x->parse_and_evaluate('ttttu');
  is ($s, '00001111', 'match once no fast move back');

  $s = '';
  $result = $nmo_parser_x->parse_and_evaluate('ttttu');
  is ($s, '00001', 'no match once');

  $s = '';

  $result = $mo_parser_y->parse_and_evaluate('ttttu');
  is ($s, '0000', 'match once fast move back');

  our $first;
  our $second;
  my $ms_parser = new Parse::Stallion(
   {   pprule => A({sub_rule_1 => qr/art/}, {sub_rule_2 => qr/hur/},
    E(sub {$matched_string = MATCHED_STRING($_[1]);
      $first = $matched_string;
      $second = $_[0]->{sub_rule_1} . $_[0]->{sub_rule_2};
     # $matched_string == 'arthur' == $_[0]->{sub_rule_1} . $_[0]->{sub_rule_2}
     }))});

   $result = $ms_parser->parse_and_evaluate('arthur');
  is ($first, $second, 'matched string');
  is ($first, 'arthur', 'matched string arthur');

my $a_grammar = new Parse::Stallion(
 { start => M(qr/a/) });

our $jj = '';
$result = $a_grammar->parse_and_evaluate('aab',
 {parse_trace_routine => sub {
#   print STDERR 'at step '.${$_[0]->{__step_ref}}."\n";
#   print STDERR 'moving forward is '.${$_[0]->{__moving_forward_ref}}."\n";
#   print STDERR 'position is '.${$_[0]->{__current_position_ref}}."\n";
   $jj .= 'at step '.${$_[0]->{__steps_ref}}."\n";
   $jj .= 'moving forward is '.${$_[0]->{__moving_forward_ref}}."\n";
   $jj .= 'position is '.${$_[0]->{__current_position_ref}}."\n";
   }
  }
);

is ($jj, 'at step 1
moving forward is 1
position is 0
at step 2
moving forward is 1
position is 1
at step 3
moving forward is 1
position is 2
at step 4
moving forward is 0
position is 2
at step 5
moving forward is 0
position is 2
at step 6
moving forward is 0
position is 2
at step 7
moving forward is 0
position is 1
at step 8
moving forward is 0
position is 1
at step 9
moving forward is 0
position is 1
at step 10
moving forward is 0
position is 0
at step 11
moving forward is 0
position is 0
', 'parse_trace_routine');

our $tj = 0;
  my $bmo_parser_4 = new Parse::Stallion(
   {start_rule => O('xrule2', 'xrule3'),
    xrule2 => A(M(qr/t/, MATCH_ONCE(), E(sub {$tj= 1;})),
       M(qr/v/, MATCH_ONCE(), E(sub {$tj=3})),
      qr/u/, MATCH_ONCE()),
    xrule3 => L(qr/.*/, E(sub {return "bmo"})),
   });
  $result = $bmo_parser_4->parse_and_evaluate('tttttvvvv',{parse_info => $pi});
#print STDERR "bresult4 $result\n";
is ($tj, 0, 'checking fast move back');
is ($result, 'bmo', 'bmo parser');

print "\nAll done\n";