Skip to content

Commit 1758155

Browse files
authored
Merge pull request #6 from robotpy/fmt-types
Add pretty formatting for types
2 parents 6ac0bdb + a5ce9a2 commit 1758155

File tree

3 files changed

+461
-21
lines changed

3 files changed

+461
-21
lines changed

cxxheaderparser/tokfmt.py

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1+
from dataclasses import dataclass, field
12
import typing
23

34
from .lexer import LexToken, PlyLexer, LexerTokenStream
4-
from .types import Token
55

66
# key: token type, value: (left spacing, right spacing)
77
_want_spacing = {
@@ -35,6 +35,27 @@
3535
_want_spacing.update(dict.fromkeys(PlyLexer.keywords, (2, 2)))
3636

3737

38+
@dataclass
39+
class Token:
40+
"""
41+
In an ideal world, this Token class would not be exposed via the user
42+
visible API. Unfortunately, getting to that point would take a significant
43+
amount of effort.
44+
45+
It is not expected that these will change, but they might.
46+
47+
At the moment, the only supported use of Token objects are in conjunction
48+
with the ``tokfmt`` function. As this library matures, we'll try to clarify
49+
the expectations around these. File an issue on github if you have ideas!
50+
"""
51+
52+
#: Raw value of the token
53+
value: str
54+
55+
#: Lex type of the token
56+
type: str = field(repr=False, compare=False, default="")
57+
58+
3859
def tokfmt(toks: typing.List[Token]) -> str:
3960
"""
4061
Helper function that takes a list of tokens and converts them to a string

cxxheaderparser/types.py

Lines changed: 126 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,7 @@
11
import typing
22
from dataclasses import dataclass, field
33

4-
5-
@dataclass
6-
class Token:
7-
"""
8-
In an ideal world, this Token class would not be exposed via the user
9-
visible API. Unfortunately, getting to that point would take a significant
10-
amount of effort.
11-
12-
It is not expected that these will change, but they might.
13-
14-
At the moment, the only supported use of Token objects are in conjunction
15-
with the ``tokfmt`` function. As this library matures, we'll try to clarify
16-
the expectations around these. File an issue on github if you have ideas!
17-
"""
18-
19-
#: Raw value of the token
20-
value: str
21-
22-
#: Lex type of the token
23-
type: str = field(repr=False, compare=False, default="")
4+
from .tokfmt import tokfmt, Token
245

256

267
@dataclass
@@ -37,6 +18,9 @@ class Value:
3718
#: Tokens corresponding to the value
3819
tokens: typing.List[Token]
3920

21+
def format(self) -> str:
22+
return tokfmt(self.tokens)
23+
4024

4125
@dataclass
4226
class NamespaceAlias:
@@ -94,6 +78,9 @@ class DecltypeSpecifier:
9478
#: Unparsed tokens within the decltype
9579
tokens: typing.List[Token]
9680

81+
def format(self) -> str:
82+
return f"decltype({tokfmt(self.tokens)})"
83+
9784

9885
@dataclass
9986
class FundamentalSpecifier:
@@ -107,6 +94,9 @@ class FundamentalSpecifier:
10794

10895
name: str
10996

97+
def format(self) -> str:
98+
return self.name
99+
110100

111101
@dataclass
112102
class NameSpecifier:
@@ -124,6 +114,12 @@ class NameSpecifier:
124114

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

117+
def format(self) -> str:
118+
if self.specialization:
119+
return f"{self.name}{self.specialization.format()}"
120+
else:
121+
return self.name
122+
127123

128124
@dataclass
129125
class AutoSpecifier:
@@ -133,6 +129,9 @@ class AutoSpecifier:
133129

134130
name: str = "auto"
135131

132+
def format(self) -> str:
133+
return self.name
134+
136135

137136
@dataclass
138137
class AnonymousName:
@@ -145,6 +144,10 @@ class AnonymousName:
145144
#: Unique id associated with this name (only unique per parser instance!)
146145
id: int
147146

147+
def format(self) -> str:
148+
# TODO: not sure what makes sense here, subject to change
149+
return f"<<id={self.id}>>"
150+
148151

149152
PQNameSegment = typing.Union[
150153
AnonymousName, FundamentalSpecifier, NameSpecifier, DecltypeSpecifier, AutoSpecifier
@@ -170,6 +173,13 @@ class PQName:
170173
#: Set to true if the type was preceded with 'typename'
171174
has_typename: bool = False
172175

176+
def format(self) -> str:
177+
tn = "typename " if self.has_typename else ""
178+
if self.classkey:
179+
return f"{tn}{self.classkey} {'::'.join(seg.format() for seg in self.segments)}"
180+
else:
181+
return tn + "::".join(seg.format() for seg in self.segments)
182+
173183

174184
@dataclass
175185
class TemplateArgument:
@@ -189,6 +199,12 @@ class TemplateArgument:
189199

190200
param_pack: bool = False
191201

202+
def format(self) -> str:
203+
if self.param_pack:
204+
return f"{self.arg.format()}..."
205+
else:
206+
return self.arg.format()
207+
192208

193209
@dataclass
194210
class TemplateSpecialization:
@@ -204,6 +220,9 @@ class TemplateSpecialization:
204220

205221
args: typing.List[TemplateArgument]
206222

223+
def format(self) -> str:
224+
return f"<{', '.join(arg.format() for arg in self.args)}>"
225+
207226

208227
@dataclass
209228
class FunctionType:
@@ -238,6 +257,23 @@ class FunctionType:
238257
#: calling convention
239258
msvc_convention: typing.Optional[str] = None
240259

260+
def format(self) -> str:
261+
vararg = "..." if self.vararg else ""
262+
params = ", ".join(p.format() for p in self.parameters)
263+
if self.has_trailing_return:
264+
return f"auto ({params}{vararg}) -> {self.return_type.format()}"
265+
else:
266+
return f"{self.return_type.format()} ({params}{vararg})"
267+
268+
def format_decl(self, name: str) -> str:
269+
"""Format as a named declaration"""
270+
vararg = "..." if self.vararg else ""
271+
params = ", ".join(p.format() for p in self.parameters)
272+
if self.has_trailing_return:
273+
return f"auto {name}({params}{vararg}) -> {self.return_type.format()}"
274+
else:
275+
return f"{self.return_type.format()} {name}({params}{vararg})"
276+
241277

242278
@dataclass
243279
class Type:
@@ -250,6 +286,17 @@ class Type:
250286
const: bool = False
251287
volatile: bool = False
252288

289+
def format(self) -> str:
290+
c = "const " if self.const else ""
291+
v = "volatile " if self.volatile else ""
292+
return f"{c}{v}{self.typename.format()}"
293+
294+
def format_decl(self, name: str):
295+
"""Format as a named declaration"""
296+
c = "const " if self.const else ""
297+
v = "volatile " if self.volatile else ""
298+
return f"{c}{v}{self.typename.format()} {name}"
299+
253300

254301
@dataclass
255302
class Array:
@@ -269,6 +316,14 @@ class Array:
269316
#: ~~
270317
size: typing.Optional[Value]
271318

319+
def format(self) -> str:
320+
s = self.size.format() if self.size else ""
321+
return f"{self.array_of.format()}[{s}]"
322+
323+
def format_decl(self, name: str) -> str:
324+
s = self.size.format() if self.size else ""
325+
return f"{self.array_of.format()} {name}[{s}]"
326+
272327

273328
@dataclass
274329
class Pointer:
@@ -282,6 +337,25 @@ class Pointer:
282337
const: bool = False
283338
volatile: bool = False
284339

340+
def format(self) -> str:
341+
c = " const" if self.const else ""
342+
v = " volatile" if self.volatile else ""
343+
ptr_to = self.ptr_to
344+
if isinstance(ptr_to, (Array, FunctionType)):
345+
return ptr_to.format_decl(f"(*{c}{v})")
346+
else:
347+
return f"{ptr_to.format()}*{c}{v}"
348+
349+
def format_decl(self, name: str):
350+
"""Format as a named declaration"""
351+
c = " const" if self.const else ""
352+
v = " volatile" if self.volatile else ""
353+
ptr_to = self.ptr_to
354+
if isinstance(ptr_to, (Array, FunctionType)):
355+
return ptr_to.format_decl(f"(*{c}{v} {name})")
356+
else:
357+
return f"{ptr_to.format()}*{c}{v} {name}"
358+
285359

286360
@dataclass
287361
class Reference:
@@ -291,6 +365,22 @@ class Reference:
291365

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

368+
def format(self) -> str:
369+
ref_to = self.ref_to
370+
if isinstance(ref_to, Array):
371+
return ref_to.format_decl("(&)")
372+
else:
373+
return f"{ref_to.format()}&"
374+
375+
def format_decl(self, name: str):
376+
"""Format as a named declaration"""
377+
ref_to = self.ref_to
378+
379+
if isinstance(ref_to, Array):
380+
return ref_to.format_decl(f"(& {name})")
381+
else:
382+
return f"{ref_to.format()}& {name}"
383+
294384

295385
@dataclass
296386
class MoveReference:
@@ -300,6 +390,13 @@ class MoveReference:
300390

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

393+
def format(self) -> str:
394+
return f"{self.moveref_to.format()}&&"
395+
396+
def format_decl(self, name: str):
397+
"""Format as a named declaration"""
398+
return f"{self.moveref_to.format()}&& {name}"
399+
303400

304401
#: A type or function type that is decorated with various things
305402
#:
@@ -505,6 +602,15 @@ class Parameter:
505602
default: typing.Optional[Value] = None
506603
param_pack: bool = False
507604

605+
def format(self) -> str:
606+
default = f" = {self.default.format()}" if self.default else ""
607+
pp = "... " if self.param_pack else ""
608+
name = self.name
609+
if name:
610+
return f"{self.type.format_decl(f'{pp}{name}')}{default}"
611+
else:
612+
return f"{self.type.format()}{pp}{default}"
613+
508614

509615
@dataclass
510616
class Function:

0 commit comments

Comments
 (0)