File Coverage

blib/lib/Data/Typed/Expression.pm
Criterion Covered Total %
statement 22 22 100.0
branch 2 4 50.0
condition n/a
subroutine 6 6 100.0
pod 1 1 100.0
total 31 33 93.9


line stmt bran cond sub pod time code
1             package Data::Typed::Expression;
2              
3 3     3   85366 use Parse::RecDescent;
  3         138879  
  3         22  
4 3     3   145 use Carp 'croak';
  3         4  
  3         189  
5              
6 3     3   15 use warnings;
  3         10  
  3         94  
7 3     3   13 use strict;
  3         4  
  3         630  
8              
9             =head1 NAME
10              
11             Data::Typed::Expression - Parsing typed expressions
12              
13             =head1 VERSION
14              
15             Version 0.005
16              
17             =cut
18              
19             our $VERSION = '0.005';
20              
21             =head1 SYNOPSIS
22              
23             use Data::Typed::Expression;
24             use Data::Typed::Expression::Env;
25            
26             my $env = Data::Typed::Expression::Env->new({
27             vertex => {
28             id => 'int',
29             lon => 'double',
30             lat => 'double'
31             },
32             arc => {
33             from => 'vertex',
34             to => 'vertex',
35             cost => 'double'
36             },
37             graph => {
38             arcs => 'arc[]',
39             vertices => 'vertex[]'
40             },
41             'int' => undef, 'double' => undef
42             }, {
43             G => 'graph',
44             i => 'int'
45             });
46             my $expr = Data::Typed::Expression->new('G.arcs[G.v[i]+1]');
47            
48             $env->validate($expr);
49            
50             =head1 DESCRIPTION
51              
52             When I was writing a LaTeX paper on mathematical model of an optimization
53             problem, I was in a need to use C-like expressions to illustrate ideas I was
54             writing about. I felt really uncomfortable beacuse I couldn't easily validate
55             the expressions I was using. Hence this module.
56              
57             The module can parse standard C expressions (or rather a small subset of them)
58             and validate them in the context of some types. Validation step checks if the
59             types of values on which artihmetics is performed are numeric, whether array
60             indices are of C type and if compund types (C-s) have components
61             referenced by the expression.
62              
63             The idea was born on this Perlmonks thread: L.
64              
65             =head1 METHODS
66              
67             =cut
68              
69              
70             =head2 new
71              
72             Creates a new expression object. The only argument is a string containing
73             expression to be parsed.
74              
75             The method dies if the expression can't be parsed (i.e. is invalid or to
76             complicated).
77              
78             Usefulness of an object itself is limited. Pass the object to e.g.
79             L to check type correctness of the expression.
80              
81             =cut
82              
83             sub new {
84 9     9 1 4412 my ($class, $str) = @_;
85 9         24 my $ast = _make_ast($str);
86 9         11953 my $self = {
87             ast => $ast
88             };
89 9         77 return bless $self, $class;
90             }
91              
92             sub _make_ast {
93 19     19   31706 my ($expr) = @_;
94 19         36 my $grammar = <<'EOT';
95              
96             {
97             sub _op {
98             if (@_ == 1) {
99             return { op => $_[0] };
100             } elsif (@_ == 2) {
101             return { op => $_[0], arg => $_[1] };
102             } else {
103             return { op => $_[0], arg => [ @_[1..$#_] ] };
104             }
105             }
106             sub _make_dot_ast {
107             my @it = @_;
108             my $tr = shift @it;
109            
110             for my $e (@it) {
111             my $op = $e->{op};
112             if ($op =~ /^[VID]$/) {
113             $tr = _op '.', $tr, $e;
114             } elsif ($op eq '[]') {
115             my $tr2 = _op '.', $tr, $e->{arg}[0];
116             $e->{arg}[0] = $tr2;
117             $tr = $e;
118             } else {
119             die "Unknown op: $op";
120             }
121             }
122            
123             $tr;
124             }
125             }
126              
127             expression: full_expr /\z/ { $item[-2] }
128              
129             full_expr:
130             expr_part expr_sep full_expr { _op $item[-2], $item[-3], $item[-1] }
131             | expr_part
132              
133             expr_part:
134             expr_noadd(s /\./) { _make_dot_ast(@{$item[1]}) }
135              
136             expr_noadd:
137             '(' full_expr ')' { $item[-2] }
138             | indexed_expr
139             | var_name
140             | const
141              
142             expr_sep: m{[-+*/]}
143              
144             indexed_expr: var_name indices { _op '[]', $item[-2], @{$item[-1]} }
145              
146             indices: index(s)
147              
148             index: '[' full_expr ']' { $item[-2] }
149              
150             var_name: /[a-zA-Z_][a-zA-Z_0-9]*/ { _op 'V', $item[-1] }
151              
152             const: int | double
153              
154             int: /(\+|-)?\d+(?![\.0-9])/ { _op 'I', $item[-1] }
155              
156             double: /(\+|-)?\d+(\.\d+)?/ { _op 'D', $item[-1] }
157              
158             EOT
159              
160 19 50       116 my $parser = Parse::RecDescent->new($grammar) or croak "Bad grammar: $!";
161 19         1293623 my $ast = $parser->expression($expr);
162 19 50       410838 defined $ast or croak "Unparseable text: $expr\n";
163 19         158 $ast;
164             }
165              
166             1;
167