Scripts/cstyle.py : Updated.

This commit is contained in:
Erik de Castro Lopo 2012-01-21 14:25:01 +11:00
parent 8508ec5679
commit 22226fabaf

View File

@ -1,40 +1,59 @@
#!/usr/bin/python
#!/usr/bin/python -tt
#
# Copyright (C) 2005-2012 Erik de Castro Lopo <erikd@mega-nerd.com>
#
# Released under the 2 clause BSD license.
# This program checks C code for compliance to coding standards used in
# libsndfile and other projects I run.
"""
This program checks C code for compliance to coding standards used in
libsndfile and other projects I run.
"""
import re, string, sys
import re, sys
# Take a line and split it ont the double quoted strings so we can test the
# non double quotes strings for double spaces.
def split_string_on_quoted_str (line):
for k in range (0, len (line)):
if line [k] == '"':
start = k
for k in range (start + 1, len (line)):
if line [k] == '"' and line [k - 1] != '\\':
return [line [:start + 1]] + split_string_on_quoted_str (line [k:])
return [line]
class comment_stripper:
# Strip C and C++ style comments from a series of lines.
class Preprocessor:
"""
Preprocess lines of C code to make it easier for the CStyleChecker class to
test for correctness. Preprocessing works on a single line at a time but
maintains state between consecutive lines so it can preprocessess multi-line
comments.
Preprocessing involves:
- Strip C++ style comments from a line.
- Strip C comments from a series of lines. When a C comment starts and
ends on the same line it will be replaced with 'comment'.
- Replace arbitrary C strings with the zero length string.
- Replace '#define f(x)' with '#define f (c)' (The C #define requires that
there be no space between defined macro name and the open paren of the
argument list).
Used by the CStyleChecker class.
"""
def __init__ (self):
self.comment_nest = 0
self.leading_space_re = re.compile ('^(\t+| )')
self.trailing_space_re = re.compile ('(\t+| )$')
self.define_hack_re = re.compile ("(#\s*define\s+[a-zA-Z0-9_]+)\(")
def comment_nesting (self):
"""
Return the currect comment nesting. At the start and end of the file,
this value should be zero. Inside C comments it should be 1 or
(possibly) more.
"""
return self.comment_nest
def strip (self, line):
orig_line = line
def __call__ (self, line):
"""
Strip the provided line of C and C++ comments. Stripping of multi-line
C comments works as expected.
"""
line = self.define_hack_re.sub (r'\1 (', line)
line = self.process_strings (line)
# Strip C++ style comments.
if self.comment_nest == 0:
line = re.sub ("\t*//.*", '', line)
line = re.sub ("( |\t*)//.*", '', line)
# Strip C style comments.
open_comment = line.find ('/*')
@ -59,109 +78,139 @@ class comment_stripper:
# so we don't need to check whitespace before and after the comment
# we're removing.
newline = line [:open_comment] + "comment" + line [close_comment + 2:]
return self.strip (newline)
return self.__call__ (newline)
return line
class cstyle_checker:
def process_strings (self, line):
"""
Given a line of C code, return a string where all literal C strings have
been replaced with the empty string literal "".
"""
for k in range (0, len (line)):
if line [k] == '"':
start = k
for k in range (start + 1, len (line)):
if line [k] == '"' and line [k - 1] != '\\':
return line [:start + 1] + '"' + self.process_strings (line [k + 1:])
return line
class CStyleChecker:
"""
A class for checking the whitespace and layout of a C code.
"""
def __init__ (self, debug):
self.debug = debug
self.filename = None
self.error_count = 0
self.line_num = 1
self.orig_line = ''
self.trailing_newline_re = re.compile ('[\r\n]+$')
self.indent_re = re.compile ("^\s*")
self.last_line_indent = ""
self.last_line_indent_curly = False
self.re_checks = \
[ ( re.compile (" "), "multiple space instead of tab" )
, ( re.compile ("\t "), "space after tab" )
, ( re.compile ("[^ ];"), "missing space before semi-colon" )
, ( re.compile ("{[^\s]"), "missing space after open brace" )
, ( re.compile ("[^\s]}"), "missing space before close brace" )
, ( re.compile ("[ \t]+$"), "contains trailing whitespace" )
, ( re.compile (",[^\s\n]"), "missing space after comma" )
, ( re.compile (";[a-zA-Z0-9]"), "missing space after semi-colon" )
, ( re.compile ("=[^\s\"'=]"), "missing space after assignment" )
# Open and close parenthesis.
, ( re.compile ("[^\s\(\[\*&']\("), "missing space before open parenthesis" )
, ( re.compile ("\)(-[^>]|[^,'\s\n\)\]-])"), "missing space after close parenthesis" )
, ( re.compile ("\s(do|for|if|when)\s.*{$"), "trailing open parenthesis at end of line" )
# Open and close square brace.
, ( re.compile ("[^\s\(\]]\["), "missing space before open square brace" )
, ( re.compile ("\][^,\)\]\[\s\.-]"), "missing space after close square brace" )
# Space around operators.
, ( re.compile ("[^\s][\*/%+-][=][^\s]"), "missing space around opassign" )
, ( re.compile ("[^\s][<>!=^/][=]{1,2}[^\s]"), "missing space around comparison" )
]
def get_error_count (self):
"""
Return the current error count for this CStyleChecker object.
"""
return self.error_count
def check_files (self, files):
"""
Run the style checker on all the specified files.
"""
for filename in files:
self.check_file (filename)
def check_file (self, filename):
"""
Run the style checker on the specified file.
"""
self.filename = filename
self.file = open (filename, "r")
cfile = open (filename, "r")
self.line_num = 1
stripper = comment_stripper ()
preprocess = Preprocessor ()
while 1:
line = self.file.readline ()
line = cfile.readline ()
if not line:
break
line = self.trailing_newline_re.sub ('', line)
self.orig_line = line
line = "".join (split_string_on_quoted_str (line))
line = stripper.strip (line)
self.line_checks (preprocess (line))
self.line_checks (line)
self.line_num += 1
self.file.close ()
cfile.close ()
self.filename = None
# Check for errors finding comments.
if stripper.comment_nesting () != 0:
print ("Weird")
if preprocess.comment_nesting () != 0:
print ("Weird, comments nested incorrectly.")
sys.exit (1)
return
def line_checks (self, line):
# print (line, end = "")
"""
Run the style checker on provided line of text, but within the context
of how the line fits within the file.
"""
# Check for error which occur in comments abd code.
if re.search ("[ \t]+$", line):
self.error ("contains trailing whitespace")
indent = len (self.indent_re.search (line).group ())
if re.search ("^\s+}", line):
if not self.last_line_indent_curly and indent != self.last_line_indent:
None # self.error ("bad indent on close curly brace")
self.last_line_indent_curly = True
else:
self.last_line_indent_curly = False
if line.find (" ") >= 0:
self.error ("multiple space instead of tab")
if re.search ("[^ ];", line):
self.error ("missing space before semi-colon")
# The C #define requires that there be no spaces in the
# first argument, remove first part of line.
if re.search ("\s*#\s*define\s+", line):
line = re.sub ("\s*#\s*define\s+[^\s]+", '', line)
# Open and close parenthesis.
if re.search ("[^\s\(\[\*&']\(", line):
self.error ("missing space before open parenthesis")
if re.search ("\)-[^>]", line) or re.search ("\)[^,'\s\n\)\]-]", line):
self.error ("missing space after close parenthesis")
# Open and close square brace.
if re.search ("[^\s\(\]]\[", line):
self.error ("missing space before open square brace")
if re.search ("\][^,\)\]\[\s\.-]", line):
self.error ("missing space after close square brace")
if re.search ("[^\s][\*/%+-][=][^\s]", line):
self.error ("missing space around [*/%+-][=]")
if re.search ("[^\s][<>!=^/][=]{1,2}[^\s]", line):
self.error ("missing space around comparison")
# Now all the regex checks.
for (check_re, msg) in self.re_checks:
if check_re.search (line):
self.error (msg)
if re.search ("[a-zA-Z0-9][<>!=^/&\|]{1,2}[a-zA-Z0-9]", line):
if not re.search (".*#include.*[a-zA-Z0-9]/[a-zA-Z]", line):
self.error ("missing space around operator")
if re.search (";[a-zA-Z0-9]", line):
self.error ("missing space after semi-colon")
# Space after comma
if re.search (",[^\s\n]", line):
self.error ("missing space after comma")
if re.search ("\s(do|for|if|when)\s.*{$", line):
self.error ("trailing open parenthesis should be on the next line")
self.last_line_indent = indent
return
def error (self, msg):
"""
Print an error message and increment the error count.
"""
print ("%s (%d) : %s" % (self.filename, self.line_num, msg))
if self.debug:
print ("'" + self.orig_line + "'")
@ -173,12 +222,12 @@ if len (sys.argv) < 1:
print ("Usage : yada yada")
sys.exit (1)
# Create a new cstyle_checker object
# Create a new CStyleChecker object
if sys.argv [1] == '-d' or sys.argv [1] == '--debug':
cstyle = cstyle_checker (True)
cstyle = CStyleChecker (True)
cstyle.check_files (sys.argv [2:])
else:
cstyle = cstyle_checker (False)
cstyle = CStyleChecker (False)
cstyle.check_files (sys.argv [1:])