Parse namespace alias and emit it

- Fixes #43
This commit is contained in:
Dustin Spicuzza 2023-03-16 18:43:37 -04:00
parent 1aa9e72ca1
commit 2fe350bf35
5 changed files with 79 additions and 3 deletions

View File

@ -35,6 +35,7 @@ from .types import (
Method, Method,
MoveReference, MoveReference,
NameSpecifier, NameSpecifier,
NamespaceAlias,
NamespaceDecl, NamespaceDecl,
Operator, Operator,
PQNameSegment, PQNameSegment,
@ -404,13 +405,26 @@ class CxxParser:
names = [] names = []
location = tok.location location = tok.location
ns_alias: typing.Union[typing.Literal[False], LexToken] = False
tok = self._next_token_must_be("NAME", "{") tok = self._next_token_must_be("NAME", "{")
if tok.type != "{": if tok.type != "{":
endtok = "{"
# Check for namespace alias here
etok = self.lex.token_if("=")
if etok:
ns_alias = tok
endtok = ";"
# They can start with ::
maybe_tok = self.lex.token_if("DBL_COLON")
if maybe_tok:
names.append(maybe_tok.value)
tok = self._next_token_must_be("NAME")
while True: while True:
names.append(tok.value) names.append(tok.value)
tok = self._next_token_must_be("DBL_COLON", "{") tok = self._next_token_must_be("DBL_COLON", endtok)
if tok.type == "{": if tok.type == endtok:
break break
tok = self._next_token_must_be("NAME") tok = self._next_token_must_be("NAME")
@ -418,7 +432,10 @@ class CxxParser:
if inline and len(names) > 1: if inline and len(names) > 1:
raise CxxParseError("a nested namespace definition cannot be inline") raise CxxParseError("a nested namespace definition cannot be inline")
# TODO: namespace_alias_definition if ns_alias:
alias = NamespaceAlias(ns_alias.value, names)
self.visitor.on_namespace_alias(self.state, alias)
return
ns = NamespaceDecl(names, inline, doxygen) ns = NamespaceDecl(names, inline, doxygen)
state = self._push_state(NamespaceBlockState, ns) state = self._push_state(NamespaceBlockState, ns)

View File

@ -39,6 +39,7 @@ from .types import (
FriendDecl, FriendDecl,
Function, Function,
Method, Method,
NamespaceAlias,
TemplateInst, TemplateInst,
Typedef, Typedef,
UsingAlias, UsingAlias,
@ -110,6 +111,7 @@ class NamespaceScope:
using: typing.List[UsingDecl] = field(default_factory=list) using: typing.List[UsingDecl] = field(default_factory=list)
using_ns: typing.List["UsingNamespace"] = field(default_factory=list) using_ns: typing.List["UsingNamespace"] = field(default_factory=list)
using_alias: typing.List[UsingAlias] = field(default_factory=list) using_alias: typing.List[UsingAlias] = field(default_factory=list)
ns_alias: typing.List[NamespaceAlias] = field(default_factory=list)
#: Explicit template instantiations #: Explicit template instantiations
template_insts: typing.List[TemplateInst] = field(default_factory=list) template_insts: typing.List[TemplateInst] = field(default_factory=list)
@ -261,6 +263,10 @@ class SimpleCxxVisitor:
self.block = self.block_stack.pop() self.block = self.block_stack.pop()
self.namespace = self.ns_stack.pop() self.namespace = self.ns_stack.pop()
def on_namespace_alias(self, state: State, alias: NamespaceAlias) -> None:
assert isinstance(self.block, NamespaceScope)
self.block.ns_alias.append(alias)
def on_forward_decl(self, state: State, fdecl: ForwardDecl) -> None: def on_forward_decl(self, state: State, fdecl: ForwardDecl) -> None:
self.block.forward_decls.append(fdecl) self.block.forward_decls.append(fdecl)

View File

@ -38,6 +38,26 @@ class Value:
tokens: typing.List[Token] tokens: typing.List[Token]
@dataclass
class NamespaceAlias:
"""
A namespace alias
.. code-block:: c++
namespace ANS = my::ns;
~~~ ~~~~~~
"""
alias: str
#: These are the names (split by ::) for the namespace that this alias
#: refers to, but does not include any parent namespace names. It may
#: include a leading "::", but does not include a following :: string.
names: typing.List[str]
@dataclass @dataclass
class NamespaceDecl: class NamespaceDecl:
""" """

View File

@ -14,6 +14,7 @@ from .types import (
FriendDecl, FriendDecl,
Function, Function,
Method, Method,
NamespaceAlias,
TemplateInst, TemplateInst,
Typedef, Typedef,
UsingAlias, UsingAlias,
@ -94,6 +95,11 @@ class CxxVisitor(Protocol):
Called at the end of a ``namespace`` block Called at the end of a ``namespace`` block
""" """
def on_namespace_alias(self, state: State, alias: NamespaceAlias) -> None:
"""
Called when a ``namespace`` alias is encountered
"""
def on_forward_decl(self, state: State, fdecl: ForwardDecl) -> None: def on_forward_decl(self, state: State, fdecl: ForwardDecl) -> None:
""" """
Called when a forward declaration is encountered Called when a forward declaration is encountered

View File

@ -4,6 +4,7 @@ from cxxheaderparser.errors import CxxParseError
from cxxheaderparser.types import ( from cxxheaderparser.types import (
ForwardDecl, ForwardDecl,
FundamentalSpecifier, FundamentalSpecifier,
NamespaceAlias,
NameSpecifier, NameSpecifier,
PQName, PQName,
Token, Token,
@ -168,3 +169,29 @@ def test_invalid_inline_namespace() -> None:
err = "<str>:1: parse error evaluating 'inline': a nested namespace definition cannot be inline" err = "<str>:1: parse error evaluating 'inline': a nested namespace definition cannot be inline"
with pytest.raises(CxxParseError, match=re.escape(err)): with pytest.raises(CxxParseError, match=re.escape(err)):
parse_string(content, cleandoc=True) parse_string(content, cleandoc=True)
def test_ns_alias() -> None:
content = """
namespace ANS = my::ns;
"""
data = parse_string(content, cleandoc=True)
assert data == ParsedData(
namespace=NamespaceScope(
ns_alias=[NamespaceAlias(alias="ANS", names=["my", "ns"])]
)
)
def test_ns_alias_global() -> None:
content = """
namespace ANS = ::my::ns;
"""
data = parse_string(content, cleandoc=True)
assert data == ParsedData(
namespace=NamespaceScope(
ns_alias=[NamespaceAlias(alias="ANS", names=["::", "my", "ns"])]
)
)