← Index
NYTProf Performance Profile   « line view »
For ./view
  Run on Fri Jul 31 18:42:36 2015
Reported on Fri Jul 31 18:48:15 2015

Filename/var/www/foswikidev/core/lib/Foswiki/Infix/Parser.pm
StatementsExecuted 6110 statements in 15.7ms
Subroutines
Calls P F Exclusive
Time
Inclusive
Time
Subroutine
84115.69ms11.4msFoswiki::Infix::Parser::::__ANON__[:306]Foswiki::Infix::Parser::__ANON__[:306]
84115.13ms18.2msFoswiki::Infix::Parser::::_parseFoswiki::Infix::Parser::_parse
197211.60ms1.99msFoswiki::Infix::Parser::::_applyFoswiki::Infix::Parser::_apply
8411989µs989µsFoswiki::Infix::Parser::::_initialiseFoswiki::Infix::Parser::_initialise
8433828µs20.0msFoswiki::Infix::Parser::::parseFoswiki::Infix::Parser::parse
111616µs3.88msFoswiki::Infix::Parser::::BEGIN@26Foswiki::Infix::Parser::BEGIN@26
8922184µs184µsFoswiki::Infix::Parser::::addOperatorFoswiki::Infix::Parser::addOperator
31150µs50µsFoswiki::Infix::Parser::::newFoswiki::Infix::Parser::new
11112µs24µsFoswiki::Infix::Parser::::BEGIN@21Foswiki::Infix::Parser::BEGIN@21
11110µs41µsFoswiki::Infix::Parser::::BEGIN@36Foswiki::Infix::Parser::BEGIN@36
1119µs13µsFoswiki::Infix::Parser::::BEGIN@22Foswiki::Infix::Parser::BEGIN@22
1119µs110µsFoswiki::Infix::Parser::::BEGIN@24Foswiki::Infix::Parser::BEGIN@24
1119µs32µsFoswiki::Infix::Parser::::BEGIN@23Foswiki::Infix::Parser::BEGIN@23
1116µs6µsFoswiki::Infix::Parser::::BEGIN@25Foswiki::Infix::Parser::BEGIN@25
1115µs5µsFoswiki::Infix::Parser::::BEGIN@28Foswiki::Infix::Parser::BEGIN@28
1112µs2µsFoswiki::Infix::Parser::::finishFoswiki::Infix::Parser::finish
0000s0sFoswiki::Infix::Parser::::__ANON__[:312]Foswiki::Infix::Parser::__ANON__[:312]
0000s0sFoswiki::Infix::Parser::::__ANON__[:317]Foswiki::Infix::Parser::__ANON__[:317]
0000s0sFoswiki::Infix::Parser::::onCloseExprFoswiki::Infix::Parser::onCloseExpr
Call graph for these subroutines as a Graphviz dot language file.
Line State
ments
Time
on line
Calls Time
in subs
Code
1# See bottom of file for license and copyright information
2
3=begin TML
4
5---+ package Foswiki::Infix::Parser
6
7A simple [[http://en.wikipedia.org/wiki/LL_parser][LL(1) parser]] that parses infix expressions with nonary,
8unary and binary operators specified using an operator table.
9
10The parser works by examining each token in the input stream from left to right, and constructs
11_parse nodes_ as soon as they are identified. The parser doesn't dictate the type of the parse nodes,
12instead using a _factory_ to generate them. the output from the parser is a
13[[http://en.wikipedia.org/wiki/Parse_tree][parse tree]] built using nodes generated by the node factory.
14
15Escapes are supported in strings, using backslash.
16
17=cut
18
19package Foswiki::Infix::Parser;
20
21230µs236µs
# spent 24µs (12+12) within Foswiki::Infix::Parser::BEGIN@21 which was called: # once (12µs+12µs) by Foswiki::Query::Parser::BEGIN@28 at line 21
use strict;
# spent 24µs making 1 call to Foswiki::Infix::Parser::BEGIN@21 # spent 12µs making 1 call to strict::import
22223µs217µs
# spent 13µs (9+4) within Foswiki::Infix::Parser::BEGIN@22 which was called: # once (9µs+4µs) by Foswiki::Query::Parser::BEGIN@28 at line 22
use warnings;
# spent 13µs making 1 call to Foswiki::Infix::Parser::BEGIN@22 # spent 4µs making 1 call to warnings::import
23231µs256µs
# spent 32µs (9+24) within Foswiki::Infix::Parser::BEGIN@23 which was called: # once (9µs+24µs) by Foswiki::Query::Parser::BEGIN@28 at line 23
use Assert;
# spent 32µs making 1 call to Foswiki::Infix::Parser::BEGIN@23 # spent 24µs making 1 call to Exporter::import
24230µs2211µs
# spent 110µs (9+101) within Foswiki::Infix::Parser::BEGIN@24 which was called: # once (9µs+101µs) by Foswiki::Query::Parser::BEGIN@28 at line 24
use Error qw( :try );
# spent 110µs making 1 call to Foswiki::Infix::Parser::BEGIN@24 # spent 101µs making 1 call to Error::import
25220µs16µs
# spent 6µs within Foswiki::Infix::Parser::BEGIN@25 which was called: # once (6µs+0s) by Foswiki::Query::Parser::BEGIN@28 at line 25
use Foswiki::Infix::Error ();
# spent 6µs making 1 call to Foswiki::Infix::Parser::BEGIN@25
262114µs13.88ms
# spent 3.88ms (616µs+3.27) within Foswiki::Infix::Parser::BEGIN@26 which was called: # once (616µs+3.27ms) by Foswiki::Query::Parser::BEGIN@28 at line 26
use Foswiki::Infix::Node ();
# spent 3.88ms making 1 call to Foswiki::Infix::Parser::BEGIN@26
27
28
# spent 5µs within Foswiki::Infix::Parser::BEGIN@28 which was called: # once (5µs+0s) by Foswiki::Query::Parser::BEGIN@28 at line 33
BEGIN {
2915µs if ( $Foswiki::cfg{UseLocale} ) {
30 require locale;
31 import locale();
32 }
33129µs15µs}
# spent 5µs making 1 call to Foswiki::Infix::Parser::BEGIN@28
34
35# Set to 1 for debug
3621.45ms272µs
# spent 41µs (10+31) within Foswiki::Infix::Parser::BEGIN@36 which was called: # once (10µs+31µs) by Foswiki::Query::Parser::BEGIN@28 at line 36
use constant MONITOR_PARSER => 0;
# spent 41µs making 1 call to Foswiki::Infix::Parser::BEGIN@36 # spent 31µs making 1 call to constant::import
37
38=begin TML
39
40---++ ClassMethod new($node_factory, \%options) -> $parser_object
41
42Creates a new infix parser. Operators must be added for it to be useful.
43
44The tokeniser matches tokens in the following order: operators,
45quotes (" and '), numbers, words, brackets. If you have any overlaps (e.g.
46an operator '<' and a bracket operator '<<') then the first choice
47will match.
48
49=$node_factory= needs to be ( the name of a package | an object ) that supports the
50following two functions:
51 * =newLeaf($val, $type)= - create a terminal. $type will be:
52 1 if the terminal matched the =words= specification (see below).
53 2 if it is a number matched the =numbers= specification (see below)
54 3 if it is a quoted string
55 * =newNode($op, @params)= - create a new operator node. @params
56 is a variable-length list of parameters, left to right. $op
57 is a reference to the operator hash in the \@opers list.
58These functions should throw Error::Simple in the event of errors.
59Foswiki::Infix::Node is such a class, ripe for subclassing.
60
61The remaining parameters are named, and specify options that affect the
62behaviour of the parser:
63 1 =words=>qr//= - should be an RE specifying legal words (unquoted
64 terminals that are not operators i.e. names and numbers). By default
65 this is =\w+=.
66 It's ok if operator names match this RE; operators always have precedence
67 over names.
68 2 =numbers=>qr//= - should be an RE specifying legal numbers (unquoted
69 terminals that are not operators or words). By default
70 this is =qr/[+-]?(?:\d+\.\d+|\d+\.|\.\d+|\d+)(?:[eE][+-]?\d+)?/=,
71 which matches integers and floating-point numbers. Number
72 matching always takes precedence over word matching (i.e. "1xy" will
73 be parsed as a number followed by a word. A typical usage of this option
74 is when you only want to recognise integers, in which case you would set
75 this to =numbers => qr/\d+/=.
76Strings should always be surrounded by 'single-quotes'. Single quotes in values may
77be escaped using backslash (\).
78
79=cut
80
81
# spent 50µs within Foswiki::Infix::Parser::new which was called 3 times, avg 17µs/call: # 3 times (50µs+0s) by Foswiki::Query::Parser::new at line 109 of /var/www/foswikidev/core/lib/Foswiki/Query/Parser.pm, avg 17µs/call
sub new {
8234µs my ( $class, $options ) = @_;
83
84329µs my $this = bless(
85 {
86 node_factory => $options->{nodeClass},
87 operators => [],
88 initialised => 0,
89 },
90 $class
91 );
92
9338µs $this->{numbers} =
94 defined( $options->{numbers} )
95 ? $options->{numbers}
96 : qr/(\d+\.\d+|\d+\.|\.\d+|\d+)([eE][+-]?\d+)?/;
97
9833µs $this->{words} =
99 defined( $options->{words} )
100 ? $options->{words}
101 : qr/\w+/;
102
103313µs return $this;
104}
105
106# Break circular references.
107
# spent 2µs within Foswiki::Infix::Parser::finish which was called: # once (2µs+0s) by Foswiki::Search::finish at line 70 of /var/www/foswikidev/core/lib/Foswiki/Search.pm
sub finish {
10815µs my $self = shift;
109
110}
111
112=begin TML
113
114---++ ObjectMethod addOperator($oper)
115Add an operator to the parser.
116
117=$oper= is an object that implements the Foswiki::Infix::OP interface.
118
119=cut
120
121
# spent 184µs within Foswiki::Infix::Parser::addOperator which was called 89 times, avg 2µs/call: # 81 times (165µs+0s) by Foswiki::Query::Parser::new at line 112 of /var/www/foswikidev/core/lib/Foswiki/Query/Parser.pm, avg 2µs/call # 8 times (19µs+0s) by Foswiki::If::Parser::new at line 49 of /var/www/foswikidev/core/lib/Foswiki/If/Parser.pm, avg 2µs/call
sub addOperator {
1228939µs my ( $this, $op ) = @_;
1238942µs push( @{ $this->{operators} }, $op );
12489164µs $this->{initialised} = 0;
125}
126
127# Initialise on demand before a first parse
128
# spent 989µs within Foswiki::Infix::Parser::_initialise which was called 84 times, avg 12µs/call: # 84 times (989µs+0s) by Foswiki::Infix::Parser::parse at line 197, avg 12µs/call
sub _initialise {
1298430µs my $this = shift;
130
13184321µs return if $this->{initialised};
132
133 # Build operator lists
1343700ns my @stdOpsRE;
13531µs my @bracketOpsRE;
13634µs foreach my $op ( @{ $this->{operators} } ) {
137
138 # Build a RE for the operator. Note that operators
139 # that end in \w are terminated with \b
1408928µs my $opre = quotemeta( $op->{name} );
1418985µs $opre .= ( $op->{name} =~ m/\w$/ ) ? '\b' : '';
1428933µs if ( $op->{casematters} ) {
143416µs $op->{InfixParser_RE} = qr/$opre/;
144 }
145 else {
14685288µs $op->{InfixParser_RE} = qr/$opre/i;
147 }
1488954µs if ( defined( $op->{close} ) ) {
149
150 # bracket op
151612µs $this->{bracket_ops}->{ lc( $op->{name} ) } = $op;
152
15363µs $opre = quotemeta( $op->{close} );
15466µs $opre .= ( $op->{close} =~ m/\w$/ ) ? '\b' : '';
15563µs if ( $op->{casematters} ) {
156 $op->{InfixParser_closeRE} = qr/$opre/;
157 }
158 else {
159618µs $op->{InfixParser_closeRE} = qr/$opre/i;
160 }
16163µs push( @bracketOpsRE, $op->{InfixParser_RE} );
162 }
163 else {
1648345µs if ( $op->{arity} == 1 ) {
165 $this->{unary_ops}->{ lc( $op->{name} ) } = $op;
166 }
167 else {
1685658µs $this->{standard_ops}->{ lc( $op->{name} ) } = $op;
169 }
1708336µs push( @stdOpsRE, $op->{InfixParser_RE} );
171 }
172 }
173
174 # Build regular expression of all standard operators.
175310µs $this->{standard_op_REs} = join( '|', @stdOpsRE );
176
177 # and repeat for bracket operators
17833µs $this->{bracket_op_REs} = join( '|', @bracketOpsRE );
179
180314µs $this->{initialised} = 1;
181}
182
183=begin TML
184
185---++ ObjectMethod parse($string) -> $parseTree
186Parses =$string=, calling =newLeaf= and =newNode= in the client class
187as necessary to create a parse tree. Returns the result of calling =newNode=
188on the root of the parse.
189
190Throws Foswiki::Infix::Error in the event of parse errors.
191
192=cut
193
194
# spent 20.0ms (828µs+19.1) within Foswiki::Infix::Parser::parse which was called 84 times, avg 238µs/call: # 43 times (266µs+8.32ms) by Foswiki::__ANON__[/var/www/foswikidev/core/lib/Foswiki/Macros/IF.pm:50] at line 41 of /var/www/foswikidev/core/lib/Foswiki/Macros/IF.pm, avg 200µs/call # 40 times (555µs+10.4ms) by Foswiki::Search::__ANON__[/var/www/foswikidev/core/lib/Foswiki/Search.pm:134] at line 133 of /var/www/foswikidev/core/lib/Foswiki/Search.pm, avg 275µs/call # once (7µs+393µs) by Foswiki::__ANON__[/var/www/foswikidev/core/lib/Foswiki/Macros/QUERY.pm:68] at line 65 of /var/www/foswikidev/core/lib/Foswiki/Macros/QUERY.pm
sub parse {
1958462µs my ( $this, $expr ) = @_;
1968434µs my $data = $expr;
19784208µs84989µs $this->_initialise();
# spent 989µs making 84 calls to Foswiki::Infix::Parser::_initialise, avg 12µs/call
19884427µs8418.2ms return _parse( $this, $expr, \$data );
# spent 18.2ms making 84 calls to Foswiki::Infix::Parser::_parse, avg 216µs/call
199}
200
201# Simple stack parser, after Knuth
202
# spent 18.2ms (5.13+13.0) within Foswiki::Infix::Parser::_parse which was called 84 times, avg 216µs/call: # 84 times (5.13ms+13.0ms) by Foswiki::Infix::Parser::parse at line 198, avg 216µs/call
sub _parse {
2038473µs my ( $this, $expr, $input, $term ) = @_;
204
2058420µs throw Foswiki::Infix::Error("Empty expression")
206 unless defined($expr);
20784156µs $$input = "()" unless $$input =~ m/\S/;
208
2098443µs my @opers = ();
2108428µs my @opands = ();
211
2128418µs $input ||= \$expr;
213
214 print STDERR "Parse: $$input\n" if MONITOR_PARSER;
2158424µs my $lastTokWasOper = 1;
216
# spent 11.4ms (5.69+5.67) within Foswiki::Infix::Parser::__ANON__[/var/www/foswikidev/core/lib/Foswiki/Infix/Parser.pm:306] which was called 84 times, avg 135µs/call: # 84 times (5.69ms+5.67ms) by Error::subs::try at line 419 of Error.pm, avg 135µs/call
try {
21784173µs while ( $$input =~ m/\S/ ) {
2182602.79ms1383µs if ( $$input =~ s/^\s*($this->{standard_op_REs})// ) {
# spent 383µs making 1 call to utf8::SWASHNEW
219113102µs my $opname = $1;
220113261µs my $op = $this->{unary_ops}->{ lc($opname) }
221 || $this->{standard_ops}->{ lc($opname) };
222113174µs if ( $lastTokWasOper
223 && $opname =~ $this->{words}
224 && $op->{arity} > 1 )
225 {
226
227 # op is a word name, and is in an operand position,
228 # and is not unary. Treat it as an operand.
229 push( @opands,
230 $this->{node_factory}
231 ->newLeaf( $opname, Foswiki::Infix::Node::NAME ) );
232 print STDERR "Operand: name '$opname'\n" if MONITOR_PARSER;
233 $lastTokWasOper = 0;
234 next;
235 }
236113104µs if ( $lastTokWasOper && $this->{unary_ops}->{ lc($opname) } ) {
237
238 # Op immediately follows another op, and allows unary.
239 $op = $this->{unary_ops}->{ lc($opname) };
240 }
241 else {
2426368µs $op = $this->{standard_ops}->{ lc($opname) }
243 || $this->{unary_ops}->{ lc($opname) };
244 }
245 print STDERR "Operator: $op\n" if MONITOR_PARSER;
24611310µs ASSERT( $op, $opname ) if DEBUG;
247113261µs113565µs _apply( $this, $op->{prec}, \@opers, \@opands );
# spent 565µs making 113 calls to Foswiki::Infix::Parser::_apply, avg 5µs/call
24811342µs push( @opers, $op );
24911347µs $lastTokWasOper = 1;
250 }
251 elsif ( $$input =~ s/^\s*(['"])(|(?:\\.|[^\\])+?)\1// ) {
2526052µs my $q = $1;
2536032µs my $val = $2;
254 print STDERR "Operand: qs '$val'\n" if MONITOR_PARSER;
255
256 # Handle escaped characters in the string. This is where
257 # expansions such as \n are handled
258 $val =~
25960160µss/(?<!\\)\\(0[0-7]{2}|x[a-fA-F0-9]{2}|x\{[a-fA-F0-9]+\}|n|t|\\|$q)/eval('"\\'.$1.'"')/ge;
26060197µs60891µs push( @opands,
# spent 891µs making 60 calls to Foswiki::Query::Node::newLeaf, avg 15µs/call
261 $this->{node_factory}
262 ->newLeaf( $val, Foswiki::Infix::Node::STRING ) );
2636028µs $lastTokWasOper = 0;
264 }
265 elsif ( $$input =~ s/^\s*($this->{numbers})// ) {
266 my $val = 0 + $1;
267 print STDERR "Operand: number $val\n" if MONITOR_PARSER;
268 push( @opands,
269 $this->{node_factory}
270 ->newLeaf( $val, Foswiki::Infix::Node::NUMBER ) );
271 $lastTokWasOper = 0;
272 }
273 elsif ( $$input =~ s/^\s*($this->{words})// ) {
274 print STDERR "Operand: word '$1'\n" if MONITOR_PARSER;
2758791µs my $val = $1;
27687475µs872.40ms push( @opands,
# spent 2.40ms making 87 calls to Foswiki::Query::Node::newLeaf, avg 28µs/call
277 $this->{node_factory}
278 ->newLeaf( $val, Foswiki::Infix::Node::NAME ) );
2798743µs $lastTokWasOper = 0;
280 }
281 elsif ( $$input =~ s/^\s*($this->{bracket_op_REs})// ) {
282 my $opname = $1;
283 print STDERR "Tok: open bracket $opname\n" if MONITOR_PARSER;
284 my $op = $this->{bracket_ops}->{ lc($opname) };
285 ASSERT($op) if DEBUG;
286 _apply( $this, $op->{prec}, \@opers, \@opands );
287 push( @opers, $op );
288 push( @opands,
289 $this->_parse( $expr, $input, $op->{InfixParser_closeRE} )
290 );
291 $lastTokWasOper = 0;
292 }
293 elsif ( defined($term) && $$input =~ s/^\s*$term// ) {
294 print STDERR "Tok: close bracket $term\n" if MONITOR_PARSER;
295
296 # if the operand stack is empty, push an empty array
297 # nonary operator
298 $this->onCloseExpr( \@opands );
299 last;
300 }
301 else {
302 throw Foswiki::Infix::Error( 'Syntax error', $expr, $$input );
303 }
304 }
30584284µs841.43ms _apply( $this, 0, \@opers, \@opands );
# spent 1.43ms making 84 calls to Foswiki::Infix::Parser::_apply, avg 17µs/call
306 }
307 catch Foswiki::Infix::Error with {
308 my $e = shift;
309
310 # Don't need to construct a new Foswiki::Infix::Error
311 throw $e;
312843.30ms252393µs };
# spent 290µs making 84 calls to Error::catch, avg 3µs/call # spent 103µs making 84 calls to Error::subs::with, avg 1µs/call # spent 12.2ms making 84 calls to Error::subs::try, avg 145µs/call, recursion: max depth 4, sum of overlapping time 12.2ms
313 catch Error with {
314
315 # Catch errors thrown during the tree building process
316 throw Foswiki::Infix::Error( shift, $expr, $$input );
31784731µs168448µs };
# spent 332µs making 84 calls to Error::catch, avg 4µs/call # spent 116µs making 84 calls to Error::subs::with, avg 1µs/call
3188447µs throw Foswiki::Infix::Error( 'Missing operator', $expr, $$input )
319 unless scalar(@opands) == 1;
320 throw Foswiki::Infix::Error(
3218427µs 'Excess operators (' . join( ' ', map { $_->{name} } @opers ) . ')',
322 $expr, $$input )
323 if scalar(@opers);
3248442µs my $result = pop(@opands);
325 print STDERR "Return " . $result->stringify() . "\n" if MONITOR_PARSER;
32684274µs return $result;
327}
328
329# Apply ops on the stack while their precedence is higher than $prec
330# For each operator on the stack with precedence >= $prec, pop the
331# required number of operands, construct a new parse node and push
332# the node back onto the operand stack.
333
# spent 1.99ms (1.60+390µs) within Foswiki::Infix::Parser::_apply which was called 197 times, avg 10µs/call: # 113 times (490µs+75µs) by Foswiki::Infix::Parser::__ANON__[/var/www/foswikidev/core/lib/Foswiki/Infix/Parser.pm:306] at line 247, avg 5µs/call # 84 times (1.11ms+315µs) by Foswiki::Infix::Parser::__ANON__[/var/www/foswikidev/core/lib/Foswiki/Infix/Parser.pm:306] at line 305, avg 17µs/call
sub _apply {
334197128µs my ( $this, $prec, $opers, $opands ) = @_;
335
336197704µs while (scalar(@$opers)
337 && $opers->[-1]->{prec} >= $prec
338 && scalar(@$opands) >= $opers->[-1]->{arity} )
339 {
34011352µs my $op = pop(@$opers);
34111335µs my $arity = $op->{arity};
34211319µs my @prams;
34311364µs while ( $arity-- ) {
34417676µs unshift( @prams, pop(@$opands) );
345
346 # Should never get thrown, but just in case...
34717677µs throw Foswiki::Infix::Error("Missing operand to '$op->{name}'")
348 unless $prams[0];
349 }
350 if (MONITOR_PARSER) {
351 print STDERR "Apply $op->{name}("
352 . join( ', ', map { $_->stringify() } @prams ) . ")\n";
353 }
35411318µs my $folded;
355113215µs if ( ref( $prams[0]->{op} )
356 && $op == $prams[0]->{op}
357 && $op->{canfold} )
358 {
3591500ns push( @{ $prams[0]->{params} }, $prams[1] );
3601200ns push( @$opands, $prams[0] );
361 }
362 else {
363112315µs112390µs push( @$opands, $this->{node_factory}->newNode( $op, @prams ) );
# spent 390µs making 112 calls to Foswiki::Infix::Node::newNode, avg 3µs/call
364 }
365 }
366}
367
368=begin TML
369
370---++ ObjectMethod onCloseExpr($@opands)
371Designed to be overridden by subclasses that need to perform an action on the
372operand stack (such as pushing) when a sub-expression is closed. Also called
373when the root expression is closed. The default is a no-op.
374
375=cut
376
377sub onCloseExpr {
378 my ( $this, $opands ) = @_;
379}
380
38112µs1;
382__END__