diff --git a/cxxheaderparser/parser.py b/cxxheaderparser/parser.py index 1c32073..da55c6a 100644 --- a/cxxheaderparser/parser.py +++ b/cxxheaderparser/parser.py @@ -12,6 +12,7 @@ from .parserstate import ( ClassBlockState, ExternBlockState, NamespaceBlockState, + NonClassBlockState, ParsedTypeModifiers, State, ) @@ -61,7 +62,6 @@ from .visitor import CxxVisitor, null_visitor LexTokenList = typing.List[LexToken] T = typing.TypeVar("T") -ST = typing.TypeVar("ST", bound=State) PT = typing.TypeVar("PT", Parameter, TemplateNonTypeParam) @@ -89,7 +89,9 @@ class CxxParser: global_ns = NamespaceDecl([], False) self.current_namespace = global_ns - self.state: State = NamespaceBlockState(None, global_ns) + self.state: State = NamespaceBlockState( + None, self.lex.current_location(), global_ns + ) self.anon_id = 0 self.verbose = True if self.options.verbose else False @@ -110,13 +112,9 @@ class CxxParser: # State management # - def _push_state(self, cls: typing.Type[ST], *args) -> ST: - state = cls(self.state, *args) + def _setup_state(self, state: State): state._prior_visitor = self.visitor - if isinstance(state, NamespaceBlockState): - self.current_namespace = state.namespace self.state = state - return state def _pop_state(self) -> State: prev_state = self.state @@ -446,24 +444,37 @@ class CxxParser: if inline and len(names) > 1: raise CxxParseError("a nested namespace definition cannot be inline") + state = self.state + if not isinstance(state, (NamespaceBlockState, ExternBlockState)): + raise CxxParseError("namespace cannot be defined in a class") + if ns_alias: alias = NamespaceAlias(ns_alias.value, names) - self.visitor.on_namespace_alias(self.state, alias) + self.visitor.on_namespace_alias(state, alias) return ns = NamespaceDecl(names, inline, doxygen) - state = self._push_state(NamespaceBlockState, ns) - state.location = location + + state = NamespaceBlockState(state, location, ns) + self._setup_state(state) + self.current_namespace = state.namespace + if self.visitor.on_namespace_start(state) is False: self.visitor = null_visitor def _parse_extern(self, tok: LexToken, doxygen: typing.Optional[str]) -> None: etok = self.lex.token_if("STRING_LITERAL", "template") if etok: + # classes cannot contain extern blocks/templates + state = self.state + if isinstance(state, ClassBlockState): + raise self._parse_error(tok) + if etok.type == "STRING_LITERAL": if self.lex.token_if("{"): - state = self._push_state(ExternBlockState, etok.value) - state.location = tok.location + state = ExternBlockState(state, tok.location, etok.value) + self._setup_state(state) + if self.visitor.on_extern_block_start(state) is False: self.visitor = null_visitor return @@ -819,7 +830,7 @@ class CxxParser: # Using directive/declaration/typealias # - def _parse_using_directive(self) -> None: + def _parse_using_directive(self, state: NonClassBlockState) -> None: """ using_directive: [attribute_specifier_seq] "using" "namespace" ["::"] [nested_name_specifier] IDENTIFIER ";" """ @@ -838,7 +849,7 @@ class CxxParser: if not names: raise self._parse_error(None, "NAME") - self.visitor.on_using_namespace(self.state, names) + self.visitor.on_using_namespace(state, names) def _parse_using_declaration(self, tok: LexToken) -> None: """ @@ -890,10 +901,11 @@ class CxxParser: raise CxxParseError( "unexpected using-directive when parsing alias-declaration", tok ) - if isinstance(self.state, ClassBlockState): + state = self.state + if not isinstance(state, (NamespaceBlockState, ExternBlockState)): raise self._parse_error(tok) - self._parse_using_directive() + self._parse_using_directive(state) elif tok.type in ("DBL_COLON", "typename") or not self.lex.token_if("="): if template: raise CxxParseError( @@ -1140,10 +1152,11 @@ class CxxParser: clsdecl = ClassDecl( typename, bases, template, explicit, final, doxygen, self._current_access ) - state = self._push_state( - ClassBlockState, clsdecl, default_access, typedef, mods + state: ClassBlockState = ClassBlockState( + self.state, location, clsdecl, default_access, typedef, mods ) - state.location = location + self._setup_state(state) + if self.visitor.on_class_start(state) is False: self.visitor = null_visitor @@ -1850,6 +1863,7 @@ class CxxParser: self.visitor.on_class_method(state, method) else: + assert isinstance(state, (ExternBlockState, NamespaceBlockState)) if not method.has_body: raise self._parse_error(None, expected="Method body") self.visitor.on_method_impl(state, method) @@ -1909,6 +1923,9 @@ class CxxParser: self.visitor.on_typedef(state, typedef) return False else: + if not isinstance(state, (ExternBlockState, NamespaceBlockState)): + raise CxxParseError("internal error") + self.visitor.on_function(state, fn) return fn.has_body or fn.has_trailing_return diff --git a/cxxheaderparser/parserstate.py b/cxxheaderparser/parserstate.py index 1bd238a..a295a19 100644 --- a/cxxheaderparser/parserstate.py +++ b/cxxheaderparser/parserstate.py @@ -35,13 +35,13 @@ T = typing.TypeVar("T") PT = typing.TypeVar("PT") -class State(typing.Generic[T, PT]): +class BaseState(typing.Generic[T, PT]): #: Uninitialized user data available for use by visitor implementations. You #: should set this in a ``*_start`` method. user_data: T #: parent state - parent: typing.Optional["State[PT, typing.Any]"] + parent: typing.Optional["State"] #: Approximate location that the parsed element was found at location: Location @@ -49,45 +49,51 @@ class State(typing.Generic[T, PT]): #: internal detail used by parser _prior_visitor: "CxxVisitor" - def __init__(self, parent: typing.Optional["State[PT, typing.Any]"]) -> None: + def __init__(self, parent: typing.Optional["State"], location: Location) -> None: self.parent = parent + self.location = location def _finish(self, visitor: "CxxVisitor") -> None: pass -class ExternBlockState(State[T, PT]): - parent: State[PT, typing.Any] +class ExternBlockState(BaseState[T, PT]): + parent: "NonClassBlockState" #: The linkage for this extern block linkage: str - def __init__(self, parent: typing.Optional[State], linkage: str) -> None: - super().__init__(parent) + def __init__( + self, parent: "NonClassBlockState", location: Location, linkage: str + ) -> None: + super().__init__(parent, location) self.linkage = linkage def _finish(self, visitor: "CxxVisitor") -> None: visitor.on_extern_block_end(self) -class NamespaceBlockState(State[T, PT]): - parent: State[PT, typing.Any] +class NamespaceBlockState(BaseState[T, PT]): + parent: "NonClassBlockState" #: The incremental namespace for this block namespace: NamespaceDecl def __init__( - self, parent: typing.Optional[State], namespace: NamespaceDecl + self, + parent: typing.Optional["NonClassBlockState"], + location: Location, + namespace: NamespaceDecl, ) -> None: - super().__init__(parent) + super().__init__(parent, location) self.namespace = namespace def _finish(self, visitor: "CxxVisitor") -> None: visitor.on_namespace_end(self) -class ClassBlockState(State[T, PT]): - parent: State[PT, typing.Any] +class ClassBlockState(BaseState[T, PT]): + parent: "State" #: class decl block being processed class_decl: ClassDecl @@ -103,13 +109,14 @@ class ClassBlockState(State[T, PT]): def __init__( self, - parent: typing.Optional[State], + parent: typing.Optional["State"], + location: Location, class_decl: ClassDecl, access: str, typedef: bool, mods: ParsedTypeModifiers, ) -> None: - super().__init__(parent) + super().__init__(parent, location) self.class_decl = class_decl self.access = access self.typedef = typedef @@ -120,3 +127,9 @@ class ClassBlockState(State[T, PT]): def _finish(self, visitor: "CxxVisitor") -> None: visitor.on_class_end(self) + + +State = typing.Union[ + NamespaceBlockState[T, PT], ExternBlockState[T, PT], ClassBlockState[T, PT] +] +NonClassBlockState = typing.Union[ExternBlockState[T, PT], NamespaceBlockState[T, PT]] diff --git a/cxxheaderparser/simple.py b/cxxheaderparser/simple.py index 612de75..a707877 100644 --- a/cxxheaderparser/simple.py +++ b/cxxheaderparser/simple.py @@ -179,10 +179,12 @@ class ParsedData: # # define what user data we store in each state type -SState = State[Block, Block] -SExternBlockState = ExternBlockState[Block, Block] -SNamespaceBlockState = NamespaceBlockState[NamespaceScope, NamespaceScope] SClassBlockState = ClassBlockState[ClassScope, Block] +SExternBlockState = ExternBlockState[NamespaceScope, NamespaceScope] +SNamespaceBlockState = NamespaceBlockState[NamespaceScope, NamespaceScope] + +SState = typing.Union[SClassBlockState, SExternBlockState, SNamespaceBlockState] +SNonClassBlockState = typing.Union[SExternBlockState, SNamespaceBlockState] class SimpleCxxVisitor: @@ -242,8 +244,9 @@ class SimpleCxxVisitor: def on_namespace_end(self, state: SNamespaceBlockState) -> None: pass - def on_namespace_alias(self, state: SState, alias: NamespaceAlias) -> None: - assert isinstance(state.user_data, NamespaceScope) + def on_namespace_alias( + self, state: SNonClassBlockState, alias: NamespaceAlias + ) -> None: state.user_data.ns_alias.append(alias) def on_forward_decl(self, state: SState, fdecl: ForwardDecl) -> None: @@ -257,19 +260,18 @@ class SimpleCxxVisitor: assert isinstance(state.user_data, NamespaceScope) state.user_data.variables.append(v) - def on_function(self, state: SState, fn: Function) -> None: - assert isinstance(state.user_data, NamespaceScope) + def on_function(self, state: SNonClassBlockState, fn: Function) -> None: state.user_data.functions.append(fn) - def on_method_impl(self, state: SState, method: Method) -> None: - assert isinstance(state.user_data, NamespaceScope) + def on_method_impl(self, state: SNonClassBlockState, method: Method) -> None: state.user_data.method_impls.append(method) def on_typedef(self, state: SState, typedef: Typedef) -> None: state.user_data.typedefs.append(typedef) - def on_using_namespace(self, state: SState, namespace: typing.List[str]) -> None: - assert isinstance(state.user_data, NamespaceScope) + def on_using_namespace( + self, state: SNonClassBlockState, namespace: typing.List[str] + ) -> None: ns = UsingNamespace("::".join(namespace)) state.user_data.using_ns.append(ns) diff --git a/cxxheaderparser/visitor.py b/cxxheaderparser/visitor.py index eb71f0c..aca2b2a 100644 --- a/cxxheaderparser/visitor.py +++ b/cxxheaderparser/visitor.py @@ -28,6 +28,7 @@ from .parserstate import ( ClassBlockState, ExternBlockState, NamespaceBlockState, + NonClassBlockState, ) @@ -81,7 +82,9 @@ class CxxVisitor(Protocol): Called at the end of a ``namespace`` block """ - def on_namespace_alias(self, state: State, alias: NamespaceAlias) -> None: + def on_namespace_alias( + self, state: NonClassBlockState, alias: NamespaceAlias + ) -> None: """ Called when a ``namespace`` alias is encountered """ @@ -101,12 +104,12 @@ class CxxVisitor(Protocol): Called when a global variable is encountered """ - def on_function(self, state: State, fn: Function) -> None: + def on_function(self, state: NonClassBlockState, fn: Function) -> None: """ Called when a function is encountered that isn't part of a class """ - def on_method_impl(self, state: State, method: Method) -> None: + def on_method_impl(self, state: NonClassBlockState, method: Method) -> None: """ Called when a method implementation is encountered outside of a class declaration. For example: @@ -134,7 +137,9 @@ class CxxVisitor(Protocol): once for ``*PT`` """ - def on_using_namespace(self, state: State, namespace: typing.List[str]) -> None: + def on_using_namespace( + self, state: NonClassBlockState, namespace: typing.List[str] + ) -> None: """ .. code-block:: c++ @@ -249,7 +254,9 @@ class NullVisitor: def on_namespace_end(self, state: NamespaceBlockState) -> None: return None - def on_namespace_alias(self, state: State, alias: NamespaceAlias) -> None: + def on_namespace_alias( + self, state: NonClassBlockState, alias: NamespaceAlias + ) -> None: return None def on_forward_decl(self, state: State, fdecl: ForwardDecl) -> None: @@ -261,16 +268,18 @@ class NullVisitor: def on_variable(self, state: State, v: Variable) -> None: return None - def on_function(self, state: State, fn: Function) -> None: + def on_function(self, state: NonClassBlockState, fn: Function) -> None: return None - def on_method_impl(self, state: State, method: Method) -> None: + def on_method_impl(self, state: NonClassBlockState, method: Method) -> None: return None def on_typedef(self, state: State, typedef: Typedef) -> None: return None - def on_using_namespace(self, state: State, namespace: typing.List[str]) -> None: + def on_using_namespace( + self, state: NonClassBlockState, namespace: typing.List[str] + ) -> None: return None def on_using_alias(self, state: State, using: UsingAlias) -> None: