Add support for conversion operators

- Fixes #9
This commit is contained in:
Dustin Spicuzza 2021-11-25 23:42:37 -05:00
parent 92f8e72779
commit 5d564687c3
3 changed files with 170 additions and 12 deletions

View File

@ -596,6 +596,9 @@ class CxxParser:
with self.lex.set_group_of_tokens(raw_toks) as remainder:
try:
parsed_type, mods = self._parse_type(None)
if parsed_type is None:
raise self._parse_error(None)
mods.validate(var_ok=False, meth_ok=False, msg="")
dtype = self._parse_cv_ptr(parsed_type, nonptr_fn=True)
self._next_token_must_be(PhonyEnding.type)
@ -762,6 +765,9 @@ class CxxParser:
"""
parsed_type, mods = self._parse_type(None)
if parsed_type is None:
raise self._parse_error(None)
mods.validate(var_ok=False, meth_ok=False, msg="parsing typealias")
dtype = self._parse_cv_ptr(parsed_type)
@ -1475,6 +1481,9 @@ class CxxParser:
# required typename + decorators
parsed_type, mods = self._parse_type(tok)
if parsed_type is None:
raise self._parse_error(None)
mods.validate(var_ok=False, meth_ok=False, msg="parsing parameter")
dtype = self._parse_cv_ptr(parsed_type)
@ -1564,6 +1573,9 @@ class CxxParser:
)
parsed_type, mods = self._parse_type(None)
if parsed_type is None:
raise self._parse_error(None)
mods.validate(var_ok=False, meth_ok=False, msg="parsing trailing return type")
dtype = self._parse_cv_ptr(parsed_type)
@ -1726,8 +1738,9 @@ class CxxParser:
friend = FriendDecl(fn=method)
self.visitor.on_class_friend(state, friend)
else:
# method must not have multiple segments
if len(pqname.segments) != 1:
# method must not have multiple segments except for operator
if len(pqname.segments) > 1:
if not pqname.segments[0].name == "operator":
raise self._parse_error(None)
self.visitor.on_class_method(state, method)
@ -1897,7 +1910,8 @@ class CxxParser:
def _parse_type(
self,
tok: typing.Optional[LexToken],
) -> typing.Tuple[Type, ParsedTypeModifiers]:
operator_ok: bool = False,
) -> typing.Tuple[typing.Optional[Type], ParsedTypeModifiers]:
"""
This parses a typename and stops parsing when it hits something
that it doesn't understand. The caller uses the results to figure
@ -1905,6 +1919,9 @@ class CxxParser:
This only parses the base type, does not parse pointers, references,
or additional const/volatile qualifiers
The returned type will only be None if operator_ok is True and an
operator is encountered.
"""
const = False
@ -1924,6 +1941,7 @@ class CxxParser:
tok = get_token()
pqname: typing.Optional[PQName] = None
pqname_optional = False
_pqname_start_tokens = self._pqname_start_tokens
_attribute_start = self._attribute_start_tokens
@ -1935,6 +1953,10 @@ class CxxParser:
if pqname is not None:
# found second set of names, done here
break
if operator_ok and tok_type == "operator":
# special case: conversion operators such as operator bool
pqname_optional = True
break
pqname, _ = self._parse_pqname(
tok, compound_ok=True, fn_ok=False, fund_ok=True
)
@ -1963,14 +1985,17 @@ class CxxParser:
tok = get_token()
if pqname is None:
if not pqname_optional:
raise self._parse_error(tok)
parsed_type = None
else:
# Construct a type from the parsed name
parsed_type = Type(pqname, const, volatile)
self.lex.return_token(tok)
# Construct a type from the parsed name
parsed_type = Type(pqname, const, volatile)
# Always return the modifiers
mods = ParsedTypeModifiers(vars, both, meths)
return parsed_type, mods
def _parse_decl(
@ -2095,6 +2120,55 @@ class CxxParser:
self._parse_field(mods, dtype, pqname, template, doxygen, location, is_typedef)
return False
def _parse_operator_conversion(
self,
mods: ParsedTypeModifiers,
location: Location,
doxygen: typing.Optional[str],
template: typing.Optional[TemplateDecl],
is_typedef: bool,
is_friend: bool,
):
tok = self._next_token_must_be("operator")
if is_typedef:
raise self._parse_error(tok, "operator not permitted in typedef")
# next piece must be the conversion type
ctype, cmods = self._parse_type(None)
if ctype is None:
raise self._parse_error(None)
cmods.validate(var_ok=False, meth_ok=False, msg="parsing conversion operator")
# then this must be a method
self._next_token_must_be("(")
# make our own pqname/op here
segments = [NameSpecifier("operator")]
pqname = PQName(segments)
op = "conversion"
if self._parse_function(
mods,
ctype,
pqname,
op,
template,
doxygen,
location,
False,
False,
is_friend,
False,
None,
):
# has function body and handled it
return
# if this is just a declaration, next token should be ;
self._next_token_must_be(";")
_class_enum_stage2 = {":", "final", "explicit", "{"}
def _parse_declarations(
@ -2114,12 +2188,16 @@ class CxxParser:
location = tok.location
# Always starts out with some kind of type name
parsed_type, mods = self._parse_type(tok)
# Almost always starts out with some kind of type name or a modifier
parsed_type, mods = self._parse_type(tok, operator_ok=True)
# Check to see if this might be a class/enum declaration
if parsed_type.typename.classkey and self._maybe_parse_class_enum_decl(
if (
parsed_type is not None
and parsed_type.typename.classkey
and self._maybe_parse_class_enum_decl(
parsed_type, mods, doxygen, template, is_typedef, is_friend, location
)
):
return
@ -2138,6 +2216,13 @@ class CxxParser:
mods.validate(var_ok=var_ok, meth_ok=meth_ok, msg=msg)
if parsed_type is None:
# this means an operator was encountered, deal with the special case
self._parse_operator_conversion(
mods, location, doxygen, template, is_typedef, is_friend
)
return
# Ok, dealing with a variable or function/method
while True:
if self._parse_decl(

View File

@ -542,6 +542,14 @@ class Method(Function):
@dataclass
class Operator(Method):
"""
Represents an operator method
"""
#: The operator type (+, +=, etc).
#:
#: In the case of a conversion operator (such as 'operator bool'), this
#: is the string "conversion" and the full Type is found in return_type
operator: str = ""

View File

@ -552,3 +552,68 @@ def test_class_operators():
]
)
)
def test_conversion_operators():
content = """
class Foo
{
public:
operator Type1() const { return SomeMethod(); }
explicit operator Type2() const;
virtual operator bool() const;
};
"""
data = parse_string(content, cleandoc=True)
assert data == ParsedData(
namespace=NamespaceScope(
classes=[
ClassScope(
class_decl=ClassDecl(
typename=PQName(
segments=[NameSpecifier(name="Foo")], classkey="class"
)
),
methods=[
Operator(
return_type=Type(
typename=PQName(segments=[NameSpecifier(name="Type1")])
),
name=PQName(segments=[NameSpecifier(name="operator")]),
parameters=[],
has_body=True,
access="public",
const=True,
operator="conversion",
),
Operator(
return_type=Type(
typename=PQName(segments=[NameSpecifier(name="Type2")])
),
name=PQName(segments=[NameSpecifier(name="operator")]),
parameters=[],
access="public",
const=True,
explicit=True,
operator="conversion",
),
Operator(
return_type=Type(
typename=PQName(
segments=[FundamentalSpecifier(name="bool")]
)
),
name=PQName(segments=[NameSpecifier(name="operator")]),
parameters=[],
access="public",
const=True,
virtual=True,
operator="conversion",
),
],
)
]
)
)