PageRenderTime 62ms CodeModel.GetById 19ms RepoModel.GetById 1ms app.codeStats 0ms

/Scripts/cstyle.py

http://github.com/erikd/libsndfile
Python | 256 lines | 207 code | 16 blank | 33 comment | 8 complexity | e97559d999a3066857cbbc4404992cab MD5 | raw file
Possible License(s): LGPL-2.1, Apache-2.0
  1. #!/usr/bin/python -tt
  2. #
  3. # Copyright (C) 2005-2017 Erik de Castro Lopo <erikd@mega-nerd.com>
  4. #
  5. # Released under the 2 clause BSD license.
  6. """
  7. This program checks C code for compliance to coding standards used in
  8. libsndfile and other projects I run.
  9. """
  10. import re
  11. import sys
  12. class Preprocessor:
  13. """
  14. Preprocess lines of C code to make it easier for the CStyleChecker class to
  15. test for correctness. Preprocessing works on a single line at a time but
  16. maintains state between consecutive lines so it can preprocessess multi-line
  17. comments.
  18. Preprocessing involves:
  19. - Strip C++ style comments from a line.
  20. - Strip C comments from a series of lines. When a C comment starts and
  21. ends on the same line it will be replaced with 'comment'.
  22. - Replace arbitrary C strings with the zero length string.
  23. - Replace '#define f(x)' with '#define f (c)' (The C #define requires that
  24. there be no space between defined macro name and the open paren of the
  25. argument list).
  26. Used by the CStyleChecker class.
  27. """
  28. def __init__ (self):
  29. self.comment_nest = 0
  30. self.leading_space_re = re.compile ('^(\t+| )')
  31. self.trailing_space_re = re.compile ('(\t+| )$')
  32. self.define_hack_re = re.compile ("(#\s*define\s+[a-zA-Z0-9_]+)\(")
  33. def comment_nesting (self):
  34. """
  35. Return the currect comment nesting. At the start and end of the file,
  36. this value should be zero. Inside C comments it should be 1 or
  37. (possibly) more.
  38. """
  39. return self.comment_nest
  40. def __call__ (self, line):
  41. """
  42. Strip the provided line of C and C++ comments. Stripping of multi-line
  43. C comments works as expected.
  44. """
  45. line = self.define_hack_re.sub (r'\1 (', line)
  46. line = self.process_strings (line)
  47. # Strip C++ style comments.
  48. if self.comment_nest == 0:
  49. line = re.sub ("( |\t*)//.*", '', line)
  50. # Strip C style comments.
  51. open_comment = line.find ('/*')
  52. close_comment = line.find ('*/')
  53. if self.comment_nest > 0 and close_comment < 0:
  54. # Inside a comment block that does not close on this line.
  55. return ""
  56. if open_comment >= 0 and close_comment < 0:
  57. # A comment begins on this line but doesn't close on this line.
  58. self.comment_nest += 1
  59. return self.trailing_space_re.sub ('', line [:open_comment])
  60. if open_comment < 0 and close_comment >= 0:
  61. # Currently open comment ends on this line.
  62. self.comment_nest -= 1
  63. return self.trailing_space_re.sub ('', line [close_comment + 2:])
  64. if open_comment >= 0 and close_comment > 0 and self.comment_nest == 0:
  65. # Comment begins and ends on this line. Replace it with 'comment'
  66. # so we don't need to check whitespace before and after the comment
  67. # we're removing.
  68. newline = line [:open_comment] + "comment" + line [close_comment + 2:]
  69. return self.__call__ (newline)
  70. return line
  71. def process_strings (self, line):
  72. """
  73. Given a line of C code, return a string where all literal C strings have
  74. been replaced with the empty string literal "".
  75. """
  76. for k in range (0, len (line)):
  77. if line [k] == '"':
  78. start = k
  79. for k in range (start + 1, len (line)):
  80. if line [k] == '"' and line [k - 1] != '\\':
  81. return line [:start + 1] + '"' + self.process_strings (line [k + 1:])
  82. return line
  83. class CStyleChecker:
  84. """
  85. A class for checking the whitespace and layout of a C code.
  86. """
  87. def __init__ (self, debug):
  88. self.debug = debug
  89. self.filename = None
  90. self.error_count = 0
  91. self.line_num = 1
  92. self.orig_line = ''
  93. self.trailing_newline_re = re.compile ('[\r\n]+$')
  94. self.indent_re = re.compile ("^\s*")
  95. self.last_line_indent = ""
  96. self.last_line_indent_curly = False
  97. self.re_checks = \
  98. [ ( re.compile (" "), "multiple space instead of tab" )
  99. , ( re.compile ("\t "), "space after tab" )
  100. , ( re.compile ("[^ ];"), "missing space before semi-colon" )
  101. , ( re.compile ("{[^\s}]"), "missing space after open brace" )
  102. , ( re.compile ("[^{\s]}"), "missing space before close brace" )
  103. , ( re.compile ("[ \t]+$"), "contains trailing whitespace" )
  104. , ( re.compile (",[^\s\n]"), "missing space after comma" )
  105. , ( re.compile (";[^\s]"), "missing space after semi-colon" )
  106. , ( re.compile ("=[^\s\"'=]"), "missing space after assignment" )
  107. # Open and close parenthesis.
  108. , ( re.compile ("[^\s\(\[\*&']\("), "missing space before open parenthesis" )
  109. , ( re.compile ("\)(-[^>]|[^,'\s\n\)\]-])"), "missing space after close parenthesis" )
  110. , ( re.compile ("\s(do|for|if|when)\s.*{$"), "trailing open parenthesis at end of line" )
  111. , ( re.compile ("\( [^;]"), "space after open parenthesis" )
  112. , ( re.compile ("[^;] \)"), "space before close parenthesis" )
  113. # Open and close square brace.
  114. , ( re.compile ("[^\s\(\]]\["), "missing space before open square brace" )
  115. , ( re.compile ("\][^,\)\]\[\s\.-]"), "missing space after close square brace" )
  116. , ( re.compile ("\[ "), "space after open square brace" )
  117. , ( re.compile (" \]"), "space before close square brace" )
  118. # Space around operators.
  119. , ( re.compile ("[^\s][\*/%+-][=][^\s]"), "missing space around opassign" )
  120. , ( re.compile ("[^\s][<>!=^/][=]{1,2}[^\s]"), "missing space around comparison" )
  121. # Parens around single argument to return.
  122. , ( re.compile ("\s+return\s+\([a-zA-Z0-9_]+\)\s+;"), "parens around return value" )
  123. # Parens around single case argument.
  124. , ( re.compile ("\s+case\s+\([a-zA-Z0-9_]+\)\s+:"), "parens around single case argument" )
  125. # Open curly at end of line.
  126. , ( re.compile ("\)\s*{\s*$"), "open curly brace at end of line" )
  127. # Pre and post increment/decrment.
  128. , ( re.compile ("[^\(\[][+-]{2}[a-zA-Z0-9_]"), "space after pre increment/decrement" )
  129. , ( re.compile ("[a-zA-Z0-9_][+-]{2}[^\)\,]]"), "space before post increment/decrement" )
  130. ]
  131. def get_error_count (self):
  132. """
  133. Return the current error count for this CStyleChecker object.
  134. """
  135. return self.error_count
  136. def check_files (self, files):
  137. """
  138. Run the style checker on all the specified files.
  139. """
  140. for filename in files:
  141. self.check_file (filename)
  142. def check_file (self, filename):
  143. """
  144. Run the style checker on the specified file.
  145. """
  146. self.filename = filename
  147. cfile = open (filename, "r")
  148. self.line_num = 1
  149. preprocess = Preprocessor ()
  150. while 1:
  151. line = cfile.readline ()
  152. if not line:
  153. break
  154. line = self.trailing_newline_re.sub ('', line)
  155. self.orig_line = line
  156. self.line_checks (preprocess (line))
  157. self.line_num += 1
  158. cfile.close ()
  159. self.filename = None
  160. # Check for errors finding comments.
  161. if preprocess.comment_nesting () != 0:
  162. print ("Weird, comments nested incorrectly.")
  163. sys.exit (1)
  164. return
  165. def line_checks (self, line):
  166. """
  167. Run the style checker on provided line of text, but within the context
  168. of how the line fits within the file.
  169. """
  170. indent = len (self.indent_re.search (line).group ())
  171. if re.search ("^\s+}", line):
  172. if not self.last_line_indent_curly and indent != self.last_line_indent:
  173. None # self.error ("bad indent on close curly brace")
  174. self.last_line_indent_curly = True
  175. else:
  176. self.last_line_indent_curly = False
  177. # Now all the regex checks.
  178. for (check_re, msg) in self.re_checks:
  179. if check_re.search (line):
  180. self.error (msg)
  181. if re.search ("[a-zA-Z0-9][<>!=^/&\|]{1,2}[a-zA-Z0-9]", line):
  182. if not re.search (".*#include.*[a-zA-Z0-9]/[a-zA-Z]", line):
  183. self.error ("missing space around operator")
  184. self.last_line_indent = indent
  185. return
  186. def error (self, msg):
  187. """
  188. Print an error message and increment the error count.
  189. """
  190. print ("%s (%d) : %s" % (self.filename, self.line_num, msg))
  191. if self.debug:
  192. print ("'" + self.orig_line + "'")
  193. self.error_count += 1
  194. #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
  195. if len (sys.argv) < 1:
  196. print ("Usage : yada yada")
  197. sys.exit (1)
  198. # Create a new CStyleChecker object
  199. if sys.argv [1] == '-d' or sys.argv [1] == '--debug':
  200. cstyle = CStyleChecker (True)
  201. cstyle.check_files (sys.argv [2:])
  202. else:
  203. cstyle = CStyleChecker (False)
  204. cstyle.check_files (sys.argv [1:])
  205. if cstyle.get_error_count ():
  206. sys.exit (1)
  207. sys.exit (0)