Skip to content

Commit 1d3638b

Browse files
committed
Added task that replaces empty C identifier lists with (void)
1 parent e340651 commit 1d3638b

File tree

3 files changed

+278
-0
lines changed

3 files changed

+278
-0
lines changed

wpiformat/test/test_cidentlist.py

Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
import os
2+
3+
from wpiformat.config import Config
4+
from wpiformat.cidentlist import CIdentList
5+
6+
7+
def test_cidentlist():
8+
task = CIdentList()
9+
10+
inputs = []
11+
outputs = []
12+
13+
# Main.cpp: signature for C++ function
14+
inputs.append(("./Main.cpp",
15+
"int main() {" + os.linesep + \
16+
" return 0;" + os.linesep + \
17+
"}" + os.linesep))
18+
outputs.append((inputs[len(inputs) - 1][1], False, True))
19+
20+
# Main.cpp: signature for C function in extern "C" block
21+
inputs.append(("./Main.cpp",
22+
"extern \"C\" {" + os.linesep + \
23+
"int main() {" + os.linesep + \
24+
" return 0;" + os.linesep + \
25+
"}" + os.linesep + \
26+
"}" + os.linesep))
27+
outputs.append((
28+
"extern \"C\" {" + os.linesep + \
29+
"int main(void) {" + os.linesep + \
30+
" return 0;" + os.linesep + \
31+
"}" + os.linesep + \
32+
"}" + os.linesep, True, True))
33+
34+
# Main.cpp: signature for C function marked extern "C"
35+
inputs.append(("./Main.cpp",
36+
"extern \"C\" int main() {" + os.linesep + \
37+
" return 0;" + os.linesep + \
38+
"}" + os.linesep))
39+
outputs.append((
40+
"extern \"C\" int main(void) {" + os.linesep + \
41+
" return 0;" + os.linesep + \
42+
"}" + os.linesep, True, True))
43+
44+
# Main.cpp: extern "C++" function nested in extern "C" block
45+
inputs.append(("./Main.cpp",
46+
"extern \"C\" {" + os.linesep + \
47+
"extern \"C++\" int main() {" + os.linesep + \
48+
" return 0;" + os.linesep + \
49+
"}" + os.linesep + \
50+
"}" + os.linesep))
51+
outputs.append((inputs[len(inputs) - 1][1], False, True))
52+
53+
# Main.c: signature for C function
54+
inputs.append(("./Main.c",
55+
"int main() {" + os.linesep + \
56+
" return 0;" + os.linesep + \
57+
"}" + os.linesep))
58+
outputs.append((
59+
"int main(void) {" + os.linesep + \
60+
" return 0;" + os.linesep + \
61+
"}" + os.linesep, True, True))
62+
63+
# Main.c: signature for C++ function in extern "C++" block
64+
inputs.append(("./Main.c",
65+
"extern \"C++\" {" + os.linesep + \
66+
"int main() {" + os.linesep + \
67+
" return 0;" + os.linesep + \
68+
"}" + os.linesep + \
69+
"}" + os.linesep))
70+
outputs.append((inputs[len(inputs) - 1][1], False, True))
71+
72+
# Main.c: signature for C++ function marked extern "C++"
73+
inputs.append(("./Main.c",
74+
"extern \"C++\" int main() {" + os.linesep + \
75+
" return 0;" + os.linesep + \
76+
"}" + os.linesep))
77+
outputs.append((inputs[len(inputs) - 1][1], False, True))
78+
79+
# Main.c: extern "C" function nested in extern "C++" block
80+
inputs.append(("./Main.c",
81+
"extern \"C++\" {" + os.linesep + \
82+
"extern \"C\" int main() {" + os.linesep + \
83+
" return 0;" + os.linesep + \
84+
"}" + os.linesep + \
85+
"}" + os.linesep))
86+
outputs.append((
87+
"extern \"C++\" {" + os.linesep + \
88+
"extern \"C\" int main(void) {" + os.linesep + \
89+
" return 0;" + os.linesep + \
90+
"}" + os.linesep + \
91+
"}" + os.linesep, True, True))
92+
93+
# Don't match function calls
94+
inputs.append(("./Main.c",
95+
"int main() {" + os.linesep + \
96+
" foo();" + os.linesep + \
97+
" return 0;" + os.linesep + \
98+
"}" + os.linesep))
99+
outputs.append((
100+
"int main(void) {" + os.linesep + \
101+
" foo();" + os.linesep + \
102+
" return 0;" + os.linesep + \
103+
"}" + os.linesep, True, True))
104+
105+
# Don't match function calls with return (return is a keyword not a return
106+
# type)
107+
inputs.append(("./Main.c",
108+
"int main() {" + os.linesep + \
109+
" return foo();" + os.linesep + \
110+
"}" + os.linesep))
111+
outputs.append((
112+
"int main(void) {" + os.linesep + \
113+
" return foo();" + os.linesep + \
114+
"}" + os.linesep, True, True))
115+
116+
# Match function prototypes
117+
inputs.append(("./Main.c",
118+
"int main();" + os.linesep + \
119+
os.linesep + \
120+
"int main() {" + os.linesep + \
121+
" foo();" + os.linesep + \
122+
" return 0;" + os.linesep + \
123+
"}" + os.linesep))
124+
outputs.append((
125+
"int main(void);" + os.linesep + \
126+
os.linesep + \
127+
"int main(void) {" + os.linesep + \
128+
" foo();" + os.linesep + \
129+
" return 0;" + os.linesep + \
130+
"}" + os.linesep, True, True))
131+
132+
# Make sure leaving extern block resets extern language type of
133+
# parent block
134+
inputs.append(("./Main.c",
135+
"extern \"C++\" {" + os.linesep + \
136+
"extern \"C\" int main() {" + os.linesep + \
137+
" return 0;" + os.linesep + \
138+
"}" + os.linesep + \
139+
"int func() {" + os.linesep + \
140+
" return 0;" + os.linesep + \
141+
"}" + os.linesep + \
142+
"}" + os.linesep))
143+
outputs.append((
144+
"extern \"C++\" {" + os.linesep + \
145+
"extern \"C\" int main(void) {" + os.linesep + \
146+
" return 0;" + os.linesep + \
147+
"}" + os.linesep + \
148+
"int func() {" + os.linesep + \
149+
" return 0;" + os.linesep + \
150+
"}" + os.linesep + \
151+
"}" + os.linesep, True, True))
152+
153+
# Don't match lambda function that takes no arguments
154+
inputs.append(("./Main.cpp",
155+
"extern \"C\" {" + os.linesep + \
156+
os.linesep + \
157+
"HAL_Bool HAL_Initialize(int32_t timeout, int32_t mode) {" + os.linesep + \
158+
" std::atexit([]() {" + os.linesep + \
159+
" // Unregister our new data condition variable." + os.linesep + \
160+
" });" + os.linesep + \
161+
"}" + os.linesep + \
162+
os.linesep + \
163+
"} // extern \"C\"" + os.linesep))
164+
outputs.append((inputs[len(inputs) - 1][1], False, True))
165+
166+
# Don't match a function call within a #ifdef
167+
inputs.append(("./Main.c",
168+
"ES_Event Elevator_Service_Run(ES_Event event) {" + os.linesep + \
169+
"#ifdef USE_TATTLETALE" + os.linesep + \
170+
" ES_Tail(); // trace call stack end" + os.linesep + \
171+
"#endif" + os.linesep))
172+
outputs.append((inputs[len(inputs) - 1][1], False, True))
173+
174+
assert len(inputs) == len(outputs)
175+
176+
config_file = Config(os.path.abspath(os.getcwd()), ".styleguide")
177+
for i in range(len(inputs)):
178+
output, file_changed, success = task.run_pipeline(
179+
config_file, inputs[i][0], inputs[i][1])
180+
assert output == outputs[i][0]
181+
assert file_changed == outputs[i][1]
182+
assert success == outputs[i][2]

wpiformat/wpiformat/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import sys
1010

1111
from wpiformat.bracecomment import BraceComment
12+
from wpiformat.cidentlist import CIdentList
1213
from wpiformat.clangformat import ClangFormat
1314
from wpiformat.config import Config
1415
from wpiformat.includeguard import IncludeGuard
@@ -329,6 +330,7 @@ def main():
329330
# so it can clean up their formatting.
330331
task_pipeline = [
331332
BraceComment(),
333+
CIdentList(),
332334
IncludeGuard(),
333335
LicenseUpdate(str(args.year)),
334336
JavaClass(),

wpiformat/wpiformat/cidentlist.py

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
"""This task replaces empty C identifier lists "()" with "(void)"."""
2+
3+
import re
4+
5+
from wpiformat.task import Task
6+
7+
8+
class CIdentList(Task):
9+
10+
def should_process_file(self, config_file, name):
11+
return config_file.is_c_file(name) or config_file.is_cpp_file(name)
12+
13+
def run_pipeline(self, config_file, name, lines):
14+
linesep = Task.get_linesep(lines)
15+
file_changed = False
16+
17+
output = ""
18+
pos = 0
19+
20+
# C files use C linkage by default
21+
is_c = config_file.is_c_file(name)
22+
23+
# Tokenize as extern "C" or extern "C++" with optional {, open brace,
24+
# close brace, or () folllowed by { to disambiguate function calls.
25+
# extern is first to try matching a brace to it before classifying the
26+
# brace as generic.
27+
#
28+
# Valid function prototypes and definitions have return type, spaces,
29+
# function name, optional spaces, then braces. They are followed by ; or
30+
# {.
31+
#
32+
# "def\\s+\w+" matches preprocessor directives "#ifdef" and "#ifndef" so
33+
# their contents aren't used as a return type.
34+
extern_str = "(?P<ext_decl>extern \"C(\+\+)?\")\s+(?P<ext_brace>\{)?|"
35+
braces_str = "\{|\}|def\s+\w+|\w+\s+\w+\s*(?P<paren>\(\))"
36+
postfix_str = "(?=\s*(;|\{))"
37+
token_regex = re.compile(extern_str + braces_str + postfix_str)
38+
39+
EXTRA_POP_OFFSET = 2
40+
41+
# If value is greater than pop offset, the value needs to be restored in
42+
# addition to an extra stack pop being performed. The pop offset is
43+
# removed before assigning to is_c.
44+
#
45+
# is_c + pop offset == 2: C lang restore that needs extra brace pop
46+
# is_c + pop offset == 3: C++ lang restore that needs extra brace pop
47+
extern_brace_indices = [is_c]
48+
49+
for match in token_regex.finditer(lines):
50+
token = match.group()
51+
52+
if token == "{":
53+
extern_brace_indices.append(is_c)
54+
elif token == "}":
55+
is_c = extern_brace_indices.pop()
56+
57+
# If the next stack frame is from an extern without braces, pop
58+
# it.
59+
if extern_brace_indices[-1] >= EXTRA_POP_OFFSET:
60+
is_c = extern_brace_indices[-1] - EXTRA_POP_OFFSET
61+
extern_brace_indices.pop()
62+
elif token.startswith("extern"):
63+
# Back up language setting first
64+
if match.group("ext_brace"):
65+
extern_brace_indices.append(is_c)
66+
else:
67+
# Handling an extern without braces changing the language
68+
# type is done by treating it as a pseudo-brace that gets
69+
# popped as well when the next "}" is encountered.
70+
# The "extra pop" offset is used as a flag on the top stack
71+
# value that is checked whenever a pop is performed.
72+
extern_brace_indices.append(is_c + EXTRA_POP_OFFSET)
73+
74+
# Change language based on extern declaration
75+
if match.group("ext_decl") == "extern \"C\"":
76+
is_c = True
77+
else:
78+
is_c = False
79+
elif match.group(
80+
"paren") and "return " not in match.group() and is_c:
81+
# Replaces () with (void)
82+
output += lines[pos:match.span("paren")[0]] + "(void)"
83+
pos = match.span("paren")[0] + len("()")
84+
85+
file_changed = True
86+
87+
# Write rest of file if it wasn't all processed
88+
if pos < len(lines):
89+
output += lines[pos:]
90+
91+
if file_changed:
92+
return (output, file_changed, True)
93+
else:
94+
return (lines, file_changed, True)

0 commit comments

Comments
 (0)