File Coverage

blib/lib/Python/Function.pm
Criterion Covered Total %
statement 18 142 12.6
branch 0 52 0.0
condition 0 45 0.0
subroutine 6 9 66.6
pod 0 3 0.0
total 24 251 9.5


line stmt bran cond sub pod time code
1             #
2             # This file is part of App-PythonToPerl
3             #
4             # This software is Copyright (c) 2023 by Auto-Parallel Technologies, Inc.
5             #
6             # This is free software, licensed under:
7             #
8             # The GNU General Public License, Version 3, June 2007
9             #
10             # [[[ HEADER ]]]
11             #use RPerl;
12             package Python::Function;
13 1     1   6 use strict;
  1         4  
  1         28  
14 1     1   5 use warnings;
  1         2  
  1         50  
15             our $VERSION = 0.015_000;
16              
17             # [[[ OO INHERITANCE ]]]
18 1     1   7 use parent qw(Python::Component);
  1         2  
  1         5  
19 1     1   51 use Python::Component;
  1         4  
  1         46  
20              
21             # [[[ DATA TYPES ]]]
22             package Python::Function::hashref::hashref; 1;
23             package Python::Function;
24              
25             # [[[ CRITICS ]]]
26             ## no critic qw(ProhibitUselessNoCritic ProhibitMagicNumbers RequireCheckedSyscalls) # USER DEFAULT 1: allow numeric values & print op
27             ## no critic qw(RequireInterpolationOfMetachars) # USER DEFAULT 2: allow single-quoted control characters & sigils
28             ## no critic qw(ProhibitConstantPragma ProhibitMagicNumbers) # USER DEFAULT 3: allow constants
29              
30             # [[[ INCLUDES ]]]
31 1     1   6 use Perl::Types;
  1         1  
  1         380  
32 1     1   7 use OpenAI::API;
  1         3  
  1         1832  
33              
34             # [[[ OO PROPERTIES ]]]
35             our hashref $properties = {
36             component_type => my string $TYPED_component_type = 'Python::Function',
37             decorators => my string $TYPED_decorators = undef,
38             indentation => my string $TYPED_indentation = undef,
39             symbol => my string $TYPED_symbol = undef,
40             symbol_scoped => my string $TYPED_symbol_scoped = undef,
41             arguments => my string $TYPED_arguments = undef,
42             return_type => my string $TYPED_return_type = undef,
43             python_file_path => my string $TYPED_python_file_path = undef,
44             python_line_number_begin => my integer $TYPED_python_line_number_begin = undef,
45             python_line_number_end => my integer $TYPED_python_line_number_end = undef,
46             python_line_number_end_header => my integer $TYPED_python_line_number_end_header = undef,
47             python_source_code => my string $TYPED_python_source_code = undef,
48             python_source_code_full => my string $TYPED_python_source_code_full = undef,
49             python_preparsed => my Python::Component::arrayref $TYPED_python_preparsed = undef,
50             python_preparsed_decorators => my Python::Component::arrayref $TYPED_python_preparsed_decorators = undef,
51             perl_source_code => my string $TYPED_perl_source_code = undef,
52             perl_source_code_full => my string $TYPED_perl_source_code_full = undef,
53             };
54              
55             # [[[ SUBROUTINES & OO METHODS ]]]
56              
57             # NEED UPGRADE: check python_source_code_full to avoid repeated de-parsing, and perl_source_code_full to avoid repeated translating
58             # NEED UPGRADE: check python_source_code_full to avoid repeated de-parsing, and perl_source_code_full to avoid repeated translating
59             # NEED UPGRADE: check python_source_code_full to avoid repeated de-parsing, and perl_source_code_full to avoid repeated translating
60              
61             # PYFU00x
62             sub python_preparsed_to_python_source {
63             # return Python source code
64 0     0 0   { my string $RETURN_TYPE };
  0            
65 0           ( my Python::Function $self ) = @ARG;
66              
67             # DEV NOTE, PYFU000: $openai not used in Python-to-Python de-parse round-tripping, no need to error check
68              
69             # error or warning if no Python source code
70 0 0 0       if ((not exists $self->{python_source_code}) or
    0          
71             (not defined $self->{python_source_code})) {
72 0           croak 'ERROR EPYFU001: non-existent or undefined Python source code, croaking';
73             }
74             elsif ($self->{python_source_code} eq '') {
75 0           carp 'WARNING WPYFU001: empty Python source code';
76             }
77              
78             # error or warning if no pre-parsed components
79 0 0 0       if ((not exists $self->{python_preparsed}) or
    0          
80             (not defined $self->{python_preparsed})) {
81 0           croak 'ERROR EPYFU002: non-existent or undefined Python pre-parsed components, croaking';
82             }
83 0           elsif ((scalar @{$self->{python_preparsed}}) == 0) {
84 0           carp 'WARNING WPYFU002: empty Python pre-parsed components';
85             }
86              
87             # initialize property that will store de-parsed source code;
88             # save fully de-parsed Python source code, to avoid repeated de-parsing
89 0           $self->{python_source_code_full} = '';
90              
91             # de-parse function header decorators, if present
92 0 0 0       if ((exists $self->{python_preparsed_decorators}) and
93             (defined $self->{python_preparsed_decorators})) {
94              
95 0           foreach my Python::Component $python_preparsed_component (@{$self->{python_preparsed_decorators}}) {
  0            
96 0           print 'in Python::Function->python_preparsed_to_python_source(), function \'', $self->{symbol_scoped}, '\', de-parsing function header decorators, about to call python_preparsed_to_python_source()...', "\n";
97 0           $self->{python_source_code_full} .= $python_preparsed_component->python_preparsed_to_python_source() . "\n";
98 0           print 'in Python::Function->python_preparsed_to_python_source(), function \'', $self->{symbol_scoped}, '\', de-parsing function header decorators, ret from call python_preparsed_to_python_source()', "\n";
99             }
100             }
101              
102             # function header goes in between decorators and function body
103 0           $self->{python_source_code_full} .= $self->{python_source_code} . "\n";
104              
105             # de-parse function body
106 0           foreach my Python::Component $python_preparsed_component (@{$self->{python_preparsed}}) {
  0            
107 0           print 'in Python::Function->python_preparsed_to_python_source(), function \'', $self->{symbol_scoped}, '\', de-parsing function body, about to call python_preparsed_to_python_source()...', "\n";
108 0           $self->{python_source_code_full} .= $python_preparsed_component->python_preparsed_to_python_source() . "\n";
109 0           print 'in Python::Function->python_preparsed_to_python_source(), function \'', $self->{symbol_scoped}, '\', de-parsing function body, ret from call python_preparsed_to_python_source()', "\n";
110             }
111              
112             # remove extra trailing newline, to match original input source code
113 0           chomp $self->{python_source_code_full};
114              
115             # return de-parsed Python source code
116 0           return $self->{python_source_code_full};
117             }
118              
119              
120             # PYFU01x
121             sub python_preparsed_to_perl_source {
122             # return Perl source code
123 0     0 0   { my string $RETURN_TYPE };
  0            
124 0           ( my Python::Function $self, my OpenAI::API $openai ) = @ARG;
125              
126             # error if no OpenAI API
127 0 0         if (not defined $openai) {
128 0           croak 'ERROR EPYFU010: undefined OpenAI API, croaking';
129             }
130              
131             # DEV NOTE, PYFU011: $self->{python_source_code} not used in this translation, no need to error check
132              
133             # error or warning if no pre-parsed components
134 0 0 0       if ((not exists $self->{python_preparsed}) or
    0          
135             (not defined $self->{python_preparsed})) {
136 0           croak 'ERROR EPYFU012: non-existent or undefined Python pre-parsed components, croaking';
137             }
138 0           elsif ((scalar @{$self->{python_preparsed}}) == 0) {
139 0           carp 'WARNING WPYFU012: empty Python pre-parsed components';
140             }
141              
142             # initialize property that will store de-parsed & translated source code;
143             # save fully translated Perl source code, to avoid repeated translating
144 0           $self->{perl_source_code_full} = '';
145              
146             # de-parse & translate function header decorators, if present
147 0 0 0       if ((exists $self->{python_preparsed_decorators}) and
148             (defined $self->{python_preparsed_decorators})) {
149              
150 0           foreach my Python::Component $python_preparsed_component (@{$self->{python_preparsed_decorators}}) {
  0            
151 0           print 'in Python::Function->python_preparsed_to_perl_source(), function \'', $self->{symbol_scoped}, '\', de-parsing & translating function header decorators, about to call python_preparsed_to_perl_source()...', "\n";
152 0           $self->{perl_source_code_full} .= $python_preparsed_component->python_preparsed_to_perl_source($openai) . "\n";
153 0           print 'in Python::Function->python_preparsed_to_perl_source(), function \'', $self->{symbol_scoped}, '\', de-parsing & translating function header decorators, ret from call python_preparsed_to_perl_source()', "\n";
154             }
155             }
156              
157              
158              
159             # NEED FIX: convert $self->{return_type} from Python to Perl type system
160             # NEED FIX: convert $self->{return_type} from Python to Perl type system
161             # NEED FIX: convert $self->{return_type} from Python to Perl type system
162              
163             # sub FOO { { my footype $RETURN_TYPE }; ( my footype $arg1, my bartype $arg2 ) = @ARG;
164             # function header goes in between decorators and function body
165              
166             # function symbol AKA function name
167 0 0 0       if ((not exists $self->{symbol}) or
      0        
168             (not defined $self->{symbol}) or
169             ($self->{symbol} eq '')) {
170 0           croak 'ERROR EPYFU013a: non-existent or undefined or empty function symbol, croaking';
171             }
172 0 0 0       if ((not exists $self->{indentation}) or
173             (not defined $self->{indentation})) {
174 0           croak 'ERROR EPYFU013b: non-existent or undefined function indentation, croaking';
175             }
176 0           $self->{perl_source_code_full} .= $self->{indentation} . 'sub ' . $self->{symbol} . ' {';
177              
178             # function return type
179 0 0 0       if ((not exists $self->{return_type}) or (not defined $self->{return_type}) or ($self->{return_type} eq '')) {
      0        
180 0           carp 'WARNING WPYFU014: non-existent or undefined or empty function return type, setting to \'unknown\', carping';
181 0           $self->{return_type} = 'unknown';
182             }
183 0           $self->{perl_source_code_full} .= ' { my ' . $self->{return_type} . ' $RETURN_TYPE };';
184 0           print 'in Python::Function->python_preparsed_to_perl_source(), function \'', $self->{symbol_scoped}, '\', after translating function return type, have $self->{perl_source_code_full} = \'', $self->{perl_source_code_full}, '\'', "\n";
185              
186             # function arguments
187 0 0 0       if ((exists $self->{arguments}) and (defined $self->{arguments})) {
188 0 0         if ($self->{arguments} eq '') {
189 0           carp 'WARNING WPYFU015: defined but empty function arguments, carping';
190             }
191 0           my string::arrayref $arguments_split = [split /\s*,\s*/, $self->{arguments}];
192 0           my string::arrayref $arguments_split_perl = [];
193              
194 0 0         if ($self->isa('Python::Method')) {
195 0 0         if ((scalar @{$arguments_split}) == 0) {
  0 0          
196 0           carp 'WARNING WPYFU016: method \'', $self->{symbol_scoped}, '\', missing \'self\' argument, no arguments provided, carping';
197             }
198             elsif ($arguments_split->[0] ne 'self') {
199 0 0         if ( grep( /^self$/, @{$arguments_split} ) ) {
  0            
200             carp 'WARNING WPYFU017a: method \'', $self->{symbol_scoped},
201 0           '\', \'self\' argument present but not first argument provided, not setting data type as class name, carping';
202             }
203             else {
204             carp 'WARNING WPYFU017b: method \'', $self->{symbol_scoped},
205 0           '\', missing \'self\' argument, not present in arguments provided, carping';
206             }
207              
208 0           $self->{perl_source_code_full} .= $self->python_preparsed_to_perl_source_arguments($arguments_split, $arguments_split_perl);
209             }
210             else {
211 0 0 0       if ((not exists $self->{scope}) or (not defined $self->{scope}) or ($self->{scope} eq '')) {
      0        
212             croak 'ERROR EPYFU018: method \'', $self->{symbol_scoped},
213 0           '\', missing \'scope\' object property, croaking';
214             }
215             # we know the first argument is correctly set to 'self', because we did not trigger the `ne 'self'` check above;
216             # current scope is the name of this method's enclosing class, so that is the same as the name of the $self data type;
217             # set $self data type and use `shift` to discard 'self' from the split arguments array
218 0           push @{$arguments_split_perl}, ('my ' . $self->{scope} . ' $self');
  0            
219 0           shift @{$arguments_split};
  0            
220              
221 0           $self->{perl_source_code_full} .= $self->python_preparsed_to_perl_source_arguments($arguments_split, $arguments_split_perl);
222             }
223             }
224             else { # not a method, either a normal function or an inner function
225 0 0         if ((scalar @{$arguments_split}) > 0) {
  0            
226 0           $self->{perl_source_code_full} .= $self->python_preparsed_to_perl_source_arguments($arguments_split, $arguments_split_perl);
227             }
228             }
229             }
230              
231              
232              
233              
234             # keep Perl source code split by component, for active component checking below
235 0           my string::arrayref $perl_source_code_split = [];
236              
237             # if there are any active components (anything other than comments and whitespace and blank lines) in the function body,
238             # then record the $perl_source_code_split index of the last active component;
239             # used to place the final closing curly brace and thus maintain the original line count
240 0           my boolean $final_active_component_index = -1;
241              
242             # de-parse & translate function body
243 0           foreach my Python::Component $python_preparsed_component (@{$self->{python_preparsed}}) {
  0            
244 0           print 'in Python::Function->python_preparsed_to_perl_source(), function \'', $self->{symbol_scoped}, '\', de-parsing & translating function body, about to call python_preparsed_to_perl_source() on $python_preparsed_component = ', Dumper($python_preparsed_component), "\n";
245 0           push @{$perl_source_code_split}, $python_preparsed_component->python_preparsed_to_perl_source($openai);
  0            
246 0           print 'in Python::Function->python_preparsed_to_perl_source(), function \'', $self->{symbol_scoped}, '\', de-parsing & translating function body, ret from call to python_preparsed_to_perl_source(), received $perl_source_code_split->[-1] = \'', $perl_source_code_split->[-1], '\'', "\n";
247              
248 0 0 0       if ((not $python_preparsed_component->isa('Python::Comment')) and
      0        
249             (not $python_preparsed_component->isa('Python::Whitespace')) and
250             (not $python_preparsed_component->isa('Python::Blank'))) {
251 0           $final_active_component_index = (scalar @{$perl_source_code_split}) - 1;
  0            
252 0           print 'in Python::Function->python_preparsed_to_perl_source(), function \'', $self->{symbol_scoped}, '\', de-parsing & translating function body, updated $final_active_component_index = ', $final_active_component_index, "\n";
253             }
254              
255 0           print 'in Python::Function->python_preparsed_to_perl_source(), function \'', $self->{symbol_scoped}, '\', de-parsing & translating function body, ret from call python_preparsed_to_perl_source()', "\n";
256             }
257              
258             # close function body with curly brace, placed after the final active component in the function body,
259             # or directly after the function header if no active components in body
260 0           my string $function_end = ' } # END sub ' . $self->{symbol} . '()';
261 0 0         if ($final_active_component_index > -1) {
262             # end function after final active component in function body,
263             # with trailing comment to label closing curly brace as end of function
264 0           $perl_source_code_split->[$final_active_component_index] .= $function_end;
265             # $perl_source_code_split->[$final_active_component_index] .= ' ## ACTIVE BODY COMPONENT';
266 0           print 'in Python::Function->python_preparsed_to_perl_source(), function \'', $self->{symbol_scoped}, '\', appending closing curly brace after final active component in function body, have $perl_source_code_split->[', $final_active_component_index, '] = \'', $perl_source_code_split->[$final_active_component_index], '\'', "\n";
267             }
268             else {
269 0           $self->{perl_source_code_full} .= $function_end;
270             # $self->{perl_source_code_full} .= ' ## HEADER COMPONENT';
271 0           print 'in Python::Function->python_preparsed_to_perl_source(), function \'', $self->{symbol_scoped}, '\', appending closing curly brace after function header due to lack of any active components in function body, have $self->{perl_source_code_full} = \'', $self->{perl_source_code_full}, '\'', "\n";
272             }
273              
274             # assemble Perl source code in function body, if present
275 0 0         if ((scalar @{$perl_source_code_split}) > 0) {
  0            
276 0           $self->{perl_source_code_full} .= "\n"; # newline after function header
277 0           $self->{perl_source_code_full} .= join "\n", @{$perl_source_code_split};
  0            
278 0           print 'in Python::Function->python_preparsed_to_perl_source(), function \'', $self->{symbol_scoped}, '\' assembling generated Perl source code from non-empty function body $perl_source_code_split = ', Dumper($perl_source_code_split), "\n";
279             }
280              
281 0           print 'in Python::Function->python_preparsed_to_perl_source(), function \'', $self->{symbol_scoped}, '\', about to return $self->{perl_source_code_full} = \'', $self->{perl_source_code_full}, '\'', "\n";
282             # if ($final_active_component_index > -1) {
283             #die 'TMP DEBUG';
284             # }
285              
286             # return de-parsed & translated Perl source code
287 0           return $self->{perl_source_code_full};
288             }
289              
290              
291             # PYFU02x
292             sub python_preparsed_to_perl_source_arguments {
293             # return Perl source code for function arguments
294 0     0 0   { my string $RETURN_TYPE };
  0            
295 0           ( my Python::Function $self, my string::arrayref $arguments_split, my string::arrayref $arguments_split_perl ) = @ARG;
296 0           print 'in Python::Function->python_preparsed_to_perl_source_arguments(), function \'', $self->{symbol_scoped}, '\', top of subroutine', "\n";
297              
298             # NEED FIX: implement Python-to-Perl translation for arguments with default values provided
299             # NEED FIX: implement Python-to-Perl translation for arguments with default values provided
300             # NEED FIX: implement Python-to-Perl translation for arguments with default values provided
301              
302             # NEED FIX: handle non-positional function arguments appearing after '*' parameter delimiter
303             # NEED FIX: handle non-positional function arguments appearing after '*' parameter delimiter
304             # NEED FIX: handle non-positional function arguments appearing after '*' parameter delimiter
305              
306             # NEED FIX: handle **kwargs & other special function arguments
307             # NEED FIX: handle **kwargs & other special function arguments
308             # NEED FIX: handle **kwargs & other special function arguments
309              
310             # def __FOO__(self, *, BAR=None, var_BAR=1e-9, force_alpha="warn"):
311             # def FOO_BAR(code, extra_preargs=[], extra_postargs=[]):
312              
313 0           my string $perl_source_code = ' ( ';
314 0           foreach my string $argument_split (@{$arguments_split}) {
  0            
315              
316             # NEED UPGRADE: keep multi-line function headers as multi-line, and preserve trailing comments
317             # NEED UPGRADE: keep multi-line function headers as multi-line, and preserve trailing comments
318             # NEED UPGRADE: keep multi-line function headers as multi-line, and preserve trailing comments
319              
320             # trim trailing comments
321 0 0         if ($argument_split =~ m/^(.*)\s*(\#.*)$/) {
322 0           $argument_split = $1;
323 0           print 'in Python::Function->python_preparsed_to_perl_source_arguments(), argument \'', $1, '\', trimming trailing comment \'', $2, '\'', "\n";
324             }
325              
326             # handle argument names
327 0 0         if ($argument_split eq '*') {
    0          
328 0           push @{$arguments_split_perl}, 'my void $ASTERISK';
  0            
329             }
330             elsif ($argument_split eq '**kwargs') {
331 0           push @{$arguments_split_perl}, 'my void $KWARGS';
  0            
332             }
333             else {
334 0           push @{$arguments_split_perl}, ('my unknown $' . $argument_split);
  0            
335             }
336             }
337 0           $perl_source_code .= join ', ', @{$arguments_split_perl};
  0            
338 0           $perl_source_code .= ' ) = @ARG;';
339              
340 0           print 'in Python::Function->python_preparsed_to_perl_source_arguments(), function \'', $self->{symbol_scoped}, '\', about to return $perl_source_code = \'', $perl_source_code, '\'', "\n";
341 0           return $perl_source_code;
342             }
343              
344             1;