/Scripts/cstyle.py
Python | 256 lines | 207 code | 16 blank | 33 comment | 8 complexity | e97559d999a3066857cbbc4404992cab MD5 | raw file
Possible License(s): LGPL-2.1, Apache-2.0
- #!/usr/bin/python -tt
- #
- # Copyright (C) 2005-2017 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.
- """
- import re
- import sys
- 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 __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)
- # Strip C style comments.
- open_comment = line.find ('/*')
- close_comment = line.find ('*/')
- if self.comment_nest > 0 and close_comment < 0:
- # Inside a comment block that does not close on this line.
- return ""
- if open_comment >= 0 and close_comment < 0:
- # A comment begins on this line but doesn't close on this line.
- self.comment_nest += 1
- return self.trailing_space_re.sub ('', line [:open_comment])
- if open_comment < 0 and close_comment >= 0:
- # Currently open comment ends on this line.
- self.comment_nest -= 1
- return self.trailing_space_re.sub ('', line [close_comment + 2:])
- if open_comment >= 0 and close_comment > 0 and self.comment_nest == 0:
- # Comment begins and ends on this line. Replace it with 'comment'
- # 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.__call__ (newline)
- return line
- 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 (";[^\s]"), "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" )
- , ( re.compile ("\( [^;]"), "space after open parenthesis" )
- , ( re.compile ("[^;] \)"), "space before close parenthesis" )
- # Open and close square brace.
- , ( re.compile ("[^\s\(\]]\["), "missing space before open square brace" )
- , ( re.compile ("\][^,\)\]\[\s\.-]"), "missing space after close square brace" )
- , ( re.compile ("\[ "), "space after open square brace" )
- , ( re.compile (" \]"), "space before close square brace" )
- # Space around operators.
- , ( re.compile ("[^\s][\*/%+-][=][^\s]"), "missing space around opassign" )
- , ( re.compile ("[^\s][<>!=^/][=]{1,2}[^\s]"), "missing space around comparison" )
- # Parens around single argument to return.
- , ( re.compile ("\s+return\s+\([a-zA-Z0-9_]+\)\s+;"), "parens around return value" )
- # Parens around single case argument.
- , ( re.compile ("\s+case\s+\([a-zA-Z0-9_]+\)\s+:"), "parens around single case argument" )
- # Open curly at end of line.
- , ( re.compile ("\)\s*{\s*$"), "open curly brace at end of line" )
- # Pre and post increment/decrment.
- , ( re.compile ("[^\(\[][+-]{2}[a-zA-Z0-9_]"), "space after pre increment/decrement" )
- , ( re.compile ("[a-zA-Z0-9_][+-]{2}[^\)\,]]"), "space before post increment/decrement" )
- ]
- 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
- cfile = open (filename, "r")
- self.line_num = 1
- preprocess = Preprocessor ()
- while 1:
- line = cfile.readline ()
- if not line:
- break
- line = self.trailing_newline_re.sub ('', line)
- self.orig_line = line
- self.line_checks (preprocess (line))
- self.line_num += 1
- cfile.close ()
- self.filename = None
- # Check for errors finding comments.
- if preprocess.comment_nesting () != 0:
- print ("Weird, comments nested incorrectly.")
- sys.exit (1)
- return
- def line_checks (self, line):
- """
- Run the style checker on provided line of text, but within the context
- of how the line fits within the file.
- """
- 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
- # 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")
- 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 + "'")
- self.error_count += 1
- #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
- if len (sys.argv) < 1:
- print ("Usage : yada yada")
- sys.exit (1)
- # Create a new CStyleChecker object
- if sys.argv [1] == '-d' or sys.argv [1] == '--debug':
- cstyle = CStyleChecker (True)
- cstyle.check_files (sys.argv [2:])
- else:
- cstyle = CStyleChecker (False)
- cstyle.check_files (sys.argv [1:])
- if cstyle.get_error_count ():
- sys.exit (1)
- sys.exit (0)