File Coverage

blib/lib/Preprocess/Ops.pm
Criterion Covered Total %
statement 69 147 46.9
branch 10 52 19.2
condition 0 6 0.0
subroutine 19 23 82.6
pod 7 9 77.7
total 105 237 44.3


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 = 202010142;
9 1     1   532 use warnings FATAL => qw(all);
  1         6  
  1         31  
10 1     1   8 use strict;
  1         1  
  1         23  
11 1     1   5 use Carp;
  1         2  
  1         66  
12 1     1   429 use Data::Dump qw(dump);
  1         6198  
  1         53  
13 1     1   2915 use Data::Table::Text qw(:all !trim);
  1         110036  
  1         1712  
14 1     1   10 use feature qw(say current_sub);
  1         1  
  1         155  
15 1     1   7 use utf8;
  1         2  
  1         6  
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         56 $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 47 {my ($line) = @_; # Line of C code
31 36 50       71 return () if $line =~ m(test.*//T\S); # Tests are never methods
32 36 100       66 if ($line =~ m(\Astatic\s*(.*?)((?:\w|\$)+)\s+//(\w*)\s*(.*)\Z)) # Static function is always a method
33 2         12 {return ($1, $2, $3, $4)
34             }
35 34 100       64 if ($line =~ m(\A(.*?)(new(?:\w|\$)+)\s+//(\w*)\s*(.*)\Z)) # Constructor is always a method
36 1         5 {return ($1, $2, $3, $4);
37             }
38             ()
39 33         56 }
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 46 {my ($line) = @_; # Line of C code
43              
44 36 100       77 if ($line =~ m(\A(typedef\s+)?struct\s+((?:\w|\$)+)\s*//(w*)\s*(.*)\Z)) # struct name, comment start, flags, comment
45 1         19 {return ($2, $3, $4)
46             }
47             ()
48 35         55 }
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 regardless of whether it is actually used in a method
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   3445 $c =~ s{($e+)\s*▷\s*(\w+)} {$1.proto->$2(&$1)}gis; # Method call with no arguments
  1         2  
  1         15  
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   23023 #-------------------------------------------------------------------------------
  1         2  
  1         35  
423              
424 1     1   5 use Exporter qw(import);
  1         2  
  1         285  
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 202010142.
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 = q(zzz);
667             my $ds = fpd($d, qw(source));
668             my $dd = fpd($d, qw(derived));
669            
670             my $sc = fpe($ds, qw(node c)); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
671              
672            
673             my $dc = fpe($dd, qw(node c)); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
674              
675             my $dh = fpe($dd, qw(node h));
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 "node.h"
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             my $c = qq((cd $dd; gcc node.c -o a; ./a)); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
712              
713            
714             is_deeply scalar(qx($c)), "data=42
715             "; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
716              
717            
718            
719             is_deeply readCFile($dc), <<'END'; # Generated base.c # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
720              
721            
722             #line 1 "node.c" # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
723              
724             #include
725            
726             typedef struct Node // Node
727             {const struct ProtoTypes_Node *proto;
728             int data;
729             } Node;
730            
731             #include "node.h"
732            
733             static Node by // New from node * number
734             (const Node * n, // Node
735             const int i) // Multiplier
736             {return newNode(({struct Node t = {data: i * n->data, proto: &ProtoTypes_Node}; t;}));
737             }
738            
739             static void dump // Dump a node to stdout
740             (const Node * n) // Node to dump
741             {printf("data=%d
742             ", n->data);
743             }
744            
745             int main(void) //TnewNode //Tdump //Tby
746             {const typeof(newNode(({struct Node t = {data: 6, proto: &ProtoTypes_Node}; t;}))) a = newNode(({struct Node t = {data: 6, proto: &ProtoTypes_Node}; t;}));
747             const typeof(a.proto->by(&a, 7)) b = a.proto->by(&a, 7);
748             b.proto->dump(&b);
749             return 0;
750             }
751             END
752            
753             is_deeply readCFile($dh), <
754             static Node by
755             (const Node * n,
756             const int i);
757             static void dump
758             (const Node * n);
759             int main(void);
760             struct ProtoTypes_Node {
761             Node (*by)( // New from node * number
762             const Node * n, // Node
763             const int i); // Multiplier
764             void (*dump)( // Dump a node to stdout
765             const Node * n); // Node to dump
766             } const ProtoTypes_Node =
767             {by, dump};
768             Node newNode(Node allocator) {return allocator;}
769             END
770            
771             clearFolder($d, 10);
772             }
773            
774             if (36) {
775             my $d = q(zzz);
776            
777             my $c = owf(fpe($d, qw(source c)), <<'END'); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
778              
779             #include
780             #include
781             int main(void)
782             {char *a = ◉;
783             a
784             b
785            
786             ✓ a[0] == 'a';
787             printf("%s", a);
788             }
789             END
790            
791             my $h = fpe($d, qw(source h));
792            
793             my $g = fpe($d, qw(derived c)); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
794              
795            
796            
797             my $r = c($c, $g, $h); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
798              
799            
800             is_deeply scalar(qx(cd $d; gcc derived.c -o a; ./a)), <
801              
802             a
803             b
804             END
805            
806             is_deeply readCFile($g), <<'END';
807            
808             #line 1 "source.c" # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
809              
810             #include
811             #include
812             int main(void)
813             {char *a =
814             "a
815             "
816             " b
817             "
818             ;
819             assert( a[0] == 'a');
820             printf("%s", a);
821             }
822             END
823             clearFolder($d, 10);
824             }
825            
826              
827              
828             =head2 PreprocessOpsMap Definition
829              
830              
831             Methods and structures in the C file being preprocessed
832              
833              
834              
835              
836             =head3 Output fields
837              
838              
839             =head4 methods
840              
841             Methods.
842              
843             =head4 structures
844              
845             Structure definitions.
846              
847              
848              
849             =head2 PreprocessOpsParse Definition
850              
851              
852             Structure of the C program being preprocessed
853              
854              
855              
856              
857             =head3 Output fields
858              
859              
860             =head4 methods
861              
862             Methods.
863              
864             =head4 structureParameters
865              
866             Structures used as parameters
867              
868             =head4 structures
869              
870             Structure definitions.
871              
872             =head4 testsFound
873              
874             Tests found
875              
876             =head4 testsNeeded
877              
878             Tests still needed
879              
880              
881              
882             =head2 PreprocessOpsStruct Definition
883              
884              
885             Structure declaration
886              
887              
888              
889              
890             =head3 Output fields
891              
892              
893             =head4 comment
894              
895             Comment for structure
896              
897             =head4 flags
898              
899             Flags for structure
900              
901             =head4 methods
902              
903             Methods.
904              
905             =head4 name
906              
907             Name of structure
908              
909             =head4 structureParameters
910              
911             Structures used as parameters
912              
913             =head4 structures
914              
915             Structure definitions.
916              
917             =head4 testsFound
918              
919             Tests found
920              
921             =head4 testsNeeded
922              
923             Tests still needed
924              
925              
926              
927             =head1 Private Methods
928              
929             =head2 trimComment($s)
930              
931             Remove trailing white space and comment
932              
933             Parameter Description
934             1 $s String
935              
936             =head2 method($line)
937              
938             Check whether a line of C code defines a method, returning (return, name, flags, comment) if it is, else ()
939              
940             Parameter Description
941             1 $line Line of C code
942              
943             =head2 structure($line)
944              
945             Check whether a line of C code defines a structure, returning (name, flags, comment) if it is, else ()
946              
947             Parameter Description
948             1 $line Line of C code
949              
950             =head2 mapCode($file)
951              
952             Find the structures and methods defined in a file
953              
954             Parameter Description
955             1 $file Input file
956              
957             =head2 printData($lineNumber, $line)
958              
959             Print statement
960              
961             Parameter Description
962             1 $lineNumber Code line number
963             2 $line Code line
964              
965             =head2 duplicateFunction($lineNumber, $inputFile, $code)
966              
967             Duplicate the previous function with the specified changes applied
968              
969             Parameter Description
970             1 $lineNumber Line number of line being expanded
971             2 $inputFile File containing line being expanded
972             3 $code Lines of code
973              
974             =head2 includeFile($lineNumber, $inputFile, $cFile, $hFile, $code)
975              
976             Expand include files so that we can pull in code and structures from other files in the includes folder.
977              
978             Parameter Description
979             1 $lineNumber Line number of line being expanded
980             2 $inputFile File containing line being expanded
981             3 $cFile Output C file
982             4 $hFile Output H file
983             5 $code Line of code
984              
985              
986             =head1 Index
987              
988              
989             1 L - Preprocess ▷ and ▶ as method dispatch operators in ANSI-C.
990              
991             2 L - Duplicate the previous function with the specified changes applied
992              
993             3 L - Expand include files so that we can pull in code and structures from other files in the includes folder.
994              
995             4 L - Find the structures and methods defined in a file
996              
997             5 L - Check whether a line of C code defines a method, returning (return, name, flags, comment) if it is, else ()
998              
999             6 L - Print statement
1000              
1001             7 L - Check whether a line of C code defines a structure, returning (name, flags, comment) if it is, else ()
1002              
1003             8 L - Remove trailing white space and comment
1004              
1005             =head1 Installation
1006              
1007             This module is written in 100% Pure Perl and, thus, it is easy to read,
1008             comprehend, use, modify and install via B:
1009              
1010             sudo cpan install Preprocess::Ops
1011              
1012             =head1 Author
1013              
1014             L
1015              
1016             L
1017              
1018             =head1 Copyright
1019              
1020             Copyright (c) 2016-2019 Philip R Brenan.
1021              
1022             This module is free software. It may be used, redistributed and/or modified
1023             under the same terms as Perl itself.
1024              
1025             =cut
1026              
1027              
1028              
1029             # Tests and documentation
1030 1     1 0 6  
1031 1         9 sub test
1032 1 50       63 {my $p = __PACKAGE__;
1033 1         53 binmode($_, ":utf8") for *STDOUT, *STDERR;
1034 1 50       7 return if eval "eof(${p}::DATA)";
1035 1     1 0 6 my $s = eval "join('', <${p}::DATA>)";
  1     1   2  
  1     1   33  
  1     1   5  
  1     3   2  
  1         25  
  1         4  
  1         2  
  1         7  
  1         679  
  1         54158  
  1         9  
  1         102  
  3         29  
  3         42  
  3         1056  
  3         22  
  3         76  
  3         31  
1036 1 50       63 $@ and die $@;
1037 1         195 eval $s;
1038             $@ and die $@;
1039             1
1040             }
1041              
1042             test unless caller;
1043              
1044             1;
1045             # podDocumentation
1046             __DATA__