Skip to content

Commit

Permalink
update CEF parser for weight_allowed flag, +24 test cases
Browse files Browse the repository at this point in the history
  • Loading branch information
ikluft committed Sep 1, 2023
1 parent 91ce334 commit d2a7e21
Show file tree
Hide file tree
Showing 3 changed files with 147 additions and 23 deletions.
44 changes: 32 additions & 12 deletions src/perl/prefvote/lib/PrefVote/Core/Input/CEF_Parser.pm
Original file line number Diff line number Diff line change
Expand Up @@ -877,47 +877,54 @@ sub
'weight', 2,
sub
#line 95 "/home/ikluft/src/github/prefvote/src/perl/prefvote/lib/PrefVote/Core/Input//CEF_Parser.yp"
{ return { weight => $_[2]}; }
{
if ( not ( $_[0]->YYData->{VOTEDEF}{weight_allowed} // 0 )) {
$_[0]->YYData->{ERRMSG} = "weight not permitted without weight_allowed flag";
$_[0]->YYError;
}
return { weight => $_[2]};
}
],
[#Rule 20
'words', 2,
sub
#line 99 "/home/ikluft/src/github/prefvote/src/perl/prefvote/lib/PrefVote/Core/Input//CEF_Parser.yp"
#line 105 "/home/ikluft/src/github/prefvote/src/perl/prefvote/lib/PrefVote/Core/Input//CEF_Parser.yp"
{ return $_[1] . " " . $_[2]; }
],
[#Rule 21
'words', 1,
sub
#line 100 "/home/ikluft/src/github/prefvote/src/perl/prefvote/lib/PrefVote/Core/Input//CEF_Parser.yp"
#line 106 "/home/ikluft/src/github/prefvote/src/perl/prefvote/lib/PrefVote/Core/Input//CEF_Parser.yp"
{ return "" . $_[1]; }
],
[#Rule 22
'word', 1,
sub
#line 104 "/home/ikluft/src/github/prefvote/src/perl/prefvote/lib/PrefVote/Core/Input//CEF_Parser.yp"
#line 110 "/home/ikluft/src/github/prefvote/src/perl/prefvote/lib/PrefVote/Core/Input//CEF_Parser.yp"
{ return "" . $_[1]; }
],
[#Rule 23
'word', 1,
sub
#line 105 "/home/ikluft/src/github/prefvote/src/perl/prefvote/lib/PrefVote/Core/Input//CEF_Parser.yp"
#line 111 "/home/ikluft/src/github/prefvote/src/perl/prefvote/lib/PrefVote/Core/Input//CEF_Parser.yp"
{ return 0 + $_[1]; }
]
],
@_);
bless($self,$class);
}

#line 108 "/home/ikluft/src/github/prefvote/src/perl/prefvote/lib/PrefVote/Core/Input//CEF_Parser.yp"
#line 114 "/home/ikluft/src/github/prefvote/src/perl/prefvote/lib/PrefVote/Core/Input//CEF_Parser.yp"


sub _Error
{
my ( $parser ) = @_;
my $expect_str = ( scalar $parser->YYExpect > 0 ) ? ( join( ' ', sort $parser->YYExpect )) : "";
my $errmsg = ( exists $parser->YYData->{ERRMSG} )
my $errmsg = (( exists $parser->YYData->{ERRMSG} )
? $parser->YYData->{ERRMSG}
: "Syntax error at position " . $parser->{USER}{CHARNO}
: "Syntax error" )
. " at position " . $parser->YYData->{CHARNO}
. (( defined $parser->YYCurtok and length $parser->YYCurtok > 0 )
? ", found " . $parser->YYCurtok . " '" . $parser->YYCurval . "'" : "" )
. (( length $expect_str > 0 ) ? ", expected $expect_str" : "" );
Expand All @@ -934,7 +941,12 @@ sub _Lexer

# remove leading whitespace before next token
if( $parser->YYData->{INPUT} =~ s/^ ( \s+ )//x ) {
$parser->{USER}{CHARNO} += length $1;
$parser->YYData->{CHARNO} += length $1;
}

# check for end of input after whitespace
if ( length $parser->YYData->{INPUT} == 0 ) {
return ( '', undef );
}

# find first token from matching list
Expand All @@ -957,7 +969,7 @@ sub _Lexer
}

# return token name and match string
$parser->{USER}{CHARNO} += length $match;
$parser->YYData->{CHARNO} += length $match;
return ( $token_name, $match );
}
}
Expand All @@ -968,9 +980,17 @@ sub _Lexer

sub parse
{
my ($self, $input_str) = @_;
my ($self, $input_str, $vote_def) = @_;

# clear data for new parse run
foreach my $key ( keys %{$self->YYData}) {
delete $self->YYData->{$key};
}

# set YYData and user info
$self->YYData->{INPUT} = $input_str;
$self->{USER}{CHARNO} = 0;
$self->YYData->{VOTEDEF} = $vote_def;
$self->YYData->{CHARNO} = 0;
my $result = $self->YYParse( yylex => \&_Lexer, yyerror => \&_Error );
return $result;
}
Expand Down
19 changes: 14 additions & 5 deletions src/perl/prefvote/lib/PrefVote/Core/Input/CEF_Parser.yp
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ quantifier:
weight:
'^' INT {
if ( not ( $_[0]->YYData->{VOTEDEF}{weight_allowed} // 0 )) {
$_[0]->YYData->{ERRMSG} = "weight not permitted without weight_allowed flag";
$_[0]->YYError;
}
return { weight => $_[2]};
Expand All @@ -116,9 +117,10 @@ sub _Error
{
my ( $parser ) = @_;
my $expect_str = ( scalar $parser->YYExpect > 0 ) ? ( join( ' ', sort $parser->YYExpect )) : "";
my $errmsg = ( exists $parser->YYData->{ERRMSG} )
my $errmsg = (( exists $parser->YYData->{ERRMSG} )
? $parser->YYData->{ERRMSG}
: "Syntax error at position " . $parser->{USER}{CHARNO}
: "Syntax error" )
. " at position " . $parser->YYData->{CHARNO}
. (( defined $parser->YYCurtok and length $parser->YYCurtok > 0 )
? ", found " . $parser->YYCurtok . " '" . $parser->YYCurval . "'" : "" )
. (( length $expect_str > 0 ) ? ", expected $expect_str" : "" );
Expand All @@ -135,7 +137,7 @@ sub _Lexer

# remove leading whitespace before next token
if( $parser->YYData->{INPUT} =~ s/^ ( \s+ )//x ) {
$parser->{USER}{CHARNO} += length $1;
$parser->YYData->{CHARNO} += length $1;
}

# check for end of input after whitespace
Expand Down Expand Up @@ -163,7 +165,7 @@ sub _Lexer
}

# return token name and match string
$parser->{USER}{CHARNO} += length $match;
$parser->YYData->{CHARNO} += length $match;
return ( $token_name, $match );
}
}
Expand All @@ -175,9 +177,16 @@ sub _Lexer
sub parse
{
my ($self, $input_str, $vote_def) = @_;

# clear data for new parse run
foreach my $key ( keys %{$self->YYData}) {
delete $self->YYData->{$key};
}

# set YYData and user info
$self->YYData->{INPUT} = $input_str;
$self->YYData->{VOTEDEF} = $vote_def;
$self->{USER}{CHARNO} = 0;
$self->YYData->{CHARNO} = 0;
my $result = $self->YYParse( yylex => \&_Lexer, yyerror => \&_Error );
return $result;
}
107 changes: 101 additions & 6 deletions src/perl/prefvote/t/020_cef_parser.t
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,11 @@ Readonly::Array my @ranking_tests => (
out => [ [ 'A', 'B' ], [ 'C', 'D' ], [ 'E', 'F' ] ],
},
{
in => "A>B ^2",
error => qr(^weight not permitted without weight_allowed flag),
},
{
vote_def => { weight_allowed => 1 },
in => "A>B ^2",
out => [ { weight => 2 }, ['A'], ['B'] ],
},
Expand All @@ -81,22 +86,47 @@ Readonly::Array my @ranking_tests => (
out => [ { quantifier => 700 }, ['C'], ['B'], ['A'] ],
},
{
in => "A>B * 5 ^2",
error => qr(^weight not permitted without weight_allowed flag),
},
{
vote_def => { weight_allowed => 1 },
in => "A>B * 5 ^2",
out => [ { quantifier => 5, weight => 2 }, ['A'], ['B'] ],
},
{
in => "A>B ^2 *5",
error => qr(^weight not permitted without weight_allowed flag),
},
{
vote_def => { weight_allowed => 1 },
in => "A>B ^2 *5",
out => [ { quantifier => 5, weight => 2 }, ['A'], ['B'] ],
},
{
in => "tag1 || A>B ^2 *5",
error => qr(^weight not permitted without weight_allowed flag),
},
{
vote_def => { weight_allowed => 1 },
in => "tag1 || A>B ^2 *5",
out => [ { tags => [qw(tag1)], quantifier => 5, weight => 2 }, ['A'], ['B'] ],
},
{
in => "tag1, tag2 || A>B ^2 *5",
error => qr(^weight not permitted without weight_allowed flag),
},
{
vote_def => { weight_allowed => 1 },
in => "tag1, tag2 || A>B ^2 *5",
out => [ { tags => [qw(tag1 tag2)], quantifier => 5, weight => 2 }, ['A'], ['B'] ],
},
{
in => "tag2,tag1||A>B*5^2",
error => qr(^weight not permitted without weight_allowed flag),
},
{
vote_def => { weight_allowed => 1 },
in => "tag2,tag1||A>B*5^2",
out => [ { tags => [qw(tag1 tag2)], quantifier => 5, weight => 2 }, ['A'], ['B'] ],
},
Expand All @@ -105,17 +135,32 @@ Readonly::Array my @ranking_tests => (
error => qr(^Syntax error at position 13, found \* '\*'),
},
{
in => "C>B>A ^ 7 ^ 2",
error => qr(^weight not permitted without weight_allowed flag),
},
{
vote_def => { weight_allowed => 1 },
in => "C>B>A ^ 7 ^ 2",
error => qr(^Syntax error at position 11, found \^ '\^'),
},
{
in => "tag1, tag2 || C>B>A ^ 7 ^ 2",
error => qr(^weight not permitted without weight_allowed flag),
},
{
vote_def => { weight_allowed => 1 },
in => "tag1, tag2 || C>B>A ^ 7 ^ 2",
error => qr(^Syntax error at position 25, found \^ '\^'),
},
{
in => "tag1, , tag2 || C>B>A ^ 7 ^ 2",
error => qr(^Syntax error at position 7, found , ',', expected INT WORD),
},
{
vote_def => { weight_allowed => 1 },
in => "tag1, , tag2 || C>B>A ^ 7 ^ 2",
error => qr(^Syntax error at position 7, found , ',', expected INT WORD),
},
{
in => "/EMPTY_RANKING/",
out => [],
Expand All @@ -125,10 +170,20 @@ Readonly::Array my @ranking_tests => (
out => [ { quantifier => 350 } ],
},
{
in => "/EMPTY_RANKING/ * 350 ^ 2",
error => qr(^weight not permitted without weight_allowed flag),
},
{
vote_def => { weight_allowed => 1 },
in => "/EMPTY_RANKING/ * 350 ^ 2",
out => [ { quantifier => 350, weight => 2 } ],
},
{
in => "/EMPTY_RANKING/^2*350",
error => qr(^weight not permitted without weight_allowed flag),
},
{
vote_def => { weight_allowed => 1 },
in => "/EMPTY_RANKING/^2*350",
out => [ { quantifier => 350, weight => 2 } ],
},
Expand All @@ -137,6 +192,11 @@ Readonly::Array my @ranking_tests => (
error => qr(^Syntax error at position 23, found \* '\*'),
},
{
in => "/EMPTY_RANKING/^7^2",
error => qr(^weight not permitted without weight_allowed flag),
},
{
vote_def => { weight_allowed => 1 },
in => "/EMPTY_RANKING/^7^2",
error => qr(^Syntax error at position 18, found \^ '\^'),
},
Expand Down Expand Up @@ -188,6 +248,31 @@ sub count_tests
return $total_tests;
}

# convert vote definition structure into a string
# recursive function to return a string for a vote definition structure or a portion within one
sub votedef2str
{
my $vote_def = shift;

# if we got a scalar, treat it as a leaf node and return it
if ( not ref $vote_def ) {
return $vote_def;
}

# handle array
if ( ref $vote_def eq "ARRAY" ) {
return '[' . join(", ", map( votedef2str($_), @$vote_def)) . ']';
}

# handle hash
if ( ref $vote_def eq "HASH" ) {
return '{' . join(", ", map($_ . "=>" . votedef2str($vote_def->{$_}), sort keys %$vote_def)) . '}';
}

# otherwise stringify it
return "" . $vote_def;
}

# declare test count
plan tests => count_tests();

Expand All @@ -202,6 +287,12 @@ plan tests => count_tests();
# perform tests from list
foreach my $test_case (@ranking_tests) {

# stringify vote_def for test name
my $def_suffix = "";
if ( exists $test_case->{vote_def}) {
$def_suffix = " / " . votedef2str($test_case->{vote_def});
}

# test for errors or successful parsing
if ( exists $test_case->{error} ) {

Expand All @@ -214,11 +305,13 @@ plan tests => count_tests();
} else {
my $in_str = $test_case->{in};
my $err_regex = $test_case->{error};
my $vote_def = $test_case->{vote_def} // {};
my $result;
dies_ok( sub { $result = $parser->parse($in_str); }, "$test_group: $in_str / dies as expected" );
dies_ok( sub { $result = $parser->parse($in_str, $vote_def); },
"$test_group: $in_str$def_suffix / dies as expected" );
my $err_result = $@;
$debug_mode and say STDERR "$test_group: in: $in_str / result: error $err_result";
like( $err_result, $err_regex, "$test_group: $in_str / expected error: $err_regex" );
$debug_mode and say STDERR "$test_group: in: $in_str$def_suffix / result: error $err_result";
like( $err_result, $err_regex, "$test_group: $in_str$def_suffix / expected error: $err_regex" );
}
}
} else {
Expand All @@ -232,10 +325,12 @@ plan tests => count_tests();
} else {
my $in_str = $test_case->{in};
my $out_struct = $test_case->{out};
my $vote_def = $test_case->{vote_def} // {};
my $result;
lives_ok( sub { $result = $parser->parse($in_str); }, "$test_group: $in_str / parser runs" );
$debug_mode and say STDERR "$test_group: in: $in_str / result: " . Dumper($result);
is_deeply( $result, $out_struct, "$test_group: $in_str / data check" );
lives_ok( sub { $result = $parser->parse($in_str, $vote_def); },
"$test_group: $in_str$def_suffix / parser runs" );
$debug_mode and say STDERR "$test_group: in: $in_str$def_suffix / result: " . Dumper($result);
is_deeply( $result, $out_struct, "$test_group: $in_str$def_suffix / data check" );
}
}
}
Expand Down

0 comments on commit d2a7e21

Please sign in to comment.