Skip to content

Commit

Permalink
Add pretty formatting for types and template specializations
Browse files Browse the repository at this point in the history
- Fixes #3
  • Loading branch information
virtuald committed Sep 29, 2023
1 parent bdcee6f commit 9d22018
Show file tree
Hide file tree
Showing 3 changed files with 417 additions and 21 deletions.
23 changes: 22 additions & 1 deletion cxxheaderparser/tokfmt.py
Original file line number Diff line number Diff line change
@@ -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 = {
Expand Down Expand Up @@ -35,6 +35,27 @@
_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
Expand Down
161 changes: 141 additions & 20 deletions cxxheaderparser/types.py
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -37,6 +18,9 @@ class Value:
#: Tokens corresponding to the value
tokens: typing.List[Token]

def __str__(self) -> str:
return tokfmt(self.tokens)


@dataclass
class NamespaceAlias:
Expand Down Expand Up @@ -94,6 +78,9 @@ class DecltypeSpecifier:
#: Unparsed tokens within the decltype
tokens: typing.List[Token]

def __str__(self) -> str:
return f"decltype({tokfmt(self.tokens)})"


@dataclass
class FundamentalSpecifier:
Expand All @@ -103,6 +90,9 @@ class FundamentalSpecifier:

name: str

def __str__(self) -> str:
return self.name


@dataclass
class NameSpecifier:
Expand All @@ -120,6 +110,12 @@ class NameSpecifier:

specialization: typing.Optional["TemplateSpecialization"] = None

def __str__(self) -> str:
if self.specialization:
return f"{self.name}{self.specialization}"
else:
return self.name


@dataclass
class AutoSpecifier:
Expand All @@ -129,6 +125,9 @@ class AutoSpecifier:

name: str = "auto"

def __str__(self) -> str:
return self.name


@dataclass
class AnonymousName:
Expand All @@ -141,6 +140,10 @@ class AnonymousName:
#: Unique id associated with this name (only unique per parser instance!)
id: int

def __str__(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
Expand All @@ -166,6 +169,13 @@ class PQName:
#: Set to true if the type was preceded with 'typename'
has_typename: bool = False

def __str__(self) -> str:
tn = "typename " if self.has_typename else ""
if self.classkey:
return f"{tn}{self.classkey} {'::'.join(map(str, self.segments))}"
else:
return tn + "::".join(map(str, self.segments))


@dataclass
class TemplateArgument:
Expand All @@ -185,6 +195,10 @@ class TemplateArgument:

param_pack: bool = False

def __str__(self):
pp = "..." if self.param_pack else ""
return f"{self.arg}{pp}"


@dataclass
class TemplateSpecialization:
Expand All @@ -200,6 +214,9 @@ class TemplateSpecialization:

args: typing.List[TemplateArgument]

def __str__(self):
return f"<{', '.join(map(str, self.args))}>"


@dataclass
class FunctionType:
Expand Down Expand Up @@ -234,6 +251,25 @@ class FunctionType:
#: calling convention
msvc_convention: typing.Optional[str] = None

def __format__(self, name: str) -> str:
if not name:
return self.__str__()

vararg = "..." if self.vararg else ""
if self.has_trailing_return:
return f"auto {name}({', '.join(map(str, self.parameters))}{vararg}) -> {self.return_type}"
else:
return f"{self.return_type} {name}({', '.join(map(str, self.parameters))}{vararg})"

def __str__(self) -> str:
vararg = "..." if self.vararg else ""
if self.has_trailing_return:
return f"auto ({', '.join(map(str, self.parameters))}{vararg}) -> {self.return_type}"
else:
return (
f"{self.return_type} ({', '.join(map(str, self.parameters))}{vararg})"
)


@dataclass
class Type:
Expand All @@ -246,6 +282,17 @@ class Type:
const: bool = False
volatile: bool = False

def __format__(self, name: str) -> str:
s = self.__str__()
if not name:
return s
return f"{s} {name}"

def __str__(self) -> str:
c = "const " if self.const else ""
v = "volatile " if self.volatile else ""
return f"{c}{v}{self.typename}"


@dataclass
class Array:
Expand All @@ -265,6 +312,17 @@ class Array:
#: ~~
size: typing.Optional[Value]

def __format__(self, name: str) -> str:
if not name:
return self.__str__()

s = self.size if self.size else ""
return f"{self.array_of} {name}[{s}]"

def __str__(self) -> str:
s = self.size if self.size else ""
return f"{self.array_of}[{s}]"


@dataclass
class Pointer:
Expand All @@ -278,6 +336,30 @@ class Pointer:
const: bool = False
volatile: bool = False

def __fmt(self) -> str:
c = " const" if self.const else ""
v = " volatile" if self.volatile else ""
return f"*{c}{v}"

def __format__(self, name: str) -> str:
if not name:
return self.__str__()

fmt = self.__fmt()
ptr_to = self.ptr_to
if isinstance(ptr_to, (Array, FunctionType)):
return ptr_to.__format__(f"({fmt} {name})")
else:
return f"{ptr_to} {fmt} {name}"

def __str__(self) -> str:
fmt = self.__fmt()
ptr_to = self.ptr_to
if isinstance(ptr_to, (Array, FunctionType)):
return ptr_to.__format__(f"({fmt})")
else:
return f"{ptr_to} {fmt}"


@dataclass
class Reference:
Expand All @@ -287,6 +369,23 @@ class Reference:

ref_to: typing.Union[Array, FunctionType, Pointer, Type]

def __format__(self, name: str) -> str:
if not name:
return self.__str__()

ref_to = self.ref_to
if isinstance(ref_to, Array):
return ref_to.__format__(f"(& {name})")
else:
return f"{ref_to}& {name}"

def __str__(self) -> str:
ref_to = self.ref_to
if isinstance(ref_to, Array):
return ref_to.__format__("(&)")
else:
return f"{ref_to}&"


@dataclass
class MoveReference:
Expand All @@ -296,11 +395,24 @@ class MoveReference:

moveref_to: typing.Union[Array, FunctionType, Pointer, Type]

def __format__(self, name: str) -> str:
if not name:
return self.__str__()
return f"{self.moveref_to} && {name}"

def __str__(self) -> str:
return f"{self.moveref_to} &&"


#: A type or function type that is decorated with various things
#:
#: .. note:: There can only be one of FunctionType or Type in a DecoratedType
#: chain
#:
#: The ``__str__`` for these types returns a canonical representation of
#: the underlying type. To get a declaration (such as a parameter), use the
#: ``__format__`` function or an f-string with the name as the format
#: specifier (``f"{type:name}``)
DecoratedType = typing.Union[Array, Pointer, MoveReference, Reference, Type]


Expand Down Expand Up @@ -501,6 +613,15 @@ class Parameter:
default: typing.Optional[Value] = None
param_pack: bool = False

def __str__(self):
default = f" = {self.default}" if self.default else ""
pp = "... " if self.param_pack else ""
name = self.name
if name:
return f"{self.type:{pp}{name}}{default}"
else:
return f"{self.type}{pp}{default}"


@dataclass
class Function:
Expand Down
Loading

0 comments on commit 9d22018

Please sign in to comment.