Support parsing MSVC calling conventions

- Fixes #5
- Fixes #33
This commit is contained in:
Dustin Spicuzza 2021-11-27 21:40:10 -05:00
parent a67c9c4afe
commit 92f8e72779
3 changed files with 94 additions and 1 deletions

View File

@ -359,6 +359,15 @@ class CxxParser:
# Various # Various
# #
_msvc_conventions = {
"__cdecl",
"__clrcall",
"__stdcall",
"__fastcall",
"__thiscall",
"__vectorcall",
}
def _parse_namespace( def _parse_namespace(
self, tok: LexToken, doxygen: typing.Optional[str], inline: bool = False self, tok: LexToken, doxygen: typing.Optional[str], inline: bool = False
) -> None: ) -> None:
@ -1655,6 +1664,7 @@ class CxxParser:
destructor: bool, destructor: bool,
is_friend: bool, is_friend: bool,
is_typedef: bool, is_typedef: bool,
msvc_convention: typing.Optional[LexToken],
) -> bool: ) -> bool:
""" """
Assumes the caller has already consumed the return type and name, this consumes the Assumes the caller has already consumed the return type and name, this consumes the
@ -1670,6 +1680,8 @@ class CxxParser:
raise self._parse_error(None) raise self._parse_error(None)
props = dict.fromkeys(mods.both.keys(), True) props = dict.fromkeys(mods.both.keys(), True)
if msvc_convention:
props["msvc_convention"] = msvc_convention.value
state = self.state state = self.state
state.location = location state.location = location
@ -1758,6 +1770,7 @@ class CxxParser:
fn.vararg, fn.vararg,
fn.has_trailing_return, fn.has_trailing_return,
noexcept=fn.noexcept, noexcept=fn.noexcept,
msvc_convention=fn.msvc_convention,
) )
typedef = Typedef(fntype, pqname.segments[0].name, self._current_access) typedef = Typedef(fntype, pqname.segments[0].name, self._current_access)
@ -1823,6 +1836,10 @@ class CxxParser:
self._parse_trailing_return_type(dtype) self._parse_trailing_return_type(dtype)
else: else:
msvc_convention = self.lex.token_if_val(*self._msvc_conventions)
if msvc_convention:
msvc_convention = msvc_convention.value
# Check to see if this is a grouping paren or something else # Check to see if this is a grouping paren or something else
if not self.lex.token_peek_if("*", "&"): if not self.lex.token_peek_if("*", "&"):
self.lex.return_token(tok) self.lex.return_token(tok)
@ -1840,7 +1857,9 @@ class CxxParser:
fn_params, vararg = self._parse_parameters() fn_params, vararg = self._parse_parameters()
# the type we already have is the return type of the function pointer # the type we already have is the return type of the function pointer
dtype = FunctionType(dtype, fn_params, vararg) dtype = FunctionType(
dtype, fn_params, vararg, msvc_convention=msvc_convention
)
# the inner tokens must either be a * or a pqname that ends # the inner tokens must either be a * or a pqname that ends
# with ::* (member function pointer) # with ::* (member function pointer)
@ -1975,6 +1994,7 @@ class CxxParser:
constructor = False constructor = False
destructor = False destructor = False
op = None op = None
msvc_convention = None
# If we have a leading (, that's either an obnoxious grouping # If we have a leading (, that's either an obnoxious grouping
# paren or it's a constructor # paren or it's a constructor
@ -2021,6 +2041,8 @@ class CxxParser:
self.lex.return_tokens(toks[1:-1]) self.lex.return_tokens(toks[1:-1])
if dtype: if dtype:
msvc_convention = self.lex.token_if_val(*self._msvc_conventions)
tok = self.lex.token_if_in_set(self._pqname_start_tokens) tok = self.lex.token_if_in_set(self._pqname_start_tokens)
if tok: if tok:
pqname, op = self._parse_pqname(tok, fn_ok=True) pqname, op = self._parse_pqname(tok, fn_ok=True)
@ -2046,7 +2068,10 @@ class CxxParser:
destructor, destructor,
is_friend, is_friend,
is_typedef, is_typedef,
msvc_convention,
) )
elif msvc_convention:
raise self._parse_error(msvc_convention)
# anything else is a field/variable # anything else is a field/variable
if is_friend: if is_friend:

View File

@ -238,6 +238,14 @@ class FunctionType:
noexcept: typing.Optional[Value] = None noexcept: typing.Optional[Value] = None
#: Only set if an MSVC calling convention (__stdcall, etc) is explictly
#: specified.
#:
#: .. note:: If your code contains things like WINAPI, you will need to
#: use a preprocessor to transform it to the appropriate
#: calling convention
msvc_convention: typing.Optional[str] = None
@dataclass @dataclass
class Type: class Type:
@ -487,6 +495,14 @@ class Function:
throw: typing.Optional[Value] = None throw: typing.Optional[Value] = None
noexcept: typing.Optional[Value] = None noexcept: typing.Optional[Value] = None
#: Only set if an MSVC calling convention (__stdcall, etc) is explictly
#: specified.
#:
#: .. note:: If your code contains things like WINAPI, you will need to
#: use a preprocessor to transform it to the appropriate
#: calling convention
msvc_convention: typing.Optional[str] = None
@dataclass @dataclass
class Method(Function): class Method(Function):

View File

@ -20,6 +20,7 @@ from cxxheaderparser.types import (
TemplateTypeParam, TemplateTypeParam,
Token, Token,
Type, Type,
Typedef,
Value, Value,
) )
from cxxheaderparser.simple import ( from cxxheaderparser.simple import (
@ -993,3 +994,54 @@ def test_fn_w_mvreference():
] ]
) )
) )
def test_msvc_conventions():
content = """
void __cdecl fn();
typedef const char* (__stdcall *wglGetExtensionsStringARB_t)(HDC theDeviceContext);
"""
data = parse_string(content, cleandoc=True)
assert data == ParsedData(
namespace=NamespaceScope(
functions=[
Function(
return_type=Type(
typename=PQName(segments=[FundamentalSpecifier(name="void")])
),
name=PQName(segments=[NameSpecifier(name="fn")]),
parameters=[],
msvc_convention="__cdecl",
)
],
typedefs=[
Typedef(
type=Pointer(
ptr_to=FunctionType(
return_type=Pointer(
ptr_to=Type(
typename=PQName(
segments=[FundamentalSpecifier(name="char")]
),
const=True,
)
),
parameters=[
Parameter(
type=Type(
typename=PQName(
segments=[NameSpecifier(name="HDC")]
)
),
name="theDeviceContext",
)
],
msvc_convention="__stdcall",
)
),
name="wglGetExtensionsStringARB_t",
)
],
)
)