cs.py 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329
  1. #
  2. # Metrix++, Copyright 2009-2013, Metrix++ Project
  3. # Link: http://metrixplusplus.sourceforge.net
  4. #
  5. # This file is a part of Metrix++ Tool.
  6. #
  7. # Metrix++ is free software: you can redistribute it and/or modify
  8. # it under the terms of the GNU General Public License as published by
  9. # the Free Software Foundation, version 3 of the License.
  10. #
  11. # Metrix++ is distributed in the hope that it will be useful,
  12. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. # GNU General Public License for more details.
  15. #
  16. # You should have received a copy of the GNU General Public License
  17. # along with Metrix++. If not, see <http://www.gnu.org/licenses/>.
  18. #
  19. import re
  20. import binascii
  21. import mpp.api
  22. import mpp.cout
  23. class Plugin(mpp.api.Plugin, mpp.api.Parent, mpp.api.IParser, mpp.api.IConfigurable, mpp.api.ICode):
  24. def declare_configuration(self, parser):
  25. parser.add_option("--std.code.cs.files", default="*.cs",
  26. help="Enumerates filename extensions to match C# files [default: %default]")
  27. def configure(self, options):
  28. self.files = options.__dict__['std.code.cs.files'].split(',')
  29. self.files.sort() # sorted list goes to properties
  30. def initialize(self):
  31. mpp.api.Plugin.initialize(self, properties=[
  32. self.Property('files', ','.join(self.files))
  33. ])
  34. self.get_plugin('std.tools.collect').register_parser(self.files, self)
  35. def process(self, parent, data, is_updated):
  36. is_updated = is_updated or self.is_updated
  37. count_mismatched_brackets = 0
  38. if is_updated == True:
  39. count_mismatched_brackets = CsCodeParser().run(data)
  40. self.notify_children(data, is_updated)
  41. return count_mismatched_brackets
  42. class CsCodeParser(object):
  43. regex_cpp = re.compile(r'''
  44. //(?=\n|\r\n|\r) # Match C# style comments (empty comment line)
  45. | //.*?(?=\n|\r\n|\r) # Match C# style comments
  46. # NOTE: end of line is NOT consumed
  47. # NOTE: it is slightly different in C++
  48. | /\*\*/ # Match C style comments (empty comment line)
  49. # NOTE: it is slightly different in C++
  50. | /\*.*?\*/ # Match C style comments
  51. # NOTE: it is slightly different in C++
  52. | \'(?:\\.|[^\\\'])*\' # Match quoted strings
  53. | "(?:\\.|[^\\"])*" # Match double quoted strings
  54. | (((?<=\n|\r)|^)[ \t]*[#].*?(?=\n|\r\n|\r)) # Match preprocessor
  55. # NOTE: end of line is NOT consumed
  56. # NOTE: beginning of line is NOT consumed
  57. # NOTE: C# does not support backslashing as C++ does
  58. | (?P<fn_name>
  59. (operator( # Match C# operator ...
  60. (\s+[_a-zA-Z][_a-zA-Z0-9]*(\s*\[\s*\])?) # - cast, true, false operators
  61. | (\s*\[\s*\]) # - operator []
  62. | (\s*\(\s*\)) # - operator ()
  63. | (\s*[+-\\*/=<>!%&^|~,?.]{1,3}) # - other operators (from 1 to 3 symbols)
  64. # NOTE: maybe dot and ? should not be in the list...
  65. ))
  66. | (([~]\s*)?[_a-zA-Z][_a-zA-Z0-9]* # ... or function or constructor
  67. (\s*[<]\s*[_a-zA-Z0-9]+\s*([,]\s*[_a-zA-Z0-9]+\s*)*[>])? # NOTE: takes care of generics with multiple parameters
  68. (\s*[.]\s*[a-zA-Z_][a-zA-Z0-9_]*
  69. (\s*[<]\s*[_a-zA-Z0-9]+\s*([,]\s*[_a-zA-Z0-9]+\s*)*[>])?)*) # NOTE: takes care of generics with multiple parameters
  70. # NOTE: C# destructor can have spaces in name after ~
  71. # NOTE: explicit interface implementation method has got a dot
  72. | (?P<prop_setget>get|set) # ... or property setter/getter
  73. )\s*(?(prop_setget)(?=[{])|[(])
  74. # LIMITATION: if there are comments after function name
  75. # and before '(', it is not detected
  76. # LIMITATION: if there are comments within operator definition,
  77. # if may be not detected
  78. # LIMITATION: if there are comments after set|get keyword,
  79. # if may be not detected
  80. | ((?P<block_type>\bclass|\bstruct|\bnamespace|\binterface) # Match class or struct or interface or namespace
  81. (?P<block_name>(\s+[a-zA-Z_][a-zA-Z0-9_]*
  82. (\s*[<]\s*[_a-zA-Z0-9]+\s*([,]\s*[_a-zA-Z0-9]+\s*)*[>])? # NOTE: takes care of generics with multiple parameters
  83. )))
  84. # NOTE: noname instances are impossible in C#
  85. # LIMITATION: if there are comments between keyword and name,
  86. # it is not detected
  87. | [\[\]{};] # Match block start/end and statement separator
  88. # NOTE: C++ parser includes processing of <> and :
  89. # to handle template definitions, it is easier in C#
  90. | ((?:\n|\r\n|\r)\s*(?:\n|\r\n|\r)) # Match double empty line
  91. ''',
  92. re.DOTALL | re.MULTILINE | re.VERBOSE
  93. )
  94. # \r\n goes before \r in order to consume right number of lines on Unix for Windows files
  95. regex_ln = re.compile(r'(\n)|(\r\n)|(\r)')
  96. def run(self, data):
  97. self.__init__() # Go to initial state if it is called twice
  98. return self.parse(data)
  99. def finalize_block(self, text, block, block_end):
  100. if block['type'] != '__global__':
  101. # do not trim spaces for __global__region
  102. space_match = re.match('^\s*', text[block['start']:block_end], re.MULTILINE)
  103. block['start'] += space_match.end() # trim spaces at the beginning
  104. block['end'] = block_end
  105. start_pos = block['start']
  106. crc32 = 0
  107. for child in block['children']:
  108. # exclude children
  109. crc32 = binascii.crc32(text[start_pos:child['start']], crc32)
  110. start_pos = child['end']
  111. block['checksum'] = binascii.crc32(text[start_pos:block['end']], crc32) & 0xffffffff # to match python 3
  112. def add_lines_data(self, text, blocks):
  113. def add_lines_data_rec(self, text, blocks):
  114. for each in blocks:
  115. # add line begin
  116. self.total_current += len(self.regex_ln.findall(text, self.total_last_pos, each['start']))
  117. each['line_begin'] = self.total_current
  118. self.total_last_pos = each['start']
  119. # process enclosed
  120. add_lines_data_rec(self, text, each['children'])
  121. # add line end
  122. self.total_current += len(self.regex_ln.findall(text, self.total_last_pos, each['end']))
  123. each['line_end'] = self.total_current
  124. self.total_last_pos = each['end']
  125. self.total_last_pos = 0
  126. self.total_current = 1
  127. add_lines_data_rec(self, text, blocks)
  128. def add_regions(self, data, blocks):
  129. # Note: data.add_region() internals depend on special ordering of regions
  130. # in order to identify enclosed regions efficiently
  131. def add_regions_rec(self, data, blocks):
  132. def get_type_id(data, named_type):
  133. if named_type == "function":
  134. return mpp.api.Region.T.FUNCTION
  135. elif named_type == "class":
  136. return mpp.api.Region.T.CLASS
  137. elif named_type == "struct":
  138. return mpp.api.Region.T.STRUCT
  139. elif named_type == "namespace":
  140. return mpp.api.Region.T.NAMESPACE
  141. elif named_type == "interface":
  142. return mpp.api.Region.T.INTERFACE
  143. elif named_type == "__global__":
  144. return mpp.api.Region.T.GLOBAL
  145. else:
  146. assert(False)
  147. for each in blocks:
  148. data.add_region(each['name'], each['start'], each['end'],
  149. each['line_begin'], each['line_end'], each['cursor'],
  150. get_type_id(data, each['type']), each['checksum'])
  151. add_regions_rec(self, data, each['children'])
  152. add_regions_rec(self, data, blocks)
  153. def parse(self, data):
  154. def reset_next_block(start):
  155. return {'name':'', 'start':start, 'cursor':0, 'type':'', 'inside_attribute':False}
  156. count_mismatched_brackets = 0
  157. text = data.get_content()
  158. indent_current = 0;
  159. blocks = [{'name':'__global__', 'start':0, 'cursor':0, 'type':'__global__', 'indent_start':indent_current, 'children':[]}]
  160. curblk = 0
  161. next_block = reset_next_block(0)
  162. cursor_last_pos = 0
  163. cursor_current = 1
  164. for m in re.finditer(self.regex_cpp, text):
  165. # Comment
  166. if text[m.start()] == '/':
  167. data.add_marker(m.start(), m.end(), mpp.api.Marker.T.COMMENT)
  168. # String
  169. elif text[m.start()] == '"' or text[m.start()] == '\'':
  170. data.add_marker(m.start() + 1, m.end() - 1, mpp.api.Marker.T.STRING)
  171. # Preprocessor (including internal comments)
  172. elif text[m.start()] == ' ' or text[m.start()] == '\t' or text[m.start()] == '#':
  173. data.add_marker(m.start(), m.end(), mpp.api.Marker.T.PREPROCESSOR)
  174. # Statement end
  175. elif text[m.start()] == ';':
  176. # Reset next block name and start
  177. next_block['name'] = ""
  178. next_block['start'] = m.end() # potential region start
  179. # Block openned by '[' bracket...
  180. elif text[m.start()] == '[':
  181. # ... may include attributes, so do not capture function names inside
  182. next_block['inside_attribute'] = True
  183. # Block closed by ']' bracket...
  184. # note: do not care about nesting for simplicity -
  185. # because attribute's statement can not have symbol ']' inside
  186. elif text[m.start()] == ']':
  187. # ... may include attributes, so do not capture function names inside
  188. next_block['inside_attribute'] = False
  189. # Double end line
  190. elif text[m.start()] == '\n' or text[m.start()] == '\r':
  191. # Reset next block start, if has not been named yet
  192. if next_block['name'] == "":
  193. next_block['start'] = m.end() # potential region start
  194. # Block start...
  195. elif text[m.start()] == '{':
  196. # shift indent right
  197. indent_current += 1
  198. # ... if name detected previously
  199. if next_block['name'] != '': # - Start of enclosed block
  200. blocks.append({'name':next_block['name'],
  201. 'start':next_block['start'],
  202. 'cursor':next_block['cursor'],
  203. 'type':next_block['type'],
  204. 'indent_start':indent_current,
  205. 'children':[]})
  206. next_block = reset_next_block(m.end())
  207. curblk += 1
  208. # ... reset next block start, otherwise
  209. else: # - unknown type of block start
  210. next_block['start'] = m.end() # potential region start
  211. # Block end...
  212. elif text[m.start()] == '}':
  213. # ... if indent level matches the start
  214. if blocks[curblk]['indent_start'] == indent_current:
  215. next_block = reset_next_block(m.end())
  216. if curblk == 0:
  217. mpp.cout.notify(data.get_path(),
  218. cursor_current + len(self.regex_ln.findall(text, cursor_last_pos, m.start())),
  219. mpp.cout.SEVERITY_WARNING,
  220. "Non-matching closing bracket '}' detected.")
  221. count_mismatched_brackets += 1
  222. continue
  223. self.finalize_block(text, blocks[curblk], m.end())
  224. assert(blocks[curblk]['type'] != '__global__')
  225. curblk -= 1
  226. assert(curblk >= 0)
  227. blocks[curblk]['children'].append(blocks.pop())
  228. # shift indent left
  229. indent_current -= 1
  230. if indent_current < 0:
  231. mpp.cout.notify(data.get_path(),
  232. cursor_current + len(self.regex_ln.findall(text, cursor_last_pos, m.start())),
  233. mpp.cout.SEVERITY_WARNING,
  234. "Non-matching closing bracket '}' detected.")
  235. count_mismatched_brackets += 1
  236. indent_current = 0
  237. # Potential namespace, struct, class, interface
  238. elif m.group('block_type') != None:
  239. if next_block['name'] == "":
  240. # - 'name'
  241. clearance_pattern = re.compile(r'\s+')
  242. next_block['name'] = clearance_pattern.sub('',m.group('block_name'))
  243. # - 'cursor'
  244. cursor_current += len(self.regex_ln.findall(text, cursor_last_pos, m.start('block_name')))
  245. cursor_last_pos = m.start('block_name')
  246. next_block['cursor'] = cursor_current
  247. # - 'type'
  248. next_block['type'] = m.group('block_type').strip()
  249. # - 'start' detected earlier
  250. # Potential function name detected...
  251. elif m.group('fn_name') != None:
  252. # ... if outside of a function
  253. # (do not detect functions enclosed directly in a function, i.e. without classes)
  254. # ... and other name before has not been matched
  255. if blocks[curblk]['type'] != 'function' and (next_block['name'] == "") \
  256. and next_block['inside_attribute'] == False:
  257. # - 'name'
  258. clearance_pattern = re.compile(r'\s+')
  259. next_block['name'] = clearance_pattern.sub('', m.group('fn_name'))
  260. # - 'cursor'
  261. cursor_current += len(self.regex_ln.findall(text, cursor_last_pos, m.start('fn_name')))
  262. cursor_last_pos = m.start('fn_name')
  263. # NOTE: cursor could be collected together with line_begin, line_end,
  264. # but we keep it here separately for easier debugging of file parsing problems
  265. next_block['cursor'] = cursor_current
  266. # - 'type'
  267. next_block['type'] = 'function'
  268. # - 'start' detected earlier
  269. else:
  270. assert(len("Unknown match by regular expression") == 0)
  271. while indent_current > 0:
  272. # log all
  273. mpp.cout.notify(data.get_path(),
  274. cursor_current + len(self.regex_ln.findall(text, cursor_last_pos, len(text))),
  275. mpp.cout.SEVERITY_WARNING,
  276. "Non-matching opening bracket '{' detected.")
  277. count_mismatched_brackets += 1
  278. indent_current -= 1
  279. for (ind, each) in enumerate(blocks):
  280. each = each # used
  281. block = blocks[len(blocks) - 1 - ind]
  282. self.finalize_block(text, block, len(text))
  283. self.add_lines_data(text, blocks)
  284. self.add_regions(data, blocks)
  285. return count_mismatched_brackets