This patch adds a new Makefile target called "check_source".  This target
scans all the .c and .h files in the directory and checks for conformance to
a number of the standards outlined in PDD 7.

--Josh

[josh-007.patch]

Index: MANIFEST
===================================================================
RCS file: /home/perlcvs/parrot/MANIFEST,v
retrieving revision 1.116
diff -u -r1.116 MANIFEST
--- MANIFEST    18 Feb 2002 18:25:49 -0000      1.116
+++ MANIFEST    19 Feb 2002 04:12:10 -0000
@@ -16,6 +16,7 @@
 chartype.c
 chartypes/unicode.c
 chartypes/usascii.c
+check_source_standards.pl
 classes/Makefile.in
 classes/array.pmc
 classes/default.pmc
Index: Makefile.in
===================================================================
RCS file: /home/perlcvs/parrot/Makefile.in,v
retrieving revision 1.134
diff -u -r1.134 Makefile.in
--- Makefile.in 18 Feb 2002 08:26:03 -0000      1.134
+++ Makefile.in 19 Feb 2002 04:12:10 -0000
@@ -161,6 +161,9 @@
 
 lib_deps: lib_deps_object lib_deps_source
 
+check_source: $(GENERAL_H_FILES)
+       $(PERL) check_source_standards.pl all_source
+
 ###############################################################################
 #
 # Shared Library Targets:
--- /dev/null   Sat Jul 14 02:37:41 2001
+++ check_source_standards.pl   Mon Feb 18 23:06:11 2002
@@ -0,0 +1,353 @@
+#!/usr/bin/perl
+
+use Text::Wrap;
+use File::Find;
+use File::Basename;
+use strict;
+
+my @files = @ARGV;
+
+if (! @files) {
+    die "Usage: $0 <source files>\n" .
+        "       $0 all_source\n";
+}
+
+if ($files[0] eq "all_source") {
+    # do a little "find" action for now.
+
+    @files = ();
+    File::Find::find({wanted => sub {
+                          /^.*\.[ch]\z/s &&
+                            push @files, $File::Find::name;
+                      }}, '.');
+}
+
+foreach my $file (@files) {
+    $file =~ s/^\.\///g;
+    if (!open(F, "<$file")) {
+        error($file, 0, "Unable to open $file: $!\n");
+        next;
+    }
+
+    my @lines = <F>;
+    close(F);
+    chomp @lines;
+
+    check_source($file, \@lines);
+}
+
+check_manifest();
+
+exit 0;
+
+###############################################################################
+
+sub check_source {
+    my ($file, $source) = @_;
+
+    info($file, 0, "Checking $file");
+    trim_whitespace($file, $source);
+
+    # The following MUST apply.
+    check_tabs($file, $source);
+    check_cpp_indents($file, $source);
+    check_code_indents($file, $source);
+    check_function_definitions($file, $source);
+    check_cuddled_else($file, $source);
+    check_comments($file, $source);
+    check_mandatory_boilerplate($file, $source);
+
+    # The following SHOULD apply.
+    check_line_length($file, $source);
+    check_returns($file, $source);
+}
+
+
+# ignore any leading or trailing whitespace on the file 
+sub trim_whitespace {
+    my ($file, $source) = @_;
+
+    shift @$source while ($source->[0] !~ /\S/);
+    pop @$source while ($source->[-1] !~ /\S/);
+}
+
+
+sub check_returns {
+    my ($file, $source) = @_;
+
+    my $line = 0;
+    foreach (@$source) {
+        if (/return\s*\(/) {
+            warning($file, $line, "possible use of return (foo); rather than return 
+foo;");
+        }
+
+
+        $line++;
+    }
+}
+
+
+sub check_line_length {
+    my ($file, $source) = @_;
+
+    my $line = 0;
+    foreach (@$source) {
+        if (length($_) > 79) {
+            warning($file, $line, "line more than 79 columns. (" . length($_) . ")");
+        }
+        $line++;
+    }
+}
+
+sub check_function_definitions {
+    # In function definitions, the name starts in column 0, with the
+    # return type on the previous line
+    
+    # XXX C::Scan, perhaps.
+}
+
+sub check_comments {
+    my ($file, $source) = @_;
+
+    my $line = 0;
+    foreach (@$source) {
+        if (/\/\//) {
+            error($file, $line, "C++ comment detected.");
+        }
+
+        if (/XXX/) {
+            info($file, $line, "To-Do (XXX) noticed.");
+        }
+        $line++;
+    }
+}
+
+
+sub check_cuddled_else {
+    my ($file, $source) = @_;
+
+    my $line = 0;
+    foreach (@$source) {
+        if (/\}\s*else\s*\{/) {
+            error($file, $line, "Cuddled else (\"} else {\") found.");
+        }
+        $line++;
+    }
+}
+
+
+# for now just try to catch glaring errors.  A real parser is probably
+# overkill for this task.  For now we just check the first line of a function,
+# and assume that more likely than not indenting is consistent within a func
+# body.
+sub check_code_indents {
+    my ($file, $source) = @_;
+
+    my $f = undef;
+    my $line = 0;
+    foreach (@$source) {
+        $line++;
+        if (/^(\s*).*\{\s*$/) {
+            # note the beginning of a block, and its indent depth.
+            $f=length($1);
+            next;
+        }
+
+        if (/^\s*([\#\}])/) {
+            # skip the last line of the func or cpp directives.
+            $f = undef if ($1 eq "}");
+            next;
+        }
+
+        if (defined($f)) {
+            # first line of a block
+            if ($f == 0) {
+                # first line of a top-level block (first line of a function,
+                # in other words)
+                my ($indent) = /^(\s*)/;
+                if ($indent =~ /\t/) {
+                    error($file, $line, "apparent non-4 space indenting (contains 
+tab(s))");
+                } elsif (length($indent) != 4) {
+                    error($file, $line, "apparent non-4 space indenting (" . 
+length($indent) . " spaces)");
+                }
+            }
+            $f = undef;
+        }
+    }
+}
+
+
+# tabs are bad, mmkay?
+sub check_tabs {
+    my ($file, $source) = @_;
+
+    my $line = 0;
+    foreach (@$source) {
+        $line++;
+        if (/\t/) {
+            s/\t/\[TAB\]/g;
+            warning($file, $line, "Tab character in source: $_\n");
+        }
+    }
+}
+
+
+sub check_cpp_indents {
+    my ($file, $source) = @_;
+
+    # check for C preprocessor directive indenting.  This is
+    # comparitively easy.  Each level of the hierarchy should be
+    # indented two spaces.
+
+    # there is one exception.  The outermost "GUARD" ifdef doesn't increase
+    # the indenting level.
+
+    my @stack;
+    my $line = 0;
+    foreach (@$source) {
+        $line++;
+        if (/^\s*\#(\s*)(ifdef|if)\s+(.*)/) {
+            my $indent = "  " x (@stack-1);
+            if ($1 ne $indent) {
+                print "Improper indenting for \"$_\" (should be \"#$indent$2 $3\")\n";
+            }
+            push @stack, "#$2 $3";
+
+            # ignore "GUARD" ifdefs for purposes of indenting.
+            if (@stack == 1 && $stack[0] =~ /_GUARD/) {
+                shift @stack;
+            }
+
+            next;
+        }
+        if (/^\s*\#(\s*)(else)/) {
+            # stay where we are, but indenting should be 
+            # back even with the opening brace.
+            my $indent = "  " x (@stack-1);
+            if ($1 ne $indent) {
+                error($file, $line, "Improper indenting for \"$_\"\n(should be 
+\"#$indent$2 $3\" because it is inside of " . (join ' > ', @stack) . ")\n")
+            }
+            next;
+        }
+        if (/^\s*\#(\s*)(endif)/) {
+            my $indent = "  " x (@stack-1);
+            if ($1 ne $indent) {
+                error($file, $line, "Improper indenting for \"$_\"\n(should be 
+\"#$indent$2 $3\" because it is inside of " . (join ' > ', @stack) . ")\n")
+            }
+            pop @stack;
+            next;
+        }
+
+        next unless @stack;
+
+        if (/^\s*\#(\s*)(.*)/) {
+            my $indent = "  " x (@stack);
+            if ($1 ne $indent) {
+                error($file, $line, "Improper indenting for \"$_\"\n(should be 
+\"#$indent$2 $3\" because it is inside of " . (join ' > ', @stack) . ")\n")
+            }
+        }
+    }
+}
+
+
+sub check_mandatory_boilerplate {
+    my ($file, $source) = @_;
+
+    my @end_boilerplate = split /\n\s*/, qq(
+       /*
+        * Local variables:
+        * c-indentation-style: bsd
+        * c-basic-offset: 4
+        * indent-tabs-mode: nil
+        * End:
+        *
+        * vim: expandtab shiftwidth=4:
+        */
+    );
+    shift @end_boilerplate;
+
+    my $ok = 1;
+    my $idx = -@end_boilerplate;
+    my $mismatch_at;
+    while ($idx < 0) {
+        if ($source->[$idx] !~ /\Q$end_boilerplate[$idx]\E/) {
+            if ($ok == 2) { $mismatch_at = $idx; }
+            $ok = 0;
+        } else {
+            $ok = 2 if $ok == 1;
+        }
+        $idx++;
+    }
+
+    if (! $ok) {
+        if ($mismatch_at >= 0) {
+            error($file, 0, "Ending boilerplate is missing.");
+        } else {
+            error($file, (@$source+$mismatch_at), "Ending boilerplate 
+incorrect.\n(mismatch at \"$source->[$mismatch_at]\" (should be 
+\"$end_boilerplate[$mismatch_at]\")");
+        }
+    }
+
+}
+
+
+sub check_manifest {
+    open(F, "<MANIFEST") || die "Unable to read MANIFEST: $!\n";
+
+    my %files_in_dir_nocase;
+    my %files_in_dir_8dot3;
+    my $line = 0;
+    while (<F>) {
+        chomp;
+
+        if (/([^A-Za-z0-9\.\-_\/])/) {
+            error("MANIFEST", $line, "invalid character '$1' in filename ($_)");
+        }
+
+        my ($filename, $dirname) = fileparse($_);
+
+        my ($filebase, $extension) = ($filename =~ /^(.*)\.(.*)/);
+        $filebase ||= $filename;
+        if ($filebase =~ /\./) {
+            error("MANIFEST", $line, "$_: more than one '.' in a filename");
+        }
+        $filebase  = substr($filebase, 0, 8);
+        $extension = substr($extension, 0, 3);
+        my $filename_8dot3 = lc("$filebase.$extension");
+
+        if (exists $files_in_dir_8dot3{$dirname}{$filename_8dot3}) {
+            error("MANIFEST", $line, "$_: 8.3 name collision with 
+$files_in_dir_8dot3{$dirname}{$filename_8dot3} ($filename_8dot3)");
+        }
+        $files_in_dir_8dot3{$dirname}{$filename_8dot3}=$_;
+
+        if (exists $files_in_dir_nocase{$dirname}{lc($filename)}) {
+            error("MANIFEST", $line, "$_: case-insensitive collision with " . 
+$files_in_dir_nocase{$dirname}{lc($filename)});
+        }
+        $files_in_dir_nocase{$dirname}{lc($filename)}=$_;
+
+        $line++;
+    }
+
+    close(F);
+}
+
+
+
+sub info {
+    my ($file, $line, $message) = @_;
+
+    print "$file:$line (INFO) " . Text::Wrap::wrap("", "        ", $message) . "\n";
+}
+
+
+sub warning {
+    my ($file, $line, $message) = @_;
+
+    print "$file:$line (WARNING) " . Text::Wrap::wrap("", "        ", $message) . 
+"\n";
+}
+
+
+sub error {
+    my ($file, $line, $message) = @_;
+
+    print "$file:$line (ERROR) " . Text::Wrap::wrap("", "        ", $message) . "\n";
+}

Reply via email to