Add pretty formatting for types and template specializations
- Fixes #3
This commit is contained in:
parent
1f5ba1b3ca
commit
a5ce9a24e6
@ -1,7 +1,7 @@
|
||||
from dataclasses import dataclass, field
|
||||
import typing
|
||||
|
||||
from .lexer import LexToken, PlyLexer, LexerTokenStream
|
||||
from .types import Token
|
||||
|
||||
# key: token type, value: (left spacing, right spacing)
|
||||
_want_spacing = {
|
||||
@ -35,6 +35,27 @@ _want_spacing = {
|
||||
_want_spacing.update(dict.fromkeys(PlyLexer.keywords, (2, 2)))
|
||||
|
||||
|
||||
@dataclass
|
||||
class Token:
|
||||
"""
|
||||
In an ideal world, this Token class would not be exposed via the user
|
||||
visible API. Unfortunately, getting to that point would take a significant
|
||||
amount of effort.
|
||||
|
||||
It is not expected that these will change, but they might.
|
||||
|
||||
At the moment, the only supported use of Token objects are in conjunction
|
||||
with the ``tokfmt`` function. As this library matures, we'll try to clarify
|
||||
the expectations around these. File an issue on github if you have ideas!
|
||||
"""
|
||||
|
||||
#: Raw value of the token
|
||||
value: str
|
||||
|
||||
#: Lex type of the token
|
||||
type: str = field(repr=False, compare=False, default="")
|
||||
|
||||
|
||||
def tokfmt(toks: typing.List[Token]) -> str:
|
||||
"""
|
||||
Helper function that takes a list of tokens and converts them to a string
|
||||
|
@ -1,26 +1,7 @@
|
||||
import typing
|
||||
from dataclasses import dataclass, field
|
||||
|
||||
|
||||
@dataclass
|
||||
class Token:
|
||||
"""
|
||||
In an ideal world, this Token class would not be exposed via the user
|
||||
visible API. Unfortunately, getting to that point would take a significant
|
||||
amount of effort.
|
||||
|
||||
It is not expected that these will change, but they might.
|
||||
|
||||
At the moment, the only supported use of Token objects are in conjunction
|
||||
with the ``tokfmt`` function. As this library matures, we'll try to clarify
|
||||
the expectations around these. File an issue on github if you have ideas!
|
||||
"""
|
||||
|
||||
#: Raw value of the token
|
||||
value: str
|
||||
|
||||
#: Lex type of the token
|
||||
type: str = field(repr=False, compare=False, default="")
|
||||
from .tokfmt import tokfmt, Token
|
||||
|
||||
|
||||
@dataclass
|
||||
@ -37,6 +18,9 @@ class Value:
|
||||
#: Tokens corresponding to the value
|
||||
tokens: typing.List[Token]
|
||||
|
||||
def format(self) -> str:
|
||||
return tokfmt(self.tokens)
|
||||
|
||||
|
||||
@dataclass
|
||||
class NamespaceAlias:
|
||||
@ -94,6 +78,9 @@ class DecltypeSpecifier:
|
||||
#: Unparsed tokens within the decltype
|
||||
tokens: typing.List[Token]
|
||||
|
||||
def format(self) -> str:
|
||||
return f"decltype({tokfmt(self.tokens)})"
|
||||
|
||||
|
||||
@dataclass
|
||||
class FundamentalSpecifier:
|
||||
@ -107,6 +94,9 @@ class FundamentalSpecifier:
|
||||
|
||||
name: str
|
||||
|
||||
def format(self) -> str:
|
||||
return self.name
|
||||
|
||||
|
||||
@dataclass
|
||||
class NameSpecifier:
|
||||
@ -124,6 +114,12 @@ class NameSpecifier:
|
||||
|
||||
specialization: typing.Optional["TemplateSpecialization"] = None
|
||||
|
||||
def format(self) -> str:
|
||||
if self.specialization:
|
||||
return f"{self.name}{self.specialization.format()}"
|
||||
else:
|
||||
return self.name
|
||||
|
||||
|
||||
@dataclass
|
||||
class AutoSpecifier:
|
||||
@ -133,6 +129,9 @@ class AutoSpecifier:
|
||||
|
||||
name: str = "auto"
|
||||
|
||||
def format(self) -> str:
|
||||
return self.name
|
||||
|
||||
|
||||
@dataclass
|
||||
class AnonymousName:
|
||||
@ -145,6 +144,10 @@ class AnonymousName:
|
||||
#: Unique id associated with this name (only unique per parser instance!)
|
||||
id: int
|
||||
|
||||
def format(self) -> str:
|
||||
# TODO: not sure what makes sense here, subject to change
|
||||
return f"<<id={self.id}>>"
|
||||
|
||||
|
||||
PQNameSegment = typing.Union[
|
||||
AnonymousName, FundamentalSpecifier, NameSpecifier, DecltypeSpecifier, AutoSpecifier
|
||||
@ -170,6 +173,13 @@ class PQName:
|
||||
#: Set to true if the type was preceded with 'typename'
|
||||
has_typename: bool = False
|
||||
|
||||
def format(self) -> str:
|
||||
tn = "typename " if self.has_typename else ""
|
||||
if self.classkey:
|
||||
return f"{tn}{self.classkey} {'::'.join(seg.format() for seg in self.segments)}"
|
||||
else:
|
||||
return tn + "::".join(seg.format() for seg in self.segments)
|
||||
|
||||
|
||||
@dataclass
|
||||
class TemplateArgument:
|
||||
@ -189,6 +199,12 @@ class TemplateArgument:
|
||||
|
||||
param_pack: bool = False
|
||||
|
||||
def format(self) -> str:
|
||||
if self.param_pack:
|
||||
return f"{self.arg.format()}..."
|
||||
else:
|
||||
return self.arg.format()
|
||||
|
||||
|
||||
@dataclass
|
||||
class TemplateSpecialization:
|
||||
@ -204,6 +220,9 @@ class TemplateSpecialization:
|
||||
|
||||
args: typing.List[TemplateArgument]
|
||||
|
||||
def format(self) -> str:
|
||||
return f"<{', '.join(arg.format() for arg in self.args)}>"
|
||||
|
||||
|
||||
@dataclass
|
||||
class FunctionType:
|
||||
@ -238,6 +257,23 @@ class FunctionType:
|
||||
#: calling convention
|
||||
msvc_convention: typing.Optional[str] = None
|
||||
|
||||
def format(self) -> str:
|
||||
vararg = "..." if self.vararg else ""
|
||||
params = ", ".join(p.format() for p in self.parameters)
|
||||
if self.has_trailing_return:
|
||||
return f"auto ({params}{vararg}) -> {self.return_type.format()}"
|
||||
else:
|
||||
return f"{self.return_type.format()} ({params}{vararg})"
|
||||
|
||||
def format_decl(self, name: str) -> str:
|
||||
"""Format as a named declaration"""
|
||||
vararg = "..." if self.vararg else ""
|
||||
params = ", ".join(p.format() for p in self.parameters)
|
||||
if self.has_trailing_return:
|
||||
return f"auto {name}({params}{vararg}) -> {self.return_type.format()}"
|
||||
else:
|
||||
return f"{self.return_type.format()} {name}({params}{vararg})"
|
||||
|
||||
|
||||
@dataclass
|
||||
class Type:
|
||||
@ -250,6 +286,17 @@ class Type:
|
||||
const: bool = False
|
||||
volatile: bool = False
|
||||
|
||||
def format(self) -> str:
|
||||
c = "const " if self.const else ""
|
||||
v = "volatile " if self.volatile else ""
|
||||
return f"{c}{v}{self.typename.format()}"
|
||||
|
||||
def format_decl(self, name: str):
|
||||
"""Format as a named declaration"""
|
||||
c = "const " if self.const else ""
|
||||
v = "volatile " if self.volatile else ""
|
||||
return f"{c}{v}{self.typename.format()} {name}"
|
||||
|
||||
|
||||
@dataclass
|
||||
class Array:
|
||||
@ -269,6 +316,14 @@ class Array:
|
||||
#: ~~
|
||||
size: typing.Optional[Value]
|
||||
|
||||
def format(self) -> str:
|
||||
s = self.size.format() if self.size else ""
|
||||
return f"{self.array_of.format()}[{s}]"
|
||||
|
||||
def format_decl(self, name: str) -> str:
|
||||
s = self.size.format() if self.size else ""
|
||||
return f"{self.array_of.format()} {name}[{s}]"
|
||||
|
||||
|
||||
@dataclass
|
||||
class Pointer:
|
||||
@ -282,6 +337,25 @@ class Pointer:
|
||||
const: bool = False
|
||||
volatile: bool = False
|
||||
|
||||
def format(self) -> str:
|
||||
c = " const" if self.const else ""
|
||||
v = " volatile" if self.volatile else ""
|
||||
ptr_to = self.ptr_to
|
||||
if isinstance(ptr_to, (Array, FunctionType)):
|
||||
return ptr_to.format_decl(f"(*{c}{v})")
|
||||
else:
|
||||
return f"{ptr_to.format()}*{c}{v}"
|
||||
|
||||
def format_decl(self, name: str):
|
||||
"""Format as a named declaration"""
|
||||
c = " const" if self.const else ""
|
||||
v = " volatile" if self.volatile else ""
|
||||
ptr_to = self.ptr_to
|
||||
if isinstance(ptr_to, (Array, FunctionType)):
|
||||
return ptr_to.format_decl(f"(*{c}{v} {name})")
|
||||
else:
|
||||
return f"{ptr_to.format()}*{c}{v} {name}"
|
||||
|
||||
|
||||
@dataclass
|
||||
class Reference:
|
||||
@ -291,6 +365,22 @@ class Reference:
|
||||
|
||||
ref_to: typing.Union[Array, FunctionType, Pointer, Type]
|
||||
|
||||
def format(self) -> str:
|
||||
ref_to = self.ref_to
|
||||
if isinstance(ref_to, Array):
|
||||
return ref_to.format_decl("(&)")
|
||||
else:
|
||||
return f"{ref_to.format()}&"
|
||||
|
||||
def format_decl(self, name: str):
|
||||
"""Format as a named declaration"""
|
||||
ref_to = self.ref_to
|
||||
|
||||
if isinstance(ref_to, Array):
|
||||
return ref_to.format_decl(f"(& {name})")
|
||||
else:
|
||||
return f"{ref_to.format()}& {name}"
|
||||
|
||||
|
||||
@dataclass
|
||||
class MoveReference:
|
||||
@ -300,6 +390,13 @@ class MoveReference:
|
||||
|
||||
moveref_to: typing.Union[Array, FunctionType, Pointer, Type]
|
||||
|
||||
def format(self) -> str:
|
||||
return f"{self.moveref_to.format()}&&"
|
||||
|
||||
def format_decl(self, name: str):
|
||||
"""Format as a named declaration"""
|
||||
return f"{self.moveref_to.format()}&& {name}"
|
||||
|
||||
|
||||
#: A type or function type that is decorated with various things
|
||||
#:
|
||||
@ -505,6 +602,15 @@ class Parameter:
|
||||
default: typing.Optional[Value] = None
|
||||
param_pack: bool = False
|
||||
|
||||
def format(self) -> str:
|
||||
default = f" = {self.default.format()}" if self.default else ""
|
||||
pp = "... " if self.param_pack else ""
|
||||
name = self.name
|
||||
if name:
|
||||
return f"{self.type.format_decl(f'{pp}{name}')}{default}"
|
||||
else:
|
||||
return f"{self.type.format()}{pp}{default}"
|
||||
|
||||
|
||||
@dataclass
|
||||
class Function:
|
||||
|
313
tests/test_typefmt.py
Normal file
313
tests/test_typefmt.py
Normal file
@ -0,0 +1,313 @@
|
||||
import typing
|
||||
|
||||
import pytest
|
||||
|
||||
from cxxheaderparser.tokfmt import Token
|
||||
from cxxheaderparser.types import (
|
||||
Array,
|
||||
DecoratedType,
|
||||
FunctionType,
|
||||
FundamentalSpecifier,
|
||||
Method,
|
||||
MoveReference,
|
||||
NameSpecifier,
|
||||
PQName,
|
||||
Parameter,
|
||||
Pointer,
|
||||
Reference,
|
||||
TemplateArgument,
|
||||
TemplateSpecialization,
|
||||
TemplateDecl,
|
||||
Type,
|
||||
Value,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"pytype,typestr,declstr",
|
||||
[
|
||||
(
|
||||
Type(typename=PQName(segments=[FundamentalSpecifier(name="int")])),
|
||||
"int",
|
||||
"int name",
|
||||
),
|
||||
(
|
||||
Type(
|
||||
typename=PQName(segments=[FundamentalSpecifier(name="int")]), const=True
|
||||
),
|
||||
"const int",
|
||||
"const int name",
|
||||
),
|
||||
(
|
||||
Type(
|
||||
typename=PQName(segments=[NameSpecifier(name="S")], classkey="struct")
|
||||
),
|
||||
"struct S",
|
||||
"struct S name",
|
||||
),
|
||||
(
|
||||
Pointer(
|
||||
ptr_to=Type(
|
||||
typename=PQName(segments=[FundamentalSpecifier(name="int")])
|
||||
)
|
||||
),
|
||||
"int*",
|
||||
"int* name",
|
||||
),
|
||||
(
|
||||
Pointer(
|
||||
ptr_to=Pointer(
|
||||
ptr_to=Type(
|
||||
typename=PQName(segments=[FundamentalSpecifier(name="int")])
|
||||
)
|
||||
)
|
||||
),
|
||||
"int**",
|
||||
"int** name",
|
||||
),
|
||||
(
|
||||
Reference(
|
||||
ref_to=Type(
|
||||
typename=PQName(segments=[FundamentalSpecifier(name="int")])
|
||||
)
|
||||
),
|
||||
"int&",
|
||||
"int& name",
|
||||
),
|
||||
(
|
||||
Reference(
|
||||
ref_to=Array(
|
||||
array_of=Type(
|
||||
typename=PQName(segments=[FundamentalSpecifier(name="int")])
|
||||
),
|
||||
size=Value(tokens=[Token(value="3")]),
|
||||
)
|
||||
),
|
||||
"int (&)[3]",
|
||||
"int (& name)[3]",
|
||||
),
|
||||
(
|
||||
MoveReference(
|
||||
moveref_to=Type(
|
||||
typename=PQName(
|
||||
segments=[NameSpecifier(name="T"), NameSpecifier(name="T")]
|
||||
)
|
||||
)
|
||||
),
|
||||
"T::T&&",
|
||||
"T::T&& name",
|
||||
),
|
||||
(
|
||||
Pointer(
|
||||
ptr_to=Array(
|
||||
array_of=Type(
|
||||
typename=PQName(segments=[FundamentalSpecifier(name="int")])
|
||||
),
|
||||
size=Value(tokens=[Token(value="3")]),
|
||||
)
|
||||
),
|
||||
"int (*)[3]",
|
||||
"int (* name)[3]",
|
||||
),
|
||||
(
|
||||
Pointer(
|
||||
ptr_to=Array(
|
||||
array_of=Type(
|
||||
typename=PQName(segments=[FundamentalSpecifier(name="int")])
|
||||
),
|
||||
size=Value(tokens=[Token(value="3")]),
|
||||
),
|
||||
const=True,
|
||||
),
|
||||
"int (* const)[3]",
|
||||
"int (* const name)[3]",
|
||||
),
|
||||
(
|
||||
FunctionType(
|
||||
return_type=Type(
|
||||
typename=PQName(segments=[FundamentalSpecifier(name="int")])
|
||||
),
|
||||
parameters=[
|
||||
Parameter(
|
||||
type=Type(
|
||||
typename=PQName(segments=[FundamentalSpecifier(name="int")])
|
||||
)
|
||||
)
|
||||
],
|
||||
),
|
||||
"int (int)",
|
||||
"int name(int)",
|
||||
),
|
||||
(
|
||||
FunctionType(
|
||||
return_type=Type(
|
||||
typename=PQName(segments=[FundamentalSpecifier(name="int")])
|
||||
),
|
||||
parameters=[
|
||||
Parameter(
|
||||
type=Type(
|
||||
typename=PQName(segments=[FundamentalSpecifier(name="int")])
|
||||
)
|
||||
)
|
||||
],
|
||||
has_trailing_return=True,
|
||||
),
|
||||
"auto (int) -> int",
|
||||
"auto name(int) -> int",
|
||||
),
|
||||
(
|
||||
FunctionType(
|
||||
return_type=Type(
|
||||
typename=PQName(
|
||||
segments=[FundamentalSpecifier(name="void")],
|
||||
),
|
||||
),
|
||||
parameters=[
|
||||
Parameter(
|
||||
type=Type(
|
||||
typename=PQName(
|
||||
segments=[FundamentalSpecifier(name="int")],
|
||||
),
|
||||
),
|
||||
name="a",
|
||||
),
|
||||
Parameter(
|
||||
type=Type(
|
||||
typename=PQName(
|
||||
segments=[FundamentalSpecifier(name="int")],
|
||||
),
|
||||
),
|
||||
name="b",
|
||||
),
|
||||
],
|
||||
),
|
||||
"void (int a, int b)",
|
||||
"void name(int a, int b)",
|
||||
),
|
||||
(
|
||||
Pointer(
|
||||
ptr_to=FunctionType(
|
||||
return_type=Type(
|
||||
typename=PQName(segments=[FundamentalSpecifier(name="int")])
|
||||
),
|
||||
parameters=[
|
||||
Parameter(
|
||||
type=Type(
|
||||
typename=PQName(
|
||||
segments=[FundamentalSpecifier(name="int")]
|
||||
)
|
||||
)
|
||||
)
|
||||
],
|
||||
)
|
||||
),
|
||||
"int (*)(int)",
|
||||
"int (* name)(int)",
|
||||
),
|
||||
(
|
||||
Type(
|
||||
typename=PQName(
|
||||
segments=[
|
||||
NameSpecifier(name="std"),
|
||||
NameSpecifier(
|
||||
name="function",
|
||||
specialization=TemplateSpecialization(
|
||||
args=[
|
||||
TemplateArgument(
|
||||
arg=FunctionType(
|
||||
return_type=Type(
|
||||
typename=PQName(
|
||||
segments=[
|
||||
FundamentalSpecifier(name="int")
|
||||
]
|
||||
)
|
||||
),
|
||||
parameters=[
|
||||
Parameter(
|
||||
type=Type(
|
||||
typename=PQName(
|
||||
segments=[
|
||||
FundamentalSpecifier(
|
||||
name="int"
|
||||
)
|
||||
]
|
||||
)
|
||||
)
|
||||
)
|
||||
],
|
||||
)
|
||||
)
|
||||
]
|
||||
),
|
||||
),
|
||||
]
|
||||
)
|
||||
),
|
||||
"std::function<int (int)>",
|
||||
"std::function<int (int)> name",
|
||||
),
|
||||
(
|
||||
Type(
|
||||
typename=PQName(
|
||||
segments=[
|
||||
NameSpecifier(
|
||||
name="foo",
|
||||
specialization=TemplateSpecialization(
|
||||
args=[
|
||||
TemplateArgument(
|
||||
arg=Type(
|
||||
typename=PQName(
|
||||
segments=[
|
||||
NameSpecifier(name=""),
|
||||
NameSpecifier(name="T"),
|
||||
],
|
||||
)
|
||||
),
|
||||
)
|
||||
]
|
||||
),
|
||||
)
|
||||
]
|
||||
),
|
||||
),
|
||||
"foo<::T>",
|
||||
"foo<::T> name",
|
||||
),
|
||||
(
|
||||
Type(
|
||||
typename=PQName(
|
||||
segments=[
|
||||
NameSpecifier(
|
||||
name="foo",
|
||||
specialization=TemplateSpecialization(
|
||||
args=[
|
||||
TemplateArgument(
|
||||
arg=Type(
|
||||
typename=PQName(
|
||||
segments=[
|
||||
NameSpecifier(name=""),
|
||||
NameSpecifier(name="T"),
|
||||
],
|
||||
has_typename=True,
|
||||
)
|
||||
),
|
||||
)
|
||||
]
|
||||
),
|
||||
)
|
||||
]
|
||||
),
|
||||
),
|
||||
"foo<typename ::T>",
|
||||
"foo<typename ::T> name",
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_typefmt(
|
||||
pytype: typing.Union[DecoratedType, FunctionType], typestr: str, declstr: str
|
||||
):
|
||||
# basic formatting
|
||||
assert pytype.format() == typestr
|
||||
|
||||
# as a type declaration
|
||||
assert pytype.format_decl("name") == declstr
|
Loading…
x
Reference in New Issue
Block a user