Skip to content

Commit 3980c3d

Browse files
authored
Merge pull request #81 from robotpy/extern-c
Fix extern C and add option to remove preprocessing
2 parents e366144 + da1a074 commit 3980c3d

File tree

2 files changed

+102
-55
lines changed

2 files changed

+102
-55
lines changed

CppHeaderParser/CppHeaderParser.py

+56-55
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,9 @@ def trace_print(*args):
139139
#: Symbols to ignore, usually special macros
140140
ignoreSymbols = ["Q_OBJECT"]
141141

142+
_BRACE_REASON_OTHER = 0
143+
_BRACE_REASON_NS = 1
144+
_BRACE_REASON_EXTERN = 2
142145

143146
# Track what was added in what order and at what depth
144147
parseHistory = []
@@ -1477,7 +1480,6 @@ class Resolver(object):
14771480
C_KEYWORDS = "extern virtual static explicit inline friend constexpr".split()
14781481
C_KEYWORDS = set(C_KEYWORDS)
14791482

1480-
SubTypedefs = {} # TODO deprecate?
14811483
NAMESPACES = []
14821484
CLASSES = {}
14831485

@@ -1506,6 +1508,11 @@ def cur_namespace(self, add_double_colon=False):
15061508
i += 1
15071509
return rtn
15081510

1511+
def cur_linkage(self):
1512+
if len(self.linkage_stack):
1513+
return self.linkage_stack[-1]
1514+
return ""
1515+
15091516
def guess_ctypes_type(self, string):
15101517
pointers = string.count("*")
15111518
string = string.replace("*", "")
@@ -1870,21 +1877,6 @@ def finalize_vars(self):
18701877
var["ctypes_type"] = "ctypes.c_void_p"
18711878
var["unresolved"] = True
18721879

1873-
elif tag in self.SubTypedefs: # TODO remove SubTypedefs
1874-
if (
1875-
"property_of_class" in var
1876-
or "property_of_struct" in var
1877-
):
1878-
trace_print(
1879-
"class:", self.SubTypedefs[tag], "tag:", tag
1880-
)
1881-
var["typedef"] = self.SubTypedefs[tag] # class name
1882-
var["ctypes_type"] = "ctypes.c_void_p"
1883-
else:
1884-
trace_print("WARN-this should almost never happen!")
1885-
trace_print(var)
1886-
var["unresolved"] = True
1887-
18881880
elif tag in self._template_typenames:
18891881
var["typename"] = tag
18901882
var["ctypes_type"] = "ctypes.c_void_p"
@@ -2061,10 +2053,6 @@ def finalize(self):
20612053
trace_print("meth returns class:", meth["returns"])
20622054
meth["returns_class"] = True
20632055

2064-
elif meth["returns"] in self.SubTypedefs:
2065-
meth["returns_class"] = True
2066-
meth["returns_nested"] = self.SubTypedefs[meth["returns"]]
2067-
20682056
elif meth["returns"] in cls._public_enums:
20692057
enum = cls._public_enums[meth["returns"]]
20702058
meth["returns_enum"] = enum.get("type")
@@ -2372,6 +2360,7 @@ def _evaluate_method_stack(self):
23722360
self._get_location(self.nameStack),
23732361
)
23742362
newMethod["parent"] = None
2363+
newMethod["linkage"] = self.cur_linkage()
23752364
self.functions.append(newMethod)
23762365

23772366
# Reset template once it has been used
@@ -2472,7 +2461,6 @@ def _evaluate_property_stack(self, clearStack=True, addToVar=None):
24722461
klass["typedefs"][self.curAccessSpecifier].append(name)
24732462
if self.curAccessSpecifier == "public":
24742463
klass._public_typedefs[name] = typedef["type"]
2475-
Resolver.SubTypedefs[name] = self.curClass
24762464
else:
24772465
assert 0
24782466
elif self.curClass:
@@ -2524,6 +2512,7 @@ def _evaluate_property_stack(self, clearStack=True, addToVar=None):
25242512
self._get_location(self.nameStack),
25252513
)
25262514
newVar["namespace"] = self.current_namespace()
2515+
newVar["linkage"] = self.cur_linkage()
25272516
if self.curClass:
25282517
klass = self.classes[self.curClass]
25292518
klass["properties"][self.curAccessSpecifier].append(newVar)
@@ -2542,6 +2531,7 @@ def _evaluate_property_stack(self, clearStack=True, addToVar=None):
25422531
self._get_location(self.nameStack),
25432532
)
25442533
newVar["namespace"] = self.cur_namespace(False)
2534+
newVar["linkage"] = self.cur_linkage()
25452535
if addToVar:
25462536
newVar.update(addToVar)
25472537
self.variables.append(newVar)
@@ -2600,6 +2590,7 @@ def _evaluate_class_stack(self):
26002590
)
26012591
self.curTemplate = None
26022592
newClass["declaration_method"] = self.nameStack[0]
2593+
newClass["linkage"] = self.cur_linkage()
26032594
self.classes_order.append(newClass) # good idea to save ordering
26042595
self.stack = [] # fixes if class declared with ';' in closing brace
26052596
self.stmtTokens = []
@@ -2702,7 +2693,14 @@ def show(self):
27022693
for className in list(self.classes.keys()):
27032694
self.classes[className].show()
27042695

2705-
def __init__(self, headerFileName, argType="file", encoding=None, **kwargs):
2696+
def __init__(
2697+
self,
2698+
headerFileName,
2699+
argType="file",
2700+
encoding=None,
2701+
preprocessed=False,
2702+
**kwargs
2703+
):
27062704
"""Create the parsed C++ header file parse tree
27072705
27082706
headerFileName - Name of the file to parse OR actual file contents (depends on argType)
@@ -2775,6 +2773,7 @@ def __init__(self, headerFileName, argType="file", encoding=None, **kwargs):
27752773
self.curAccessSpecifier = "private" # private is default
27762774
self.curTemplate = None
27772775
self.accessSpecifierStack = []
2776+
self.linkage_stack = []
27782777
debug_print(
27792778
"curAccessSpecifier changed/defaulted to %s", self.curAccessSpecifier
27802779
)
@@ -2802,31 +2801,24 @@ def __init__(self, headerFileName, argType="file", encoding=None, **kwargs):
28022801
"[ ]+", " ", supportedAccessSpecifier[i]
28032802
).strip()
28042803

2805-
# Change multi line #defines and expressions to single lines maintaining line nubmers
2806-
# Based from http://stackoverflow.com/questions/2424458/regular-expression-to-match-cs-multiline-preprocessor-statements
2807-
matches = re.findall(r"(?m)^(?:.*\\\r?\n)+.*$", headerFileStr)
2808-
is_define = re.compile(r"[ \t\v]*#[Dd][Ee][Ff][Ii][Nn][Ee]")
2809-
for m in matches:
2810-
# Keep the newlines so that linecount doesnt break
2811-
num_newlines = len([a for a in m if a == "\n"])
2812-
if is_define.match(m):
2813-
new_m = m.replace("\n", "<CppHeaderParser_newline_temp_replacement>\\n")
2814-
else:
2815-
# Just expression taking up multiple lines, make it take 1 line for easier parsing
2816-
new_m = m.replace("\\\n", " ")
2817-
if num_newlines > 0:
2818-
new_m += "\n" * (num_newlines)
2819-
headerFileStr = headerFileStr.replace(m, new_m)
2820-
2821-
# Filter out Extern "C" statements. These are order dependent
2822-
matches = re.findall(
2823-
re.compile(r'extern[\t ]+"[Cc]"[\t \n\r]*{', re.DOTALL), headerFileStr
2824-
)
2825-
for m in matches:
2826-
# Keep the newlines so that linecount doesnt break
2827-
num_newlines = len([a for a in m if a == "\n"])
2828-
headerFileStr = headerFileStr.replace(m, "\n" * num_newlines)
2829-
headerFileStr = re.sub(r'extern[ ]+"[Cc]"[ ]*', "", headerFileStr)
2804+
if not preprocessed:
2805+
# Change multi line #defines and expressions to single lines maintaining line nubmers
2806+
# Based from http://stackoverflow.com/questions/2424458/regular-expression-to-match-cs-multiline-preprocessor-statements
2807+
matches = re.findall(r"(?m)^(?:.*\\\r?\n)+.*$", headerFileStr)
2808+
is_define = re.compile(r"[ \t\v]*#[Dd][Ee][Ff][Ii][Nn][Ee]")
2809+
for m in matches:
2810+
# Keep the newlines so that linecount doesnt break
2811+
num_newlines = len([a for a in m if a == "\n"])
2812+
if is_define.match(m):
2813+
new_m = m.replace(
2814+
"\n", "<CppHeaderParser_newline_temp_replacement>\\n"
2815+
)
2816+
else:
2817+
# Just expression taking up multiple lines, make it take 1 line for easier parsing
2818+
new_m = m.replace("\\\n", " ")
2819+
if num_newlines > 0:
2820+
new_m += "\n" * (num_newlines)
2821+
headerFileStr = headerFileStr.replace(m, new_m)
28302822

28312823
# Filter out any ignore symbols that end with "()" to account for #define magic functions
28322824
for ignore in ignoreSymbols:
@@ -2866,6 +2858,8 @@ def __init__(self, headerFileName, argType="file", encoding=None, **kwargs):
28662858
)
28672859

28682860
self.braceDepth = 0
2861+
self.braceReason = []
2862+
self.lastBraceReason = _BRACE_REASON_OTHER
28692863

28702864
lex = Lexer(self.headerFileName)
28712865
lex.input(headerFileStr)
@@ -2938,23 +2932,20 @@ def __init__(self, headerFileName, argType="file", encoding=None, **kwargs):
29382932
continue
29392933

29402934
if parenDepth == 0 and tok.type == "{":
2935+
self.lastBraceReason = _BRACE_REASON_OTHER
29412936
if len(self.nameStack) >= 2 and is_namespace(
29422937
self.nameStack
29432938
): # namespace {} with no name used in boost, this sets default?
2944-
if (
2945-
self.nameStack[1]
2946-
== "__IGNORED_NAMESPACE__CppHeaderParser__"
2947-
): # Used in filtering extern "C"
2948-
self.nameStack[1] = ""
29492939
self.nameSpaces.append("".join(self.nameStack[1:]))
29502940
ns = self.cur_namespace()
29512941
self.stack = []
29522942
self.stmtTokens = []
29532943
if ns not in self.namespaces:
29542944
self.namespaces.append(ns)
2945+
self.lastBraceReason = _BRACE_REASON_NS
29552946
# Detect special condition of macro magic before class declaration so we
29562947
# can filter it out
2957-
if "class" in self.nameStack and self.nameStack[0] != "class":
2948+
elif "class" in self.nameStack and self.nameStack[0] != "class":
29582949
classLocationNS = self.nameStack.index("class")
29592950
classLocationS = self.stack.index("class")
29602951
if (
@@ -2987,14 +2978,20 @@ def __init__(self, headerFileName, argType="file", encoding=None, **kwargs):
29872978
self.stmtTokens = []
29882979
if not self.braceHandled:
29892980
self.braceDepth += 1
2981+
self.braceReason.append(self.lastBraceReason)
29902982

29912983
elif parenDepth == 0 and tok.type == "}":
29922984
if self.braceDepth == 0:
29932985
continue
2994-
if self.braceDepth == len(self.nameSpaces):
2995-
tmp = self.nameSpaces.pop()
2986+
reason = self.braceReason.pop()
2987+
if reason == _BRACE_REASON_NS:
2988+
self.nameSpaces.pop()
29962989
self.stack = [] # clear stack when namespace ends?
29972990
self.stmtTokens = []
2991+
elif reason == _BRACE_REASON_EXTERN:
2992+
self.linkage_stack.pop()
2993+
self.stack = [] # clear stack when linkage ends?
2994+
self.stmtTokens = []
29982995
else:
29992996
self._evaluate_stack()
30002997
self.braceDepth -= 1
@@ -3358,8 +3355,10 @@ def _evaluate_stack(self, token=None):
33583355
pass
33593356
elif len(self.nameStack) == 2 and self.nameStack[0] == "extern":
33603357
debug_print("trace extern")
3358+
self.linkage_stack.append(self.nameStack[1].strip('"'))
33613359
self.stack = []
33623360
self.stmtTokens = []
3361+
self.lastBraceReason = _BRACE_REASON_EXTERN
33633362
elif (
33643363
len(self.nameStack) == 2 and self.nameStack[0] == "friend"
33653364
): # friend class declaration
@@ -3667,12 +3666,14 @@ def _parse_enum(self):
36673666
def _install_enum(self, newEnum, instancesData):
36683667
if len(self.curClass):
36693668
newEnum["namespace"] = self.cur_namespace(False)
3669+
newEnum["linkage"] = self.cur_linkage()
36703670
klass = self.classes[self.curClass]
36713671
klass["enums"][self.curAccessSpecifier].append(newEnum)
36723672
if self.curAccessSpecifier == "public" and "name" in newEnum:
36733673
klass._public_enums[newEnum["name"]] = newEnum
36743674
else:
36753675
newEnum["namespace"] = self.cur_namespace(True)
3676+
newEnum["linkage"] = self.cur_linkage()
36763677
self.enums.append(newEnum)
36773678
if "name" in newEnum and newEnum["name"]:
36783679
self.global_enums[newEnum["name"]] = newEnum

test/test_CppHeaderParser.py

+46
Original file line numberDiff line numberDiff line change
@@ -4183,5 +4183,51 @@ def test_fn3(self):
41834183
self.assertEqual(fn["pure_virtual"], True)
41844184

41854185

4186+
class ExternCQuirk(unittest.TestCase):
4187+
# bug where extern "C" reset the namespace
4188+
def setUp(self):
4189+
self.cppHeader = CppHeaderParser.CppHeader(
4190+
"""
4191+
namespace cs {
4192+
extern "C" {
4193+
struct InCSAndExtern {};
4194+
void FnInCSAndExtern(InCSAndExtern *n);
4195+
}
4196+
4197+
class InCS {};
4198+
4199+
}
4200+
4201+
void FnNotInCSOrExtern();
4202+
4203+
""",
4204+
"string",
4205+
)
4206+
4207+
def test_fn(self):
4208+
4209+
# NotCS should be in namespace cs, extern C
4210+
c = self.cppHeader.classes["InCSAndExtern"]
4211+
self.assertEqual(c["namespace"], "cs")
4212+
self.assertEqual(c["linkage"], "C")
4213+
4214+
# FnNotCS should be in namespace cs, extern C
4215+
fn = self.cppHeader.functions[0]
4216+
self.assertEqual(fn["name"], "FnInCSAndExtern")
4217+
self.assertEqual(fn["namespace"], "cs::")
4218+
self.assertEqual(fn["linkage"], "C")
4219+
4220+
# InCS should be in namespace cs
4221+
c = self.cppHeader.classes["InCS"]
4222+
self.assertEqual(c["namespace"], "cs")
4223+
self.assertEqual(c["linkage"], "")
4224+
4225+
# FnNotCS should not in namespace cs nor extern C
4226+
fn = self.cppHeader.functions[1]
4227+
self.assertEqual(fn["name"], "FnNotInCSOrExtern")
4228+
self.assertEqual(fn["namespace"], "")
4229+
self.assertEqual(fn["linkage"], "")
4230+
4231+
41864232
if __name__ == "__main__":
41874233
unittest.main()

0 commit comments

Comments
 (0)