File Coverage

blib/lib/Math/Symbolic/Parser.pm
Criterion Covered Total %
statement 44 47 93.6
branch 11 16 68.7
condition 2 2 100.0
subroutine 8 8 100.0
pod 1 1 100.0
total 66 74 89.1


line stmt bran cond sub pod time code
1              
2             =encoding utf8
3              
4             =head1 NAME
5              
6             Math::Symbolic::Parser - Parse strings into Math::Symbolic trees
7              
8             =head1 SYNOPSIS
9              
10             use Math::Symbolic::Parser;
11             my $parser = Math::Symbolic::Parser->new();
12             $string =~ s/\s+//g;
13             my $tree = $parser->parse($string);
14            
15             # or better:
16             use Math::Symbolic;
17             my $tree = Math::Symbolic->parse_from_string($string);
18              
19             =head1 DESCRIPTION
20              
21             This module contains the parsing routines used by Math::Symbolic to
22             parse strings into Math::Symbolic trees. Usually, you will want
23             to simply use the Math::Symbolic->parse_from_string() class method
24             instead of this module directly. If you do use this module directly,
25             however, make sure to remove any whitespace from your input string.
26              
27             =head2 NOTE
28              
29             With version 0.501 of Math::Symbolic, an experimental, new parser is
30             introduced, but it is not enabled by default. The new parser is based
31             on Parse::Yapp instead of Parse::RecDescent and comes with an at least
32             ten fold speed increase. However, it has not been available for a long
33             time and is not as well tested.
34             Since version 2.00 of the Math::SymbolicX::ParserExtensionFactory module,
35             it's possible to extend Yapp parsers.
36              
37             B
38             default!> It is suggested you test your code against it before that.
39             Code that uses the RecDescent based parser's C method may
40             fail!
41              
42             Until then, you need to load it by hand as follows:
43              
44             $Math::Symbolic::Parser = Math::Symbolic::Parser->new(
45             implementation=>'Yapp'
46             );
47              
48             This replaces the default Math::Symbolic parser with an instance of the
49             new Yapp parser.
50              
51             =head2 STRING FORMAT
52              
53             The parser has been designed to parse strings that are reminiscient of
54             ordinary algebraic expressions including the standard arithmetic infix
55             operators such as multiplication. Many functions such as a rather
56             comprehensive set of trigonometric functions are parsed in prefix form
57             like 'sin(expression)' or 'log(base, expression)'. Unknown identifiers starting with a letter and containing only letters, digits, and underscores are
58             parsed as variables. If these identifiers are followed by parenthesis
59             containing a list of identifiers, the list is parsed as the signature of
60             the variable. Example: '5*x(t)' is parsed as the product of the constant five
61             and the variable 'x' which depends on 't'. These dependencies are
62             important for total derivatives.
63              
64             The supported builtin-functions are listed in the documentation for
65             Math::Symbolic::Operator in the section on the new() constructor.
66              
67             =head2 EXTENSIONS
68              
69             In version 0.503, a function named C is recognized and
70             transformed into C internally. In version 0.506, a function
71             named C was added which is transformed into C<(...)^0.5>.
72             Version 0.511 added support for the typical C syntax for
73             derivatives. For details, refer to the section on parsing
74             derivatives below.
75              
76             =head2 EXAMPLES
77              
78             # An example from analytical mechanics:
79             my $hamilton_function =
80             Math::Symbolic->parse_from_string(
81             'p_q(q, dq_dt, t) * dq_dt(q, t) - Lagrange(q, p_q, t)'
82             );
83              
84             This parses as "The product
85             of the generalized impulse p_q (which is a function of the generalized
86             coordinate q, its derivative, and the time) and the derivative of the
87             generalized coordinate dq_dt (which depends on q itself and the time).
88             This term minus the Lagrange Function (of q, the impulse, and the time)
89             is the Hamilton Function."
90              
91             Well, that's how it parses in my head anyway. The parser will generate a tree
92             like this:
93              
94             Operator {
95             type => difference,
96             operands => (
97             Operator {
98             type => product,
99             operands => (
100             Variable {
101             name => p_q,
102             dependencies => q, dq_dt, t
103             },
104             Variable {
105             name => dq_dt,
106             dependencies => q, t
107             }
108             )
109             },
110             Variable {
111             name => Lagrange,
112             dependencies => q, p_q, t
113             }
114             )
115             }
116              
117             Possibly a simpler example would be 'amplitude * sin(phi(t))' which
118             descibes an oscillation. sin(...) is assumed to be the sine function,
119             amplitude is assumed to be a symbol / variable that doesn't depend on any
120             others. phi is recognized as a variable that changes over time (t). So
121             phi(t) is actually a function of t that hasn't yet been specified.
122             phi(t) could look like 'omega*t + theta' where strictly speaking,
123             omega, t, and theta are all symbols without dependencies. So omega and theta
124             would be treated as constants if you derived them in respect to t.
125             Figuratively speaking, omega would be a frequency and theta would be a
126             initial value.
127              
128             =head2 PARSING DERIVATIVES
129              
130             The traditional way of specifying a derivative for parsing was
131             C where C
132             can be any valid expression and C is a variable name.
133             The syntax denotes a partial derivative of the expression with respect
134             to the variable. The same syntax is available for total derivatives.
135              
136             With version 0.511, a new syntax for specifying partial derivatives
137             was added to the parser(s). C denotes the first partial
138             derivative of C with respect to C. If C<(x)> is omitted,
139             C defaults to using C. C is the second order partial
140             derivative with respect to C. If there are multiple variables
141             in the parenthesis, a la C, the first variable is
142             used for the derivatives.
143              
144             =head2 EXPORT
145              
146             None by default.
147              
148             =head1 CLASS DATA
149              
150             While working with this module, you might get into the not-so-convient position
151             of having to debug the parser and/or its grammar. In order to make this
152             possible, there's the $DEBUG package variable which, when set to 1, makes
153             the parser warn which grammar elements are being processed. Note, however,
154             that their order is bottom-up, not top-down.
155              
156             =cut
157              
158             package Math::Symbolic::Parser;
159              
160 23     23   404 use 5.006;
  23         77  
  23         895  
161 23     23   119 use strict;
  23         54  
  23         669  
162 23     23   138 use warnings;
  23         59  
  23         638  
163              
164 23     23   127 use Carp;
  23         60  
  23         1559  
165              
166 23     23   145 use Math::Symbolic::ExportConstants qw/:all/;
  23         42  
  23         22456  
167              
168             #use Parse::RecDescent;
169             my $Required_Parse_RecDescent = 0;
170              
171             our $VERSION = '0.612';
172             our $DEBUG = 0;
173              
174             # Functions that are parsed and translated to specific M::S trees
175             # *by the parser*.
176             our %Parser_Functions = (
177             'exp' => sub {
178             my $func = shift;
179             my $arg = shift;
180             return Math::Symbolic::Operator->new(
181             '^',
182             Math::Symbolic::Constant->euler(),
183             $arg
184             );
185             },
186             'sqrt' => sub {
187             my $func = shift;
188             my $arg = shift;
189             return Math::Symbolic::Operator->new(
190             '^',
191             $arg,
192             Math::Symbolic::Constant->new(0.5)
193             );
194             },
195             );
196              
197             our $Grammar = <<'GRAMMAR_END';
198             parse: expr /^\Z/
199             {
200             $return = $item[1]
201             }
202             | // {undef}
203              
204             expr: addition
205             {
206             #warn 'expr ' if $Math::Symbolic::Parser::DEBUG;
207             $item[1]
208             }
209              
210             addition:
211             {
212             #warn 'addition '
213             # if $Math::Symbolic::Parser::DEBUG;
214             if (@{$item[1]} == 1) {
215             $item[1][0]
216             }
217             else {
218             my @it = @{$item[1]};
219             my $tree = shift @it;
220             while (@it) {
221             $tree = Math::Symbolic::Operator->new(
222             shift(@it), $tree, shift(@it)
223             );
224             }
225             $tree;
226             }
227             }
228              
229             add_op: '+'
230             | '-'
231              
232             multiplication:
233             {
234             #warn 'multiplication '
235             # if $Math::Symbolic::Parser::DEBUG;
236             if (@{$item[1]} == 1) {
237             $item[1][0]
238             }
239             else {
240             my @it = @{$item[1]};
241             my $tree = shift @it;
242             while (@it) {
243             $tree = Math::Symbolic::Operator->new(
244             shift(@it), $tree, shift(@it)
245             );
246             }
247             $tree;
248             }
249             }
250              
251             mult_op: '*'
252             | '/'
253              
254              
255             exp:
256             {
257             #warn 'exp ' if $Math::Symbolic::Parser::DEBUG;
258             if (@{$item[1]} == 1) {
259             $item[1][0]
260             }
261             else {
262             my @it = reverse @{$item[1]};
263             my $tree = shift @it;
264             while (@it) {
265             $tree = Math::Symbolic::Operator->new(
266             '^', shift(@it), $tree
267             );
268             }
269             $tree;
270             }
271             }
272              
273             factor: /(?:\+|-)*/ number
274             {
275             #warn 'unary_n '
276             # if $Math::Symbolic::Parser::DEBUG;
277             if ($item[1]) {
278             my @it = split //, $item[1];
279             my $ret = $item[2];
280             foreach (grep {$_ eq '-'} @it) {
281             $ret = Math::Symbolic::Operator->new('neg',$ret);
282             }
283             $ret
284             }
285             else {
286             $item[2]
287             }
288             }
289              
290             | /(?:\+|-)*/ function
291             {
292             #warn 'unary_f '
293             # if $Math::Symbolic::Parser::DEBUG;
294             if ($item[1]) {
295             my @it = split //, $item[1];
296             my $ret = $item[2];
297             foreach (grep {$_ eq '-'} @it) {
298             $ret = Math::Symbolic::Operator->new('neg',$ret);
299             }
300             $ret
301             }
302             else {
303             $item[2]
304             }
305             }
306              
307             | /(?:\+|-)*/ variable
308             {
309             #warn 'unary_v '
310             # if $Math::Symbolic::Parser::DEBUG;
311             if ($item[1]) {
312             my @it = split //, $item[1];
313             my $ret = $item[2];
314             foreach (grep {$_ eq '-'} @it) {
315             $ret = Math::Symbolic::Operator->new('neg',$ret);
316             }
317             $ret
318             }
319             else {
320             $item[2]
321             }
322             }
323              
324             | /(?:\+|-)*/ '(' expr ')'
325             {
326             #warn 'unary_expr '
327             # if $Math::Symbolic::Parser::DEBUG;
328             if ($item[1]) {
329             my @it = split //, $item[1];
330             my $ret = $item[3];
331             foreach (grep {$_ eq '-'} @it) {
332             $ret = Math::Symbolic::Operator->new('neg',$ret);
333             }
334             $ret
335             }
336             else {
337             $item[3]
338             }
339             }
340              
341             number: /([+-]?)(?=\d|\.\d)\d*(\.\d*)?([Ee]([+-]?\d+))?/
342             {
343             #warn 'number '
344             # if $Math::Symbolic::Parser::DEBUG;
345             Math::Symbolic::Constant->new($item[1])
346             }
347              
348             function: function_name '(' expr_list ')'
349             {
350             #warn 'function '
351             # if $Math::Symbolic::Parser::DEBUG;
352             my $fname = $item[1];
353             my $function;
354             if (exists($Math::Symbolic::Parser::Parser_Functions{$fname})) {
355             $function = $Math::Symbolic::Parser::Parser_Functions{$fname}->($fname, @{$item[3]});
356             die "Invalid function '$fname'!"
357             unless defined $function;
358             }
359             else {
360             $function = $Math::Symbolic::Operator::Op_Symbols{ $fname };
361             die "Invalid function '$fname'!"
362             unless defined $function;
363             $function = Math::Symbolic::Operator->new(
364             { type => $function, operands => $item[3] }
365             );
366             }
367             $function
368             }
369              
370             function_name: 'log'
371             | 'partial_derivative'
372             | 'total_derivative'
373             | 'sinh'
374             | 'cosh'
375             | 'asinh'
376             | 'acosh'
377             | 'asin'
378             | 'acos'
379             | 'atan2'
380             | 'atan'
381             | 'acot'
382             | 'sin'
383             | 'cos'
384             | 'tan'
385             | 'cot'
386             | 'exp'
387             | 'sqrt'
388              
389              
390             expr_list:
391             {
392             #warn 'expr_list '
393             # if $Math::Symbolic::Parser::DEBUG;
394             $item[1]
395             }
396              
397             variable: /[a-zA-Z][a-zA-Z0-9_]*/ /\'*/ '(' identifier_list ')'
398             {
399             #warn 'variable '
400             # if $Math::Symbolic::Parser::DEBUG;
401             my $varname = $item[1];
402             my $ticks = $item[2];
403             if ($ticks) {
404             my $n = length($ticks);
405             my $sig = $item[4] || ['x'];
406             my $dep_var = $sig->[0];
407             my $return = Math::Symbolic::Variable->new(
408             { name => $varname, signature => $sig }
409             );
410             foreach (1..$n) {
411             $return = Math::Symbolic::Operator->new(
412             'partial_derivative',
413             $return, $dep_var,
414             );
415             }
416             $return;
417             }
418             else {
419             Math::Symbolic::Variable->new(
420             { name => $varname, signature => $item[4] }
421             );
422             }
423             }
424              
425             | /[a-zA-Z][a-zA-Z0-9_]*/ /\'*/
426             {
427             #warn 'variable '
428             # if $Math::Symbolic::Parser::DEBUG;
429             my $varname = $item[1];
430             my $ticks = $item[2];
431             if ($ticks) {
432             my $n = length($ticks);
433             my $return = Math::Symbolic::Variable->new(
434             { name => $varname, signature => ['x'] }
435             );
436             foreach (1..$n) {
437             $return = Math::Symbolic::Operator->new(
438             'partial_derivative',
439             $return, 'x',
440             );
441             }
442             $return;
443             }
444             else {
445             Math::Symbolic::Variable->new( $varname );
446             }
447             }
448              
449             identifier_list:
450             {
451             #warn 'identifier_list '
452             # if $Math::Symbolic::Parser::DEBUG;
453             $item[1]
454             }
455            
456             GRAMMAR_END
457              
458              
459             =head2 Constructor new
460              
461             This constructor does not expect any arguments and returns a Parse::RecDescent
462             parser to parse algebraic expressions from a string into Math::Symbolic
463             trees.
464              
465             The constructor takes key/value pairs of options.
466              
467             You can regenerate the parser from the grammar in the scalar
468             C<$Math::Symbolic::Parser::Grammar> instead of using the (slightly faster)
469             precompiled grammar from L.
470             You can enable recompilation from the grammar with the option
471             C 1>. This only has an effect if the implementation
472             is the L based parser (which is the default).
473              
474             If you care about parsing speed more than about being able to extend the
475             parser at run-time, you can specify the C option. Currently
476             recognized are C and C implementations. C is
477             the default and C is significantly faster. The L based
478             implementation may not support all extension modules. It has been tested
479             with Math::SymbolicX::ParserExtensionFactory and Math::SymbolicX::Complex.
480              
481             =cut
482              
483             sub new {
484 27     27 1 2161 my $class = shift;
485 27         71 my %args = @_;
486              
487 27   100     224 my $impl = $args{implementation} || 'RecDescent';
488              
489 27 100       128 if ($impl eq 'RecDescent') {
    100          
490 25         142 return $class->_new_recdescent(\%args);
491             }
492             elsif ($impl eq 'Yapp') {
493 1         5 return $class->_new_yapp(\%args);
494             }
495             else {
496 1         162 croak("'implementation' must be one of RecDescent or Yapp");
497             }
498             }
499              
500             sub _new_recdescent {
501 25     25   56 my $class = shift;
502 25         1663 my $args = shift;
503              
504 25 50       116 if ( not $Required_Parse_RecDescent ) {
505 25         46 local $@;
506 25         7701 eval 'require Parse::RecDescent;';
507 25 50       1356433 croak "Could not require Parse::RecDescent. Please install\n"
508             . "Parse::RecDescent in order to use Math::Symbolic::Parser.\n"
509             . "(Error: $@)"
510             if $@;
511             }
512              
513 25         70 my $parser;
514              
515 25 100       129 if ( $args->{recompile} ) {
516 1         9 $parser = Parse::RecDescent->new($Grammar);
517 1         169612 $parser->{__PRIV_EXT_FUNC_REGEX} = qr/(?!)/;
518             }
519             else {
520 24         1501 eval 'require Math::Symbolic::Parser::Precompiled;';
521 24 50       222 if ($@) {
522 0         0 $parser = Parse::RecDescent->new($Grammar);
523 0         0 $parser->{__PRIV_EXT_FUNC_REGEX} = qr/(?!)/;
524             }
525             else {
526 24         301 $parser = Math::Symbolic::Parser::Precompiled->new();
527 24         580 $parser->{__PRIV_EXT_FUNC_REGEX} = qr/(?!)/;
528             }
529             }
530 25         228 return $parser;
531             }
532              
533             sub _new_yapp {
534 1     1   2 my $class = shift;
535 1         2 my $args = shift;
536 1         71 eval 'require Math::Symbolic::Parser::Yapp';
537 1         7 my %yapp_args;
538 1 50       7 $yapp_args{predicates} = $args->{yapp_predicates}
539             if $args->{yapp_predicates};
540 1 50       5 if ($@) {
541 0         0 croak("Could not load Math::Symbolic::Parser::Yapp. Error: $@");
542             }
543             else {
544 1         11 return Math::Symbolic::Parser::Yapp->new(%yapp_args);
545             }
546             }
547              
548             1;
549             __END__