Skip to content

Commit bdcee6f

Browse files
authored
Merge pull request #68 from robotpy/simplify-blocks
breaking change: remove empty block state and update visitor types
2 parents 8c69970 + bf54be5 commit bdcee6f

File tree

6 files changed

+95
-177
lines changed

6 files changed

+95
-177
lines changed

cxxheaderparser/parser.py

Lines changed: 37 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@
1010
from .options import ParserOptions
1111
from .parserstate import (
1212
ClassBlockState,
13-
EmptyBlockState,
1413
ExternBlockState,
1514
NamespaceBlockState,
15+
NonClassBlockState,
1616
ParsedTypeModifiers,
1717
State,
1818
)
@@ -62,7 +62,6 @@
6262
LexTokenList = typing.List[LexToken]
6363
T = typing.TypeVar("T")
6464

65-
ST = typing.TypeVar("ST", bound=State)
6665
PT = typing.TypeVar("PT", Parameter, TemplateNonTypeParam)
6766

6867

@@ -90,7 +89,9 @@ def __init__(
9089
global_ns = NamespaceDecl([], False)
9190
self.current_namespace = global_ns
9291

93-
self.state: State = NamespaceBlockState(None, global_ns)
92+
self.state: State = NamespaceBlockState(
93+
None, self.lex.current_location(), global_ns
94+
)
9495
self.anon_id = 0
9596

9697
self.verbose = True if self.options.verbose else False
@@ -111,13 +112,9 @@ def debug_print(fmt: str, *args: typing.Any) -> None:
111112
# State management
112113
#
113114

114-
def _push_state(self, cls: typing.Type[ST], *args) -> ST:
115-
state = cls(self.state, *args)
115+
def _setup_state(self, state: State):
116116
state._prior_visitor = self.visitor
117-
if isinstance(state, NamespaceBlockState):
118-
self.current_namespace = state.namespace
119117
self.state = state
120-
return state
121118

122119
def _pop_state(self) -> State:
123120
prev_state = self.state
@@ -447,24 +444,37 @@ def _parse_namespace(
447444
if inline and len(names) > 1:
448445
raise CxxParseError("a nested namespace definition cannot be inline")
449446

447+
state = self.state
448+
if not isinstance(state, (NamespaceBlockState, ExternBlockState)):
449+
raise CxxParseError("namespace cannot be defined in a class")
450+
450451
if ns_alias:
451452
alias = NamespaceAlias(ns_alias.value, names)
452-
self.visitor.on_namespace_alias(self.state, alias)
453+
self.visitor.on_namespace_alias(state, alias)
453454
return
454455

455456
ns = NamespaceDecl(names, inline, doxygen)
456-
state = self._push_state(NamespaceBlockState, ns)
457-
state.location = location
457+
458+
state = NamespaceBlockState(state, location, ns)
459+
self._setup_state(state)
460+
self.current_namespace = state.namespace
461+
458462
if self.visitor.on_namespace_start(state) is False:
459463
self.visitor = null_visitor
460464

461465
def _parse_extern(self, tok: LexToken, doxygen: typing.Optional[str]) -> None:
462466
etok = self.lex.token_if("STRING_LITERAL", "template")
463467
if etok:
468+
# classes cannot contain extern blocks/templates
469+
state = self.state
470+
if isinstance(state, ClassBlockState):
471+
raise self._parse_error(tok)
472+
464473
if etok.type == "STRING_LITERAL":
465474
if self.lex.token_if("{"):
466-
state = self._push_state(ExternBlockState, etok.value)
467-
state.location = tok.location
475+
state = ExternBlockState(state, tok.location, etok.value)
476+
self._setup_state(state)
477+
468478
if self.visitor.on_extern_block_start(state) is False:
469479
self.visitor = null_visitor
470480
return
@@ -510,9 +520,7 @@ def _consume_static_assert(
510520
def _on_empty_block_start(
511521
self, tok: LexToken, doxygen: typing.Optional[str]
512522
) -> None:
513-
state = self._push_state(EmptyBlockState)
514-
if self.visitor.on_empty_block_start(state) is False:
515-
self.visitor = null_visitor
523+
raise self._parse_error(tok)
516524

517525
def _on_block_end(self, tok: LexToken, doxygen: typing.Optional[str]) -> None:
518526
old_state = self._pop_state()
@@ -822,7 +830,7 @@ def _consume_attribute_specifier_seq(
822830
# Using directive/declaration/typealias
823831
#
824832

825-
def _parse_using_directive(self) -> None:
833+
def _parse_using_directive(self, state: NonClassBlockState) -> None:
826834
"""
827835
using_directive: [attribute_specifier_seq] "using" "namespace" ["::"] [nested_name_specifier] IDENTIFIER ";"
828836
"""
@@ -841,7 +849,7 @@ def _parse_using_directive(self) -> None:
841849
if not names:
842850
raise self._parse_error(None, "NAME")
843851

844-
self.visitor.on_using_namespace(self.state, names)
852+
self.visitor.on_using_namespace(state, names)
845853

846854
def _parse_using_declaration(self, tok: LexToken) -> None:
847855
"""
@@ -893,10 +901,11 @@ def _parse_using(
893901
raise CxxParseError(
894902
"unexpected using-directive when parsing alias-declaration", tok
895903
)
896-
if isinstance(self.state, ClassBlockState):
904+
state = self.state
905+
if not isinstance(state, (NamespaceBlockState, ExternBlockState)):
897906
raise self._parse_error(tok)
898907

899-
self._parse_using_directive()
908+
self._parse_using_directive(state)
900909
elif tok.type in ("DBL_COLON", "typename") or not self.lex.token_if("="):
901910
if template:
902911
raise CxxParseError(
@@ -1143,10 +1152,11 @@ def _parse_class_decl(
11431152
clsdecl = ClassDecl(
11441153
typename, bases, template, explicit, final, doxygen, self._current_access
11451154
)
1146-
state = self._push_state(
1147-
ClassBlockState, clsdecl, default_access, typedef, mods
1155+
state: ClassBlockState = ClassBlockState(
1156+
self.state, location, clsdecl, default_access, typedef, mods
11481157
)
1149-
state.location = location
1158+
self._setup_state(state)
1159+
11501160
if self.visitor.on_class_start(state) is False:
11511161
self.visitor = null_visitor
11521162

@@ -1853,6 +1863,7 @@ def _parse_function(
18531863

18541864
self.visitor.on_class_method(state, method)
18551865
else:
1866+
assert isinstance(state, (ExternBlockState, NamespaceBlockState))
18561867
if not method.has_body:
18571868
raise self._parse_error(None, expected="Method body")
18581869
self.visitor.on_method_impl(state, method)
@@ -1912,6 +1923,9 @@ def _parse_function(
19121923
self.visitor.on_typedef(state, typedef)
19131924
return False
19141925
else:
1926+
if not isinstance(state, (ExternBlockState, NamespaceBlockState)):
1927+
raise CxxParseError("internal error")
1928+
19151929
self.visitor.on_function(state, fn)
19161930
return fn.has_body or fn.has_trailing_return
19171931

cxxheaderparser/parserstate.py

Lines changed: 28 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -35,66 +35,65 @@ def validate(self, *, var_ok: bool, meth_ok: bool, msg: str) -> None:
3535
PT = typing.TypeVar("PT")
3636

3737

38-
class State(typing.Generic[T, PT]):
38+
class BaseState(typing.Generic[T, PT]):
3939
#: Uninitialized user data available for use by visitor implementations. You
4040
#: should set this in a ``*_start`` method.
4141
user_data: T
4242

4343
#: parent state
44-
parent: typing.Optional["State[PT, typing.Any]"]
44+
parent: typing.Optional["State"]
4545

4646
#: Approximate location that the parsed element was found at
4747
location: Location
4848

4949
#: internal detail used by parser
5050
_prior_visitor: "CxxVisitor"
5151

52-
def __init__(self, parent: typing.Optional["State[PT, typing.Any]"]) -> None:
52+
def __init__(self, parent: typing.Optional["State"], location: Location) -> None:
5353
self.parent = parent
54+
self.location = location
5455

5556
def _finish(self, visitor: "CxxVisitor") -> None:
5657
pass
5758

5859

59-
class EmptyBlockState(State[T, PT]):
60-
parent: State[PT, typing.Any]
61-
62-
def _finish(self, visitor: "CxxVisitor") -> None:
63-
visitor.on_empty_block_end(self)
64-
65-
66-
class ExternBlockState(State[T, PT]):
67-
parent: State[PT, typing.Any]
60+
class ExternBlockState(BaseState[T, PT]):
61+
parent: "NonClassBlockState"
6862

6963
#: The linkage for this extern block
7064
linkage: str
7165

72-
def __init__(self, parent: typing.Optional[State], linkage: str) -> None:
73-
super().__init__(parent)
66+
def __init__(
67+
self, parent: "NonClassBlockState", location: Location, linkage: str
68+
) -> None:
69+
super().__init__(parent, location)
7470
self.linkage = linkage
7571

7672
def _finish(self, visitor: "CxxVisitor") -> None:
7773
visitor.on_extern_block_end(self)
7874

7975

80-
class NamespaceBlockState(State[T, PT]):
81-
parent: State[PT, typing.Any]
76+
class NamespaceBlockState(BaseState[T, PT]):
77+
parent: "NonClassBlockState"
8278

8379
#: The incremental namespace for this block
8480
namespace: NamespaceDecl
8581

8682
def __init__(
87-
self, parent: typing.Optional[State], namespace: NamespaceDecl
83+
self,
84+
parent: typing.Optional["NonClassBlockState"],
85+
location: Location,
86+
namespace: NamespaceDecl,
8887
) -> None:
89-
super().__init__(parent)
88+
super().__init__(parent, location)
9089
self.namespace = namespace
9190

9291
def _finish(self, visitor: "CxxVisitor") -> None:
9392
visitor.on_namespace_end(self)
9493

9594

96-
class ClassBlockState(State[T, PT]):
97-
parent: State[PT, typing.Any]
95+
class ClassBlockState(BaseState[T, PT]):
96+
parent: "State"
9897

9998
#: class decl block being processed
10099
class_decl: ClassDecl
@@ -110,13 +109,14 @@ class ClassBlockState(State[T, PT]):
110109

111110
def __init__(
112111
self,
113-
parent: typing.Optional[State],
112+
parent: typing.Optional["State"],
113+
location: Location,
114114
class_decl: ClassDecl,
115115
access: str,
116116
typedef: bool,
117117
mods: ParsedTypeModifiers,
118118
) -> None:
119-
super().__init__(parent)
119+
super().__init__(parent, location)
120120
self.class_decl = class_decl
121121
self.access = access
122122
self.typedef = typedef
@@ -127,3 +127,9 @@ def _set_access(self, access: str) -> None:
127127

128128
def _finish(self, visitor: "CxxVisitor") -> None:
129129
visitor.on_class_end(self)
130+
131+
132+
State = typing.Union[
133+
NamespaceBlockState[T, PT], ExternBlockState[T, PT], ClassBlockState[T, PT]
134+
]
135+
NonClassBlockState = typing.Union[ExternBlockState[T, PT], NamespaceBlockState[T, PT]]

cxxheaderparser/simple.py

Lines changed: 13 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,6 @@
5151

5252
from .parserstate import (
5353
State,
54-
EmptyBlockState,
5554
ClassBlockState,
5655
ExternBlockState,
5756
NamespaceBlockState,
@@ -180,11 +179,12 @@ class N::C {
180179
#
181180

182181
# 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]
187182
SClassBlockState = ClassBlockState[ClassScope, Block]
183+
SExternBlockState = ExternBlockState[NamespaceScope, NamespaceScope]
184+
SNamespaceBlockState = NamespaceBlockState[NamespaceScope, NamespaceScope]
185+
186+
SState = typing.Union[SClassBlockState, SExternBlockState, SNamespaceBlockState]
187+
SNonClassBlockState = typing.Union[SExternBlockState, SNamespaceBlockState]
188188

189189

190190
class SimpleCxxVisitor:
@@ -209,16 +209,6 @@ def on_pragma(self, state: SState, content: Value) -> None:
209209
def on_include(self, state: SState, filename: str) -> None:
210210
self.data.includes.append(Include(filename))
211211

212-
def on_empty_block_start(self, state: SEmptyBlockState) -> typing.Optional[bool]:
213-
# this matters for some scope/resolving purposes, but you're
214-
# probably going to want to use clang if you care about that
215-
# level of detail
216-
state.user_data = state.parent.user_data
217-
return None
218-
219-
def on_empty_block_end(self, state: SEmptyBlockState) -> None:
220-
pass
221-
222212
def on_extern_block_start(self, state: SExternBlockState) -> typing.Optional[bool]:
223213
state.user_data = state.parent.user_data
224214
return None
@@ -254,8 +244,9 @@ def on_namespace_start(self, state: SNamespaceBlockState) -> typing.Optional[boo
254244
def on_namespace_end(self, state: SNamespaceBlockState) -> None:
255245
pass
256246

257-
def on_namespace_alias(self, state: SState, alias: NamespaceAlias) -> None:
258-
assert isinstance(state.user_data, NamespaceScope)
247+
def on_namespace_alias(
248+
self, state: SNonClassBlockState, alias: NamespaceAlias
249+
) -> None:
259250
state.user_data.ns_alias.append(alias)
260251

261252
def on_forward_decl(self, state: SState, fdecl: ForwardDecl) -> None:
@@ -269,19 +260,18 @@ def on_variable(self, state: SState, v: Variable) -> None:
269260
assert isinstance(state.user_data, NamespaceScope)
270261
state.user_data.variables.append(v)
271262

272-
def on_function(self, state: SState, fn: Function) -> None:
273-
assert isinstance(state.user_data, NamespaceScope)
263+
def on_function(self, state: SNonClassBlockState, fn: Function) -> None:
274264
state.user_data.functions.append(fn)
275265

276-
def on_method_impl(self, state: SState, method: Method) -> None:
277-
assert isinstance(state.user_data, NamespaceScope)
266+
def on_method_impl(self, state: SNonClassBlockState, method: Method) -> None:
278267
state.user_data.method_impls.append(method)
279268

280269
def on_typedef(self, state: SState, typedef: Typedef) -> None:
281270
state.user_data.typedefs.append(typedef)
282271

283-
def on_using_namespace(self, state: SState, namespace: typing.List[str]) -> None:
284-
assert isinstance(state.user_data, NamespaceScope)
272+
def on_using_namespace(
273+
self, state: SNonClassBlockState, namespace: typing.List[str]
274+
) -> None:
285275
ns = UsingNamespace("::".join(namespace))
286276
state.user_data.using_ns.append(ns)
287277

0 commit comments

Comments
 (0)