3 # Copyright (C) 2005-2012 Erik de Castro Lopo <erikd@mega-nerd.com>
5 # Released under the 2 clause BSD license.
8 This program checks C code for compliance to coding standards used in
9 libsndfile and other projects I run.
18 Preprocess lines of C code to make it easier for the CStyleChecker class to
19 test for correctness. Preprocessing works on a single line at a time but
20 maintains state between consecutive lines so it can preprocessess multi-line
22 Preprocessing involves:
23 - Strip C++ style comments from a line.
24 - Strip C comments from a series of lines. When a C comment starts and
25 ends on the same line it will be replaced with 'comment'.
26 - Replace arbitrary C strings with the zero length string.
27 - Replace '#define f(x)' with '#define f (c)' (The C #define requires that
28 there be no space between defined macro name and the open paren of the
30 Used by the CStyleChecker class.
34 self.leading_space_re = re.compile ('^(\t+| )')
35 self.trailing_space_re = re.compile ('(\t+| )$')
36 self.define_hack_re = re.compile ("(#\s*define\s+[a-zA-Z0-9_]+)\(")
38 def comment_nesting (self):
40 Return the currect comment nesting. At the start and end of the file,
41 this value should be zero. Inside C comments it should be 1 or
44 return self.comment_nest
46 def __call__ (self, line):
48 Strip the provided line of C and C++ comments. Stripping of multi-line
49 C comments works as expected.
52 line = self.define_hack_re.sub (r'\1 (', line)
54 line = self.process_strings (line)
56 # Strip C++ style comments.
57 if self.comment_nest == 0:
58 line = re.sub ("( |\t*)//.*", '', line)
60 # Strip C style comments.
61 open_comment = line.find ('/*')
62 close_comment = line.find ('*/')
64 if self.comment_nest > 0 and close_comment < 0:
65 # Inside a comment block that does not close on this line.
68 if open_comment >= 0 and close_comment < 0:
69 # A comment begins on this line but doesn't close on this line.
70 self.comment_nest += 1
71 return self.trailing_space_re.sub ('', line [:open_comment])
73 if open_comment < 0 and close_comment >= 0:
74 # Currently open comment ends on this line.
75 self.comment_nest -= 1
76 return self.trailing_space_re.sub ('', line [close_comment + 2:])
78 if open_comment >= 0 and close_comment > 0 and self.comment_nest == 0:
79 # Comment begins and ends on this line. Replace it with 'comment'
80 # so we don't need to check whitespace before and after the comment
82 newline = line [:open_comment] + "comment" + line [close_comment + 2:]
83 return self.__call__ (newline)
87 def process_strings (self, line):
89 Given a line of C code, return a string where all literal C strings have
90 been replaced with the empty string literal "".
92 for k in range (0, len (line)):
95 for k in range (start + 1, len (line)):
96 if line [k] == '"' and line [k - 1] != '\\':
97 return line [:start + 1] + '"' + self.process_strings (line [k + 1:])
103 A class for checking the whitespace and layout of a C code.
105 def __init__ (self, debug):
111 self.trailing_newline_re = re.compile ('[\r\n]+$')
112 self.indent_re = re.compile ("^\s*")
113 self.last_line_indent = ""
114 self.last_line_indent_curly = False
116 [ ( re.compile ("^ "), "leading space as indentation instead of tab - use tabs to indent, spaces to align" )
117 , ( re.compile ("{[^\s]"), "missing space after open brace" )
118 , ( re.compile ("[^\s]}"), "missing space before close brace" )
119 , ( re.compile ("[ \t]+$"), "contains trailing whitespace" )
121 , ( re.compile (",[^\s\n]"), "missing space after comma" )
122 , ( re.compile (";[a-zA-Z0-9]"), "missing space after semi-colon" )
123 , ( re.compile ("=[^\s\"'=]"), "missing space after assignment" )
125 # Open and close parenthesis.
126 , ( re.compile ("[^\s\(\[\*&']\("), "missing space before open parenthesis" )
127 , ( re.compile ("\)(-[^>]|[^,'\s\n\)\]-])"), "missing space after close parenthesis" )
128 , ( re.compile ("\( [^;]"), "space after open parenthesis" )
129 , ( re.compile ("[^;] \)"), "space before close parenthesis" )
131 # Open and close square brace.
132 , ( re.compile ("\[ "), "space after open square brace" )
133 , ( re.compile (" \]"), "space before close square brace" )
135 # Space around operators.
136 , ( re.compile ("[^\s][\*/%+-][=][^\s]"), "missing space around opassign" )
137 , ( re.compile ("[^\s][<>!=^/][=]{1,2}[^\s]"), "missing space around comparison" )
139 # Parens around single argument to return.
140 , ( re.compile ("\s+return\s+\([a-zA-Z0-9_]+\)\s+;"), "parens around return value" )
144 def get_error_count (self):
146 Return the current error count for this CStyleChecker object.
148 return self.error_count
150 def check_files (self, files):
152 Run the style checker on all the specified files.
154 for filename in files:
155 self.check_file (filename)
157 def check_file (self, filename):
159 Run the style checker on the specified file.
161 self.filename = filename
163 cfile = open (filename, "r")
169 preprocess = Preprocessor ()
171 line = cfile.readline ()
175 line = self.trailing_newline_re.sub ('', line)
176 self.orig_line = line
178 self.line_checks (preprocess (line))
185 # Check for errors finding comments.
186 if preprocess.comment_nesting () != 0:
187 print ("Weird, comments nested incorrectly.")
192 def line_checks (self, line):
194 Run the style checker on provided line of text, but within the context
195 of how the line fits within the file.
198 indent = len (self.indent_re.search (line).group ())
199 if re.search ("^\s+}", line):
200 if not self.last_line_indent_curly and indent != self.last_line_indent:
201 None # self.error ("bad indent on close curly brace")
202 self.last_line_indent_curly = True
204 self.last_line_indent_curly = False
206 # Now all the regex checks.
207 for (check_re, msg) in self.re_checks:
208 if check_re.search (line):
211 if re.search ("[a-zA-Z0-9][<>!=^/&\|]{1,2}[a-zA-Z0-9]", line):
212 if not re.search (".*#include.*[a-zA-Z0-9]/[a-zA-Z]", line):
213 self.error ("missing space around operator")
215 self.last_line_indent = indent
218 def error (self, msg):
220 Print an error message and increment the error count.
222 print ("%s (%d) : %s" % (self.filename, self.line_num, msg))
224 print ("'" + self.orig_line + "'")
225 self.error_count += 1
227 #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
229 if len (sys.argv) < 1:
230 print ("Usage : yada yada")
233 # Create a new CStyleChecker object
234 if sys.argv [1] == '-d' or sys.argv [1] == '--debug':
235 cstyle = CStyleChecker (True)
236 cstyle.check_files (sys.argv [2:])
238 cstyle = CStyleChecker (False)
239 cstyle.check_files (sys.argv [1:])
242 if cstyle.get_error_count ():