← Index
NYTProf Performance Profile   « line view »
For ./view
  Run on Fri Jul 31 19:05:14 2015
Reported on Fri Jul 31 19:08:10 2015

Filename/var/www/foswiki11/lib/Foswiki/Infix/Parser.pm
StatementsExecuted 5657 statements in 30.5ms
Subroutines
Calls P F Exclusive
Time
Inclusive
Time
Subroutine
831113.4ms19.8msFoswiki::Infix::Parser::::__ANON__[:277]Foswiki::Infix::Parser::__ANON__[:277]
83114.87ms31.6msFoswiki::Infix::Parser::::_parseFoswiki::Infix::Parser::_parse
189211.91ms2.48msFoswiki::Infix::Parser::::_applyFoswiki::Infix::Parser::_apply
8311947µs947µsFoswiki::Infix::Parser::::_initialiseFoswiki::Infix::Parser::_initialise
8333889µs33.5msFoswiki::Infix::Parser::::parseFoswiki::Infix::Parser::parse
111807µs878µsFoswiki::Infix::Parser::::BEGIN@21Foswiki::Infix::Parser::BEGIN@21
51871686µs686µsFoswiki::Infix::Parser::::MONITOR_PARSERFoswiki::Infix::Parser::MONITOR_PARSER
111424µs522µsFoswiki::Infix::Parser::::BEGIN@20Foswiki::Infix::Parser::BEGIN@20
6522208µs208µsFoswiki::Infix::Parser::::addOperatorFoswiki::Infix::Parser::addOperator
31161µs61µsFoswiki::Infix::Parser::::newFoswiki::Infix::Parser::new
11129µs327µsFoswiki::Infix::Parser::::BEGIN@19Foswiki::Infix::Parser::BEGIN@19
11126µs48µsFoswiki::Infix::Parser::::BEGIN@16Foswiki::Infix::Parser::BEGIN@16
11116µs25µsFoswiki::Infix::Parser::::BEGIN@17Foswiki::Infix::Parser::BEGIN@17
11116µs39µsFoswiki::Infix::Parser::::BEGIN@18Foswiki::Infix::Parser::BEGIN@18
1112µs2µsFoswiki::Infix::Parser::::finishFoswiki::Infix::Parser::finish
0000s0sFoswiki::Infix::Parser::::__ANON__[:282]Foswiki::Infix::Parser::__ANON__[:282]
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 stack-based parser that parses infix expressions with nonary,
8unary and binary operators specified using an operator table.
9
10Escapes are supported in strings, using backslash.
11
12=cut
13
14package Foswiki::Infix::Parser;
15
16246µs271µs
# spent 48µs (26+23) within Foswiki::Infix::Parser::BEGIN@16 which was called: # once (26µs+23µs) by Foswiki::Query::Parser::BEGIN@17 at line 16
use strict;
# spent 48µs making 1 call to Foswiki::Infix::Parser::BEGIN@16 # spent 23µs making 1 call to strict::import
17245µs234µs
# spent 25µs (16+9) within Foswiki::Infix::Parser::BEGIN@17 which was called: # once (16µs+9µs) by Foswiki::Query::Parser::BEGIN@17 at line 17
use warnings;
# spent 25µs making 1 call to Foswiki::Infix::Parser::BEGIN@17 # spent 9µs making 1 call to warnings::import
18246µs262µs
# spent 39µs (16+23) within Foswiki::Infix::Parser::BEGIN@18 which was called: # once (16µs+23µs) by Foswiki::Query::Parser::BEGIN@17 at line 18
use Assert;
# spent 39µs making 1 call to Foswiki::Infix::Parser::BEGIN@18 # spent 23µs making 1 call to Assert::import
19268µs2626µs
# spent 327µs (29+298) within Foswiki::Infix::Parser::BEGIN@19 which was called: # once (29µs+298µs) by Foswiki::Query::Parser::BEGIN@17 at line 19
use Error qw( :try );
# spent 327µs making 1 call to Foswiki::Infix::Parser::BEGIN@19 # spent 298µs making 1 call to Error::import
202186µs1522µs
# spent 522µs (424+99) within Foswiki::Infix::Parser::BEGIN@20 which was called: # once (424µs+99µs) by Foswiki::Query::Parser::BEGIN@17 at line 20
use Foswiki::Infix::Error ();
# spent 522µs making 1 call to Foswiki::Infix::Parser::BEGIN@20
2122.36ms1878µs
# spent 878µs (807+71) within Foswiki::Infix::Parser::BEGIN@21 which was called: # once (807µs+71µs) by Foswiki::Query::Parser::BEGIN@17 at line 21
use Foswiki::Infix::Node ();
# spent 878µs making 1 call to Foswiki::Infix::Parser::BEGIN@21
22
23# Set to 1 for debug
245181.55ms
# spent 686µs within Foswiki::Infix::Parser::MONITOR_PARSER which was called 518 times, avg 1µs/call: # 106 times (108µs+0s) by Foswiki::Infix::Parser::__ANON__[/var/www/foswiki11/lib/Foswiki/Infix/Parser.pm:277] at line 224, avg 1µs/call # 106 times (105µs+0s) by Foswiki::Infix::Parser::_apply at line 315, avg 992ns/call # 83 times (228µs+0s) by Foswiki::Infix::Parser::_parse at line 219, avg 3µs/call # 83 times (103µs+0s) by Foswiki::Infix::Parser::_parse at line 290, avg 1µs/call # 81 times (90µs+0s) by Foswiki::Infix::Parser::__ANON__[/var/www/foswiki11/lib/Foswiki/Infix/Parser.pm:277] at line 251, avg 1µs/call # 58 times (50µs+0s) by Foswiki::Infix::Parser::__ANON__[/var/www/foswiki11/lib/Foswiki/Infix/Parser.pm:277] at line 233, avg 857ns/call # once (2µs+0s) by Foswiki::Infix::Parser::__ANON__[/var/www/foswiki11/lib/Foswiki/Infix/Parser.pm:277] at line 245
sub MONITOR_PARSER { 0 }
25
26=begin TML
27
28---++ new($client_class, \%options) -> parser object
29
30Creates a new infix parser. Operators must be added for it to be useful.
31
32The tokeniser matches tokens in the following order: operators,
33quotes (" and '), numbers, words, brackets. If you have any overlaps (e.g.
34an operator '<' and a bracket operator '<<') then the first choice
35will match.
36
37=$client_class= needs to be the _name_ of a _package_ that supports the
38following two functions:
39 * =newLeaf($val, $type)= - create a terminal. $type will be:
40 1 if the terminal matched the =words= specification (see below).
41 2 if it is a number matched the =numbers= specification (see below)
42 3 if it is a quoted string
43 * =newNode($op, @params) - create a new operator node. @params
44 is a variable-length list of parameters, left to right. $op
45 is a reference to the operator hash in the \@opers list.
46These functions should throw Error::Simple in the event of errors.
47Foswiki::Infix::Node is such a class, ripe for subclassing.
48
49The remaining parameters are named, and specify options that affect the
50behaviour of the parser:
51 1 =words=>qr//= - should be an RE specifying legal words (unquoted
52 terminals that are not operators i.e. names and numbers). By default
53 this is =\w+=.
54 It's ok if operator names match this RE; operators always have precedence
55 over atoms.
56 2 =numbers=>qr//= - should be an RE specifying legal numbers (unquoted
57 terminals that are not operators or words). By default
58 this is =qr/[+-]?(?:\d+\.\d+|\d+\.|\.\d+|\d+)(?:[eE][+-]?\d+)?/=,
59 which matches integers and floating-point numbers. Number
60 matching always takes precedence over word matching (i.e. "1xy" will
61 be parsed as a number followed by a word. A typical usage of this option
62 is when you only want to recognise integers, in which case you would set
63 this to =numbers => qr/\d+/=.
64
65=cut
66
67
# spent 61µs within Foswiki::Infix::Parser::new which was called 3 times, avg 20µs/call: # 3 times (61µs+0s) by Foswiki::Query::Parser::new at line 41 of /var/www/foswiki11/lib/Foswiki/Query/Parser.pm, avg 20µs/call
sub new {
6833µs my ( $class, $options ) = @_;
69
70333µs my $this = bless(
71 {
72 client_class => $options->{nodeClass},
73 operators => [],
74 initialised => 0,
75 },
76 $class
77 );
78
79311µs $this->{numbers} =
80 defined( $options->{numbers} )
81 ? $options->{numbers}
82 : qr/[+-]?(\d+\.\d+|\d+\.|\.\d+|\d+)([eE][+-]?\d+)?/;
83
8434µs $this->{words} =
85 defined( $options->{words} )
86 ? $options->{words}
87 : qr/\w+/;
88
89319µs return $this;
90}
91
92=begin TML
93
94---++ ObjectMethod finish()
95Break circular references.
96
97=cut
98
99
# spent 2µs within Foswiki::Infix::Parser::finish which was called: # once (2µs+0s) by Foswiki::Search::finish at line 69 of /var/www/foswiki11/lib/Foswiki/Search.pm
sub finish {
10016µs my $self = shift;
101
102}
103
104=begin TML
105
106---++ ObjectMethod addOperator(\%oper)
107Add an operator to the parser.
108
109=\%oper= is a hash (or an object), containing the following fields:
110 * =name= - operator string
111 * =prec= - operator precedence, positive non-zero integer.
112 Larger number => higher precedence.
113 * =arity= - set to 1 if this operator is unary, 2 for binary. Arity 0
114 is legal, should you ever need it.
115 * =close= - used with bracket operators. =name= should be the open
116 bracket string, and =close= the close bracket. The existance of =close=
117 marks this as a bracket operator.
118 * =casematters== - indicates that the parser should check case in the
119 operator name (i.e. treat 'AND' and 'and' as different).
120 By default operators are case insensitive. *Note* that operator
121 names must be caselessly unique i.e. you can't define 'AND' and 'and'
122 as different operators in the same parser. Does not affect the
123 interpretation of non-operator terminals (names).
124Other fields in the hash can be used for other purposes; the parse tree
125generated by this parser will point to the hashes passed to this function.
126
127Field names in the hash starting with =InfixParser_= are reserved for use
128by the parser.
129
130=cut
131
132
# spent 208µs within Foswiki::Infix::Parser::addOperator which was called 65 times, avg 3µs/call: # 57 times (175µs+0s) by Foswiki::Query::Parser::new at line 47 of /var/www/foswiki11/lib/Foswiki/Query/Parser.pm, avg 3µs/call # 8 times (32µs+0s) by Foswiki::If::Parser::new at line 31 of /var/www/foswiki11/lib/Foswiki/If/Parser.pm, avg 4µs/call
sub addOperator {
1336540µs my ( $this, $op ) = @_;
1346556µs push( @{ $this->{operators} }, $op );
13565182µs $this->{initialised} = 0;
136}
137
138# Initialise on demand before a first parse
139
# spent 947µs within Foswiki::Infix::Parser::_initialise which was called 83 times, avg 11µs/call: # 83 times (947µs+0s) by Foswiki::Infix::Parser::parse at line 203, avg 11µs/call
sub _initialise {
1408332µs my $this = shift;
141
14283326µs return if $this->{initialised};
143
144 # Build operator lists
1453800ns my @stdOpsRE;
1463400ns my @bracketOpsRE;
14734µs foreach my $op ( @{ $this->{operators} } ) {
148
149 # Build a RE for the operator. Note that operators
150 # that end in \w are terminated with \b
1516535µs my $opre = quotemeta( $op->{name} );
1526584µs $opre .= ( $op->{name} =~ /\w$/ ) ? '\b' : '';
1536531µs if ( $op->{casematters} ) {
154421µs $op->{InfixParser_RE} = qr/$opre/;
155 }
156 else {
15761296µs $op->{InfixParser_RE} = qr/$opre/i;
158 }
1596551µs if ( defined( $op->{close} ) ) {
160
161 # bracket op
162614µs $this->{bracket_ops}->{ lc( $op->{name} ) } = $op;
163
16463µs $opre = quotemeta( $op->{close} );
16567µs $opre .= ( $op->{close} =~ /\w$/ ) ? '\b' : '';
16663µs if ( $op->{casematters} ) {
167 $op->{InfixParser_closeRE} = qr/$opre/;
168 }
169 else {
170623µs $op->{InfixParser_closeRE} = qr/$opre/i;
171 }
17264µs push( @bracketOpsRE, $op->{InfixParser_RE} );
173 }
174 else {
1755982µs $this->{standard_ops}->{ lc( $op->{name} ) } = $op;
1765930µs push( @stdOpsRE, $op->{InfixParser_RE} );
177 }
178 }
179
180 # Build regular expression of all standard operators.
181313µs $this->{standard_op_REs} = join( '|', @stdOpsRE );
182
183 # and repeat for bracket operators
18434µs $this->{bracket_op_REs} = join( '|', @bracketOpsRE );
185
186320µs $this->{initialised} = 1;
187}
188
189=begin TML
190
191---++ ObjectMethod parse($string) -> $parseTree
192Parses =$string=, calling =newLeaf= and =newNode= in the client class
193as necessary to create a parse tree. Returns the result of calling =newNode=
194on the root of the parse.
195
196Throws Foswiki::Infix::Error in the event of parse errors.
197
198=cut
199
200
# spent 33.5ms (889µs+32.6) within Foswiki::Infix::Parser::parse which was called 83 times, avg 403µs/call: # 41 times (347µs+22.2ms) by Foswiki::__ANON__[/var/www/foswiki11/lib/Foswiki/Macros/IF.pm:43] at line 34 of /var/www/foswiki11/lib/Foswiki/Macros/IF.pm, avg 549µs/call # 41 times (522µs+9.85ms) by Foswiki::Search::__ANON__[/var/www/foswiki11/lib/Foswiki/Search.pm:133] at line 132 of /var/www/foswiki11/lib/Foswiki/Search.pm, avg 253µs/call # once (20µs+566µs) by Foswiki::__ANON__[/var/www/foswiki11/lib/Foswiki/Macros/QUERY.pm:56] at line 52 of /var/www/foswiki11/lib/Foswiki/Macros/QUERY.pm
sub parse {
2018372µs my ( $this, $expr ) = @_;
2028339µs my $data = $expr;
20383185µs83947µs $this->_initialise();
# spent 947µs making 83 calls to Foswiki::Infix::Parser::_initialise, avg 11µs/call
20483420µs8331.6ms return _parse( $this, $expr, \$data );
# spent 31.6ms making 83 calls to Foswiki::Infix::Parser::_parse, avg 381µs/call
205}
206
207# Simple stack parser, after Knuth
208
# spent 31.6ms (4.87+26.8) within Foswiki::Infix::Parser::_parse which was called 83 times, avg 381µs/call: # 83 times (4.87ms+26.8ms) by Foswiki::Infix::Parser::parse at line 204, avg 381µs/call
sub _parse {
2098386µs my ( $this, $expr, $input, $term ) = @_;
210
21183205µs throw Foswiki::Infix::Error("Empty expression")
212 unless defined($expr) && $expr =~ /\S/;
213
2148340µs my @opers = ();
2158322µs my @opands = ();
216
2178329µs $input ||= \$expr;
218
21983249µs83228µs print STDERR "Parse: $$input\n" if MONITOR_PARSER;
# spent 228µs making 83 calls to Foswiki::Infix::Parser::MONITOR_PARSER, avg 3µs/call
220
# spent 19.8ms (13.4+6.36) within Foswiki::Infix::Parser::__ANON__[/var/www/foswiki11/lib/Foswiki/Infix/Parser.pm:277] which was called 83 times, avg 238µs/call: # 83 times (13.4ms+6.36ms) by Error::subs::try at line 419 of Error.pm, avg 238µs/call
try {
22183191µs while ( $$input =~ /\S/ ) {
2222462.32ms if ( $$input =~ s/^\s*($this->{standard_op_REs})// ) {
22310699µs my $opname = $1;
224106137µs106108µs print STDERR "Tok: op '$opname'\n" if MONITOR_PARSER;
# spent 108µs making 106 calls to Foswiki::Infix::Parser::MONITOR_PARSER, avg 1µs/call
225106171µs my $op = $this->{standard_ops}->{ lc($opname) };
226106140µs106120µs ASSERT( $op, $opname ) if DEBUG;
# spent 120µs making 106 calls to Assert::ASSERTS_OFF, avg 1µs/call
227106269µs106636µs _apply( $this, $op->{prec}, \@opers, \@opands );
# spent 636µs making 106 calls to Foswiki::Infix::Parser::_apply, avg 6µs/call
22810698µs push( @opers, $op );
229 }
230 elsif ( $$input =~ s/^\s*(['"])(|.*?[^\\])\1// ) {
2315852µs my $q = $1;
2325834µs my $val = $2;
2335874µs5850µs print STDERR "Tok: qs '$q'\n" if MONITOR_PARSER;
# spent 50µs making 58 calls to Foswiki::Infix::Parser::MONITOR_PARSER, avg 857ns/call
234
235 # Handle escaped characters in the string. This is where
236 # expansions such as \n are handled
237 $val =~
23858145µss/(?<!\\)\\(0[0-7]{2}|x[a-fA-F0-9]{2}|x{[a-fA-F0-9]+}|n|t|\\|$q)/eval('"\\'.$1.'"')/ge;
23958219µs58962µs push( @opands,
# spent 962µs making 58 calls to Foswiki::Query::Node::newLeaf, avg 17µs/call
240 $this->{client_class}
241 ->newLeaf( $val, $Foswiki::Infix::Node::STRING ) );
242 }
243 elsif ( $$input =~ s/^\s*($this->{numbers})// ) {
24414µs my $val = 0 + $1;
24512µs12µs print STDERR "Tok: number '$val'\n" if MONITOR_PARSER;
# spent 2µs making 1 call to Foswiki::Infix::Parser::MONITOR_PARSER
246112µs143µs push( @opands,
# spent 43µs making 1 call to Foswiki::Query::Node::newLeaf
247 $this->{client_class}
248 ->newLeaf( $val, $Foswiki::Infix::Node::NUMBER ) );
249 }
250 elsif ( $$input =~ s/^\s*($this->{words})// ) {
25181127µs8190µs print STDERR "Tok: word '$1'\n" if MONITOR_PARSER;
# spent 90µs making 81 calls to Foswiki::Infix::Parser::MONITOR_PARSER, avg 1µs/call
2528196µs my $val = $1;
25381449µs812.51ms push( @opands,
# spent 2.51ms making 81 calls to Foswiki::Query::Node::newLeaf, avg 31µs/call
254 $this->{client_class}
255 ->newLeaf( $val, $Foswiki::Infix::Node::NAME ) );
256 }
257 elsif ( $$input =~ s/^\s*($this->{bracket_op_REs})// ) {
258 my $opname = $1;
259 print STDERR "Tok: open bracket $opname\n" if MONITOR_PARSER;
260 my $op = $this->{bracket_ops}->{ lc($opname) };
261 ASSERT($op) if DEBUG;
262 _apply( $this, $op->{prec}, \@opers, \@opands );
263 push( @opers, $op );
264 push( @opands,
265 $this->_parse( $expr, $input, $op->{InfixParser_closeRE} )
266 );
267 }
268 elsif ( defined($term) && $$input =~ s/^\s*$term// ) {
269 print STDERR "Tok: close bracket $term\n" if MONITOR_PARSER;
270 last;
271 }
272 else {
273 throw Foswiki::Infix::Error( 'Syntax error', $expr, $$input );
274 }
275 }
276835.64ms831.84ms _apply( $this, 0, \@opers, \@opands );
# spent 1.84ms making 83 calls to Foswiki::Infix::Parser::_apply, avg 22µs/call
277 }
278 catch Error::Simple with {
279
280 # Catch errors thrown during the tree building process
281 throw Foswiki::Infix::Error( shift, $expr, $$input );
282833.33ms249461µs };
# spent 350µs making 83 calls to Error::catch, avg 4µs/call # spent 111µs making 83 calls to Error::subs::with, avg 1µs/call # spent 26.0ms making 83 calls to Error::subs::try, avg 313µs/call, recursion: max depth 4, sum of overlapping time 26.0ms
2838354µs throw Foswiki::Infix::Error( 'Missing operator', $expr, $$input )
284 unless scalar(@opands) == 1;
285 throw Foswiki::Infix::Error(
2868325µs 'Excess operators (' . join( ' ', map { $_->{name} } @opers ) . ')',
287 $expr, $$input )
288 if scalar(@opers);
2898339µs my $result = pop(@opands);
29083149µs83103µs print STDERR "Return " . $result->stringify() . "\n" if MONITOR_PARSER;
# spent 103µs making 83 calls to Foswiki::Infix::Parser::MONITOR_PARSER, avg 1µs/call
29183248µs return $result;
292}
293
294# Apply ops on the stack while their precedence is higher than $prec
295# For each operator on the stack with precedence >= $prec, pop the
296# required number of operands, construct a new parse node and push
297# the node back onto the operand stack.
298
# spent 2.48ms (1.91+574µs) within Foswiki::Infix::Parser::_apply which was called 189 times, avg 13µs/call: # 106 times (536µs+100µs) by Foswiki::Infix::Parser::__ANON__[/var/www/foswiki11/lib/Foswiki/Infix/Parser.pm:277] at line 227, avg 6µs/call # 83 times (1.37ms+474µs) by Foswiki::Infix::Parser::__ANON__[/var/www/foswiki11/lib/Foswiki/Infix/Parser.pm:277] at line 276, avg 22µs/call
sub _apply {
299189141µs my ( $this, $prec, $opers, $opands ) = @_;
300
3011898.21ms while (scalar(@$opers)
302 && $opers->[-1]->{prec} >= $prec
303 && scalar(@$opands) >= $opers->[-1]->{arity} )
304 {
30510658µs my $op = pop(@$opers);
30610634µs my $arity = $op->{arity};
30710619µs my @prams;
30810665µs while ( $arity-- ) {
30916388µs unshift( @prams, pop(@$opands) );
310
311 # Should never get thrown, but just in case...
31216391µs throw Foswiki::Infix::Error("Missing operand to '$op->{name}'")
313 unless $prams[0];
314 }
315106153µs106105µs if (MONITOR_PARSER) {
# spent 105µs making 106 calls to Foswiki::Infix::Parser::MONITOR_PARSER, avg 992ns/call
316 print STDERR "Apply $op->{name}("
317 . join( ', ', map { $_->stringify() } @prams ) . ")\n";
318 }
319106458µs106468µs push( @$opands, $this->{client_class}->newNode( $op, @prams ) );
# spent 468µs making 106 calls to Foswiki::Infix::Node::newNode, avg 4µs/call
320 }
321}
322
32314µs1;
324__END__