2 # Copyright (c) 2020, The Tor Project, Inc.
3 # See LICENSE for licensing information.
6 # DO NOT COMMIT OR MERGE CODE THAT IS RUN THROUGH THIS TOOL YET.
8 # WE ARE STILL DISCUSSING OUR DESIRED STYLE AND ITERATING ON IT,
9 # ALONG WITH THE TOOLS THAT ACHIEVE IT.
14 This program uses a set of pluggable filters to inspect and transform
23 """A Filter transforms a string containing a C program."""
27 def transform(self
, s
):
30 class CompoundFilt(Filter
):
31 """A CompoundFilt runs another set of filters, in sequence."""
32 def __init__(self
, items
=()):
34 self
._filters
= list(items
)
37 self
._filters
.append(filt
)
40 def transform(self
, s
):
41 for f
in self
._filters
:
46 class SplitError(Exception):
47 """Exception: raised if split_comments() can't understand a C file."""
50 def split_comments(s
):
51 r
"""Iterate over the C code in 's', and yield a sequence of (code,
52 comment) pairs. Each pair will contain either a nonempty piece
53 of code, a nonempty comment, or both.
55 >>> list(split_comments("hello // world\n"))
56 [('hello ', '// world'), ('\n', '')]
58 >>> list(split_comments("a /* b cd */ efg // hi"))
59 [('a ', '/* b cd */'), (' efg ', '// hi')]
62 # Matches a block of code without any comments.
63 PAT_CODE
= re
.compile(r
'''^(?: [^/"']+ |
67 )*''', re
.VERBOSE|re
.DOTALL
)
69 # Matches a C99 "//" comment.
70 PAT_C99_COMMENT
= re
.compile(r
'^//.*$', re
.MULTILINE
)
72 # Matches a C "/* */" comment.
73 PAT_C_COMMENT
= re
.compile(r
'^/\*(?:[^*]|\*+[^*/])*\*+/', re
.DOTALL
)
76 # Find some non-comment code at the start of the string.
79 # If we found some code here, save it and advance the string.
80 # Otherwise set 'code' to "".
87 # Now we have a comment, or the end of the string. Find out which
88 # one, and how long it is.
89 if s
.startswith("//"):
90 m
= PAT_C99_COMMENT
.match(s
)
92 m
= PAT_C_COMMENT
.match(s
)
94 # If we got a comment, save it and advance the string. Otherwise
95 # set 'comment' to "".
102 # If we found no code and no comment, we should be at the end of
104 if code
== "" and comment
== "":
106 # But in case we *aren't* at the end of the string, raise
109 # ... all is well, we're done scanning the code.
112 yield (code
, comment
)
114 class IgnoreCommentsFilt(Filter
):
115 """Wrapper: applies another filter to C code only, excluding comments.
117 def __init__(self
, filt
):
121 def transform(self
, s
):
123 for code
, comment
in split_comments(s
):
124 result
.append(self
._filt
.transform(code
))
125 result
.append(comment
)
126 return "".join(result
)
129 class RegexFilt(Filter
):
130 """A regex filter applies a regular expression to some C code."""
131 def __init__(self
, pat
, replacement
, flags
=0):
133 self
._pat
= re
.compile(pat
, flags
)
134 self
._replacement
= replacement
136 def transform(self
, s
):
137 s
, _
= self
._pat
.subn(self
._replacement
, s
)
140 def revise(fname
, filt
):
141 """Run 'filt' on the contents of the file in 'fname'. If any
142 changes are made, then replace the file with its new contents.
143 Otherwise, leave the file alone.
145 contents
= open(fname
, 'r').read()
146 result
= filt
.transform(contents
)
147 if result
== contents
:
150 tmpname
= "{}_codetool_tmp".format(fname
)
152 with
open(tmpname
, 'w') as f
:
154 os
.rename(tmpname
, fname
)
159 ##############################
161 ##############################
163 # Make sure that there is a newline after the first comma in a MOCK_IMPL()
164 BREAK_MOCK_IMPL
= RegexFilt(
165 r
'^MOCK_IMPL\(([^,]+),\s*(\S+)',
166 r
'MOCK_IMPL(\1,\n\2',
169 # Make sure there is no newline between } and a loop iteration terminator.
170 RESTORE_SMARTLIST_END
= RegexFilt(
171 r
'}\s*(SMARTLIST|DIGESTMAP|DIGEST256MAP|STRMAP|MAP)_FOREACH_END\s*\(',
172 r
'} \1_FOREACH_END (',
176 F
.add(IgnoreCommentsFilt(CompoundFilt([
177 RESTORE_SMARTLIST_END
,
180 if __name__
== '__main__':
181 for fname
in sys
.argv
[1:]: