File Coverage

blib/lib/Python/Include.pm
Criterion Covered Total %
statement 21 90 23.3
branch 0 22 0.0
condition 0 9 0.0
subroutine 7 8 87.5
pod 0 1 0.0
total 28 130 21.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             # ABSTRACT: an include statement
12             #use RPerl;
13             package Python::Include;
14 1     1   6 use strict;
  1         2  
  1         30  
15 1     1   5 use warnings;
  1         12  
  1         43  
16             our $VERSION = 0.008_000;
17              
18             # [[[ OO INHERITANCE ]]]
19 1     1   5 use parent qw(Python::Component);
  1         3  
  1         18  
20 1     1   58 use Python::Component;
  1         3  
  1         45  
21              
22             # [[[ DATA TYPES ]]]
23             package Python::Include::hashref::hashref; 1;
24             package Python::Include;
25              
26             # [[[ CRITICS ]]]
27             ## no critic qw(ProhibitUselessNoCritic ProhibitMagicNumbers RequireCheckedSyscalls) # USER DEFAULT 1: allow numeric values & print op
28             ## no critic qw(RequireInterpolationOfMetachars) # USER DEFAULT 2: allow single-quoted control characters & sigils
29             ## no critic qw(ProhibitConstantPragma ProhibitMagicNumbers) # USER DEFAULT 3: allow constants
30              
31             # [[[ INCLUDES ]]]
32 1     1   6 use Perl::Types;
  1         2  
  1         351  
33             #use re 'debugcolor'; # output regex debugging info
34 1     1   7 use Champ;
  1         1  
  1         37  
35              
36             # DEV NOTE: not actually used in this class, but needed so python_preparsed_to_perl_source()
37             # can accept the same args as all Python::Component classes
38 1     1   5 use OpenAI::API;
  1         2  
  1         1850  
39              
40             # [[[ OO PROPERTIES ]]]
41             our hashref $properties = {
42             component_type => my string $TYPED_component_type = 'Python::Function',
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_source_code => my string $TYPED_python_source_code = undef,
47             python_modules => my string::arrayref $TYPED_python_modules = undef,
48             python_subcomponents => my string::arrayref $TYPED_python_subcomponents = undef,
49             python_alias => my string $TYPED_python_alias = undef,
50             python_has_parentheses => my boolean $TYPED_python_has_parentheses = undef,
51             perl_source_code => my string $TYPED_perl_source_code = undef,
52             };
53              
54             # [[[ SUBROUTINES ]]]
55              
56             # PYIN01x
57             sub python_preparsed_to_perl_source {
58             # translate a chunk of Python source code into Perl source code
59 0     0 0   { my string $RETURN_TYPE };
  0            
60 0           ( my Python::Include $self, my OpenAI::API $openai ) = @ARG;
61              
62             # DEV NOTE: this subroutine should handle most of the common types of includes in Python, as described in unofficial Python websites
63             # https://note.nkmk.me/en/python-import-usage
64             # https://realpython.com/python-import/
65              
66             # NEED UPGRADE: correctly handle all advanced functionality and edge cases, as specified in the official Python documentation
67             # https://docs.python.org/3/reference/import.html
68              
69             # error if no OpenAI API
70 0 0         if (not defined $openai) {
71 0           croak 'ERROR EPYIN010: undefined OpenAI API, croaking';
72             }
73              
74             # error or warning if no Python source code
75 0 0 0       if ((not exists $self->{python_source_code}) or
      0        
76             (not defined $self->{python_source_code}) or
77             ($self->{python_source_code} eq q{})) {
78 0           croak 'ERROR EPYIN011: non-existent or undefined or empty Python source code, croaking';
79             }
80              
81             # DEV NOTE, PYIN012: $self->{python_preparsed} not used in this class, no need to error check
82              
83             # initialize object properties
84 0           $self->{python_modules} = [undef];
85 0           $self->{python_subcomponents} = [undef];
86 0           $self->{python_alias} = undef;
87              
88 0           print 'in Python::Include::python_preparsed_to_perl_source(), have champ($self->{python_source_code}) = \'', champ($self->{python_source_code}), '\'', "\n";
89              
90             # remove all backslashes, which can only appear at the end of a line and which only signify line continuation;
91             # since \s matches \n, removal of backslashes allows all the following regexes to automatically work for multi-line includes
92 0           my string $python_source_code_clean = $self->{python_source_code};
93 0           $python_source_code_clean =~ s/\\//gxms;
94              
95             # also remove all trailing comments, because only comments on the last line are correctly matched by the regexes below
96 0           $python_source_code_clean =~ s/\#[^\n]*\n/\n/gxms;
97              
98 0           print 'in Python::Include::python_preparsed_to_perl_source(), have champ($python_source_code_clean) = \'', champ($python_source_code_clean), '\'', "\n";
99              
100             # DEV NOTE, CORRELATION PYIN000: any changes to test includes below must also be made in t/00_includes.py
101             # DEV NOTE: comments do not need to be preceded by whitespace
102             # import FOO
103             # import F.OO
104             # import F.OO# test comment
105             # import F.OO # test comment
106             # import \
107             # FOO
108 0 0         if ($python_source_code_clean =~ m/^\s*import\s+([\w\.]+)\s*(\#.*)?$/) {
    0          
    0          
    0          
    0          
    0          
    0          
109 0           print 'in Python::Include::python_preparsed_to_perl_source(), matched regex #0, have $1 = \'', $1, '\'', "\n";
110 0           $self->{python_modules} = [$1];
111 0           $self->{perl_source_code} = 'use ' . $1 . ';';
112             }
113             # DEV NOTE: commas may be either preceded or followed by optional whitespace
114             # import FOO, BAR, BAT, BAX
115             # import F.OO , B.AR, BA.T, B.A.X
116             # import F.OO,B.AR,BA.T,B.A.X# test comment
117             # import F.OO, B.AR, BA.T, B.A.X # test comment
118             # import \
119             # FOO, B.AR, \
120             # BA.T, B.A.X # test comment
121             elsif ($python_source_code_clean =~ m/^\s*import\s+((?:[\w\.]+\s*\,\s*)+[\w\.]+)\s*(\#.*)?$/) {
122 0           print 'in Python::Include::python_preparsed_to_perl_source(), matched regex #1, have $1 = \'', $1, '\'', "\n";
123 0           $self->{python_modules} = [split /\,\s+/, $1];
124 0           foreach my string $python_module (@{$self->{python_modules}}) {
  0            
125 0           $self->{perl_source_code} .= 'use ' . $python_module . '; ';
126             }
127 0           substr $self->{perl_source_code}, -2, 2, ''; # strip trailing ' ' created by for() loop above
128             }
129             # from FOO import BAR
130             # from F.OO import B.AR
131             # from F.OO import B.AR# test comment
132             # from F.OO import B.AR # test comment
133             # from \
134             # F.OO \
135             # import \
136             # B.AR # test comment
137             # from . import BAR
138             # from . import B.AR
139             # from . import B.AR# test comment
140             # from . import B.AR # test comment
141             # from \
142             # . \
143             # import \
144             # B.AR # test comment
145             elsif ($python_source_code_clean =~ m/^\s*from\s+([\w\.]+)\s+import\s+([\w\.]+)\s*(\#.*)?$/) {
146 0           print 'in Python::Include::python_preparsed_to_perl_source(), matched regex #2, have $1 = \'', $1, '\', $2 = \'', $2, '\'', "\n";
147 0           $self->{python_modules} = [$1];
148 0           $self->{python_subcomponents} = [$2];
149 0           $self->{perl_source_code} = 'use ' . $1 . ' qw(' . $2 . ');';
150             }
151             # DEV NOTE: commas may be either preceded or followed by optional whitespace
152             # from FOO import BAR, BAT, BAX
153             # from F.OO import B.AR , BA.T, B.A.X
154             # from F.OO import B.AR,BA.T,B.A.X# test comment
155             # from F.OO import B.AR, BA.T, B.A.X # test comment
156             # from \
157             # F.OO \
158             # import B.AR, \
159             # BA.T, B.A.X # test comment
160             # from FOO import \
161             # BAR, \
162             # BAT, \
163             # BAX
164             # from FOO import FU, FEW, \
165             # BAR, BHAR \
166             # ,BAT, \
167             # BAX, BHAX
168             # from F.OO import F.U, F.E.W, \
169             # B.AR, BH.AR, \
170             # BA.T, \
171             # B.A.X, B.H.A.X # test comment
172             elsif ($python_source_code_clean =~ m/^\s*from\s+([\w\.]+)\s+import\s+((?:[\w\.]+\s*\,\s*)+[\w\.]+)\s*(\#.*)?$/) {
173 0           print 'in Python::Include::python_preparsed_to_perl_source(), matched regex #3, have $1 = \'', $1, '\', $2 = \'', $2, '\'', "\n";
174 0           $self->{python_modules} = [$1];
175 0           $self->{python_subcomponents} = [split /\,\s+/, $2];
176 0           $self->{perl_source_code} = 'use ' . $1 . ' qw(' . join(' ', @{$self->{python_subcomponents}}) . ');';
  0            
177             }
178             # DEV NOTE: parentheses do not need to be surrounded by whitespace
179             # DEV NOTE: commas may be either preceded or followed by optional whitespace
180             # DEV NOTE: extra commas are allowed at the end of parentheses-enclosed lists
181             # from FOO import ( BAR, BAT, BAX )
182             # from FOO import ( B.AR , BA.T, B.A.X, )
183             # from FOO import(B.AR,BA.T,B.A.X)#test comment
184             # from FOO import ( B.AR, BA.T, B.A.X ) # test comment
185             # NEED ANSWER: how to handle comments in multi-line includes?
186             # from FOO import (
187             # BAR,
188             # BAT,
189             # BAX
190             # )
191             # from FOO import ( FU, FEW,
192             # BAR, BHAR,
193             # BAT,
194             # BAX, BHAX,
195             # )
196             # from F.OO import ( F.U, F.E.W,
197             # B.AR, BH.AR,
198             # BA.T,
199             # B.A.X, B.H.A.X
200             # )
201             # from FOO import ( \
202             # BAR, \
203             # BAT,# test comment
204             # BAX \
205             # )# test comment
206             # from FOO import ( FU, FEW, \
207             # BAR, BHAR, \
208             # BAT,
209             # BAX, BHAX \
210             # )
211             # from \
212             # F.OO \
213             # import \
214             # ( \
215             # F.U, F.E.W, \
216             # B.AR, BH.AR, \
217             # BA.T, \
218             # B.A.X, B.H.A.X \
219             # )
220             elsif ($python_source_code_clean =~ m/^\s*from\s+([\w\.]+)\s+import\s*\(\s*((?:[\w\.]+\s*\,\s*)+[\w\.]+\s*\,?)\s*\)\s*(\#.*)?$/) {
221 0           print 'in Python::Include::python_preparsed_to_perl_source(), matched regex #4, have $1 = \'', $1, '\', $2 = \'', $2, '\'', "\n";
222 0           $self->{python_modules} = [$1];
223 0           $self->{python_subcomponents} = [split /\,\s+/, $2];
224 0           $self->{perl_source_code} = 'use ' . $1 . ' qw(' . join(' ', @{$self->{python_subcomponents}}) . ');';
  0            
225             }
226             # import FOO as F
227             # import F.OO as F
228             # import F.OO as F# test comment
229             # import F.OO as F # test comment
230             # import \
231             # F.OO \
232             # as \
233             # F # test comment
234             elsif ($python_source_code_clean =~ m/^\s*import\s+([\w\.]+)\s+as\s+([\w\.]+)\s*(\#.*)?$/) {
235 0           print 'in Python::Include::python_preparsed_to_perl_source(), matched regex #5, have $1 = \'', $1, '\', $2 = \'', $2, '\'', "\n";
236 0           $self->{python_modules} = [$1];
237 0           $self->{python_alias} = $2;
238             # NEED ANSWER: does this work???
239             # NEED ANSWER: do we need the 'use FOO qw(:ALL);' below?
240             # NEED ANSWER: do we need to use Exporter below???
241             # alias a Perl package/class as another pre-existing package/class
242             # by creating a new empty package that just inherits from the other one
243             # eval(q{package F; use parent qw(FOO); use FOO qw(:ALL); 1;}); use F;
244 0           $self->{perl_source_code} = 'eval(q{package ' . $2 . '; use parent qw(' . $1 . '); use ' . $1 . ' qw(:ALL); 1;}); use ' . $2 . ';';
245             }
246             # from FOO import BAR as B
247             # from F.OO import B.AR as B
248             # from F.OO import B.AR as B# test comment
249             # from F.OO import B.AR as B # test comment
250             # from \
251             # F.OO \
252             # import \
253             # B.AR \
254             # as \
255             # B # test comment
256             elsif ($python_source_code_clean =~ m/^\s*from\s+([\w\.]+)\s+import\s+([\w\.]+)\s+as\s+([\w\.]+)\s*(\#.*)?$/) {
257 0           print 'in Python::Include::python_preparsed_to_perl_source(), matched regex #6, have $1 = \'', $1, '\', $2 = \'', $2, '\', $3 = \'', $3, '\'', "\n";
258 0           $self->{python_modules} = [$1];
259 0           $self->{python_subcomponents} = [$2];
260 0           $self->{python_alias} = $3;
261             # NEED ANSWER: does this work???
262             # alias a Perl function/variable/etc as another pre-existing function/variable/etc
263             # by creating a new typeglob and setting it to the other one
264             # use FOO; *B = *FOO::BAR;
265 0           $self->{perl_source_code} = 'use ' . $1 . '; *' . $3 . ' = *' . $1 . '::' . $2 . ';';
266             }
267             else {
268 0           croak 'ERROR EPYIN013: cannot parse unrecognized include line \'', $self->{python_source_code}, '\', croaking';
269             }
270              
271             # NEED ANSWER: is the following statement correct?
272             # Python syntax does not allow an import statement with both multiple modules and multiple subcomponents simultaneously
273 0 0 0       if (((scalar @{$self->{python_modules}}) > 1) and
  0            
274 0           ((scalar @{$self->{python_subcomponents}}) > 1)) {
275 0           croak 'ERROR EPYIN014: multiple modules and multiple subcomponents should never be parsed in a single include statement, croaking';
276             }
277              
278             # for multi-line includes, append newline character(s) to generated Perl source code,
279             # in order to match original Python line spacing
280 0           my string::arrayref $perl_source_code_lines = [split(/\n/, $self->{perl_source_code})];
281 0           my integer $line_count_perl = scalar @{$perl_source_code_lines};
  0            
282             # add 1 because beginning & ending on the same line counts as 1 line
283 0           my integer $line_count_python = ($self->{python_line_number_end} - $self->{python_line_number_begin}) + 1;
284 0           my integer $line_count_difference = $line_count_python - $line_count_perl;
285 0           print 'in Python::Include::python_preparsed_to_perl_source(), have newline-appended $line_count_difference = ', $line_count_difference, "\n";
286 0 0         if ($line_count_difference > 0) {
287 0           for (my integer $i = 0; $i < $line_count_difference; $i++) {
288 0           push @{$perl_source_code_lines}, '';
  0            
289 0           print 'in Python::Include::python_preparsed_to_perl_source(), appending newline to match Python multi-line include', "\n";
290             }
291 0           $self->{perl_source_code} = join "\n", @{$perl_source_code_lines};
  0            
292 0           print 'in Python::Include::python_preparsed_to_perl_source(), have newline-appended $self->{perl_source_code} = \'', $self->{perl_source_code}, '\'', "\n";
293             #die 'TMP DEBUG, MULTI-LINE INCLUDES';
294             }
295              
296 0           print 'in Python::Include::python_preparsed_to_perl_source(), end of subroutine, about to return $self->{perl_source_code} = \'', $self->{perl_source_code}, '\'', "\n";
297             #die 'TMP DEBUG, INCLUDES';
298              
299 0           return $self->{perl_source_code};
300             }
301              
302             1;