Skip to content

Commit f836f81

Browse files
authored
Merge pull request #64 from robotpy/state-user-data
Allow parser visitors to leverage the parser's state instead of creating their own stacks
2 parents a110a55 + 3bae95f commit f836f81

File tree

5 files changed

+125
-78
lines changed

5 files changed

+125
-78
lines changed

cxxheaderparser/parser.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,8 @@ def debug_print(fmt: str, *args: typing.Any) -> None:
106106
else:
107107
self.debug_print = lambda fmt, *args: None
108108

109+
self.visitor.on_parse_start(self.state)
110+
109111
#
110112
# State management
111113
#
@@ -505,7 +507,8 @@ def _consume_static_assert(
505507
def _on_empty_block_start(
506508
self, tok: LexToken, doxygen: typing.Optional[str]
507509
) -> None:
508-
self._push_state(EmptyBlockState)
510+
state = self._push_state(EmptyBlockState)
511+
self.visitor.on_empty_block_start(state)
509512

510513
def _on_block_end(self, tok: LexToken, doxygen: typing.Optional[str]) -> None:
511514
old_state = self._pop_state()

cxxheaderparser/parserstate.py

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -28,26 +28,41 @@ def validate(self, *, var_ok: bool, meth_ok: bool, msg: str) -> None:
2828
raise CxxParseError(f"{msg}: unexpected '{tok.value}'")
2929

3030

31-
class State:
31+
#: custom user data for this state type
32+
T = typing.TypeVar("T")
33+
34+
#: type of custom user data for a parent state
35+
PT = typing.TypeVar("PT")
36+
37+
38+
class State(typing.Generic[T, PT]):
39+
#: Uninitialized user data available for use by visitor implementations. You
40+
#: should set this in a ``*_start`` method.
41+
user_data: T
42+
3243
#: parent state
33-
parent: typing.Optional["State"]
44+
parent: typing.Optional["State[PT, typing.Any]"]
3445

3546
#: Approximate location that the parsed element was found at
3647
location: Location
3748

38-
def __init__(self, parent: typing.Optional["State"]) -> None:
49+
def __init__(self, parent: typing.Optional["State[PT, typing.Any]"]) -> None:
3950
self.parent = parent
4051

4152
def _finish(self, visitor: "CxxVisitor") -> None:
4253
pass
4354

4455

45-
class EmptyBlockState(State):
56+
class EmptyBlockState(State[T, PT]):
57+
parent: State[PT, typing.Any]
58+
4659
def _finish(self, visitor: "CxxVisitor") -> None:
4760
visitor.on_empty_block_end(self)
4861

4962

50-
class ExternBlockState(State):
63+
class ExternBlockState(State[T, PT]):
64+
parent: State[PT, typing.Any]
65+
5166
#: The linkage for this extern block
5267
linkage: str
5368

@@ -59,7 +74,9 @@ def _finish(self, visitor: "CxxVisitor") -> None:
5974
visitor.on_extern_block_end(self)
6075

6176

62-
class NamespaceBlockState(State):
77+
class NamespaceBlockState(State[T, PT]):
78+
parent: State[PT, typing.Any]
79+
6380
#: The incremental namespace for this block
6481
namespace: NamespaceDecl
6582

@@ -73,7 +90,9 @@ def _finish(self, visitor: "CxxVisitor") -> None:
7390
visitor.on_namespace_end(self)
7491

7592

76-
class ClassBlockState(State):
93+
class ClassBlockState(State[T, PT]):
94+
parent: State[PT, typing.Any]
95+
7796
#: class decl block being processed
7897
class_decl: ClassDecl
7998

cxxheaderparser/simple.py

Lines changed: 64 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,13 @@ class N::C {
179179
# Visitor implementation
180180
#
181181

182+
# define what user data we store in each state type
183+
SState = State[Block, Block]
184+
SEmptyBlockState = EmptyBlockState[Block, Block]
185+
SExternBlockState = ExternBlockState[Block, Block]
186+
SNamespaceBlockState = NamespaceBlockState[NamespaceScope, NamespaceScope]
187+
SClassBlockState = ClassBlockState[ClassScope, Block]
188+
182189

183190
class SimpleCxxVisitor:
184191
"""
@@ -190,43 +197,35 @@ class SimpleCxxVisitor:
190197
"""
191198

192199
data: ParsedData
193-
namespace: NamespaceScope
194-
block: Block
195-
196-
def __init__(self) -> None:
197-
self.namespace = NamespaceScope("")
198-
self.block = self.namespace
199-
200-
self.ns_stack = typing.Deque[NamespaceScope]()
201-
self.block_stack = typing.Deque[Block]()
202200

203-
self.data = ParsedData(self.namespace)
201+
def on_parse_start(self, state: SNamespaceBlockState) -> None:
202+
ns = NamespaceScope("")
203+
self.data = ParsedData(ns)
204+
state.user_data = ns
204205

205-
def on_pragma(self, state: State, content: Value) -> None:
206+
def on_pragma(self, state: SState, content: Value) -> None:
206207
self.data.pragmas.append(Pragma(content))
207208

208-
def on_include(self, state: State, filename: str) -> None:
209+
def on_include(self, state: SState, filename: str) -> None:
209210
self.data.includes.append(Include(filename))
210211

211-
def on_empty_block_start(self, state: EmptyBlockState) -> None:
212+
def on_empty_block_start(self, state: SEmptyBlockState) -> None:
212213
# this matters for some scope/resolving purposes, but you're
213214
# probably going to want to use clang if you care about that
214215
# level of detail
215-
pass
216+
state.user_data = state.parent.user_data
216217

217-
def on_empty_block_end(self, state: EmptyBlockState) -> None:
218+
def on_empty_block_end(self, state: SEmptyBlockState) -> None:
218219
pass
219220

220-
def on_extern_block_start(self, state: ExternBlockState) -> None:
221-
pass # TODO
221+
def on_extern_block_start(self, state: SExternBlockState) -> None:
222+
state.user_data = state.parent.user_data
222223

223-
def on_extern_block_end(self, state: ExternBlockState) -> None:
224+
def on_extern_block_end(self, state: SExternBlockState) -> None:
224225
pass
225226

226-
def on_namespace_start(self, state: NamespaceBlockState) -> None:
227-
parent_ns = self.namespace
228-
self.block_stack.append(parent_ns)
229-
self.ns_stack.append(parent_ns)
227+
def on_namespace_start(self, state: SNamespaceBlockState) -> None:
228+
parent_ns = state.parent.user_data
230229

231230
ns = None
232231
names = state.namespace.names
@@ -247,81 +246,76 @@ def on_namespace_start(self, state: NamespaceBlockState) -> None:
247246
ns.inline = state.namespace.inline
248247
ns.doxygen = state.namespace.doxygen
249248

250-
self.block = ns
251-
self.namespace = ns
249+
state.user_data = ns
252250

253-
def on_namespace_end(self, state: NamespaceBlockState) -> None:
254-
self.block = self.block_stack.pop()
255-
self.namespace = self.ns_stack.pop()
251+
def on_namespace_end(self, state: SNamespaceBlockState) -> None:
252+
pass
256253

257-
def on_namespace_alias(self, state: State, alias: NamespaceAlias) -> None:
258-
assert isinstance(self.block, NamespaceScope)
259-
self.block.ns_alias.append(alias)
254+
def on_namespace_alias(self, state: SState, alias: NamespaceAlias) -> None:
255+
assert isinstance(state.user_data, NamespaceScope)
256+
state.user_data.ns_alias.append(alias)
260257

261-
def on_forward_decl(self, state: State, fdecl: ForwardDecl) -> None:
262-
self.block.forward_decls.append(fdecl)
258+
def on_forward_decl(self, state: SState, fdecl: ForwardDecl) -> None:
259+
state.user_data.forward_decls.append(fdecl)
263260

264-
def on_template_inst(self, state: State, inst: TemplateInst) -> None:
265-
assert isinstance(self.block, NamespaceScope)
266-
self.block.template_insts.append(inst)
261+
def on_template_inst(self, state: SState, inst: TemplateInst) -> None:
262+
assert isinstance(state.user_data, NamespaceScope)
263+
state.user_data.template_insts.append(inst)
267264

268-
def on_variable(self, state: State, v: Variable) -> None:
269-
assert isinstance(self.block, NamespaceScope)
270-
self.block.variables.append(v)
265+
def on_variable(self, state: SState, v: Variable) -> None:
266+
assert isinstance(state.user_data, NamespaceScope)
267+
state.user_data.variables.append(v)
271268

272-
def on_function(self, state: State, fn: Function) -> None:
273-
assert isinstance(self.block, NamespaceScope)
274-
self.block.functions.append(fn)
269+
def on_function(self, state: SState, fn: Function) -> None:
270+
assert isinstance(state.user_data, NamespaceScope)
271+
state.user_data.functions.append(fn)
275272

276-
def on_method_impl(self, state: State, method: Method) -> None:
277-
assert isinstance(self.block, NamespaceScope)
278-
self.block.method_impls.append(method)
273+
def on_method_impl(self, state: SState, method: Method) -> None:
274+
assert isinstance(state.user_data, NamespaceScope)
275+
state.user_data.method_impls.append(method)
279276

280-
def on_typedef(self, state: State, typedef: Typedef) -> None:
281-
self.block.typedefs.append(typedef)
277+
def on_typedef(self, state: SState, typedef: Typedef) -> None:
278+
state.user_data.typedefs.append(typedef)
282279

283-
def on_using_namespace(self, state: State, namespace: typing.List[str]) -> None:
284-
assert isinstance(self.block, NamespaceScope)
280+
def on_using_namespace(self, state: SState, namespace: typing.List[str]) -> None:
281+
assert isinstance(state.user_data, NamespaceScope)
285282
ns = UsingNamespace("::".join(namespace))
286-
self.block.using_ns.append(ns)
283+
state.user_data.using_ns.append(ns)
287284

288-
def on_using_alias(self, state: State, using: UsingAlias) -> None:
289-
self.block.using_alias.append(using)
285+
def on_using_alias(self, state: SState, using: UsingAlias) -> None:
286+
state.user_data.using_alias.append(using)
290287

291-
def on_using_declaration(self, state: State, using: UsingDecl) -> None:
292-
self.block.using.append(using)
288+
def on_using_declaration(self, state: SState, using: UsingDecl) -> None:
289+
state.user_data.using.append(using)
293290

294291
#
295292
# Enums
296293
#
297294

298-
def on_enum(self, state: State, enum: EnumDecl) -> None:
299-
self.block.enums.append(enum)
295+
def on_enum(self, state: SState, enum: EnumDecl) -> None:
296+
state.user_data.enums.append(enum)
300297

301298
#
302299
# Class/union/struct
303300
#
304301

305-
def on_class_start(self, state: ClassBlockState) -> None:
302+
def on_class_start(self, state: SClassBlockState) -> None:
303+
parent = state.parent.user_data
306304
block = ClassScope(state.class_decl)
307-
self.block.classes.append(block)
308-
self.block_stack.append(self.block)
309-
self.block = block
305+
parent.classes.append(block)
306+
state.user_data = block
310307

311-
def on_class_field(self, state: State, f: Field) -> None:
312-
assert isinstance(self.block, ClassScope)
313-
self.block.fields.append(f)
308+
def on_class_field(self, state: SClassBlockState, f: Field) -> None:
309+
state.user_data.fields.append(f)
314310

315-
def on_class_method(self, state: ClassBlockState, method: Method) -> None:
316-
assert isinstance(self.block, ClassScope)
317-
self.block.methods.append(method)
311+
def on_class_method(self, state: SClassBlockState, method: Method) -> None:
312+
state.user_data.methods.append(method)
318313

319-
def on_class_friend(self, state: ClassBlockState, friend: FriendDecl) -> None:
320-
assert isinstance(self.block, ClassScope)
321-
self.block.friends.append(friend)
314+
def on_class_friend(self, state: SClassBlockState, friend: FriendDecl) -> None:
315+
state.user_data.friends.append(friend)
322316

323-
def on_class_end(self, state: ClassBlockState) -> None:
324-
self.block = self.block_stack.pop()
317+
def on_class_end(self, state: SClassBlockState) -> None:
318+
pass
325319

326320

327321
def parse_string(

cxxheaderparser/visitor.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,11 @@ class CxxVisitor(Protocol):
3737
Defines the interface used by the parser to emit events
3838
"""
3939

40+
def on_parse_start(self, state: NamespaceBlockState) -> None:
41+
"""
42+
Called when parsing begins
43+
"""
44+
4045
def on_pragma(self, state: State, content: Value) -> None:
4146
"""
4247
Called once for each ``#pragma`` directive encountered

tests/test_misc.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -351,3 +351,29 @@ def test_warning_directive() -> None:
351351
data = parse_string(content, cleandoc=True)
352352

353353
assert data == ParsedData()
354+
355+
356+
def test_empty_block() -> None:
357+
"""
358+
Ensure the simple visitor doesn't break with an empty block
359+
"""
360+
content = """
361+
{
362+
class X {};
363+
}
364+
"""
365+
data = parse_string(content, cleandoc=True)
366+
367+
assert data == ParsedData(
368+
namespace=NamespaceScope(
369+
classes=[
370+
ClassScope(
371+
class_decl=ClassDecl(
372+
typename=PQName(
373+
segments=[NameSpecifier(name="X")], classkey="class"
374+
)
375+
)
376+
)
377+
]
378+
)
379+
)

0 commit comments

Comments
 (0)