File Coverage

blib/lib/Cucumber/TagExpressions.pm
Criterion Covered Total %
statement 62 63 98.4
branch 40 42 95.2
condition 8 9 88.8
subroutine 8 8 100.0
pod 1 1 100.0
total 119 123 96.7


line stmt bran cond sub pod time code
1              
2             package Cucumber::TagExpressions;
3             $Cucumber::TagExpressions::VERSION = '4.1.0';
4             =head1 NAME
5              
6             Cucumber::TagExpressions - Tag expression parser
7              
8             =head1 SYNOPSIS
9              
10             use Cucumber::TagExpressions;
11              
12             my $expr = Cucumber::TagExpressions->parse( '@a and @b' );
13             if ( $expr->evaluate( qw/x y z/ ) ) {
14             say "The evaluation returned false";
15             }
16              
17              
18             =head1 DESCRIPTION
19              
20             Cucumber tag expressions allow users to define the subset of Gherkin
21             scenarios they want to run. This library parses the expression and
22             returns an evaluator object which can be used to test the tags specified
23             on a scenario against the filter expression.
24              
25             =head1 CLASS METHODS
26              
27             =cut
28              
29              
30 1     1   218590 use Cucumber::TagExpressions::Node;
  1         4  
  1         777  
31              
32             sub _expect_token {
33 4     4   7 my ( $state, $token ) = @_;
34              
35 4         9 my $actual = _get_token( $state );
36 4 50       9 die "Expecting token '$token' but found '$actual'"
37             if $token ne $actual;
38             }
39              
40             sub _consume_char {
41 254     254   354 my ( $state, $allow_eof ) = @_;
42              
43 254 100       431 if ( length($state->{text}) <= $state->{pos} ) {
44 48 100       94 return if $allow_eof;
45 1         9 die "Unexpected end of string parsing tag expression: $state->{text}";
46             }
47 206         421 return substr( $state->{text}, $state->{pos}++, 1 );
48             }
49              
50             sub _get_token {
51 110     110   1401 my ( $state ) = @_;
52              
53 110 100       227 return delete $state->{saved_token} if defined $state->{saved_token};
54              
55 101         151 my $token = _consume_char( $state, 1 );
56 101 100       202 return if not defined $token;
57              
58 71 100 100     215 if ( $token eq '(' or $token eq ')' ) {
59 6         13 return $token;
60             }
61             else {
62 65 100       189 $token = '' if $token =~ /\s/;
63 65         88 while (1) {
64 148         212 my $char = _consume_char( $state, 1 );
65 148 100       258 return $token if not defined $char;
66              
67 131 100 66     385 if ( $char =~ m/\s/ ) {
    100          
68 43         102 return $token;
69             }
70             elsif ( $char eq '(' or $char eq ')' ) {
71 4         12 _save_token( $state, $char );
72 4         7 return $token;
73             }
74 84 100       117 if ( $char eq "\\" ) {
75 5         11 $token .= _consume_char( $state );
76             }
77             else {
78 79         122 $token .= $char;
79             }
80             }
81             }
82             }
83              
84             sub _save_token {
85 9     9   16 my ( $state, $token ) = @_;
86              
87 9         15 $state->{saved_token} = $token;
88             }
89              
90             sub _term_expr {
91 51     51   80 my ( $state ) = @_;
92              
93 51         90 my $token = _get_token( $state );
94              
95 50 100       111 die 'Unexpected end of input parsing tag expression'
96             if not defined $token;
97              
98 48 100       155 if ( $token eq '(' ) {
    100          
    50          
99 5         18 my $expr = _expr( $state );
100 4         47 _expect_token( $state, ')' );
101              
102 4         21 return $expr;
103             }
104             elsif ( $token eq 'not' ) {
105 5         15 return Cucumber::TagExpressions::NotNode->new(
106             expression => _term_expr( $state )
107             );
108             }
109             elsif ( $token =~ /^@/ ) {
110 38 100       76 die 'Tag must be longer than the at-sign (@) only'
111             if $token eq '@';
112              
113 36         724 return Cucumber::TagExpressions::LiteralNode->new( tag => $token );
114             }
115             else {
116 0         0 die "Unexpected input '$token'";
117             }
118             }
119              
120             sub _expr {
121 28     28   44 my ( $state ) = @_;
122              
123 28         53 my @terms = ();
124 28         61 push @terms, _term_expr( $state );
125 25         13078 while ( my $token = _get_token( $state ) ) {
126 26 100       54 if ( $token eq ')' ) {
127 5         10 _save_token( $state, ')' );
128 5         9 last;
129             }
130 21 100 100     81 if ( not ( $token eq 'or'
131             or $token eq 'and' ) ) {
132 3         27 die "Found '$token' where operator ('and'/'or') expected";
133             }
134              
135 18         30 my $term = _term_expr( $state );
136 15 100       264 if ( $token eq 'and' ) {
137             # immediately combine _and_ terms
138 7         128 push @terms,
139             Cucumber::TagExpressions::AndNode->new(
140             terms => [ $term, pop(@terms) ]
141             );
142             }
143             else {
144             # collect _or_ terms
145 8         17 push @terms, $term;
146             }
147             }
148              
149 19 100       40 if ( scalar(@terms) > 1 ) {
150 8         126 return Cucumber::TagExpressions::OrNode->new(
151             terms => \@terms
152             );
153             }
154             # don't wrap a single-term expression in an Or node
155 11         26 return $terms[0];
156             }
157              
158             =head2 $class->parse( $expression )
159              
160             Parses the string specified in C<$expression> and returns a
161             L instance.
162              
163             =cut
164              
165             sub parse {
166 23     23 1 3781 my ( $class, $text ) = @_;
167 23         83 my $state = { pos => 0, text => $text, saved_token => undef };
168 23         57 my $expr = _expr( $state );
169              
170 15         1055 my $token = _get_token( $state );
171 15 100       42 die "Junk at end of expression: $token"
172             if defined $token;
173              
174 14         250 return Cucumber::TagExpressions::ExpressionNode->new(
175             sub_expression => $expr
176             );
177             }
178              
179             1;
180              
181             __END__