annotate_ifdef_directives 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119
  1. #!/usr/bin/python
  2. # Copyright (c) 2017-2019, The Tor Project, Inc.
  3. # See LICENSE for licensing information
  4. # This script iterates over a list of C files. For each file, it looks at the
  5. # #if/#else C macros, and annotates them with comments explaining what they
  6. # match.
  7. #
  8. # For example, it replaces this:
  9. #
  10. # #ifdef HAVE_OCELOT
  11. # // 500 lines of ocelot code
  12. # #endif
  13. #
  14. # with this:
  15. #
  16. # #ifdef HAVE_OCELOT
  17. # // 500 lines of ocelot code
  18. # #endif /* defined(HAVE_OCELOT) */
  19. #
  20. # Note that only #else and #endif lines are annotated. Existing comments
  21. # on those lines are removed.
  22. import re
  23. # Any block with fewer than this many lines does not need annotations.
  24. LINE_OBVIOUSNESS_LIMIT = 4
  25. class Problem(Exception):
  26. pass
  27. def commented_line(fmt, argument):
  28. """
  29. Return fmt%argument, for use as a commented line.
  30. """
  31. return fmt % argument
  32. def uncomment(s):
  33. """
  34. Remove existing trailing comments from an #else or #endif line.
  35. """
  36. s = re.sub(r'//.*','',s)
  37. s = re.sub(r'/\*.*','',s)
  38. return s.strip()
  39. def translate(f_in, f_out):
  40. """
  41. Read a file from f_in, and write its annotated version to f_out.
  42. """
  43. # A stack listing our current if/else state. Each member of the stack
  44. # is a list of directives. Each directive is a 3-tuple of
  45. # (command, rest, lineno)
  46. # where "command" is one of if/ifdef/ifndef/else/elif, and where
  47. # "rest" is an expression in a format suitable for use with #if, and where
  48. # lineno is the line number where the directive occurred.
  49. stack = []
  50. # the stack element corresponding to the top level of the file.
  51. whole_file = []
  52. cur_level = whole_file
  53. lineno = 0
  54. for line in f_in:
  55. lineno += 1
  56. m = re.match(r'\s*#\s*(if|ifdef|ifndef|else|endif|elif)\b\s*(.*)',
  57. line)
  58. if not m:
  59. # no directive, so we can just write it out.
  60. f_out.write(line)
  61. continue
  62. command,rest = m.groups()
  63. if command in ("if", "ifdef", "ifndef"):
  64. # The #if directive pushes us one level lower on the stack.
  65. if command == 'ifdef':
  66. rest = "defined(%s)"%uncomment(rest)
  67. elif command == 'ifndef':
  68. rest = "!defined(%s)"%uncomment(rest)
  69. elif rest.endswith("\\"):
  70. rest = rest[:-1]+"..."
  71. rest = uncomment(rest)
  72. new_level = [ (command, rest, lineno) ]
  73. stack.append(cur_level)
  74. cur_level = new_level
  75. f_out.write(line)
  76. elif command in ("else", "elif"):
  77. # We stay at the same level on the stack. If we have an #else,
  78. # we comment it.
  79. if len(cur_level) == 0 or cur_level[-1][0] == 'else':
  80. raise Problem("Unexpected #%s on %d"% (command,lineno))
  81. if (len(cur_level) == 1 and command == 'else' and
  82. lineno > cur_level[0][2] + LINE_OBVIOUSNESS_LIMIT):
  83. f_out.write(commented_line("#else /* !(%s) */\n",
  84. cur_level[0][1]))
  85. else:
  86. f_out.write(line)
  87. cur_level.append((command, rest, lineno))
  88. else:
  89. # We pop one element on the stack, and comment an endif.
  90. assert command == 'endif'
  91. if len(stack) == 0:
  92. raise Problem("Unmatched #%s on %s"% (command,lineno))
  93. if lineno <= cur_level[0][2] + LINE_OBVIOUSNESS_LIMIT:
  94. f_out.write(line)
  95. elif len(cur_level) == 1 or (
  96. len(cur_level) == 2 and cur_level[1][0] == 'else'):
  97. f_out.write(commented_line("#endif /* %s */\n",
  98. cur_level[0][1]))
  99. else:
  100. f_out.write(commented_line("#endif /* %s || ... */\n",
  101. cur_level[0][1]))
  102. cur_level = stack.pop()
  103. if len(stack) or cur_level != whole_file:
  104. raise Problem("Missing #endif")
  105. import sys,os
  106. for fn in sys.argv[1:]:
  107. with open(fn+"_OUT", 'w') as output_file:
  108. translate(open(fn, 'r'), output_file)
  109. os.rename(fn+"_OUT", fn)