File Coverage

lib/Spreadsheet/Engine/Fn/base.pm
Criterion Covered Total %
statement 101 103 98.0
branch 38 46 82.6
condition 20 24 83.3
subroutine 18 19 94.7
pod 7 7 100.0
total 184 199 92.4


line stmt bran cond sub pod time code
1             package Spreadsheet::Engine::Fn::base;
2              
3 28     28   176 use strict;
  28         62  
  28         1103  
4 28     28   154 use warnings;
  28         63  
  28         746  
5              
6 28     28   154 use Carp;
  28         162  
  28         2170  
7 28     28   44704 use Class::Struct;
  28         65242  
  28         194  
8 28     28   4650 use Encode;
  28         71  
  28         3257  
9              
10 28     28   13698 use Spreadsheet::Engine::Value;
  28         115  
  28         827  
11 28     28   12477 use Spreadsheet::Engine::Error;
  28         81  
  28         759  
12 28     28   12266 use Spreadsheet::Engine::Fn::Operand;
  28         73  
  28         944  
13 28         45435 use Spreadsheet::Engine::Sheet qw/ copy_function_args lookup_result_type
14             operand_value_and_type operand_as_number operand_as_text
15 28     28   175 top_of_stack_value_and_type /;
  28         65  
16              
17             struct(__PACKAGE__,
18             {
19             fname => '$',
20             operand => '$',
21             errortext => '$',
22             typelookup => '$',
23             sheetdata => '$',
24             _opstore => '@',
25             }
26             );
27              
28             sub execute {
29 18252     18252 1 25655 my $self = shift;
30              
31 18252         26380 my $result = eval {
32              
33             # make sure we check args even for functions that don't need any
34             # TODO rearrange this logic
35 18252         41588 my @foperand = $self->foperand;
36 17970         60204 $self->result;
37             };
38              
39 18252 100       521655 if ($@) {
40 1150 50       3675 die $@ unless ref $@;
41 1150         1905 $result = $@;
42 1150         2324 $result = $@;
43             }
44              
45 18252 50       45414 push @{ $self->operand }, { type => $result->type, value => $result->value }
  18252         402759  
46             if $result;
47 18252         673197 return;
48             }
49              
50             sub foperand {
51 59395     59395 1 230255 my $self = shift;
52 59395 100       740798 return $self->{_foperand} if defined $self->{_foperand};
53              
54 18252         419992 copy_function_args($self->operand, \my @foperand);
55              
56 18252 50 66     127557 if ($self->can('argument_count') or $self->can('signature')) {
57 6899         25720 my ($min_args, $max_args) =
58             $self->can('argument_count')
59             ? ($self->argument_count)
60 18252 100       86061 : (0 + @{ [ $self->signature ] });
61 18252         32703 my $have_args = scalar @foperand;
62              
63 18252 100 100     170051 if ( ($min_args < 0 and $have_args < -$min_args)
      100        
      66        
      100        
      66        
64             or ($min_args >= 0 and $have_args != $min_args)
65             or (defined $max_args and $have_args > $max_args)) {
66 282         6222 die Spreadsheet::Engine::Error->val(
67             sprintf('Incorrect arguments to function "%s". ', $self->fname),
68             );
69             }
70             }
71              
72 17970         64817 return ($self->{_foperand} = \@foperand);
73             }
74              
75 1539     1539   3813 sub _opvals { map $_->value, shift->_ops }
76              
77             sub _ops {
78 15595     15595   57146 my $self = shift;
79 15595 100       16589 if (@{ $self->_opstore } == 0) {
  15595         358620  
80 10051         70567 my $numargs = scalar @{ $self->foperand };
  10051         19313  
81 10051         28102 my @argdef = $self->signature;
82              
83 10051         15931 my @operands = ();
84              
85             # Loop over args, not defs, so that optional args don't fail
86 10051         31173 for my $sig (@argdef[ 0 .. $numargs - 1 ]) {
87 19866         21398 my $op;
88              
89 19866 100       52132 if ($sig eq 'n') { # any number - was 0
    100          
    100          
    100          
90 11765         27992 $op = $self->next_operand_as_number;
91             } elsif ($sig eq 't') { # any string - was 1
92 2150         5436 $op = $self->next_operand_as_text;
93             } elsif ($sig eq '*') { # anything at all
94 2743         5720 $op = $self->next_operand;
95             } elsif ($sig eq 'r') { # range
96             # TODO refactor this into Range object
97             # For now we'll just hijack an op
98 124         292 $op = Spreadsheet::Engine::Fn::Operand->new(
99             type => 'r',
100 124         222 value => pop @{ $self->foperand },
101             );
102 124 50       4755 die Spreadsheet::Engine::Error->val('Incorrect arguments')
103             unless $op->value->{type} eq 'range';
104             } else {
105 3084 50       7936 croak 'Missing signature value in ' . $self->fname unless $sig;
106 3084         8427 $op = $self->next_operand_as_number;
107 3084 100       75181 my @tests = (ref $sig eq 'ARRAY') ? @{$sig} : $sig;
  464         1332  
108 3084         5656 foreach my $check (@tests) {
109 3548 100       19480 if (ref $check eq 'CODE') {
    50          
110 1 50       21 die Spreadsheet::Engine::Error->val('Invalid arguments')
111             unless $check->($op->value);
112             } elsif ($check =~ /^([!<>]=?)(-?\d+)/) { # >=0 <1 etc.
113 3547         11014 my ($test, $num) = ($1, $2);
114 3547         75877 my $val = $op->value;
115 3547 100       243559 die Spreadsheet::Engine::Error->val('Invalid arguments')
116             unless eval "$val $test $num";
117             } else {
118 0         0 croak "Error in signature ($check) of " . $self->fname;
119             }
120             }
121             }
122              
123 19435 50 66     540690 die $op if $op->is_error and not $self->_error_ops_ok;
124 19435         181298 push @operands, $op;
125             }
126 9620         209117 $self->_opstore(\@operands);
127             }
128 15164         125504 return @{ $self->_opstore };
  15164         311425  
129             }
130              
131             # Usually, when extracting the operands based on the signature, any
132             # operand that is of type 'error' will cause the entire function to die
133             # with that error. Subclassing this method to return a true value will
134             # allow that error to be passed through as-is.
135              
136 0     0   0 sub _error_ops_ok { 0 }
137              
138             sub next_operand {
139 11186     11186 1 39169 my $self = shift;
140 11186         248237 my $value =
141             operand_value_and_type($self->sheetdata, $self->foperand,
142             $self->errortext, \my $tostype);
143 11186         259006 return Spreadsheet::Engine::Fn::Operand->new(
144             value => $value,
145             type => $tostype
146             );
147             }
148              
149             sub next_operand_as_number {
150 15007     15007 1 20280 my $self = shift;
151 15007         324868 my $value =
152             operand_as_number($self->sheetdata, $self->foperand, $self->errortext,
153             \my $tostype);
154 15007         377717 return Spreadsheet::Engine::Fn::Operand->new(
155             value => $value,
156             type => $tostype
157             );
158             }
159              
160             sub next_operand_as_text {
161 2150     2150 1 2409 my $self = shift;
162 2150         46794 my $value =
163             operand_as_text($self->sheetdata, $self->foperand, $self->errortext,
164             \my $tostype);
165 2150         9474 return Spreadsheet::Engine::Fn::Operand->new(
166             value => decode(utf8 => $value),
167             type => $tostype
168             );
169             }
170              
171             sub top_of_stack {
172 396     396 1 571 my $self = shift;
173 396         9177 my ($value, $type) =
174             top_of_stack_value_and_type($self->sheetdata, $self->foperand,
175             $self->errortext);
176 396 100       1049 return unless $type;
177 342         7128 return Spreadsheet::Engine::Fn::Operand->new(
178             value => $value,
179             type => $type
180             );
181             }
182              
183             sub optype {
184 8945     8945 1 47484 my ($self, $operation, @op) = @_;
185              
186 8945         197736 my $tl = $self->typelookup->{$operation};
187              
188 8945         61845 my $first = shift @op;
189 8945         181839 my $type = $first->type;
190              
191             # Check against self if no others supplied
192 8945 100 100     81251 push @op, $first if $operation eq 'oneargnumeric' and not @op;
193              
194 8945         27751 while (my $next = shift @op) {
195 9076 100       165008 $type = lookup_result_type($type, (ref $next ? $next->type : $next), $tl);
196             }
197 8945         200825 return Spreadsheet::Engine::Value->new(type => $type, value => 0);
198             }
199              
200             1;
201              
202             __END__