File Coverage

blib/lib/Preprocess/Ops.pm
Criterion Covered Total %
statement 70 148 47.3
branch 10 52 19.2
condition 0 6 0.0
subroutine 19 23 82.6
pod 7 9 77.7
total 106 238 44.5


line stmt bran cond sub pod time code
1             #!/usr/bin/perl -I/home/phil/perl/cpan/DataTableText/lib
2             #-------------------------------------------------------------------------------
3             # Preprocess ◁, ◀, ▷ and ▶ as operators in ANSI-C.
4             # Philip R Brenan at gmail dot com, Appa Apps Ltd Inc., 2020
5             #-------------------------------------------------------------------------------
6             # podDocumentation - the extra final digit in the version represents the tenth of the day
7             package Preprocess::Ops;
8             our $VERSION = 202010141;
9 1     1   696 use warnings FATAL => qw(all);
  1         8  
  1         38  
10 1     1   5 use strict;
  1         1  
  1         29  
11 1     1   5 use Carp;
  1         2  
  1         86  
12 1     1   518 use Data::Dump qw(dump);
  1         7701  
  1         64  
13 1     1   3618 use Data::Table::Text qw(:all !trim);
  1         135754  
  1         1820  
14 1     1   12 use feature qw(say current_sub);
  1         2  
  1         155  
15 1     1   8 use utf8;
  1         3  
  1         7  
16              
17             my $logC = q(/home/phil/c/z/z/zzz.c); # Log to this file if present
18             my $logH = q(/home/phil/c/z/z/zzz.h);
19              
20             binModeAllUtf8; # Helps with debugging unicode characters
21              
22             #D1 Preprocess # Preprocess ◁, ◀, ▷ and ▶ as operators in ANSI-C.
23              
24             sub trimComment($) #P Remove trailing white space and comment
25 6     6 1 13 {my ($s) = @_; # String
26 6         65 $s =~ s(\s*//.*\n) ()r;
27             }
28              
29             sub method($) #P Check whether a line of C code defines a method, returning (return, name, flags, comment) if it is, else ()
30 36     36 1 62 {my ($line) = @_; # Line of C code
31 36 50       81 return () if $line =~ m(test.*//T\S); # Tests are never methods
32 36 100       79 if ($line =~ m(\Astatic\s*(.*?)((?:\w|\$)+)\s+//(\w*)\s*(.*)\Z)) # Static function is always a method
33 2         13 {return ($1, $2, $3, $4)
34             }
35 34 100       75 if ($line =~ m(\A(.*?)(new(?:\w|\$)+)\s+//(\w*)\s*(.*)\Z)) # Constructor is always a method
36 1         6 {return ($1, $2, $3, $4);
37             }
38             ()
39 33         64 }
40              
41             sub structure($) #P Check whether a line of C code defines a structure, returning (name, flags, comment) if it is, else ()
42 36     36 1 60 {my ($line) = @_; # Line of C code
43              
44 36 100       86 if ($line =~ m(\A(typedef\s+)?struct\s+((?:\w|\$)+)\s*//(w*)\s*(.*)\Z)) # struct name, comment start, flags, comment
45 1         21 {return ($2, $3, $4)
46             }
47             ()
48 35         68 }
49              
50             sub mapCode($) #P Find the structures and methods defined in a file
51 0     0 1 0 {my ($file) = @_; # Input file
52              
53 0         0 my %methods; # Method descriptions
54             my %structures; # Structures defined
55              
56 0         0 my @code = readFile($file); # Read input file
57 0         0 for my $line(@code) # Index of each line
58 0 0       0 {next if $line =~ m(\A//); # Ignore comment lines
59              
60 0         0 my ($return, $name, $flags, $comment) = method($line); # Parse function return, name, description comment
61 0 0       0 if ($name)
62 0         0 {$methods{$name}++ # Return type
63             }
64             else
65 0         0 {my ($name, $flags, $comment) = structure($line); # Parse structure definition
66 0 0       0 if ($name)
67 0         0 {$structures{$name}++
68             }
69             }
70             }
71              
72 0         0 genHash(q(PreprocessOpsMap), # Methods and structures in the C file being preprocessed
73             methods => \%methods, # Methods.
74             structures => \%structures, # Structure definitions.
75             );
76             }
77              
78             sub printData($$) #P Print statement
79 0     0 1 0 {my ($lineNumber, $line) = @_; # Code line number, code line
80              
81 0         0 my ($command, @w) = split m/\s+/, $line; # Parse print line
82 0         0 my @f;
83 0         0 for my $w(@w) # Each variable to be printed
84 0 0       0 {push @f, join ' ', $w, "=", $w =~ m((\A|\.|\->)[i-n]) ? "%lu" : "%s";
85             }
86 0         0 my $f = join " ", @f;
87 0         0 my $w = join ", ", @w;
88 0         0 my $l = $lineNumber + 1;
89 0         0 qq(fprintf(stderr, "Line $l: $f\\n", $w);\n);
90             }
91              
92             sub duplicateFunction($$$) #P Duplicate the previous function with the specified changes applied
93 0     0 1 0 {my ($lineNumber, $inputFile, $code) = @_; # Line number of line being expanded, file containing line being expanded, lines of code
94 0 0       0 if ($$code[$lineNumber] =~ m(\A(duplicate)\s+)) # Parse duplicate statement: the words after are comma separated lists of regular expressions that change the text of the preceding function
95 0         0 {my ($command, @changes) = split /\s+/, $$code[$lineNumber];
96 0         0 my @c;
97 0         0 for(my $i = $lineNumber - 1; $i >= 0; --$i) # Text of preceding function to duplicate
98 0         0 {unshift @c, my $c = $$code[$i];
99 0 0       0 last if $c =~ m(\A\S);
100             }
101 0         0 my @r; # Resulting code
102 0         0 for my $change(@changes) # Apply changes
103 0         0 {my @C; # Code after each change
104 0         0 for my $c(@c) # Each change
105 0         0 {local $_ = $c;
106 0         0 for my $r(split/,/, $change) # Each re in the change
107 0         0 {eval $r;
108 0 0       0 confess "Cannot make change: $r in: $change\n$@\n" if $@;
109             }
110 0         0 push @C, $_; # Save accumulated changes
111             }
112              
113 0         0 my $l = $lineNumber + 1; # Save duplicate code with accumulated changes
114 0         0 push @r, join '', @C;
115 0         0 push @r, qq(#line $l "$inputFile"\n);
116             }
117              
118 0         0 my $r = join '', @r; # Changed code
119 0         0 return $r;
120             }
121              
122 0         0 confess $$code[$lineNumber]," is not a 'duplicate' command";
123             }
124              
125             sub includeFile($$$$$) #P Expand include files so that we can pull in code and structures from other files in the includes folder.
126 0     0 1 0 {my ($lineNumber, $inputFile, $cFile, $hFile, $code) = @_; # Line number of line being expanded, file containing line being expanded, output C file, output H file, line of code
127 0 0       0 if ($code =~ m(\A(include)\s+)) # Parse preprocessor statement
128 0         0 {my ($command, $relFile, @items) = split /\s+/, $code;
129 0         0 my %items = map {$_=>1} @items;
  0         0  
130 0         0 my $file = sumAbsAndRel($inputFile, $relFile);
131 0 0       0 -e $file or confess "Cannot find include file: $file\n";
132              
133 0         0 my @code = readFile($file);
134             # my $map = mapCode($inputFile);
135              
136 0         0 for(my $i = 0; $i < @code; ++$i) # Expand duplicate commands
137 0 0       0 {if ($code[$i] =~ m(\Aduplicate )) # Duplicate the previous function with changes
138 0         0 {$code[$i] = duplicateFunction($i, $inputFile, \@code);
139             }
140             }
141              
142 0         0 my @c;
143 0         0 for(my $i = 0; $i < @code; ++$i) # Expand exports/include commands in included file
144 0         0 {my $c = $code[$i]; # With trailing comment
145 0         0 my $d = $c =~ s(//.*\Z) ()gsr; # Without trailing comment
146 0 0 0     0 if ($c =~ m(\Ainclude)) # Expand include files so that we can pull in code and structures from other files in the includes folder.
    0          
    0          
147 0         0 {push @c, &includeFile($i, $file, $cFile, $hFile, $d);
148             }
149             elsif ($c =~ m(\Aexports\s)) # Add exports from included package if named in the include list
150 0         0 {my ($command, $name, @exports) = split m/\s+/, $d; # Export command, list name, exports in list
151 0 0       0 if ($items{qq(:$name)}) # Requested this list
152 0         0 {for my $e(@exports) # Add exports unless they have been excluded
153 0 0       0 {$items{$e} ++ unless $items{qq(!$e)};
154             }
155             }
156             }
157             elsif (method($c) or structure($c)) # Method or structure definition
158 0 0       0 {if ($c =~ m((\S+)\s*//)) # Method or structure name
159 0         0 {my $item = $1;
160 0 0 0     0 if ($command =~ m(include) && $items {$item}) # Include specifies the exact name of the thing we want
161 0         0 {push @c, join ' ', "#line", $i+2, qq("$file"), "\n";
162 0         0 my @l;
163 0         0 for(; $i < @code; ++$i)
164 0         0 {push @l, $code[$i];
165 0 0       0 last if $code[$i] =~ m(\A })
166             }
167 0 0       0 if (@l) # Save included struct or method
168 0         0 {$l[0] =~ s(//) (//I); # Mark as included
169             # $l[0] =~ s/\Astatic /static __attribute__ ((unused)) /; # Mark included methods as potentially unused
170 0         0 push @c, @l;
171             }
172             }
173             }
174             }
175             }
176 0         0 my $l = $lineNumber + 2; # Adjust line numbers to reflect unexpanded source
177 0         0 return join '', @c, qq(#line $l "$inputFile"\n);
178             # return join '', @c;
179             }
180 0         0 confess "Unable to parse include statement:\n$code";
181             } # includeFile
182              
183             sub c($$$;$) # Preprocess ▷ and ▶ as method dispatch operators in ANSI-C.
184             {my ($inputFile, $cFile, $hFile, $column) = @_; # Input file, C output file, H output file, optional start column for comments (80)
185              
186             my $baseFile = fn $inputFile; # The base name of the file
187             my($shortBaseFile)= split /_/, $baseFile; # Base name of the file preceding first underscore
188             my $packageName = ucfirst $baseFile; # The package name which is used to replace $
189             my $commentColumn = ($column // 80) - 1; # Column in which to start comments
190              
191             my %methods; # Method descriptions
192             my %structures; # Structures defined
193             my %structureParameters; # Structures used as parameters
194             my %testsFound; # Tests found
195             my %testsNeeded; # Tests needed
196             my @forwards; # Forward declarations of functions used as methods
197             my @code = readFile($inputFile); # Read code
198             my %exports; # Export statements encountered
199              
200             for my $i(keys @code) # Execute preprocessor commands found in the source
201             {my $c = $code[$i];
202             if ($c =~ m(\A(include)\s+)) # Expand include files so that we can pull in code and structures from other files in the includes folder.
203             {$code[$i] = includeFile($i, $inputFile, $cFile, $hFile, $c);
204             }
205             elsif ($c =~ m(\Aduplicate )) # Duplicate the previous function with changes
206             {$code[$i] = duplicateFunction($i, $inputFile, \@code);
207             }
208             elsif ($c =~ m(\A(exports)\s+)) # Skip export commands in open source
209             {$exports{$c} = $i+1;
210             $code[$i] = "\n";
211             }
212             elsif ($c =~ m(\Aprint)) # Expand print statements
213             {$code[$i] = printData($i, $c);
214             }
215             }
216              
217             @code = map {"$_\n"} split /\n/, join '', @code; # Resplit code plus any additions into lines
218              
219             my sub expand($) # Expand $ and @
220             {$_[0] =~ s(\$\$) ($baseFile)gs; # $$ is base file name with first char lower cased
221             $_[0] =~ s(\$) ($packageName)gs; # $ is base file name with first character uppercased
222             $_[0] =~ s(\@) (${shortBaseFile}_)gs; # @ is translated to short base file name followed by underscore to support Gnome naming conventions
223             }
224              
225             expand($_) for @code; # Replace $ with package name.
226              
227             if (1) # Parse source code
228             {my %duplicates; my @duplicates; # Duplication check for first parameter plus short method name
229             for my $i(keys @code) # Index of each line
230             {my $line = $code[$i];
231             next if $line =~ m(\A//); # Ignore comment lines
232              
233             my ($return, $name, $flags, $comment) = method($line); # Parse function return, name, description comment
234             if ($name)
235             {$methods{$name}{return} = $return; # Return type
236             $methods{$name}{flags} = {map {$_=>1} split //, $flags}; # Flags after comment start
237             $methods{$name}{comment} = $comment; # Comment
238             ($methods{$name}{name}) = split /_/, $name; # Short name as used after call operator
239             push @forwards, join ' ', trimComment($line); # Save function definition for forward declaration
240              
241             if ($line !~ m(\(void\))) # Parse parameter definitions if there are any
242             {for $i($i+1..$#code) # Parameter definitions
243             {$line = $code[$i];
244             if ($line =~ m(\A\s*[(]?(.*?)\s*(\w+)[,)]\s*//\s*(.*)\Z)) # Variable: Get type, parameter name, comment
245             {push $methods{$name}{parameters}->@*, [$1, $2, $3];
246             }
247             elsif ($line =~ m(\A\s*(.*?)\s*\(\*(\s*(const)?\s*\w+)\)\s*(.*?)[,\)]\s*//\s*(.*)\Z)) # Function: Get type, parameter name, comment
248             {push $methods{$name}{parameters}->@*, ["$1 (*$2) $4", $2, $5];
249             }
250             elsif ($line =~ m(\A\s*\.\.\.\)\s*//\s*(.*)\Z)) # Variadic list
251             {push $methods{$name}{parameters}->@*, ["", "...", $1];
252             }
253              
254             push @forwards, trimComment($line); # Save function definition for forward declaration
255             last if $line =~ m(\)\s*//); # End of parameter list
256             }
257             }
258              
259             $forwards[-1] .= ';'; # Terminate forward declaration
260             if (my $o = $methods{$name}{structure} = $methods{$name}{parameters}[0][0]) # Structure parameter
261             {$o =~ s((\A|\s+)const\s+) ()g; # Remove const from structure name
262             $o =~ s([*]) ()g; # Remove pointer from structure name
263             $o = nws($o); # Normalize white space
264             $structureParameters{$o}{$name}++; # Record methods in each structure
265             if (my $d = $duplicates{"$name$o"}) # Check for duplicate
266             {push @duplicates, [$name, $o, $i, $d]; # Record duplicate
267             }
268             $duplicates{"$name$o"} = $i;
269             }
270             }
271             if (1)
272             {my ($name, $flags, $comment) = structure($line); # Parse structure definition
273             if ($name)
274             {$structures{$name} = genHash(q(PreprocessOpsStruct), # Structure declaration
275             name => $name, # Name of structure
276             flags => $flags, # Flags for structure
277             comment => $comment); # Comment for structure
278             }
279             }
280             }
281             if (@duplicates) # Print duplicates
282             {for my $i(keys @code) # Index of each line
283             {my $line = $code[$i];
284             say STDERR sprintf("%06d %s\n", $i, $line);
285             }
286             confess join "\n", "Duplicates:", dump(\@duplicates);
287             }
288             if (1) # Locate tests for each method
289             {my %m; # Methods that need tests
290             for my $m(sort keys %methods)
291             {my $flags = $methods{$m}{flags}; # Flags for method
292             next if $$flags{I} or $$flags{P}; # Ignore private methods marked with P and included methods marked with I
293             $testsNeeded{$methods{$m}{name}}++;
294             }
295              
296             for my $l(@code) # Each code line
297             {my @t = $l =~ m((//T\w+))g; # Tests located
298             for my $t(@t) # Each test marker //T...
299             {my $n = $t =~ s(\A//T) ()r; # Remove //T
300             delete $testsNeeded{$n}; # Test found for this method
301             $testsFound{$n}++; # The tests we have found
302             }
303             }
304              
305             if (keys %testsNeeded) # Report methods that need tests
306             {lll "The following methods need tests:\n", join "\n", sort keys %testsNeeded;
307             }
308             }
309             }
310              
311             if (1) # Write prototypes
312             {my @h; # Generated code
313             for my $struct(sort keys %structures) # Each structure
314             {push @h, "struct ProtoTypes_$struct {"; # Start structure
315             for my $m(sort keys $structureParameters{$struct}->%*) # Methods in structure
316             {my $method = $methods{$m}; # Method
317             my $s = join '', ' ', $$method{return}, ' (*', $$method{name}, ')('; # Start signature
318             my $t = join ' ', pad($s, $commentColumn), '//', $$method{comment};
319             push @h, $t;
320             my @p = $$method{parameters}->@*; # Parameters for method
321             for my $i(keys @p) # Each parameter
322             {my ($return, $name, $comment) = $p[$i]->@*;
323              
324             my $cc = $commentColumn; # Comment column
325             my $comma = $i == $#p ? ');' : ','; # Comma as separator
326             my $Comment = "// $comment"; # Format comment
327             my $off = " " x 4;
328             if ($return =~ m(\(*\s*(const)?\s*\w+\))) # Function parameter
329             {push @h, join ' ', pad(qq($off$return$comma), $cc), $Comment;
330             }
331             else # Variable parameter
332             {push @h, join ' ', pad(qq($off$return $name$comma), $cc), $Comment;
333             }
334             }
335             }
336             push @h, join '', " } const ProtoTypes_", $struct, ' ='; # Load prototype
337             my $j = join(', ', sort keys $structureParameters{$struct}->%*); # List of functions
338             push @h, join '', "{$j};";
339             push @h, <
340             $struct new$struct($struct allocator) {return allocator;}
341             END
342             }
343             owf($hFile, join "\n", @forwards, @h, '');
344             }
345              
346             if (1) # Preprocess input C file
347             {my $e = q([a-z0-9𝗮-𝘇\$_>.*-]);
348             for my $c(@code) # Source code lines
349             {$c =~ s{($e+)\s*◀\s*(.*?);} {typeof($2) $1 = $2;}gis; # Variable creation
350             $c =~ s{($e+)\s*◁\s*(.*?);} {const typeof($2) $1 = $2;}gis; # Constant creation
351              
352             $c =~ s{new\s*(\w+\s*)\(([^:)]*:[^)]*)\)} # Constructor with named arguments in parenthesis based on: https://gcc.gnu.org/onlinedocs/gcc-10.2.0/gcc/Designated-Inits.html#Designated-Inits
353             {new$1(({struct $1 t = {$2, proto: &ProtoTypes_$1}; t;}))}gs;
354              
355             $c =~ s{new\s*(\w+\s*)(\(\))?([,;])} # Constructor followed by [,;] calls for default constructor.
356             {new$1(({struct $1 t = {proto: &ProtoTypes_$1}; t;}))$3}gs;
357              
358             $c =~ s{($e+)\s*▶\s*(\w+)\s*\(} {$1->proto->$2($1, }gis; # Method call with arguments
359             $c =~ s{($e+)\s*▶\s*(\w+)} {$1->proto->$2($1)}gis; # Method call with no arguments
360             $c =~ s{($e+)\s*▷\s*(\w+)\s*\(} {$1.proto->$2(&$1, }gis; # Method call with arguments
361 1     1   4240 $c =~ s{($e+)\s*▷\s*(\w+)} {$1.proto->$2(&$1)}gis; # Method call with no arguments
  1         3  
  1         18  
362              
363             $c =~ s{✓([^;]*)} {assert($1)}gis; # Tick becomes assert
364              
365             $c =~ s( +\Z) ()gs; # Remove trailing spaces at line ends
366             }
367             }
368              
369             if (1) # Preprocess input C file here documents
370             {my $state; # True if we are in a here document
371             for(my $i = 0; $i < @code; ++$i) # Each start line
372             {my $c = $code[$i];
373             if ($c =~ m(\A◉)) # End of here document
374             {if ($state) {$code[$i] = "$state\n";} # In here document
375             else # No current here document
376             {my $j = $i + 1;
377             lll "No opening here document for closure on line: $j";
378             }
379             $state = undef;
380             }
381             elsif ($state) # In here document
382             {chomp($c);
383             $code[$i] = qq("$c\\n"\n);
384             }
385             elsif ($code[$i] =~ s(\s*◉(.*)\Z)()) {$state = $1} # Start here document
386             }
387             }
388              
389             if (1) # Report export requests for methods that are missing
390             {my @m;
391             for my $x(sort keys %exports)
392             {my ($command, $list, @e) = split /\s+/, $x;
393              
394             for my $e(@e)
395             {expand($e);
396             next unless $e =~ m(\A[a-z])i;
397             push @m, [$exports{$x}, $e] unless $methods{$e} or $structures{$e};
398             }
399             }
400             if (keys @m)
401             {say STDERR formatTable(\@m, <
402             Line Line on which the missing method was exported
403             Export Method requested but missing
404             END
405             title => q(Missing exports));
406             }
407             }
408              
409             owf($cFile, qq(#line 1 "$inputFile"\n).join('', @code)); # Output C file
410              
411             genHash(q(PreprocessOpsParse), # Structure of the C program being preprocessed
412             methods => \%methods, # Methods.
413             structures => \%structures, # Structure definitions.
414             structureParameters => \%structureParameters, # Structures used as parameters
415             testsFound => \%testsFound, # Tests found
416             testsNeeded => \%testsNeeded) # Tests still needed
417             }
418              
419             #D0
420             #-------------------------------------------------------------------------------
421             # Export
422 1     1   28589 #-------------------------------------------------------------------------------
  1         4  
  1         46  
423              
424 1     1   6 use Exporter qw(import);
  1         2  
  1         346  
425              
426             use vars qw(@ISA @EXPORT @EXPORT_OK %EXPORT_TAGS);
427              
428             @ISA = qw(Exporter);
429             @EXPORT_OK = qw(
430             );
431             %EXPORT_TAGS = (all=>[@EXPORT, @EXPORT_OK]);
432              
433             # podDocumentation
434              
435             =pod
436              
437             =encoding utf-8
438              
439             =head1 Name
440              
441             Preprocess::Ops - Preprocess ◁, ◀, ▷ and ▶ as operators in ANSI-C.
442              
443             =head1 Synopsis
444              
445             See the final lines of:
446             L
447             for working examples of the following operators.
448              
449             =head2 Method dispatch operators: ▷ and ▶
450              
451             Preprocess ▷ and ▶ as method dispatch by translating:
452              
453             p = node ▷ key("a");
454              
455             to:
456              
457             p = node . proto->key(&node, "a");
458              
459             and:
460              
461             p = node ▶ key("a");
462              
463             to:
464              
465             p = node -> proto->key(node, "a");
466              
467             =head2 Constant and variable creation operators: ◁ and ◀
468              
469             Preprocess instances of ◁ as a constant creation operator:
470              
471             c ◁ sfc("cba");
472              
473             to get:
474              
475             const typeof(sfc("cba")) c = sfc("cba");
476              
477             Preprocess instances of ◀ as a variable creation operator:
478              
479             d ◀ sfc("cba");
480              
481             to get:
482              
483             typeof(sfc("cba")) c = sfc("cba");
484              
485             which, in effect, produces:
486              
487             const char c = sfc("cba");
488             char d = sfc("cba");
489              
490             in the context of:
491              
492             char sfc(char *s) {return *s;}
493              
494             int main(void) {
495             c ◁ sfc("cba");
496             d ◀ sfc("cba");
497             assert(c == 'c');
498             }
499              
500             =head2 Here documents starting with ◉
501              
502             Preprocess instances of ◉ as starting a L document:
503              
504             char *c = ◉;
505             a
506             b
507            
508              
509             to get:
510              
511             char *a =
512             "a\n"
513             " b\n"
514             ;
515              
516             =head2 Substituting ✓ for assert(...);
517              
518             Convert instances of ✓ as in:
519              
520             ✓ a == 1;
521              
522             to:
523              
524             assert(a == 1);
525              
526             to make B function calls more prominent in tests.
527              
528             =head2 Substituting $ with the base file name.
529              
530             Occurrences of the B<$> character are replaced by the base name of the file
531             containing the source with the first letter capitalized, so that:
532              
533             typedef struct $Node {...} $Node;
534              
535             in a file called B becomes:
536              
537             typedef struct TreeNode {...} TreeNode;
538              
539             =head2 new operator
540              
541             Occurrences of:
542              
543             new XXX
544              
545             are replaced by:
546              
547             newXXX(({struct XXX t = {proto: &ProtoTypes_$1}; t;}))
548              
549             Occurrences of:
550              
551             new XXX(a:1)
552              
553             are replaced by:
554              
555             newXXX(({struct XXX t = {a:1, proto: &ProtoTypes_$1}; t;}))
556              
557             The prototype vectors are generated by examining all the methods defined in the
558             B file. The prototype vectors are written to the specified B file which
559             should be included in the B file for use via the ▷ and ▶ operators.
560              
561             =head2 Marking tests with //T
562              
563             B immediately followed by the name of a method up to its first B<_> (if
564             any or the end of the name otherwise) marks a function as testing all the
565             methods that start with that name:
566              
567             void test10() //Tsystem //TprintsAs
568             { a ◁ make$FromString("uname");
569             a ▷ system;
570             ✓ a ▷ containsString("Linux");
571             ✓ a ▷ printsAs(◉);
572             Linux
573            
574             a ▷ free;
575             }
576              
577             Function B is marked as testing both B and
578             B. Functions that are declared B but
579             have no associated tests are listed in the preprocessor output as in:
580              
581             The following methods need tests:
582             parseXmlFromString
583              
584             after preprocessing a file called xml.c containing:
585              
586             static $Parse parse$FromString_$Parse_StringBuffer
587             (StringBuffer string)
588             {return make$ParseFromString(0, string, strlen(string));
589             }
590              
591             with no test function marked with B.
592              
593             =head2 Preprocessor commands
594              
595             =head3 duplicate
596              
597             The B command generates the previous function with the changes
598             indicated in the words following the command as in:
599              
600             static char * key_$Node // Get the key for a node
601             (const $Node n) // Node
602             {return n.key;
603             }
604             duplicate s/key/data/g
605              
606             which adds the following code to the current output file:
607              
608             static char * data_$Node // Get the data for a node
609             (const $Node n) // Node
610             {return n.data;
611             }
612              
613             =head3 exports
614              
615             The B command provides a name for or a collection of functions that
616             can be Bd in generated output files, for instance:
617              
618             exports aaa new$Node key_$Node
619              
620             creates a new set of exports called B which contains the two functions
621             mentioned. As these names have B<$> in them they will be expanded with the base
622             name of the file into which they are being copied.
623              
624             =head3 include
625              
626             The B command copies the named function, structures, and exported
627             collections from the specified file into the current output file. For instance:
628              
629             include ../arenaTree.c :arena !key_$Node data_$Node
630              
631             reads the relative file B<../arenaTree.c> and copies in all the structures
632             mentioned in collection B except for B as well as copying the
633             explicitly mentioned function B.
634              
635             =head1 Description
636              
637             Preprocess ◁, ◀, ▷ and ▶ as operators in ANSI-C.
638              
639              
640             Version 202010141.
641              
642              
643             The following sections describe the methods in each functional area of this
644             module. For an alphabetic listing of all methods by name see L.
645              
646              
647              
648             =head1 Preprocess
649              
650             Preprocess ◁, ◀, ▷ and ▶ as operators in ANSI-C.
651              
652             =head2 c($inputFile, $cFile, $hFile, $column)
653              
654             Preprocess ▷ and ▶ as method dispatch operators in ANSI-C.
655              
656             Parameter Description
657             1 $inputFile Input file
658             2 $cFile C output file
659             3 $hFile H output file
660             4 $column Optional start column for comments (80)
661              
662             B
663              
664              
665             if (88) {
666             my $d = temporaryFolder;
667            
668             my $sc = fpe($d, qw(source node c)); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
669              
670             my $sh = fpe($d, qw(source node h));
671            
672             my $dc = fpe($d, qw(derived node c)); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
673              
674             my $dh = fpe($d, qw(derived node h));
675             my $se = fpe($d, qw(source node));
676            
677             owf($sc, <
678             #include
679            
680             typedef struct Node // Node
681             {const struct ProtoTypes_Node *proto;
682             int data;
683             } Node;
684            
685             #include <$dh>
686            
687             static Node by // New from node * number
688             (const Node * n, // Node
689             const int i) // Multiplier
690             {return new Node(data: i * n->data);
691             }
692            
693             static void dump // Dump a node to stdout
694             (const Node * n) // Node to dump
695             {printf("data=%d\
696             ", n->data);
697             }
698            
699             int main(void) //TnewNode //Tdump //Tby
700             {a ◁ new Node(data: 6);
701             b ◁ a ▷ by(7);
702             b ▷ dump;
703             return 0;
704             }
705             END
706            
707            
708             my $r = c($sc, $dc, $dh); # Preprocess source c to get derived c # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
709              
710            
711             is_deeply qx(gcc $dc -o $se; $se), "data=42
712             "; # Expected output
713            
714            
715             is_deeply readCFile($dc), <<'END'; # Generated base.c # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
716              
717            
718             #line 1 "node.c" # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
719              
720             #include
721            
722             typedef struct Node // Node
723             {const struct ProtoTypes_Node *proto;
724             int data;
725             } Node;
726            
727             #include
728            
729             static Node by // New from node * number
730             (const Node * n, // Node
731             const int i) // Multiplier
732             {return newNode(({struct Node t = {data: i * n->data, proto: &ProtoTypes_Node}; t;}));
733             }
734            
735             static void dump // Dump a node to stdout
736             (const Node * n) // Node to dump
737             {printf("data=%d
738             ", n->data);
739             }
740            
741             int main(void) //TnewNode //Tdump //Tby
742             {const typeof(newNode(({struct Node t = {data: 6, proto: &ProtoTypes_Node}; t;}))) a = newNode(({struct Node t = {data: 6, proto: &ProtoTypes_Node}; t;}));
743             const typeof(a.proto->by(&a, 7)) b = a.proto->by(&a, 7);
744             b.proto->dump(&b);
745             return 0;
746             }
747             END
748            
749             is_deeply readCFile($dh), <
750             static Node by
751             (const Node * n,
752             const int i);
753             static void dump
754             (const Node * n);
755             int main(void);
756             struct ProtoTypes_Node {
757             Node (*by)( // New from node * number
758             const Node * n, // Node
759             const int i); // Multiplier
760             void (*dump)( // Dump a node to stdout
761             const Node * n); // Node to dump
762             } const ProtoTypes_Node =
763             {by, dump};
764             Node newNode(Node allocator) {return allocator;}
765             END
766            
767             clearFolder($d, 10);
768             }
769            
770             if (36) {
771             my $d = temporaryFolder;
772            
773             my $c = owf(fpe($d, qw(source c)), <<'END'); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
774              
775             #include
776             #include
777             int main(void)
778             {char *a = ◉;
779             a
780             b
781            
782             ✓ a[0] == 'a';
783             printf("%s", a);
784             }
785             END
786            
787             my $h = fpe($d, qw(source h));
788            
789             my $g = fpe($d, qw(derived c)); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
790              
791             my $o = fpe($d, qw(out));
792            
793            
794             my $r = c($c, $g, $h); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
795              
796             is_deeply scalar(qx(gcc $g -o $o; $o)), <
797             a
798             b
799             END
800            
801             is_deeply readCFile($g), <<'END';
802            
803             #line 1 "source.c" # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
804              
805             #include
806             #include
807             int main(void)
808             {char *a =
809             "a
810             "
811             " b
812             "
813             ;
814             assert( a[0] == 'a');
815             printf("%s", a);
816             }
817             END
818             }
819            
820              
821              
822             =head2 PreprocessOpsMap Definition
823              
824              
825             Methods and structures in the C file being preprocessed
826              
827              
828              
829              
830             =head3 Output fields
831              
832              
833             =head4 methods
834              
835             Methods.
836              
837             =head4 structures
838              
839             Structure definitions.
840              
841              
842              
843             =head2 PreprocessOpsParse Definition
844              
845              
846             Structure of the C program being preprocessed
847              
848              
849              
850              
851             =head3 Output fields
852              
853              
854             =head4 methods
855              
856             Methods.
857              
858             =head4 structureParameters
859              
860             Structures used as parameters
861              
862             =head4 structures
863              
864             Structure definitions.
865              
866             =head4 testsFound
867              
868             Tests found
869              
870             =head4 testsNeeded
871              
872             Tests still needed
873              
874              
875              
876             =head2 PreprocessOpsStruct Definition
877              
878              
879             Structure declaration
880              
881              
882              
883              
884             =head3 Output fields
885              
886              
887             =head4 comment
888              
889             Comment for structure
890              
891             =head4 flags
892              
893             Flags for structure
894              
895             =head4 methods
896              
897             Methods.
898              
899             =head4 name
900              
901             Name of structure
902              
903             =head4 structureParameters
904              
905             Structures used as parameters
906              
907             =head4 structures
908              
909             Structure definitions.
910              
911             =head4 testsFound
912              
913             Tests found
914              
915             =head4 testsNeeded
916              
917             Tests still needed
918              
919              
920              
921             =head1 Private Methods
922              
923             =head2 trimComment($s)
924              
925             Remove trailing white space and comment
926              
927             Parameter Description
928             1 $s String
929              
930             =head2 method($line)
931              
932             Check whether a line of C code defines a method, returning (return, name, flags, comment) if it is, else ()
933              
934             Parameter Description
935             1 $line Line of C code
936              
937             =head2 structure($line)
938              
939             Check whether a line of C code defines a structure, returning (name, flags, comment) if it is, else ()
940              
941             Parameter Description
942             1 $line Line of C code
943              
944             =head2 mapCode($file)
945              
946             Find the structures and methods defined in a file
947              
948             Parameter Description
949             1 $file Input file
950              
951             =head2 printData($lineNumber, $line)
952              
953             Print statement
954              
955             Parameter Description
956             1 $lineNumber Code line number
957             2 $line Code line
958              
959             =head2 duplicateFunction($lineNumber, $inputFile, $code)
960              
961             Duplicate the previous function with the specified changes applied
962              
963             Parameter Description
964             1 $lineNumber Line number of line being expanded
965             2 $inputFile File containing line being expanded
966             3 $code Lines of code
967              
968             =head2 includeFile($lineNumber, $inputFile, $cFile, $hFile, $code)
969              
970             Expand include files so that we can pull in code and structures from other files in the includes folder.
971              
972             Parameter Description
973             1 $lineNumber Line number of line being expanded
974             2 $inputFile File containing line being expanded
975             3 $cFile Output C file
976             4 $hFile Output H file
977             5 $code Line of code
978              
979              
980             =head1 Index
981              
982              
983             1 L - Preprocess ▷ and ▶ as method dispatch operators in ANSI-C.
984              
985             2 L - Duplicate the previous function with the specified changes applied
986              
987             3 L - Expand include files so that we can pull in code and structures from other files in the includes folder.
988              
989             4 L - Find the structures and methods defined in a file
990              
991             5 L - Check whether a line of C code defines a method, returning (return, name, flags, comment) if it is, else ()
992              
993             6 L - Print statement
994              
995             7 L - Check whether a line of C code defines a structure, returning (name, flags, comment) if it is, else ()
996              
997             8 L - Remove trailing white space and comment
998              
999             =head1 Installation
1000              
1001             This module is written in 100% Pure Perl and, thus, it is easy to read,
1002             comprehend, use, modify and install via B:
1003              
1004             sudo cpan install Preprocess::Ops
1005              
1006             =head1 Author
1007              
1008             L
1009              
1010             L
1011              
1012             =head1 Copyright
1013              
1014             Copyright (c) 2016-2019 Philip R Brenan.
1015              
1016             This module is free software. It may be used, redistributed and/or modified
1017             under the same terms as Perl itself.
1018              
1019             =cut
1020              
1021              
1022              
1023             # Tests and documentation
1024 1     1 0 7  
1025 1         10 sub test
1026 1 50       80 {my $p = __PACKAGE__;
1027 1         66 binmode($_, ":utf8") for *STDOUT, *STDERR;
1028 1 50       9 return if eval "eof(${p}::DATA)";
1029 1     1 0 6 my $s = eval "join('', <${p}::DATA>)";
  1     1   3  
  1     1   40  
  1     1   9  
  1     3   3  
  1         29  
  1         5  
  1         2  
  1         7  
  1         827  
  1         65012  
  1         10  
  1         84  
  3         27  
  3         54  
  3         1238  
  3         67  
  3         31  
  3         102  
  3         36  
1030 1 50       39 $@ and die $@;
1031 1         186 eval $s;
1032             $@ and die $@;
1033             1
1034             }
1035              
1036             test unless caller;
1037              
1038             1;
1039             # podDocumentation
1040             __DATA__