Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add pretty formatting for types #6

Merged
merged 1 commit into from
Oct 4, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
146 changes: 126 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 format(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 format(self) -> str:
return f"decltype({tokfmt(self.tokens)})"


@dataclass
class FundamentalSpecifier:
Expand All @@ -107,6 +94,9 @@ class FundamentalSpecifier:

name: str

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


@dataclass
class NameSpecifier:
Expand All @@ -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:
Expand All @@ -133,6 +129,9 @@ class AutoSpecifier:

name: str = "auto"

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


@dataclass
class AnonymousName:
Expand All @@ -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
Expand All @@ -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:
Expand All @@ -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:
Expand All @@ -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:
Expand Down Expand Up @@ -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:
Expand All @@ -250,6 +286,17 @@ class Type:
const: bool = False
volatile: bool = False

def format(self) -> str:
virtuald marked this conversation as resolved.
Show resolved Hide resolved
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:
Expand All @@ -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:
Expand All @@ -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:
Expand All @@ -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:
Expand All @@ -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
#:
Expand Down Expand Up @@ -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:
Expand Down
Loading