File Coverage

blib/lib/Math/Project3D/Function.pm
Criterion Covered Total %
statement 44 44 100.0
branch 8 10 80.0
condition n/a
subroutine 8 8 100.0
pod 1 1 100.0
total 61 63 96.8


line stmt bran cond sub pod time code
1            
2             # See the POD documentation at the end of this
3             # document for detailed copyright information.
4             # (c) 2002-2006 Steffen Mueller, all rights reserved.
5            
6             package Math::Project3D::Function;
7            
8 2     2   10 use strict;
  2         6  
  2         73  
9 2     2   10 use warnings;
  2         4  
  2         58  
10 2     2   11 use vars qw/$VERSION/;
  2         3  
  2         112  
11            
12             $VERSION = 1.02;
13            
14 2     2   9 use Carp;
  2         4  
  2         930  
15            
16            
17             # Uncreatively named public class method "new"
18             #
19             # One of three syntaxes may be used:
20             # new(coderefs)
21             # new(param_names, expressions)
22             # new(param_names, expressions_and_coderefs_mixed)
23             # Returns a compiled anonymous subroutine
24            
25             sub new {
26 6     6 1 18 my @components = @_;
27            
28             # 'tis a class method after all, so remove the
29             # class name if necessary.
30 6 50       27 shift @components if $components[0] eq __PACKAGE__;
31            
32             # Check for the existance of plaintext components
33 6         13 my $has_uncompiled_components = 0;
34 6         13 foreach my $component (@components) {
35 8 100       35 $has_uncompiled_components++, last
36             if ref $component ne 'CODE';
37             }
38            
39 6         11 my $function;
40            
41             # Call another subroutine to do the dirty work of
42             # compiling plaintext expressions if necessary.
43 6 100       17 if ($has_uncompiled_components) {
44 5         16 my $param_names = shift @components;
45 5         22 $function = _from_uncompiled_components($param_names, \@components);
46             } else {
47            
48             # Have the function return whatever the applied components
49             # return. @components is accessible because
50             # the function in $function is a closure.
51             $function = sub {
52 25     25   10285 return map { $_->(@_) } @components;
  75         408  
53 1         5 };
54             }
55            
56 6         21 return $function;
57             }
58            
59            
60             # internal subroutine _from_uncompiled_components
61             #
62             # Does the dirty work of compiling plaintext expressions.
63             # Ugly. Very Ugly.
64             # Takes a string of parameter names (without dollar)
65             # separated by commas and an array reference to an
66             # array of components as arguments.
67             # Returns compiled function (anon sub).
68            
69             sub _from_uncompiled_components {
70             # parameter names separated by commas
71 5     5   22 my @param_names = split /,/, shift;
72 5         11 my $components = shift;
73            
74             # This var will hold the plaintext of the function we will compile
75             # later.
76 5         51 my $function_string = "sub {\n";
77            
78             # For all declared parameter names, alias the n-th function
79             # argument to a lexical of the n-th name.
80 5         20 for (my $param_no = 0; $param_no < @param_names; $param_no++ ) {
81 16         59 $function_string .= " my \$$param_names[$param_no] = \$_[$param_no];\n";
82             }
83            
84             # Return results of the applied component functions or expressions.
85 5         9 $function_string .= " return ";
86            
87 5         9 my $component_count = 0;
88            
89 5         12 foreach my $component (@$components) {
90            
91 15 100       34 if (ref $component eq 'CODE') {
92            
93             # We're a coderef. Run the anon sub associated with this
94             # component number and supply it with all function args.
95 1         4 $function_string .= "\$components[$component_count]->(\@_), ";
96            
97             } else {
98            
99             # We're a plaintext expression. Insert it, but wrap a
100             # "scalar()" around it to prevent expressions like "x,y"
101             # from screwing up the order of return values.
102 14         26 $function_string .= "scalar($component), ";
103            
104             }
105            
106 15         23 $component_count++;
107            
108             }
109            
110 5         11 $function_string .= "\n};";
111            
112             # Call yet another subroutine to make sure the number
113             # of lexicals in scope is minimal.
114 5         17 return _compile_function($components, $function_string);
115             }
116            
117            
118             # Evil evaluator subroutine _compile_function
119             #
120             # Takes an array ref of components and a function_string
121             # as arguments.
122             # Returns compiled function or croaks about evaluation errors.
123            
124             sub _compile_function {
125            
126             # We want an array for speed. Dereferencing every time is
127             # quite a bit slow IIRC.
128             # This array will be accessed by the function (closure) in
129             # order to call the precompiled component functions.
130 5     5   20 my @components = @{+shift(@_)};
  5         15  
131            
132             # Do it. Mwaha.
133 5         902 my $function = eval shift;
134 5 50       19 croak $@ if $@;
135            
136             # All went well.
137 5         21 return $function;
138             }
139            
140            
141            
142             1;
143            
144             __END__