File Coverage

blib/lib/Python/Class.pm
Criterion Covered Total %
statement 18 88 20.4
branch 0 26 0.0
condition 0 21 0.0
subroutine 6 8 75.0
pod 0 2 0.0
total 24 145 16.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::Class;
13 1     1   8 use strict;
  1         3  
  1         27  
14 1     1   5 use warnings;
  1         6  
  1         63  
15             our $VERSION = 0.007_000;
16              
17             # [[[ OO INHERITANCE ]]]
18 1     1   7 use parent qw(Python::Component);
  1         2  
  1         9  
19 1     1   52 use Python::Component;
  1         4  
  1         48  
20              
21             # [[[ DATA TYPES ]]]
22             package Python::Class::hashref::hashref; 1;
23             package Python::Class;
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   15 use Perl::Types;
  1         2  
  1         351  
32 1     1   8 use OpenAI::API;
  1         2  
  1         981  
33              
34             # [[[ OO PROPERTIES ]]]
35             our hashref $properties = {
36             component_type => my string $TYPED_component_type = 'Python::Class',
37             indentation => my string $TYPED_indentation = undef,
38             symbol => my string $TYPED_symbol = undef,
39             symbol_scoped => my string $TYPED_symbol_scoped = undef,
40             parents => my string $TYPED_parents = undef,
41             python_file_path => my string $TYPED_python_file_path = undef,
42             python_line_number_begin => my integer $TYPED_python_line_number_begin = undef,
43             python_line_number_end => my integer $TYPED_python_line_number_end = undef,
44             python_line_number_end_header => my integer $TYPED_python_line_number_end_header = undef,
45             python_source_code => my string $TYPED_python_source_code = undef,
46             python_source_code_full => my string $TYPED_python_source_code_full = undef,
47             python_preparsed => my Python::Component::arrayref $TYPED_python_preparsed = undef,
48             perl_source_code => my string $TYPED_perl_source_code = undef,
49             };
50              
51             # [[[ SUBROUTINES & OO METHODS ]]]
52              
53             # NEED UPGRADE: check python_source_code_full to avoid repeated de-parsing, and perl_source_code_full to avoid repeated translating
54             # NEED UPGRADE: check python_source_code_full to avoid repeated de-parsing, and perl_source_code_full to avoid repeated translating
55             # NEED UPGRADE: check python_source_code_full to avoid repeated de-parsing, and perl_source_code_full to avoid repeated translating
56              
57             # PYCL00x
58             sub python_preparsed_to_python_source {
59             # return Python source code
60 0     0 0   { my string $RETURN_TYPE };
  0            
61 0           ( my Python::Class $self ) = @ARG;
62              
63             # DEV NOTE, PYCL000: $openai not used in Python-to-Python de-parse round-tripping, no need to error check
64              
65             # error or warning if no Python source code
66 0 0 0       if ((not exists $self->{python_source_code}) or
    0          
67             (not defined $self->{python_source_code})) {
68 0           croak 'ERROR EPYCL001: non-existent or undefined Python source code, croaking';
69             }
70             elsif ($self->{python_source_code} eq '') {
71 0           carp 'WARNING WPYCL001: empty Python source code';
72             }
73              
74             # error or warning if no pre-parsed components
75 0 0 0       if ((not exists $self->{python_preparsed}) or
    0          
76             (not defined $self->{python_preparsed})) {
77 0           croak 'ERROR EPYCL002: non-existent or undefined Python pre-parsed components, croaking';
78             }
79 0           elsif ((scalar @{$self->{python_preparsed}}) == 0) {
80 0           carp 'WARNING WPYCL002: empty Python pre-parsed components';
81             }
82              
83             # initialize property that will store de-parsed source code;
84             # save fully de-parsed Python source code, to avoid repeated de-parsing
85 0           $self->{python_source_code_full} = '';
86              
87             # class header goes before class body
88 0           $self->{python_source_code_full} = $self->{python_source_code} . "\n";
89              
90             # de-parse class body
91 0           foreach my Python::Component $python_preparsed_component (@{$self->{python_preparsed}}) {
  0            
92 0           print 'in Python::Class->python_preparsed_to_python_source(), class \'', $self->{symbol_scoped}, '\', de-parsing class body, about to call python_preparsed_to_python_source()...', "\n";
93 0           $self->{python_source_code_full} .= $python_preparsed_component->python_preparsed_to_python_source() . "\n";
94 0           print 'in Python::Class->python_preparsed_to_python_source(), class \'', $self->{symbol_scoped}, '\', de-parsing class body, ret from call python_preparsed_to_python_source()', "\n";
95             }
96              
97             # remove extra trailing newline, to match original input source code
98 0           chomp $self->{python_source_code_full};
99              
100             # return de-parsed Python source code
101 0           return $self->{python_source_code_full};
102             }
103              
104              
105             # PYCL01x
106             sub python_preparsed_to_perl_source {
107             # return Perl source code
108 0     0 0   { my string $RETURN_TYPE };
  0            
109 0           ( my Python::Class $self, my OpenAI::API $openai ) = @ARG;
110              
111             # error if no OpenAI API
112 0 0         if (not defined $openai) {
113 0           croak 'ERROR EPYCL010: undefined OpenAI API, croaking';
114             }
115              
116             # DEV NOTE, PYCL011: $self->{python_source_code} not used in this translation, no need to error check
117              
118             # error or warning if no pre-parsed components
119 0 0 0       if ((not exists $self->{python_preparsed}) or
    0          
120             (not defined $self->{python_preparsed})) {
121 0           croak 'ERROR EPYCL012: non-existent or undefined Python pre-parsed components, croaking';
122             }
123 0           elsif ((scalar @{$self->{python_preparsed}}) == 0) {
124 0           carp 'WARNING WPYCL012: empty Python pre-parsed components';
125             }
126              
127             # initialize property that will store de-parsed & translated source code;
128             # save fully translated Perl source code, to avoid repeated translating
129 0           $self->{perl_source_code_full} = '';
130              
131             # package Foo::Baz; use strict; use warnings; use parent qw(Foo Bar); use Foo; use Bar;
132             # class header goes before class body
133              
134             # class symbol AKA class name
135 0 0 0       if ((not exists $self->{symbol}) or
      0        
136             (not defined $self->{symbol}) or
137             ($self->{symbol} eq '')) {
138 0           croak 'ERROR EPYCL013a: non-existent or undefined or empty class symbol, croaking';
139             }
140 0 0 0       if ((not exists $self->{indentation}) or
141             (not defined $self->{indentation})) {
142 0           croak 'ERROR EPYCL013b: non-existent or undefined class indentation, croaking';
143             }
144              
145 0           my string $symbol_scoped_perl = $self->{symbol};
146 0           $symbol_scoped_perl =~ s/\./::/g; # replace Python's dot '.' scope delimiter with Perl's double colon '::' scope delimiter
147 0           $self->{perl_source_code_full} .= $self->{indentation} . 'package ' . $symbol_scoped_perl . ';';
148              
149             # NEED ANSWER: is it correct to insert the strict & warnings pragmas inside each class header like this, or should they be elsewhere?
150             # NEED ANSWER: is it correct to insert the strict & warnings pragmas inside each class header like this, or should they be elsewhere?
151             # NEED ANSWER: is it correct to insert the strict & warnings pragmas inside each class header like this, or should they be elsewhere?
152              
153             # enable strict & warnings pragmas for all classes
154 0           $self->{perl_source_code_full} .= ' use strict; use warnings;';
155              
156              
157             # NEED FIX: how to handle equal signs in the class parents?
158             # NEED FIX: how to handle equal signs in the class parents?
159             # NEED FIX: how to handle equal signs in the class parents?
160              
161             # class Foo(Bar, Bat, metaclass=Quux):
162              
163              
164             # parent classes of this class
165 0 0 0       if ((exists $self->{parents}) and (defined $self->{parents})) {
166 0 0         if ($self->{parents} eq '') {
167 0           carp 'WARNING WPYCL014: defined but empty class parents, carping';
168             }
169 0           my string::arrayref $parents_split = [split /\s*,\s*/, $self->{parents}];
170 0           my string::arrayref $parents_split_perl = [];
171 0           my string::arrayref $parents_split_perl_use = [];
172 0 0         if ((scalar @{$parents_split}) > 0) {
  0            
173 0           $self->{perl_source_code_full} .= ' use parent qw(';
174 0           foreach my string $parent_split (@{$parents_split}) {
  0            
175              
176             # NEED UPGRADE: keep multi-line class headers as multi-line, and preserve trailing comments
177             # NEED UPGRADE: keep multi-line class headers as multi-line, and preserve trailing comments
178             # NEED UPGRADE: keep multi-line class headers as multi-line, and preserve trailing comments
179              
180             # trim trailing comments
181 0 0         if ($parent_split =~ m/^(.*)\s*(\#.*)$/) {
182 0           $parent_split = $1;
183 0           print 'in Python::Class->python_preparsed_to_perl_source(), parent class \'', $1, '\', trimming trailing comment \'', $2, '\'', "\n";
184             }
185              
186 0           $parent_split =~ s/\./::/g; # replace Python's dot '.' scope delimiter with Perl's double colon '::' scope delimiter
187 0           push @{$parents_split_perl}, $parent_split;
  0            
188 0           push @{$parents_split_perl_use}, 'use ' . $parent_split . ';';
  0            
189             }
190 0           $self->{perl_source_code_full} .= join ' ', @{$parents_split_perl};
  0            
191 0           $self->{perl_source_code_full} .= ');';
192 0           $self->{perl_source_code_full} .= ' ' . join ' ', @{$parents_split_perl_use};
  0            
193             }
194             }
195              
196             # newline after class header
197 0           $self->{perl_source_code_full} .= "\n";
198              
199 0           print 'in Python::Class->python_preparsed_to_perl_source(), class \'', $self->{symbol_scoped}, '\', after translating class header, have $self->{perl_source_code_full} = \'', $self->{perl_source_code_full}, '\'', "\n";
200              
201             # de-parse & translate class body
202 0           foreach my Python::Component $python_preparsed_component (@{$self->{python_preparsed}}) {
  0            
203 0           print 'in Python::Class->python_preparsed_to_perl_source(), class \'', $self->{symbol_scoped}, '\', de-parsing & translating class body, about to call python_preparsed_to_perl_source()...', "\n";
204 0           $self->{perl_source_code_full} .= $python_preparsed_component->python_preparsed_to_perl_source($openai) . "\n";
205 0           print 'in Python::Class->python_preparsed_to_perl_source(), class \'', $self->{symbol_scoped}, '\', de-parsing & translating class body, ret from call python_preparsed_to_perl_source()', "\n";
206             }
207              
208              
209             # NEED ANSWER: is it safe to omit the customary hard-coded '1;' at the end of each Perl package, or at least the last package in a file???
210             # NEED ANSWER: is it safe to omit the customary hard-coded '1;' at the end of each Perl package, or at least the last package in a file???
211             # NEED ANSWER: is it safe to omit the customary hard-coded '1;' at the end of each Perl package, or at least the last package in a file???
212              
213              
214             # remove extra trailing newline, to match original input source code
215 0           chomp $self->{perl_source_code_full};
216              
217             # return de-parsed & translated Perl source code
218 0           return $self->{perl_source_code_full};
219             }
220              
221             1;