File Coverage

blib/lib/Imager/Expr.pm
Criterion Covered Total %
statement 216 266 81.2
branch 63 96 65.6
condition 10 18 55.5
subroutine 20 23 86.9
pod 13 13 100.0
total 322 416 77.4


line stmt bran cond sub pod time code
1             package Imager::Expr;
2 6     6   212745 use 5.006;
  6         24  
3 6     6   3099 use Imager::Regops;
  6         49  
  6         1078  
4 6     6   54 use strict;
  6         10  
  6         15944  
5              
6             our $VERSION = "1.008";
7              
8             my %expr_types;
9              
10             my $error;
11              
12             sub error {
13 0 0   0 1 0 shift if UNIVERSAL::isa($_[0], 'Imager::Expr');
14 0 0       0 if (@_) {
15 0         0 $error = "@_";
16             }
17             else {
18 0         0 return $error;
19             }
20             }
21              
22             # what else?
23             my %default_constants =
24             (
25             # too many digits, better than too few
26             pi=>3.14159265358979323846264338327950288419716939937510582097494
27             );
28              
29             sub new {
30 31     31 1 429683 my ($class, $opts) = @_;
31              
32             # possibly this is a very bad idea
33 31         179 my ($type) = grep exists $expr_types{$_}, keys %$opts;
34 31 50       93 die "Imager::Expr: No known expression type"
35             if !defined $type;
36 31         112 my $self = bless {}, $expr_types{$type};
37 31         59 $self->{variables} = [ @{$opts->{variables}} ];
  31         124  
38 31 50       67 $self->{constants} = { %default_constants, %{$opts->{constants} || {}} };
  31         229  
39 31 50       130 $self->{ops} = $self->compile($opts->{$type}, $opts)
40             or return;
41 31 50       143 $self->optimize()
42             or return;
43 31 50       121 $self->{code} = $self->assemble()
44             or return;
45 31         121 $self;
46             }
47              
48             sub register_type {
49 16     16 1 49 my ($pack, $name) = @_;
50 16         73 $expr_types{$name} = $pack;
51             }
52              
53             sub type_registered {
54 1     1 1 181900 my ($class, $name) = @_;
55              
56 1         4 $expr_types{$name};
57             }
58              
59             sub _variables {
60 37     37   75 return @{$_[0]->{variables}};
  37         145  
61             }
62              
63             sub code {
64 28     28 1 87 return $_[0]->{code};
65             }
66              
67             sub nregs {
68 28     28 1 74 return $_[0]->{nregs};
69             }
70              
71             sub cregs {
72 28     28 1 52136 return $_[0]->{cregs};
73             }
74              
75             my $numre = '[+-]?(?:\d+\.?\d*|\.\d+)(?:[eE][+-]?\d+)?';
76              
77             sub numre {
78 1     1 1 4 $numre;
79             }
80              
81             # optimize the code
82             sub optimize {
83 31     31 1 110 my ($self) = @_;
84              
85 31         59 my @ops = @{$self->{ops}};
  31         96  
86              
87             # this function cannot current handle code with jumps
88 31 100       169 return 1 if grep $_->[0] =~ /^jump/, @ops;
89              
90             # optimization - common sub-expression elimination
91             # it's possible to fold this into the code generation - but it will wait
92              
93 30         46 my $max_opr = $Imager::Regops::MaxOperands;
94 30         84 my $attr = \%Imager::Regops::Attr;
95 30         46 my $foundops = 1;
96 30         83 while ($foundops) {
97 30         53 $foundops = 0;
98 30         92 my %seen;
99             my $index;
100 30         0 my @out;
101 30         66 while (@ops) {
102 188         287 my $op = shift @ops;
103 188         289 my $desc = join(",", @{$op}[0..$max_opr]);
  188         497  
104 188 50       402 if ($seen{$desc}) {
105 0         0 push(@out, @ops);
106 0         0 my $old = $op->[-1];
107 0         0 my $new = $seen{$desc};
108 0         0 for $op (@out) {
109 0         0 for my $reg (@{$op}[1..$max_opr]) {
  0         0  
110 0 0       0 $reg = $new if $reg eq $old;
111             }
112             }
113 0         0 $foundops=1;
114 0         0 last;
115             }
116 188         403 $seen{$desc} = $op->[-1];
117 188         449 push(@out, $op);
118             }
119 30         179 @ops = @out;
120             }
121             # strength reduction
122 30         66 for my $op (@ops) {
123             # reduce division by a constant to multiplication by a constant
124 188 100 66     569 if ($op->[0] eq 'div' && $op->[2] =~ /^r(\d+)/
      100        
125             && defined($self->{"nregs"}[$1])) {
126 12         22 my $newreg = @{$self->{"nregs"}};
  12         29  
127 12         19 push(@{$self->{"nregs"}}, 1.0/$self->{"nregs"}[$1]);
  12         72  
128 12         25 $op->[0] = 'mult';
129 12         34 $op->[2] = 'r'.$newreg;
130             }
131             }
132 30         182 $self->{ops} = \@ops;
133 30         102 1;
134             }
135              
136             sub assemble {
137 31     31 1 64 my ($self) = @_;
138 31         50 my $attr = \%Imager::Regops::Attr;
139 31         48 my $max_opr = $Imager::Regops::MaxOperands;
140 31         45 my @ops = @{$self->{ops}};
  31         80  
141 31         62 for my $op (@ops) {
142 194         481 $op->[0] = $attr->{$op->[0]}{opcode};
143 194         341 for (@{$op}[1..$max_opr+1]) { s/^[rpj]// }
  194         334  
  970         2191  
144             }
145 31         105 my $pack = $Imager::Regops::PackCode x (2+$Imager::Regops::MaxOperands);
146              
147 31         95 return join("", ,map { pack($pack, @$_, ) } @ops);
  194         992  
148             }
149              
150             # converts stack code to register code
151             sub stack_to_reg {
152 29     29 1 181 my ($self, @st_ops) = @_;
153 29         55 my @regstack;
154             my %nregs;
155 29         97 my @vars = $self->_variables();
156 29         92 my @nregs = (0) x scalar(@vars);
157 29         43 my @cregs;
158 29         60 my $attr = \%Imager::Regops::Attr;
159 29         48 my %vars;
160             my %names;
161 29         46 my $max_opr = $Imager::Regops::MaxOperands;
162 29         83 @vars{@vars} = map { "r$_" } 0..$#vars;
  58         253  
163              
164 29         59 my @ops;
165 29         49 for (@st_ops) {
166 362 100 66     2970 if (/^$numre$/) {
    100          
    100          
    100          
    50          
167             # combining constants makes the optimization below work
168 146 100       282 if (exists $nregs{$_}) {
169 64         127 push(@regstack, $nregs{$_});
170             }
171             else {
172 82         229 $nregs{$_} = "r".@nregs;
173 82         173 push(@regstack,"r".@nregs);
174 82         192 push(@nregs, $_);
175             }
176             }
177             elsif (exists $vars{$_}) {
178 20         64 push(@regstack, $vars{$_});
179             }
180             elsif (exists $attr->{$_} && length $attr->{$_}{types}) {
181 154 50       433 if (@regstack < $attr->{$_}{parms}) {
182 0         0 error("Imager::transform2: stack underflow on $_");
183 0         0 return;
184             }
185 154         375 my @parms = splice(@regstack, -$attr->{$_}{parms});
186 154         267 my $types = join("", map {substr($_,0,1)} @parms);
  307         785  
187 154 100       389 if ($types ne $attr->{$_}{types}) {
188 6 50 33     42 if (exists $attr->{$_.'p'} && $types eq $attr->{$_.'p'}{types}) {
189 6         12 $_ .= 'p';
190             }
191             else {
192 0         0 error("Imager::transform2: Call to $_ with incorrect types");
193 0         0 return;
194             }
195             }
196 154         198 my $result;
197 154 100       299 if ($attr->{$_}{result} eq 'r') {
198 104         185 $result = "r".@nregs;
199 104         193 push(@nregs, undef);
200             }
201             else {
202 50         86 $result = "p".@cregs;
203 50         111 push(@cregs, -1);
204             }
205 154         248 push(@regstack, $result);
206 154         614 push(@parms, "0") while @parms < $max_opr;
207 154         701 push(@ops, [ $_, @parms, $result ]);
208             #print "$result <- $_ @parms\n";
209             }
210             elsif (/^!(\w+)$/) {
211 13 50       52 if (!@regstack) {
212 0         0 error("Imager::transform2: stack underflow with $_");
213 0         0 return;
214             }
215 13         62 $names{$1} = pop(@regstack);
216             }
217             elsif (/^\@(\w+)$/) {
218 29 50       78 if (exists $names{$1}) {
219 29         70 push(@regstack, $names{$1});
220             }
221             else {
222 0         0 error("Imager::Expr: unknown storage \@$1");
223 0         0 return;
224             }
225             }
226             else {
227 0         0 error("Imager::Expr: unknown operator $_");
228 0         0 return;
229             }
230             }
231 29 50       68 if (@regstack != 1) {
232 0         0 error("stack must have only one item at end");
233 0         0 return;
234             }
235 29 50       110 if ($regstack[0] !~ /^p/) {
236 0         0 error("you must have a color value at the top of the stack at end");
237 0         0 return;
238             }
239 29         84 push(@ops, [ "ret", $regstack[0], (-1) x $max_opr ]);
240              
241 29         80 $self->{"nregs"} = \@nregs;
242 29         56 $self->{"cregs"} = \@cregs;
243              
244 29         299 return \@ops;
245             }
246              
247             sub dumpops {
248 0     0 1 0 my $result = '';
249 0         0 for my $op (@{$_[0]->{ops}}) {
  0         0  
250 0         0 $result .= "@{$op}\n";
  0         0  
251             }
252 0         0 $result;
253             }
254              
255             # unassembles the compiled code
256             sub dumpcode {
257 3     3 1 2817 my ($self) = @_;
258 3         11 my $code = $self->{"code"};
259 3         61 my $attr = \%Imager::Regops::Attr;
260 3         27 my @code = unpack("${Imager::Regops::PackCode}*", $code);
261 3         72 my %names = map { $attr->{$_}{opcode}, $_ } keys %Imager::Regops::Attr;
  159         585  
262 3         44 my @vars = $self->_variables();
263 3         8 my $result = '';
264 3         7 my $index = 0;
265 3         23 while (my @op = splice(@code, 0, 2+$Imager::Regops::MaxOperands)) {
266 15         30 my $opcode = shift @op;
267 15         32 my $name = $names{$opcode};
268 15 50       34 if ($name) {
269 15         36 $result .= "j$index: $name($opcode)";
270 15         47 my @types = split //, $attr->{$name}{types};
271 15         28 for my $parm (@types) {
272 26         37 my $reg = shift @op;
273 26         87 $result .= " $parm$reg";
274 26 100       60 if ($parm eq 'r') {
275 23 100       107 if ($reg < @vars) {
    100          
276 6         18 $result.= "($vars[$reg])";
277             }
278             elsif (defined $self->{"nregs"}[$reg]) {
279 9         33 $result .= "($self->{\"nregs\"}[$reg])";
280             }
281             }
282             }
283              
284             $result .= " -> $attr->{$name}{result}$op[-1]"
285 15 100       60 if $attr->{$name}{result};
286 15         36 $result .= "\n";
287             }
288             else {
289 0         0 $result .= "unknown($opcode) @op\n";
290             }
291 15         56 ++$index;
292             }
293              
294 3         99 $result;
295             }
296              
297             package Imager::Expr::Postfix;
298             our @ISA = qw(Imager::Expr);
299              
300             Imager::Expr::Postfix->register_type('rpnexpr');
301              
302             my %op_names = ( '+'=>'add', '-'=>'subtract', '*'=>'mult', '/' => 'div',
303             '%'=>'mod', '**'=>'pow' );
304              
305             sub compile {
306 29     29   69 my ($self, $expr, $opts) = @_;
307              
308 29         89 $expr =~ s/#.*//; # remove comments
309 29         154 my @st_ops = split ' ', $expr;
310              
311 29         68 for (@st_ops) {
312 362 100       675 $_ = $op_names{$_} if exists $op_names{$_};
313 362 100       731 $_ = $self->{constants}{$_} if exists $self->{constants}{$_};
314             }
315 29         114 return $self->stack_to_reg(@st_ops);
316             }
317              
318             package Imager::Expr::Infix;
319              
320             our @ISA = qw(Imager::Expr);
321 6     6   45 use Imager::Regops qw(%Attr $MaxOperands);
  6         10  
  6         7709  
322              
323             {
324             local @INC = @INC;
325             pop @INC if $INC[-1] eq '.';
326 6     6   7507 eval "use Parse::RecDescent;";
  6         279411  
  6         49  
327             __PACKAGE__->register_type('expr') if !$@;
328             }
329              
330             # I really prefer bottom-up parsers
331             my $grammar = <<'GRAMMAR';
332              
333             code : assigns 'return' expr
334             { $return = [ @item[1,3] ] }
335              
336             assigns : assign(s?) { $return = [ @{$item[1]} ] }
337              
338             assign : identifier '=' expr ';'
339             { $return = [ @item[1,3] ] }
340              
341             expr : relation
342              
343             relation : addition (relstuff)(s?)
344             {
345             $return = $item[1];
346             for my $op(@{$item[2]}) { $return = [ $op->[0], $return, $op->[1] ] }
347             1;
348             }
349              
350             relstuff : relop addition { $return = [ @item[1,2] ] }
351              
352             relop : '<=' { $return = 'le' }
353             | '<' { $return = 'lt' }
354             | '==' { $return = 'eq' }
355             | '>=' { $return = 'ge' }
356             | '>' { $return = 'gt' }
357             | '!=' { $return = 'ne' }
358              
359             addition : multiply (addstuff)(s?)
360             {
361             $return = $item[1];
362             # for my $op(@{$item[2]}) { $return .= " @{$op}[1,0]"; }
363             for my $op(@{$item[2]}) { $return = [ $op->[0], $return, $op->[1] ] }
364             1;
365             }
366             addstuff : addop multiply { $return = [ @item[1,2] ] }
367             addop : '+' { $return = 'add' }
368             | '-' { $return = 'subtract' }
369              
370             multiply : power mulstuff(s?)
371             { $return = $item[1];
372             # for my $op(@{$item[2]}) { $return .= " @{$op}[1,0]"; }
373             for my $op(@{$item[2]}) { $return = [ $op->[0], $return, $op->[1] ] }
374             1;
375             }
376              
377             mulstuff : mulop power { $return = [ @item[1,2] ] }
378             mulop : '*' { $return = 'mult' }
379             | '/' { $return = 'div' }
380             | '%' { $return = 'mod' }
381              
382             power : powstuff(s?) atom
383             {
384             $return = $item[2];
385             for my $op(reverse @{$item[1]}) { $return = [ @{$op}[1,0], $return ] }
386             1;
387             }
388             | atom
389             powstuff : atom powop { $return = [ @item[1,2] ] }
390             powop : '**' { $return = 'pow' }
391              
392             atom: '(' expr ')' { $return = $item[2] }
393             | '-' atom { $return = [ uminus=>$item[2] ] }
394             | number
395             | funccall
396             | identifier
397              
398             number : /[+-]?(?:\d+\.?\d*|\.\d+)(?:[eE][+-]?\d+)?/
399              
400             exprlist : expr ',' exprlist { $return = [ $item[1], @{$item[3]} ] }
401             | expr { $return = [ $item[1] ] }
402              
403             funccall : identifier '(' exprlist ')'
404             { $return = [ $item[1], @{$item[3]} ] }
405              
406             identifier : /[^\W\d]\w*/ { $return = $item[1] }
407              
408             GRAMMAR
409              
410             my $parser;
411              
412             sub init_parser {
413 0 0   0   0 if (!$parser) {
414 0         0 $parser = Parse::RecDescent->new($grammar);
415             }
416             }
417              
418             sub compile {
419 1     1   3 my ($self, $expr, $opts) = @_;
420 1 50       2 if (!$parser) {
421 1         7 $parser = Parse::RecDescent->new($grammar);
422             }
423 1         105363 my $optree = $parser->code($expr);
424 1 50       67623 if (!$optree) {
425 0         0 $self->error("Error in $expr\n");
426 0         0 return;
427             }
428              
429 1         16 @{$self->{inputs}}{$self->_variables} = ();
  1         6  
430 1         4 $self->{varregs} = {};
431 1         4 @{$self->{varregs}}{$self->_variables} = map { "r$_" } 0..$self->_variables-1;
  1         4  
  2         10  
432 1         5 $self->{"nregs"} = [ (undef) x $self->_variables ];
433 1         3 $self->{"cregs"} = [];
434 1         3 $self->{"lits"} = {};
435              
436 1         3 eval {
437             # generate code for the assignments
438 1         4 for my $assign (@{$optree->[0]}) {
  1         4  
439 1         4 my ($varname, $tree) = @$assign;
440 1 50       6 if (exists $self->{inputs}{$varname}) {
441 0         0 $self->error("$varname is an input - you can't assign to it");
442 0         0 return;
443             }
444 1         6 $self->{varregs}{$varname} = $self->gencode($tree);
445             }
446              
447             # generate the final result
448 1         5 my $result = $self->gencode($optree->[1]);
449 1 50       8 if ($result !~ /^p\d+$/) {
450 0         0 $self->error("You must return a color value");
451 0         0 return;
452             }
453 1         3 push(@{$self->{genops}}, [ 'ret', $result, (0) x $MaxOperands ])
  1         6  
454             };
455 1 50       4 if ($@) {
456 0         0 $self->error($@);
457 0         0 return;
458             }
459              
460 1         10 return $self->{genops};
461             }
462              
463             sub gencode {
464 11     11   23 my ($self, $tree) = @_;
465              
466 11 100 66     198 if (ref $tree) {
    100          
    50          
467 4         13 my ($op, @parms) = @$tree;
468              
469 4 50       14 if (!exists $Attr{$op}) {
470 0         0 die "Unknown operator or function $op";
471             }
472              
473 4         7 for my $subtree (@parms) {
474 9         30 $subtree = $self->gencode($subtree);
475             }
476 4         10 my $types = join("", map {substr($_,0,1)} @parms);
  9         27  
477              
478 4 50       69 if (length($types) < length($Attr{$op}{types})) {
479 0         0 die "Too few parameters in call to $op";
480             }
481 4 50       14 if ($types ne $Attr{$op}{types}) {
482             # some alternate operators have the same name followed by p
483 0         0 my $opp = $op."p";
484 0 0 0     0 if (exists $Attr{$opp} &&
485             $types eq $Attr{$opp}{types}) {
486 0         0 $op = $opp;
487             }
488             else {
489 0         0 die "Call to $_ with incorrect types";
490             }
491             }
492 4         8 my $result;
493 4 100       9 if ($Attr{$op}{result} eq 'r') {
494 3         5 $result = "r".@{$self->{nregs}};
  3         10  
495 3         6 push(@{$self->{nregs}}, undef);
  3         9  
496             }
497             else {
498 1         3 $result = "p".@{$self->{cregs}};
  1         3  
499 1         2 push(@{$self->{cregs}}, undef);
  1         4  
500             }
501 4         37 push(@parms, "0") while @parms < $MaxOperands;
502 4         8 push(@{$self->{genops}}, [ $op, @parms, $result]);
  4         18  
503 4         18 return $result;
504             }
505             elsif (exists $self->{varregs}{$tree}) {
506 3         10 return $self->{varregs}{$tree};
507             }
508             elsif ($tree =~ /^$numre$/ || exists $self->{constants}{$tree}) {
509 4 100       19 $tree = $self->{constants}{$tree} if exists $self->{constants}{$tree};
510              
511 4 100       11 if (exists $self->{lits}{$tree}) {
512 1         4 return $self->{lits}{$tree};
513             }
514 3         7 my $reg = "r".@{$self->{nregs}};
  3         12  
515 3         4 push(@{$self->{nregs}}, $tree);
  3         11  
516 3         9 $self->{lits}{$tree} = $reg;
517              
518 3         18 return $reg;
519             }
520             }
521              
522             1;
523              
524             __END__