@rem = ' @perl.exe %~f0 %* @goto :EOF '; undef @rem; # # This script verifies adherence to the style document # # Include 'spelling.pl' from the same directory as this script $dir=$0; $dir =~ s/\\[^\\]*$//; require "$dir\\spelling.pl"; # List of error classes to keep quiet about # Options: (spell_c, indent, line_length, tabs, braces, eof) @suppress_errors = (spell_c); foreach $t (@suppress_errors) { $suppress_errors{$t} = 1; } # Process command-line options @args=@ARGV; undef @ARGV; $syntax = "style [-?] [ ...]"; for ($i=0;$i<=$#args;$i++) { $_ = $args[$i]; if (0) { } elsif (/^[-\/]\w$/) { $writeSpellingErrors = 1; } elsif (/^[-\/]\?$/) { print <=0) { @filespecs = @ARGV; } # Supply a default file set if none given @exts = ("cpp", "cxx", "c", "h", "hxx", "hpp"); if ($#filespecs<0) { @filespecs = map("*.$_", @exts); } # Subroutines # Report a file-wide error sub fileError { local ($type, $string) = @_; return if $suppress_errors{$type}; print "$filename: $string\n"; } # Report an error at a given line sub lineError { local ($type, $string) = @_; return if $suppress_errors{$type}; print "$filename($line): $string\n"; } # Spellcheck a comment sub checkComment { local ($_) = @_; while (/([a-zA-Z_0-9']*)/g) { my $word=$1; # Skip numbers next if ($word =~ /^[0-9]*[Ll]?$/); next if ($word =~ /^0[Xx][0-9a-fA-F]+[lL]?$/); # Skip words 3 or fewer letters long, containing digits next if ($word =~ /[0-9]/ && length($word)<=3); # Remove leading/trailing hyphens $word =~ s/^'//g; $word =~ s/'$//g; next if !$word; # Try an exact match against the English dictionary next if $dic_english{$word}; # Try the code dictionary next if $dic_code{$word}; # Check for shifty words if ($dic_shifty{$word}) { &lineError("spell_c", "Shifty word: \"$word\""); next; } # If it's capitalized, or all caps, look for the lowercase # version in the English dictionary if ($word =~ /^([A-Z])([^A-Z]*)$/) { my $i = $1; $i =~ tr/A-Z/a-z/; next if $dic_english{"$i$2"}; } elsif ($word !~ /[a-z]/) { $word =~ tr/A-Z/a-z/; next if $dic_english{"$i$2"}; } # Okay, it's a spelling error if ($writeSpellingErrors) { # Put it in the English error list if it's not recognizably an # identifier if (($word !~ /^.+[A-Z0-9]/) && ($word !~ /_/)) { push (@errors_english, $word); } else { push (@errors_code, $word); } } else { &lineError("spell_c", "Misspelt word: \"$word\""); } } } &loadDictionaries() unless $suppress_errors{spell_c}; # Check the indentation level # # Uses $lastIndentErrorLine and $actualIndent sub checkIndent { my $pos; foreach $pos (@_) { if ($pos == $actualIndent) { return 1; } } if ($line != $lastIndentErrorLine) { &lineError("indent", "Indentation error: Actual pos $actualIndent, expected ". join(', ', grep($_ != -1, @_))); $lastIndentErrorLine = $line; } return 0; } sub currentIndent { $mainIndentPos[$#mainIndentPos]; } # Evil function which references these global variables: # FILE, $line, $altindentpos, $lineAccum, $commentAccum, $tempindentpos sub getLine { local $_; LINE: { $_ = ; defined($_) || last; chop; $line++; # Check line length /^(.*)$/; my $l = length($1); if ($l > 80) { &lineError("line_length", "Line too long"); } # Accumulate lines which have escaped newlines if (/\\$/) { $lineAccum .= $_; redo LINE; } elsif (defined ($lineAccum)) { $lineAccum .= $_; $_ = $lineAccum; $lineAccum = undef; } COMMENTLOOP: { # Accumulate comments if (defined($commentAccum)) { if (s;^(.*?)\*/;;) { # End of multi-line comment $commentAccum .= " $1"; &checkComment($commentAccum); $commentAccum = undef; } else { # Inside multi-line comment $commentAccum .= " $_"; redo LINE; } } if ((s;//(.*)$;;) || (s;/\*(.*?)\*/;;)) { # Single-line comment - either // or /* ... */ &checkComment($1); redo COMMENTLOOP; } if (s;/\*(.*)$;;) { # Start of multi-line comment $commentAccum = $1; } } # Skip blank lines redo LINE if /^\s*$/; # No tabs allowed if (s/\t/ /g) { &lineError("tabs", "Line contains tab characters"); } # # Indentation checking # /^( *)[^ ]/; local $actualIndent = length($1); # Was the previous line #if* ? if ($prevLineHashIf) { $prevLineHashIf = 0; &checkIndent(¤tIndent, ¤tIndent+4); push(@mainIndentPos, $actualIndent); } # #if*, #endif if (/^ *#if/ || /^ *#endif/) { if (/^ *#if/) { $prevLineHashIf = 1; } else { pop(@mainIndentPos); } &checkIndent(¤tIndent); redo LINE; } # #else if (/^ *#else/) { &checkIndent($mainIndentPos[$#mainIndentPos-1]); redo LINE; } # Process braces. my $braceDelta = 0; while (/{/g) { $braceDelta++; } while (/}/g) { $braceDelta--; } if ($braceDelta != 0) { # Check for braces not on their own lines (except for matched braces) if (!/^[\s{};]*$/) { &lineError("braces", "Brace should be on separate line"); } if ($braceDelta > 0) { &checkIndent(¤tIndent); push(@mainIndentPos, $actualIndent+4); } else { pop(@mainIndentPos); &checkIndent(¤tIndent); } redo LINE; } # Certain constructs effectively cause a temporary unindent by one # level. These are: # "case XYZ:","default:", "public:", "private:", # "protected:", and labels. if (/case .*:/ || /default:/ || /public:/ || /private:/ || /protected:/ || /^ *[a-zA-Z_0-9]+: *$/) { &checkIndent(¤tIndent-4); redo LINE; } # Test the indent level # Hack - for now, $tempindentpos!=-1, we skip the check if ($tempIndentPos == -1) { &checkIndent(¤tIndent, $altIndentPos, $tempIndentPos); } # Multi-line statements may be temporarily indented if (($braceDelta == 0) && (!/; *$/)) { $tempIndentPos = ¤tIndent+4; } else { $tempIndentPos = -1; } } $_; } # Evil function which references global variables $token, $ttype, FILE sub getToken { while (1) { if ($parseLine =~ /^\s*$/) { $parseLine = &getLine(); $parseLine || return 0; next; } # if (/([0-9\.]([0-9e\.]*))\b/g) { # } # $token = $1; # for now, eat everything $parseLine = ""; } return 1; } $findstr = "dir /b " . join(" ", @filespecs)." 2>nul"; open(FIND, "$findstr|"); @files = ; close(FIND); # Remove blank lines @files = grep(!/^\s*$/, @files); chop @files; $#files >= 0 || die "No files found (".join(", ", @filespecs).").\n"; foreach $filename (@files) { open(FILE, "$filename"); # Remove path from filename $filename =~ s/.*\\([^\\]*)/$1/; $line=0; $lastIndentErrorLine=0; $tempIndentPos = -1; # Temporary indentation, used for multi-line statements @mainIndentPos = (0); # Stack of indent positions $prevLineHashIf = 0; # If true, the previous line was a #if $parseLine = ""; undef $lineAccum; undef $commentAccum; while (&getToken()) { } close(FILE); if (defined($lineAccum) || defined($commentAccum)) { &lineError("eof", "Unexpected EOF"); } } if ($writeSpellingErrors) { &writeSpellingErrors("english", "code"); }