File Coverage

blib/lib/Language/Expr/Parser.pm
Criterion Covered Total %
statement 15 97 15.4
branch 3 40 7.5
condition 1 53 1.8
subroutine 3 3 100.0
pod 1 1 100.0
total 23 194 11.8


line stmt bran cond sub pod time code
1             package Language::Expr::Parser;
2              
3             our $DATE = '2016-07-01'; # DATE
4             our $VERSION = '0.28'; # VERSION
5              
6 5     5   64 use 5.010001;
  5         10  
7             # now can't compile with this on?
8             #use strict;
9             #use warnings;
10              
11 5     5   5279 use Regexp::Grammars;
  5         87456  
  5         34  
12              
13             require Exporter;
14             our @ISA = qw(Exporter);
15             our @EXPORT_OK = qw(parse_expr);
16              
17             my $MAX_LEVELS = 3;
18              
19             # WARN: this is not thread-safe!?
20             our $obj;
21              
22             sub parse_expr {
23 398     398 1 439 my ($str, $obj_arg, $level) = @_;
24              
25 398   50     1317 $level //= 0;
26 398 50       688 die "Recursion level ($level) too deep (max $MAX_LEVELS)" if $level >= $MAX_LEVELS;
27              
28             # WARN: this is not thread-safe!?
29 398         557 local $subexpr_stack = [];
30              
31             # create not just 1 but 0..$MAX_LEVELS-1 of grammar objects, each
32             # for each recursion level (e.g. for map/grep/usort), fearing that
33             # the grammar is not reentrant. but currently no luck yet, still
34             # results in segfault/bus error.
35              
36 398         436 state $grammars = [ map { qr{
37             ^\s*<answer>\s*$
38              
39             <rule: answer>
40             <MATCH=or_xor>
41              
42             # precedence level: left =>
43             <rule: pair>
44             <key=(\w++)> =\> <value=answer>
45 0         0 (?{ $MATCH = $obj->rule_pair_simple(match=>\%MATCH) })
46             | <key=squotestr> =\> <value=answer>
47 0         0 (?{ $MATCH = $obj->rule_pair_string(match=>\%MATCH) })
48             | <key=dquotestr> =\> <value=answer>
49 0         0 (?{ $MATCH = $obj->rule_pair_string(match=>\%MATCH) })
50              
51             # precedence level: left || // ^^
52             <rule: or_xor>
53             <[operand=ternary]> ** <[op=(\|\||//|\^\^)]>
54             (?{
55 0 0 0     0 if ($MATCH{op} && @{ $MATCH{op} }) {
  0         0  
56 0         0 $MATCH = $obj->rule_or_xor(match=>\%MATCH);
57             } else {
58 0         0 $MATCH = $MATCH{operand}[0];
59             }
60             })
61              
62             # precedence level: right ?:
63             <rule: ternary>
64             <[operand=and]> ** <[op=(\?|:)]>
65             (?{
66 0 0 0     0 if ($MATCH{op} && @{ $MATCH{op} }) {
  0         0  
67 0 0 0     0 unless (@{ $MATCH{op} } == 2 &&
  0   0     0  
68             $MATCH{op}[0] eq '?' &&
69             $MATCH{op}[1] eq ':') {
70 0         0 die "Invalid syntax for ternary, please use X ? Y : Z syntax";
71             }
72 0         0 $MATCH = $obj->rule_ternary(match=>\%MATCH);
73             } else {
74 0         0 $MATCH = $MATCH{operand}[0];
75             }
76             })
77              
78             # precedence level: left &&
79             <rule: and>
80             <[operand=bit_or_xor]> ** <[op=(&&)]>
81             (?{
82 0 0 0     0 if ($MATCH{op} && @{ $MATCH{op} }) {
  0         0  
83 0         0 $MATCH = $obj->rule_and(match=>\%MATCH);
84             } else {
85 0         0 $MATCH = $MATCH{operand}[0];
86             }
87             })
88              
89             # precedence level: left | ^
90             <rule: bit_or_xor>
91             <[operand=bit_and]> ** <[op=(\||\^)]>
92             (?{
93 0 0 0     0 if ($MATCH{op} && @{ $MATCH{op} }) {
  0         0  
94 0         0 $MATCH = $obj->rule_bit_or_xor(match=>\%MATCH);
95             } else {
96 0         0 $MATCH = $MATCH{operand}[0];
97             }
98             })
99              
100             # precedence level: left &
101             <rule: bit_and>
102             <[operand=comparison3]> ** <[op=(&)]>
103             (?{
104 0 0 0     0 if ($MATCH{op} && @{ $MATCH{op} }) {
  0         0  
105 0         0 $MATCH = $obj->rule_bit_and(match=>\%MATCH);
106             } else {
107 0         0 $MATCH = $MATCH{operand}[0];
108             }
109             })
110              
111             # NOTE: \x3c = "<", \x3e = ">"
112              
113             # precedence level: nonassoc (currently the grammar says assoc) <=> cmp
114             <rule: comparison3>
115             <[operand=comparison]> ** <[op=(\x3c=\x3e|cmp)]>
116             (?{
117 0 0 0     0 if ($MATCH{op} && @{ $MATCH{op} }) {
  0         0  
118 0         0 $MATCH = $obj->rule_comparison3(match=>\%MATCH);
119             } else {
120 0         0 $MATCH = $MATCH{operand}[0];
121             }
122             })
123              
124             # precedence level: left == != eq ne < > <= >= ge gt le lt
125             <rule: comparison>
126             <[operand=bit_shift]> ** <[op=(==|!=|eq|ne|\x3c=?|\x3e=?|lt|gt|le|ge)]>
127             (?{
128 0 0 0     0 if ($MATCH{op} && @{ $MATCH{op} }) {
  0         0  
129 0         0 $MATCH = $obj->rule_comparison(match=>\%MATCH);
130             } else {
131 0         0 $MATCH = $MATCH{operand}[0];
132             }
133             })
134              
135             # precedence level: left << >>
136             <rule: bit_shift>
137             <[operand=add]> ** <[op=(\x3c\x3c|\x3e\x3e)]>
138             (?{
139 0 0 0     0 if ($MATCH{op} && @{ $MATCH{op} }) {
  0         0  
140 0         0 $MATCH = $obj->rule_bit_shift(match=>\%MATCH);
141             } else {
142 0         0 $MATCH = $MATCH{operand}[0];
143             }
144             })
145              
146             # precedence level: left + - .
147             <rule: add>
148             <[operand=mult]> ** <[op=(\+|-|\.)]>
149             (?{
150 0 0 0     0 if ($MATCH{op} && @{ $MATCH{op} }) {
  0         0  
151 0         0 $MATCH = $obj->rule_add(match=>\%MATCH);
152             } else {
153 0         0 $MATCH = $MATCH{operand}[0];
154             }
155             })
156              
157             # precedence level: left * / % x
158             <rule: mult>
159             <[operand=unary]> ** <[op=(\*|/|%|x)]>
160             (?{
161 0 0 0     0 if ($MATCH{op} && @{ $MATCH{op} }) {
  0         0  
162 0         0 $MATCH = $obj->rule_mult(match=>\%MATCH);
163             } else {
164 0         0 $MATCH = $MATCH{operand}[0];
165             }
166             })
167              
168             # precedence level: right ! ~ unary+ unary-
169             <rule: unary>
170             <[op=(!|~|\+|-)]>* <operand=power>
171             (?{
172 0 0 0     0 if ($MATCH{op} && @{ $MATCH{op} }) {
  0         0  
173 0         0 $MATCH = $obj->rule_unary(match=>\%MATCH);
174             } else {
175 0         0 $MATCH = $MATCH{operand};
176             }
177             })
178              
179             # precedence level: right **
180             <rule: power>
181             <[operand=subscripting]> ** <[op=(\*\*)]>
182             (?{
183 0 0 0     0 if ($MATCH{op} && @{ $MATCH{op} }) {
  0         0  
184 0         0 $MATCH = $obj->rule_power(match=>\%MATCH);
185             } else {
186 0         0 $MATCH = $MATCH{operand}[0];
187             }
188             })
189              
190             # precedence level: left hash[s], array[i]
191             <rule: subscripting>
192             <operand=var0> <[subscript]>*
193             (?{
194 0 0 0     0 if ($MATCH{subscript} && @{ $MATCH{subscript} }) {
  0         0  
195 0         0 $MATCH = $obj->rule_subscripting_var(match=>\%MATCH);
196             } else {
197 0         0 $MATCH = $MATCH{operand};
198             }
199             })
200             | <operand=term> <[subscript]>*
201             (?{
202 0 0 0     0 if ($MATCH{subscript} && @{ $MATCH{subscript} }) {
  0         0  
203 0         0 $MATCH = $obj->rule_subscripting_expr(match=>\%MATCH);
204             } else {
205 0         0 $MATCH = $MATCH{operand};
206             }
207             })
208              
209             <rule: subscript>
210             \[ <MATCH=term> \]
211              
212             # precedence level: left term (variable, str/num literals, func(), (paren))
213             <rule: term>
214             <MATCH=func>
215             | <MATCH=var0>
216             | <MATCH=str0>
217             | <MATCH=undef>
218             | <MATCH=num0>
219             | <MATCH=bool0>
220             | <MATCH=array>
221             | <MATCH=hash>
222             | \( <answer> \)
223 0   0     0 (?{ $MATCH = $obj->rule_parenthesis(match=>\%MATCH) // $MATCH{answer} })
224              
225             <rule: array>
226             \[ \]
227 0         0 (?{ $MATCH = $obj->rule_array(match=>{element=>[]}) })
228             | \[ <[element=answer]> ** (,) \]
229 0         0 (?{ $MATCH = $obj->rule_array(match=>\%MATCH) })
230              
231             <rule: hash>
232             \{ \}
233 0         0 (?{ $MATCH = $obj->rule_hash(match=>{pair=>[]}) })
234             | \{ <[pair]> ** (,) \}
235 0         0 (?{ $MATCH = $obj->rule_hash(match=>\%MATCH) })
236              
237             <token: undef>
238             undef
239 0         0 (?{ $MATCH = $obj->rule_undef() })
240              
241             <token: bool0>
242             <bool=(true|false)>
243 0         0 (?{ $MATCH = $obj->rule_bool(match=>\%MATCH) })
244              
245             <token: num0>
246             <sign0a=([+-]?+)> 0x <num0a=([0-9A-Fa-f]++)>
247             (?{ $MATCH = $obj->rule_num(match=>{num=>
248 0 0       0 ($MATCH{sign0a} eq '-' ? -1:1) * hex($MATCH{num0a})}) })
249             | <sign0b=([+-]?+)> 0o <num0b=([0-7]++)>
250             (?{ $MATCH = $obj->rule_num(match=>{num=>
251 0 0       0 ($MATCH{sign0b} eq '-' ? -1:1) * oct($MATCH{num0b})}) })
252             | <sign0c=([+-]?+)> 0b <num0c=([0-1]++)>
253             (?{ $MATCH = $obj->rule_num(match=>{num=>
254 0 0       0 ($MATCH{sign0c} eq '-' ? -1:1) * oct("0b".$MATCH{num0c})}) })
255             | <num0c=( [+-]?\d++(?:\.\d++)?+ | inf | nan)>
256 0         0 (?{ $MATCH = $obj->rule_num(match=>{num=>$MATCH{num0c}}) })
257              
258             <rule: str0>
259             <MATCH=squotestr>
260             | <MATCH=dquotestr>
261              
262             <token: squotestr>
263             '<[part=(\\\\|\\'|\\|[^\\']++)]>*'
264 0         0 (?{ $MATCH = $obj->rule_squotestr(match=>\%MATCH) })
265              
266             <token: dquotestr>
267             "<[part=([^"\044\\]++|\$\.\.?|\$\w+|\$\{[^\}]+\}|\\\\|\\'|\\"|\\[tnrfbae\$]|\\[0-7]{1,3}|\\x[0-9A-Fa-f]{1,2}|\\x\{[0-9A-Fa-f]{1,4}\}|\\)]>*"
268 0         0 (?{ $MATCH = $obj->rule_dquotestr(match=>\%MATCH) })
269              
270             <rule: var0>
271             \$ <var=(\w++(?:::\w+)*+)>
272 0         0 (?{ $MATCH = $obj->rule_var(match=>\%MATCH) })
273             | \$ \{ <var=([^\}]++)> \}
274 0         0 (?{ $MATCH = $obj->rule_var(match=>\%MATCH) })
275              
276             <rule: func>
277             <func_name=([A-Za-z_]\w*+)> \( \)
278 0         0 (?{ $MATCH = $obj->rule_func(match=>{func_name=>$MATCH{func_name}, args=>[]}) })
279 0         0 | <func_name=(map|grep|usort)> \( \{ <expr=answer> \} (?{ push @$subexpr_stack, $CONTEXT }), <input_array=answer> \)
280 0         0 (?{ my $meth = "rule_func_$MATCH{func_name}";
281 0         0 $MATCH = $obj->$meth(match=>{expr=>pop(@$subexpr_stack), array=>$MATCH{input_array}}) })
282             | <func_name=([A-Za-z_]\w*+)> \( <[args=answer]> ** (,) \)
283 0         0 (?{ $MATCH = $obj->rule_func(match=>\%MATCH) })
284              
285 15         104 }xms } 0..($MAX_LEVELS-1)];
286              
287 398         329 $obj = $obj_arg;
288 398         951 $obj_arg->expr_preprocess(string_ref => \$str, level => $level);
289             #print "DEBUG: Parsing expression `$str` with grammars->[$level] ...\n";
290 398 100       11615 die "Invalid syntax in expression `$str`" unless $str =~ $grammars->[$level];
291 338         1194 $obj_arg->expr_postprocess(result => $/{answer});
292             }
293              
294             1;
295             # ABSTRACT: Parse Language::Expr expression
296              
297             __END__
298              
299             =pod
300              
301             =encoding UTF-8
302              
303             =head1 NAME
304              
305             Language::Expr::Parser - Parse Language::Expr expression
306              
307             =head1 VERSION
308              
309             This document describes version 0.28 of Language::Expr::Parser (from Perl distribution Language-Expr), released on 2016-07-01.
310              
311             =head1 KNOWN BUGS
312              
313             =over 4
314              
315             =item * Ternary operator is not chainable yet.
316              
317             =back
318              
319             =head1 METHODS
320              
321             =head2 parse_expr($str, $obj)
322              
323             Parse expression in $str. Will call various rule_*() methods in $obj.
324              
325             =head1 HOMEPAGE
326              
327             Please visit the project's homepage at L<https://metacpan.org/release/Language-Expr>.
328              
329             =head1 SOURCE
330              
331             Source repository is at L<https://github.com/perlancar/perl-Language-Expr>.
332              
333             =head1 BUGS
334              
335             Please report any bugs or feature requests on the bugtracker website L<https://rt.cpan.org/Public/Dist/Display.html?Name=Language-Expr>
336              
337             When submitting a bug or request, please include a test-file or a
338             patch to an existing test-file that illustrates the bug or desired
339             feature.
340              
341             =head1 AUTHOR
342              
343             perlancar <perlancar@cpan.org>
344              
345             =head1 COPYRIGHT AND LICENSE
346              
347             This software is copyright (c) 2016 by perlancar@cpan.org.
348              
349             This is free software; you can redistribute it and/or modify it under
350             the same terms as the Perl 5 programming language system itself.
351              
352             =cut